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

648 lines
17 KiB
C++

/*
* 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 "terminal.hpp"
#include "serial_print.hpp"
#define STRINGIFY(x) #x
namespace terminal_n {
void Terminal::reset(){
terminal_pos = 0;
terminal_last_pos = 0;
terminal_buffer[0] = '\0';
terminal_last_ok = false;
print_prompt();
}
/**
* Stops the terminal
*/
void Terminal::disable(){
disabled = true;
}
void Terminal::enable(){
terminal_last_ok = false;
disabled = false;
}
/**
* Write the terminal prompt
*/
void Terminal::print_prompt(){
print(PROMPT);
}
void Terminal::setup(HardwareSerial* serial){
io = serial;
if(terminal_command_count >= terminal_max_commands){
println("There are too many commands. Increase the number of commands.");
println("Define the macro TERMINAL_MAX_COMMANDS with a bigger number before including the terminal library.");
}
if(serial){
silent = false;
}else{
silent = true;
}
enable();
print_prompt();
check_eeprom_names();
load_config_from_eeprom();
}
void Terminal::terminal_silent(bool silent)
{
silent = silent;
}
const terminal_command *
Terminal::find_command(
char *command_name, uint32_t command_name_length
){
uint32_t i;
for(i=0; i<terminal_command_count; i++){
const terminal_command *command = terminal_commands[i];
if(
strlen(command->name) == command_name_length &&
strncmp(terminal_buffer, command->name, command_name_length) == 0
){
return command;
}
}
return NULL;
}
/***
* Executes the given command with given parameters
*/
bool Terminal::terminal_execute(
char *command_name, uint32_t command_name_length, uint32_t argc, char **argv
){
uint32_t i;
const terminal_command *command;
// Try to find and execute the command
command = find_command(command_name, command_name_length);
if(command != NULL){
command->command(argc, argv);
if(command->auto_save_after_command){
save_value_to_eeprom(command);
}
}
// If it fails, try to parse the command as an allocation (a=b)
if (command == NULL) {
for(i=0; i<command_name_length; i++){
if(command_name[i] == '='){
command_name[i] = '\0';
command_name_length = strlen(command_name);
command = find_command(command_name, command_name_length);
if(command && command->parameter){
argv[0] = command_name+i+1;
argv[1] = NULL;
argc = 1;
command->command(argc, argv);
if(command->auto_save_after_command){
save_value_to_eeprom(command);
}
}else{
command = NULL;
}
if(!command){
print("Unknown parameter: ");
write(command_name, command_name_length);
println("");
return false;
}
}
}
}
// If it fails again, display the "unknown command" message
if (command == NULL) {
print("Unknown command: ");
write(command_name, command_name_length);
println("");
return false;
}
return true;
}
/***
* Process the receive buffer to parse the command and executes it
*/
void Terminal::terminal_process(){
char *saveptr;
uint32_t command_name_length;
uint32_t argc = 0;
char* argv[terminal_max_arguments+1];
println("");
strtok_r(terminal_buffer, " ", &saveptr);
while (
(argv[argc] = strtok_r(NULL, " ", &saveptr)) != NULL &&
argc < terminal_max_arguments
){
*(argv[argc]-1) = '\0';
argc++;
}
if(terminal_max_arguments == argc){
println("Nb. of parameters are limited.");
}
if (saveptr != NULL) {
*(saveptr - 1) = ' ';
}
command_name_length = strlen(terminal_buffer);
if (command_name_length > 0) {
terminal_last_ok = terminal_execute(
terminal_buffer, command_name_length, argc, argv
);
}else{
terminal_last_ok = false;
}
terminal_last_pos = terminal_pos;
terminal_pos = 0;
print_prompt();
}
/**
* Ticking the terminal, this will cause lookup for characters
* and eventually a call to the process function on new lines
*/
void Terminal::update(){
if(disabled || !terminal_has_io()){
return;
}
char c;
uint8_t input;
while(io->available()){
input = io->read();
c = (char)input;
if(c == '\0'){
continue;
}
//Return key
if(c == '\r' || c == '\n'){
if(terminal_pos == 0 && terminal_last_ok){
// If the user pressed no keys, restore the last
// command and run it again
uint32_t i;
for(i=0; i<terminal_last_pos; i++){
if(terminal_buffer[i] == '\0'){
terminal_buffer[i] = ' ';
}
}
terminal_pos = terminal_last_pos;
}
terminal_buffer[terminal_pos] = '\0';
terminal_process();
//Back key
}else if(c == '\x7f'){
if(terminal_pos > 0){
terminal_pos--;
print("\x8 \x8");
}
//Special key
}else if(c == '\x1b'){
while(!io->available());
io->read();
while(!io->available());
io->read();
//Others
}else{
terminal_buffer[terminal_pos] = c;
if(terminal_echo_mode){
print(c);
}
if(terminal_pos < terminal_buffer_size-1){
terminal_pos++;
}
}
}
}
/**
* Registers a command
*/
void Terminal::register_command(struct terminal_command *command){
if(terminal_command_count >= terminal_max_commands) return;
terminal_commands[terminal_command_count] = command;
terminal_command_count = terminal_command_count + 1;
}
void Terminal::displayHelp(bool parameter){
char buffer[1000];
uint32_t i;
if(parameter){
println("Available parameters:");
} else {
print(documentation);
println("Available commands:");
}
println("");
for(i=0; i<terminal_command_count; i++){
const struct terminal_command *command = terminal_commands[i];
if((command->parameter!=0) != parameter){
continue;
}
int32_t namesize = strlen(command->name);
int32_t descsize = strlen(command->description);
int32_t typesize = (command->parameter_type == NULL) ? 0 : strlen(command->parameter_type);
memcpy(buffer, command->name, namesize);
buffer[namesize++] = ' ';
buffer[namesize++] = ' ';
buffer[namesize++] = ':';
buffer[namesize++] = ' ';
buffer[namesize++] = ' ';
// buffer[namesize++] = '\r';
//buffer[namesize++] = '\n';
//buffer[namesize++] = '\t';
memcpy(buffer+namesize, command->description, descsize);
if(typesize){
buffer[namesize+descsize++] = ' ';
buffer[namesize+descsize++] = '(';
memcpy(buffer+namesize+descsize, command->parameter_type, typesize);
buffer[namesize+descsize+typesize++] = ')';
}
buffer[namesize+descsize+typesize++] = '\r';
buffer[namesize+descsize+typesize++] = '\n';
write(buffer, namesize+descsize+typesize);
}
}
void Terminal::terminal_params_show(){
for(uint32_t i=0; i<terminal_command_count; i++){
const terminal_command *command = terminal_commands[i];
if (command->parameter) {
command->command(0, NULL);
}
}
}
void Terminal::echo_command(uint32_t argc, char *argv[]){
if(
(argc == 1 && strcmp("on", argv[0])==0)
||
(argc == 0 && terminal_echo_mode==false)
){
terminal_echo_mode = true;
println("Echo enabled");
}else{
terminal_echo_mode = false;
println("Echo disabled");
}
}
void Terminal::params_command(uint32_t argc, char *argv[]){
if(argc && strcmp(argv[0], "show")==0){
terminal_params_show();
}else{
displayHelp(true);
}
}
bool Terminal::have_to_be_saved_in_eeprom(const terminal_command *command){
if(command->parameter == NONE_PARAMETER) return false;
if(command->eeprom_name[0] == '\0') return false;
return true;
}
void Terminal::save_value_to_eeprom(const terminal_command *command){
if(!have_to_be_saved_in_eeprom(command)) return;
const constexpr bool read_only = false;
preferences.begin(
preferences_namespace, read_only, preferences_nvs_partition
);
switch(command->parameter){
case FLOAT_PARAMETER :
preferences.putFloat(command->eeprom_name, *static_cast<volatile const float*>(command->value_ptr));
break;
case DOUBLE_PARAMETER :
preferences.putDouble(command->eeprom_name, *static_cast<volatile const double*>(command->value_ptr));
break;
case INT_PARAMETER :
preferences.putInt(command->eeprom_name, *static_cast<volatile const int32_t*>(command->value_ptr));
break;
case UINT_PARAMETER :
preferences.putUInt(command->eeprom_name, *static_cast<volatile const uint32_t*>(command->value_ptr));
break;
case LONGINT_PARAMETER :
preferences.putLong64(command->eeprom_name, *static_cast<volatile const int64_t*>(command->value_ptr));
break;
case LONGUINT_PARAMETER :
preferences.putULong64(command->eeprom_name, *static_cast<volatile const uint64_t*>(command->value_ptr));
break;
case BOOL_PARAMETER :
preferences.putBool(command->eeprom_name, *static_cast<volatile const bool*>(command->value_ptr));
break;
case NONE_PARAMETER :
default :
println("Error -- line:" STRINGIFY(__LINE__) ", file" __FILE__ );
break;
}
preferences.end();
}
void Terminal::save_config_to_eeprom(){
for(uint32_t i=0; i<terminal_command_count; i++){
save_value_to_eeprom(terminal_commands[i]);
}
}
bool Terminal::save_config_to_eeprom(const char* eeprom_name){
for(uint32_t i=0; i<terminal_command_count; i++){
if(
!strncmp(
terminal_commands[i]->eeprom_name, eeprom_name,
MAX_EEPROM_NAME_LENGTH
)
){
save_value_to_eeprom(terminal_commands[i]);
return true;
}
}
return false;
}
void Terminal::print_value_from_eeprom(const struct terminal_command *command){
if(!have_to_be_saved_in_eeprom(command)) return;
const constexpr bool read_only = true;
preferences.begin(
preferences_namespace, read_only, preferences_nvs_partition
);
if( preferences.isKey(command->eeprom_name) ){
print("\"");
print(command->eeprom_name);
print("\"");
print(" : ");
switch(command->parameter){
case FLOAT_PARAMETER :
print(preferences.getFloat(command->eeprom_name));
break;
case DOUBLE_PARAMETER :
print(preferences.getDouble(command->eeprom_name));
break;
case INT_PARAMETER :
print(preferences.getInt(command->eeprom_name));
break;
case UINT_PARAMETER :
print(preferences.getUInt(command->eeprom_name));
break;
case LONGINT_PARAMETER :
print(preferences.getLong64(command->eeprom_name));
break;
case LONGUINT_PARAMETER :
print(preferences.getULong64(command->eeprom_name));
break;
case BOOL_PARAMETER :
print(preferences.getBool(command->eeprom_name));
break;
case NONE_PARAMETER :
default :
println("Error -- line:" STRINGIFY(__LINE__) ", file" __FILE__ );
break;
}
println(";");
}
preferences.end();
}
void Terminal::print_config_from_eeprom(){
println("{");
for (uint32_t i=0; i<terminal_command_count; i++) {
print_value_from_eeprom(terminal_commands[i]);
}
println("}");
}
bool Terminal::print_config_from_eeprom(const char* eeprom_name){
for(uint32_t i=0; i<terminal_command_count; i++){
if(
!strncmp(
terminal_commands[i]->eeprom_name, eeprom_name,
MAX_EEPROM_NAME_LENGTH
)
){
print_value_from_eeprom(terminal_commands[i]);
return true;
}
}
return false;
}
void Terminal::reset_to_constructor_config(){
print("Reseting to constructor configuration ... ");
const constexpr bool read_only = false;
preferences.begin(
preferences_namespace, read_only, preferences_nvs_partition
);
preferences.clear();
preferences.end();
println("Done.");
println("Please reset the card.");
}
bool Terminal::reset_to_constructor_config(const char* eeprom_name){
for(uint32_t i=0; i<terminal_command_count; i++){
if(
!strncmp(
terminal_commands[i]->eeprom_name, eeprom_name,
MAX_EEPROM_NAME_LENGTH
)
){
const constexpr bool read_only = false;
preferences.begin(
preferences_namespace, read_only, preferences_nvs_partition
);
if( preferences.isKey(eeprom_name) ){
preferences.remove(eeprom_name);
}
preferences.end();
return true;
}
}
return false;
}
void Terminal::check_eeprom_names(){
for(int j=1; j<terminal_command_count; j++){
for(int i=0; i<j-1; i++){
if(
strncmp(terminal_commands[i]->eeprom_name, "", MAX_EEPROM_NAME_LENGTH)
&&
strncmp(terminal_commands[j]->eeprom_name, "", MAX_EEPROM_NAME_LENGTH)
&&
!strncmp(
terminal_commands[i]->eeprom_name, terminal_commands[j]->eeprom_name,
MAX_EEPROM_NAME_LENGTH
)
){
while(true){
print("Error : ");
print(terminal_commands[i]->name);
print(" and ");
print(terminal_commands[i]->name);
print(" have the same eeprom name : ");
println(terminal_commands[i]->eeprom_name);
delay(1000);
}
}
}
}
}
void Terminal::print_saved_parameter(const terminal_command *command){
if(!have_to_be_saved_in_eeprom(command)) return;
print(command->name);
print(" : ");
switch(command->parameter){
case FLOAT_PARAMETER :
print(*static_cast<volatile float*>(command->value_ptr));
break;
case DOUBLE_PARAMETER :
print(*static_cast<volatile double*>(command->value_ptr));
break;
case INT_PARAMETER :
print(*static_cast<volatile int32_t*>(command->value_ptr));
break;
case UINT_PARAMETER :
print(*static_cast<volatile uint32_t*>(command->value_ptr));
break;
case LONGINT_PARAMETER :
print(*static_cast<volatile int64_t*>(command->value_ptr));
break;
case LONGUINT_PARAMETER :
print(*static_cast<volatile uint64_t*>(command->value_ptr));
break;
case BOOL_PARAMETER :
print(*static_cast<volatile bool*>(command->value_ptr));
break;
case NONE_PARAMETER :
default :
println("Error -- line:" STRINGIFY(__LINE__) ", file" __FILE__ );
break;
}
}
void Terminal::load_value_from_eeprom(
const terminal_command *command, bool enable_print
){
if(!have_to_be_saved_in_eeprom(command)) return;
const constexpr bool read_only = true;
preferences.begin(
preferences_namespace, read_only, preferences_nvs_partition
);
if( preferences.isKey(command->eeprom_name) ){
switch(command->parameter){
case FLOAT_PARAMETER :
*static_cast<volatile float*>(command->value_ptr) =
preferences.getFloat(command->eeprom_name);
break;
case DOUBLE_PARAMETER :
*static_cast<volatile double*>(command->value_ptr) =
preferences.getDouble(command->eeprom_name);
break;
case INT_PARAMETER :
*static_cast<volatile int32_t*>(command->value_ptr) =
preferences.getInt(command->eeprom_name);
break;
case UINT_PARAMETER :
*static_cast<volatile uint32_t*>(command->value_ptr) =
preferences.getUInt(command->eeprom_name);
break;
case LONGINT_PARAMETER :
*static_cast<volatile int64_t*>(command->value_ptr) =
preferences.getLong64(command->eeprom_name);
break;
case LONGUINT_PARAMETER :
*static_cast<volatile uint64_t*>(command->value_ptr) =
preferences.getULong64(command->eeprom_name);
break;
case BOOL_PARAMETER :
*static_cast<volatile bool*>(command->value_ptr) =
preferences.getBool(command->eeprom_name);
break;
case NONE_PARAMETER :
default :
println("Error -- line:" STRINGIFY(__LINE__) ", file" __FILE__ );
break;
}
if(enable_print){ print("(eeprom ) "); }
}else{
if(enable_print){ print("(construction) "); }
}
if(enable_print){ print_saved_parameter(command); println("");}
preferences.end();
}
void Terminal::load_config_from_eeprom(bool enable_print){
if(enable_print){ println("Loading config :"); }
for (uint32_t i=0; i<terminal_command_count; i++) {
load_value_from_eeprom(terminal_commands[i], enable_print);
}
if(enable_print){ println("Config is Loaded."); }
}
bool Terminal::load_config_from_eeprom(
const char* eeprom_name, bool enable_print
){
for(uint32_t i=0; i<terminal_command_count; i++){
if(
!strncmp(
terminal_commands[i]->eeprom_name, eeprom_name,
MAX_EEPROM_NAME_LENGTH
)
){
load_value_from_eeprom(terminal_commands[i], enable_print);
return true;
}
}
return false;
}
}