feat: working test

This commit is contained in:
Alessevan 2025-05-07 18:23:21 +02:00
parent 50cf3b3241
commit 4cd08211a3
4 changed files with 1320 additions and 60 deletions

View File

@ -1,7 +1,42 @@
#include <ESP32Servo.h>
#include <Wire.h>
#include "Adafruit_VL53L0X.h"
#include "PCF8574.h"
#include <Adafruit_PCF8574.h>
#include "terminal.hpp"
//
// ATTENTION : pour que les commandes se déclarent automatiquement au démarrage
// auprès du terminal, le fichier conteant la fonction setup() et loop() doit
// être mis en derniere position dans la liste des fichiers lors de l'édition
// des liens.
// Dans platformio, pour faire cela, il suffit de commencer le nom du fichier
// contenant les fonctions loop() et setup() par la lettre z.
// Par exemple : zzz_main.cpp.
//
extern terminal_n::Terminal terminal;
TERMINAL_PARAMETER_INT(
terminal,
power_mode, // variable name to declare
"power_mode", // EEPROM name (15 char. max),
// If you dont want to save in EEPROM, set ""
"the power mode", // description
0, // default constructor value
false, // auto save in eeprom after command modification
terminal_n::filter_nothing, // function to filter the value
terminal_n::do_nothing // function executed at the end of the command
);
TERMINAL_COMMAND(terminal, ping, "Print pong followoing by the received arguments")
{
terminal.print("pong");
for (uint32_t i=0; i<argc; i++) {
terminal.print(" ");
terminal.print(argv[i]);
}
terminal.println("");
}
#include <WiFi.h>
#include <AsyncTCP.h>
@ -29,8 +64,10 @@ struct tube {
int id;
};
const int minHeight = 50;
const int maxHeight = 445;
struct tube all_tubes[6] = {0};
const float minHeight = 50;
const float maxHeight = 445;
int minServo = 0;
int maxServo = 180;
@ -48,7 +85,7 @@ AsyncWebServer server(80);
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP Web Server</title>
<title>Balle Ping Pong flottante</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
@ -65,14 +102,14 @@ const char index_html[] PROGMEM = R"rawliteral(
</style>
</head>
<body>
<h2>ESP Web Server</h2>
<h2>Balle Ping Pong flottante</h2>
%BUTTONPLACEHOLDER%
<script>
function sendSliderValue(element) {
var slider1 = document.getElementById("slider1");
function sendSliderValue(id, element) {
var slider = document.getElementById(`slider${id}`);
var xhr = new XMLHttpRequest();
xhr.open("GET", "/update?output="+slider1.id+"&state=" + slider1.value, true);
xhr.open("GET", "/update?output="+id+"&state=" + slider.value, true);
xhr.send();
}
@ -86,11 +123,11 @@ String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider1\" onchange=\"sendSliderValue(this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider2\" onchange=\"sendSliderValue(this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider3\" onchange=\"sendSliderValue(this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider4\" onchange=\"sendSliderValue(this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider5\" onchange=\"sendSliderValue(this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider0\" onchange=\"sendSliderValue(0, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider1\" onchange=\"sendSliderValue(1, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider2\" onchange=\"sendSliderValue(2, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider3\" onchange=\"sendSliderValue(3, this.value)\"\"></div>";
buttons += "<div class=\"slidecontainer\"><input type=\"range\" min=\"20\" max=\"450\" value=\"250\" class=\"slider\" id=\"slider4\" onchange=\"sendSliderValue(4, this.value)\"\"></div>";
return buttons;
@ -98,27 +135,25 @@ String processor(const String& var){
return String();
}
auto mesureHeight(Adafruit_VL53L0X lox){
auto mesureHeight(Adafruit_VL53L0X lox, struct tube *tube){
VL53L0X_RangingMeasurementData_t measure;
lox.rangingTest(&measure, false); // pass in 'true' to get debug data printout!
if (measure.RangeStatus != 4) { // phase failures have incorrect data
float height = measure.RangeMilliMeter;
if (height < 1000){
curHeight = height;
if (height < 1000.0){
tube->height = height;
return height;
} else {
Serial.println("Incoherent measurement. Returning previous one.");
return curHeight;
return tube->height;
}
}
else {
Serial.println(" out of range ");
return curHeight;
return tube->height;
}
}
int requestedValue = 250;
void wifisetup() {
Serial.print("Setting AP (Access Point)…");
WiFi.softAP(ssid);
@ -140,7 +175,7 @@ void wifisetup() {
if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
requestedValue = inputMessage2.toInt();
all_tubes[0].target = inputMessage2.toFloat();
}
else {
inputMessage1 = "No message sent";
@ -157,49 +192,52 @@ void wifisetup() {
server.begin();
}
PCF8574 pcf8574(0x20);
Adafruit_PCF8574 pcf8574;
void change_pin(struct pin pin, int state) {
if (pin.is_extender) {
pcf8574.digitalWrite(pin, state);
pcf8574.digitalWrite(pin.id, state);
} else {
digitalWrite(pin, state);
digitalWrite(pin.id, state);
}
}
struct tube all_tubes[6] = {0};
Adafruit_VL53L0X tofs_talk_through[6] = {Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X(), Adafruit_VL53L0X()};
void init_tube(struct tube *tube, char id, struct pin servo_pin, struct pin gpio, struct pin xshut) {
Serial.print(" Initialiazing tube #");
Serial.println(id);
Serial.println((int) id);
tube->id = id;
tube->servo.pin = servo_pin;
tube->tof.gpio = gpio;
tube->tof.xshut = xshut;
tube->height = 250.0;
tube->target = 250.0;
tube->height = 250.0f;
tube->target = 250.0f;
change_pin(gpio, HIGH);
change_pin(xshut, HIGH);
change_pin(servo_pin, HIGH);
Serial.println("Beginning boot sequence VL53L0X");
if (!tofs_talk_through[id].begin(0x30 + id)) {
Serial.println(F("Failed to boot VL53L0X"));
while(1);
}
tube->servo.hard.attach(tube->servo.pin);
tube->servo.hard.attach(tube->servo.pin.id);
tube->servo.hard.write(minServo);
delay(5000);
Serial.println("Setuping downards");
float h = mesureHeight(tofs_talk_through[id]);
float h = mesureHeight(tofs_talk_through[id], tube);
int angle = minServo;
while (h > maxHeight) {
++angle;
tube->servo.hard.write(angle);
delay(100);
h = mesureHeight(tofs_talk_through[id]);
delay(300);
h = mesureHeight(tofs_talk_through[id], tube);
}
angle -= 2;
tube->servo.hard.write(angle);
angle -= 5;
tube->servo.last_angle = angle;
tube->servo.hard.write(tube->servo.last_angle);
tube->servo.equilibrium = angle;
Serial.println("Done.");
}
@ -207,23 +245,23 @@ void init_tube(struct tube *tube, char id, struct pin servo_pin, struct pin gpio
const int ACTIVES = 1;
void setup_all_tubes() {
const int all_xshuts[6] = {
const struct pin all_xshuts[6] = {
{.id = 17, .is_extender = 0},
{.id = P2, .is_extender = 1},
{.id = P3, .is_extender = 1},
{.id = P5, .is_extender = 1},
{.id = P7, .is_extender = 1},
{.id = P1, .is_extender = 1}
{.id = 2, .is_extender = 1},
{.id = 3, .is_extender = 1},
{.id = 5, .is_extender = 1},
{.id = 7, .is_extender = 1},
{.id = 1, .is_extender = 1}
};
const int all_gpios[6] = {
const struct pin all_gpios[6] = {
{.id = 5, .is_extender = 0},
{.id = 18, .is_extender = 0},
{.id = 19, .is_extender = 0},
{.id = P4, .is_extender = 1},
{.id = P6, .is_extender = 1},
{.id = P0, .is_extender = 1}
{.id = 4, .is_extender = 1},
{.id = 6, .is_extender = 1},
{.id = 0, .is_extender = 1}
};
const int all_servos[6] = {
const struct pin all_servos[6] = {
{.id = 32, .is_extender = 0},
{.id = 33, .is_extender = 0},
{.id = 23, .is_extender = 0},
@ -247,18 +285,33 @@ void setup_all_tubes() {
}
}
TERMINAL_ADD_DEFAULT_COMMANDS(terminal) // help, params, load_config, reset_config, echo ...
terminal_n::Terminal terminal("Pingpong flottant.\n\r\n\r");
void setup() {
Serial.begin(115200); // Starts the serial communication
while (! Serial) {
delay(1);
}
Wire.begin();
pcf8574.begin();
if (!pcf8574.begin(0x38, &Wire)) {
Serial.println("Couldn't find PCF8574");
while (1);
}
for (uint8_t p=0; p<8; p++) {
pcf8574.pinMode(p, OUTPUT);
}
Serial.println("PCF8574 seemks OK");
wifisetup();
terminal.setup(&Serial);
setup_all_tubes();
Serial.println("Done setuping have funning");
}
@ -272,28 +325,41 @@ int servoPosition(float ratio){
}
void loop() {
terminal.update();
for (int i = 0; i < ACTIVES; i++) {
struct tube tube = all_tubes[i];
tube.height = mesureHeight(tofs_talk_through[tube.id]);
struct tube *tube = all_tubes + i;
/*
Serial.print("Tube ");
Serial.println(tube->id);
*/
tube->height = mesureHeight(tofs_talk_through[tube->id], tube);
/*
Serial.print("Measure = ");
Serial.println(tube.height);
Serial.println(tube->height);
*/
int angle = 0;
float diff = tube.height - tube.target;
float diff = tube->height - tube->target;
if (abs(diff) > 5.0f) {
Serial.println("HEYEYEYEYEYEYEY");
angle = tube.servo.equilibrium + (diff * 1 / (7));
angle = tube->servo.equilibrium + (diff * 1 / (7));
} else if (abs(diff) > 2.0f) {
angle = tube.servo.equilibrium + (diff * 1 / (7));
angle = tube->servo.equilibrium + (diff * 1 / (7));
} else {
error = 0;
angle = tube.servo.equilibrium;
angle = tube->servo.equilibrium;
}
/*
Serial.print("angle = ");
Serial.print(angle);
Serial.print("; equilibrium = ");
Serial.println(tube.servo.equilibrium);
tube.servo.last_angle = angle;
tube.servo.hard.write(min(max(angle, minServo), maxServo));
Serial.println(tube->servo.equilibrium);
*/
tube->servo.last_angle = min(max(angle, minServo), maxServo);
/* Serial.print("Diff = ");
Serial.print(diff);
Serial.print("; Requested = ");
Serial.print(tube->target);
Serial.print("; Angle = ");
Serial.println(tube->servo.last_angle);
*/tube->servo.hard.write(tube->servo.last_angle);
}
delay(1);

83
prog/serial_print.hpp Normal file
View File

@ -0,0 +1,83 @@
#pragma once
template <typename T>
inline void serial_print(HardwareSerial* serial, T v){
serial->print(v);
}
template <typename T>
inline void serial_println(HardwareSerial* serial, T v){
serial->println(v);
}
template <typename T>
static inline char hex_conv(T val){
return val < 10 ? '0' + val : 'A' + (val-10);
}
static char eol[3] = "\r\n";
inline void serial_print_eol(HardwareSerial* serial){
serial->write((const uint8_t*)eol, sizeof(eol));
}
inline void serial_print_hex(HardwareSerial* serial, int32_t n){
constexpr const int SIZE_BUFF_HEX = 2*4+2;
char buff[SIZE_BUFF_HEX];
buff[0] = '0';
buff[1] = 'x';
buff[2] = hex_conv((n & 0xF0000000) >> 7*4 );
buff[3] = hex_conv((n & 0x0F000000) >> 6*4 );
buff[4] = hex_conv((n & 0x00F00000) >> 5*4 );
buff[5] = hex_conv((n & 0x000F0000) >> 4*4 );
buff[6] = hex_conv((n & 0x0000F000) >> 3*4 );
buff[7] = hex_conv((n & 0x00000F00) >> 2*4 );
buff[8] = hex_conv((n & 0x000000F0) >> 1*4 );
buff[9] = hex_conv((n & 0x0000000F) );
serial->write((const uint8_t*)&buff, SIZE_BUFF_HEX);
}
inline void serial_print_hex(HardwareSerial* serial, int64_t n){
constexpr const int SIZE_BUFF_HEX = 4*4+2;
char buff[SIZE_BUFF_HEX];
buff[0] = '0';
buff[1] = 'x';
buff[2] = hex_conv((n & 0xF000000000000000) >> 15*4 );
buff[3] = hex_conv((n & 0x0F00000000000000) >> 14*4 );
buff[4] = hex_conv((n & 0x00F0000000000000) >> 13*4 );
buff[5] = hex_conv((n & 0x000F000000000000) >> 12*4 );
buff[6] = hex_conv((n & 0x0000F00000000000) >> 11*4 );
buff[7] = hex_conv((n & 0x00000F0000000000) >> 10*4 );
buff[8] = hex_conv((n & 0x000000F000000000) >> 9*4 );
buff[9] = hex_conv((n & 0x0000000F00000000) >> 8*4 );
buff[10] = hex_conv((n & 0x00000000F0000000) >> 7*4 );
buff[11] = hex_conv((n & 0x000000000F000000) >> 6*4 );
buff[12] = hex_conv((n & 0x0000000000F00000) >> 5*4 );
buff[13] = hex_conv((n & 0x00000000000F0000) >> 4*4 );
buff[14] = hex_conv((n & 0x000000000000F000) >> 3*4 );
buff[15] = hex_conv((n & 0x0000000000000F00) >> 2*4 );
buff[16] = hex_conv((n & 0x00000000000000F0) >> 1*4 );
buff[17] = hex_conv((n & 0x000000000000000F) );
serial->write((const uint8_t*)&buff, SIZE_BUFF_HEX);
}
inline void serial_println_hex(HardwareSerial* serial, int32_t n){
serial_print_hex(serial, n);
serial_print_eol(serial);
}
inline void serial_print_hex(HardwareSerial* serial, uint32_t number){
serial->print(number);
}
inline void serial_println_hex(HardwareSerial* serial, uint32_t number){
serial->println(number);
}
inline void serial_println_hex(HardwareSerial* serial, int64_t n){
serial_print_hex(serial, n);
serial_print_eol(serial);
}
inline void serial_print_hex(HardwareSerial* serial, uint64_t number){
serial->print(number);
}
inline void serial_println_hex(HardwareSerial* serial, uint64_t number){
serial->println(number);
}

647
prog/terminal.cpp Normal file
View File

@ -0,0 +1,647 @@
/*
* 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;
}
}

464
prog/terminal.hpp Normal file
View File

@ -0,0 +1,464 @@
#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); \
}
}