465 lines
14 KiB
C++
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); \
|
|
}
|
|
|
|
}
|