je-menvol/prog/terminal.hpp
2025-05-07 18:23:21 +02:00

465 lines
14 KiB
C++

#pragma once
/*
* Copyright (C) 2015-2019 Grégoire Passault <gregoire.passault@u-bordeaux.fr>
* Copyright (C) 2019-2025 Adrien Boussicault <adrien.boussicault@labri.fr>
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include <Preferences.h>
#include "serial_print.hpp"
namespace terminal_n {
enum paramter_type {
NONE_PARAMETER,
FLOAT_PARAMETER,
DOUBLE_PARAMETER,
INT_PARAMETER,
UINT_PARAMETER,
LONGINT_PARAMETER,
LONGUINT_PARAMETER,
BOOL_PARAMETER
};
/**
* To defined a new command, use the MACRO
* TERMINAL_COMMAND(terminal, name, description)
*
* @param name : the name of the command, without space, without quotes
* @param description : the description of the command, with quote
*
* use terminal_print() to print on output
* use the variables arc and argv to get the string of given arguments
*
* If you declared the terminal in the following way, in another file :
*
* #include "terminal.hpp"
* terminal_n::Terminal terminal("The terminal\n\r");
*
* you can add that command in other file :
*
* #include "terminal.hpp"
* extern terminal_n::Terminal terminal;
*
* TERMINAL_COMMAND(terminal, hello, "Print a friendly warming welcoming message")
* {
* if (argc > 0) {
* terminal.print("Hello ");
* terminal.println(argv[0]);
* terminal.print("Other params: ");
* for (uint32_t i=1;i<argc;i++) {
* terminal.print(argv[i]);
* terminal.print(" ");
* }
* terminal.println("");
* } else {
* terminal.println("Hello world");
* }
* }
*
*
* WARNING: In order for the commands to be automatically registered with the
* terminal at startup, the file containing the setup() and loop() functions
* must be placed last in the list of files during the linking stage.
*
* In PlatformIO, to do this, simply begin the name of the file containing the
* loop() and setup() functions with the letter z.
*
* For example: zzz_main.cpp.
*
*/
const constexpr int MAX_EEPROM_NAME_LENGTH = 15;
/**
* Prototype of a terminal command
*/
typedef void terminal_command_fn(uint32_t argc, char *argv[]);
/**
* A command definition for the terminal
*/
struct terminal_command
{
char *name;
char *description;
terminal_command_fn *command;
paramter_type parameter;
char *parameter_type;
bool auto_save_after_command;
volatile void* value_ptr;
char eeprom_name[MAX_EEPROM_NAME_LENGTH+1];
};
inline float atof(char *str){
int32_t sign = (str[0]=='-') ? -1 : 1;
float f = atoi(str);
char *savePtr;
strtok_r(str, ".", &savePtr);
char *floatPart = strtok_r(NULL, ".", &savePtr);
if (floatPart != NULL) {
float divide = 1;
for (char *tmp=floatPart; *tmp!='\0'; tmp++) {
divide *= 10;
}
f += sign*atoi(floatPart)/divide;
}
return f;
}
#ifndef PREFERENCES_NAMESPACE
#define PREFERENCES_NAMESPACE "terminal"
#endif
#ifndef PREFERENCES_NVS_PART
#define PREFERENCES_NVS_PARTITION "nvs"
#endif
#ifndef TERMINAL_BUFFER_SIZE
/**
* Maximum length of a command line
* and its argument
*/
#define TERMINAL_BUFFER_SIZE 64
#endif
#ifndef TERMINAL_MAX_ARGUMENTS
/**
* Maximum number of command arguments
*/
#define TERMINAL_MAX_ARGUMENTS 12
#endif
#ifndef TERMINAL_MAX_COMMANDS
/**
* Maximum number of commands
* which ca be registered
*/
#define TERMINAL_MAX_COMMANDS 100
#endif
struct Terminal {
const char* documentation;
Preferences preferences;
static const constexpr char* preferences_namespace = PREFERENCES_NAMESPACE;
static const constexpr char* preferences_nvs_partition = PREFERENCES_NVS_PARTITION;
static constexpr const int terminal_buffer_size = TERMINAL_BUFFER_SIZE;
static constexpr const int terminal_max_arguments = TERMINAL_MAX_ARGUMENTS;
static constexpr const int terminal_max_commands = TERMINAL_MAX_COMMANDS;
char terminal_buffer[terminal_buffer_size] = {0};
HardwareSerial* io = 0;
bool silent;
bool disabled = false;
bool terminal_last_ok = false;
uint32_t terminal_last_pos = 0;
uint32_t terminal_pos = 0;
bool terminal_echo_mode = true;
uint32_t terminal_command_count = 0;
const struct terminal_command *terminal_commands[terminal_max_commands];
static constexpr const char* PROMPT = "$ ";
Terminal(const char* documentation=""):
documentation(documentation)
{}
inline void write_char(char c){
if(!silent){ io->write(c); }
}
inline void write(const char* buf, uint32_t len){
if(!silent){ io->write((const uint8_t*)buf, len); }
}
template <typename T>
inline void print(T v){
if(!silent){ serial_print(io, v); }
}
template <typename T>
inline void println(T v){
if(!silent){ serial_println(io, v); }
}
template <typename T>
inline void print_hex(T v){
if(!silent){ serial_print_hex(io, v); }
}
template <typename T>
inline void println_hex(T v){
if(!silent){ serial_println_hex(io, v); }
}
inline bool terminal_has_io(){
return io;
}
inline void print_hexa(uint8_t* buf, uint32_t n){
uint8_t first=0, last=0;
for( uint32_t i=0; i<n; i++ ){
first = buf[i] >> 4;
last = buf[i] & 0x0F;
print( 'x' );
print( static_cast<char>( (first < 10 ? '0' : 'A'-10) + first ) );
print( static_cast<char>( (last < 10 ? '0' : 'A'-10) + last ) );
}
}
inline void println_hexa(uint8_t* buf, uint32_t n){
print_hexa(buf,1);
println("");
}
/**
* Resets the terminal
*/
void reset();
/**
* Enable the terminal
*/
void enable();
/**
* Disable the terminal
*/
void disable();
void print_prompt();
void setup(HardwareSerial* serial);
/**
* Terminal update
* Call this function in your main loop
* to fetch serial port and handle terminal
*/
void update();
const terminal_command * find_command(
char *command_name, uint32_t command_name_length
);
bool terminal_execute(
char *command_name, uint32_t command_name_length, uint32_t argc, char **argv
);
void terminal_process();
bool have_to_be_saved_in_eeprom(const terminal_command *command);
void check_eeprom_names();
void save_value_to_eeprom(const terminal_command *command);
bool save_config_to_eeprom(const char* eeprom_name);
void save_config_to_eeprom();
void load_config_from_eeprom(bool enable_print=true);
bool load_config_from_eeprom(const char* eeprom_name, bool enable_print);
void load_value_from_eeprom(
const terminal_command *command, bool enable_print=false
);
void print_saved_parameter(const terminal_command *command);
void print_value_from_eeprom(const struct terminal_command *command);
void print_config_from_eeprom();
bool print_config_from_eeprom(const char* eeprom_name);
void reset_to_constructor_config();
bool reset_to_constructor_config(const char* eeprom_name);
/**
* Registers a command
*/
void register_command(struct terminal_command *command);
/**
* Mute the terminal
*/
void terminal_silent(bool silent);
void displayHelp(bool parameter);
void terminal_params_show();
void echo_command(uint32_t argc, char *argv[]);
void params_command(uint32_t argc, char *argv[]);
};
/**
* ----------------------------------------------------------------------------
* ----------------------------------------------------------------------------
* ----------------------------------------------------------------------------
*/
#define TERMINAL_COMMAND_INTERNAL(term, name, eeprom_name, description, parameter, parameterType, auto_save_after_command, value_ptr) terminal_n::terminal_command_fn terminal_command_ ## name; \
\
char terminal_command_name_ ## name [] = #name; \
char terminal_command_description_ ## name [] = description; \
\
struct terminal_n::terminal_command terminal_command_definition_ ## name = { \
terminal_command_name_ ## name , \
terminal_command_description_ ## name , \
terminal_command_ ## name, \
parameter, \
parameterType, \
auto_save_after_command, \
value_ptr, \
eeprom_name \
}; \
\
__attribute__((constructor)) \
void terminal_command_init_ ## name () { \
term.register_command(&terminal_command_definition_ ## name ); \
} \
\
void terminal_command_ ## name (uint32_t argc, char *argv[])
#define TERMINAL_COMMAND(term, name, description) \
TERMINAL_COMMAND_INTERNAL(term, name, "", description, terminal_n::NONE_PARAMETER, NULL, false, NULL)
#define TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, type, enum_type, conversion, auto_save_after_command, filter_function, finish_function) \
volatile static type name = startValue; \
char terminal_parameter_type_ ## name [] = #type; \
\
TERMINAL_COMMAND_INTERNAL(term, name, eeprom_name, description, enum_type, terminal_parameter_type_ ## name, auto_save_after_command, &name) \
{ \
type g; \
if (argc) { \
g = conversion(argv[0]); \
if( filter_function(g, name) ){ \
type old = name; \
name = g; \
finish_function(name, old); \
} \
} \
term.print( #name ); \
term.print("="); \
term.println( name ); \
}
template <typename T>
inline bool filter_nothing(T & new_value, T old_value){return true;}
template <typename T>
inline void do_nothing(T new_value, T old_value){}
#define TERMINAL_PARAMETER_FLOAT(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, float, terminal_n::FLOAT_PARAMETER, terminal_n::atof, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_PARAMETER_DOUBLE(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, double, terminal_n::DOUBLE_PARAMETER, terminal_n::atof, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_PARAMETER_INT(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, int32_t, terminal_n::INT_PARAMETER, atoi, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_PARAMETER_UINT(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, uint32_t, terminal_n::UINT_PARAMETER, atoi, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_PARAMETER_LONGINT(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, int64_t, terminal_n::LONGINT_PARAMETER, atoi, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_PARAMETER_LONGUINT(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, uint64_t, terminal_n::LONGUINT_PARAMETER, atoi, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_PARAMETER_BOOL(term, name, eeprom_name, description, startValue, auto_save_after_command, filter_function, finish_function) \
TERMINAL_PARAMETER(term, name, eeprom_name, description, startValue, bool, terminal_n::BOOL_PARAMETER, (bool)atoi, auto_save_after_command, filter_function, finish_function)
#define TERMINAL_ADD_DEFAULT_COMMANDS(term) \
TERMINAL_COMMAND(term, save_config, "Save the config to EEPROM (save_config [eeprom_name])") \
{ \
if(argc !=0 and argc != 1){ term.println("Invalid parameters"); return; } \
if(argc == 0){ \
term.save_config_to_eeprom(); \
term.println("All is saved."); \
}else{ \
if( \
term.save_config_to_eeprom(argv[0]) \
){ \
term.println("Saved.");\
}else{ \
term.println("Failed.");\
}; \
} \
} \
\
TERMINAL_COMMAND(term, load_config, "Load config from EEPROM (load_config [eeprom_name])") \
{ \
bool print = true; \
if(argc !=0 and argc != 1){ term.println("Invalid parameters"); return; } \
if(argc == 0){ \
term.load_config_from_eeprom(print); \
}else{ \
if( \
! term.load_config_from_eeprom(argv[0], print) \
){ \
term.println("Failed.");\
}; \
} \
} \
\
TERMINAL_COMMAND(term, reset_config, "Reset to constructor configuration (reset_config [eeprom_name])") \
{ \
if(argc !=0 and argc != 1){ term.println("Invalid parameters"); return; } \
if(argc == 0){ \
term.reset_to_constructor_config(); \
term.println("All is reseted."); \
}else{ \
if( \
term.reset_to_constructor_config(argv[0]) \
){ \
term.println("Reseted.");\
}else{ \
term.println("Failed.");\
}; \
} \
} \
\
TERMINAL_COMMAND(term, print_config, "Print EEPROM configuration (print_config [eeprom_name])") \
{ \
if(argc !=0 and argc != 1){ term.println("Invalid parameters"); return; } \
if(argc == 0){ \
term.print_config_from_eeprom(); \
}else{ \
if( \
! term.print_config_from_eeprom(argv[0]) \
){ \
term.println("Failed.");\
}; \
} \
} \
\
TERMINAL_COMMAND(term, help, "Displays the help about commands") \
{ \
term.displayHelp(false); \
} \
\
TERMINAL_COMMAND(term, params, "Displays the available parameters. Usage: params [show]") \
{ \
term.params_command(argc, argv); \
} \
\
TERMINAL_COMMAND(term, echo, "Switch echo mode. Usage echo [on|off]") \
{ \
term.echo_command(argc, argv); \
}
}