648 lines
17 KiB
C++
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;
|
|
}
|
|
|
|
|
|
}
|