diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7f974d29..589f5aa1 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include /* ------------------------------ Config -------------------------------- */ @@ -52,8 +53,6 @@ #define ADMIN_PASSWORD "password" #endif -#define MIN_LOCAL_ADVERT_INTERVAL 60 - #if defined(HELTEC_LORA_V3) #include #include @@ -83,16 +82,6 @@ /* ------------------------------ Code -------------------------------- */ -// Believe it or not, this std C function is busted on some platforms! -static uint32_t _atoi(const char* sp) { - uint32_t n = 0; - while (*sp && *sp >= '0' && *sp <= '9') { - n *= 10; - n += (*sp++ - '0'); - } - return n; -} - #define CMD_GET_STATUS 0x01 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -126,29 +115,14 @@ struct ClientInfo { // NOTE: need to space the ACK and the reply text apart (in CLI) #define CLI_REPLY_DELAY_MILLIS 1500 -struct NodePrefs { // persisted to file - float airtime_factor; - char node_name[32]; - double node_lat, node_lon; - char password[16]; - float freq; - uint8_t tx_power_dbm; - uint8_t disable_fwd; - uint8_t advert_interval; // minutes / 2 - uint8_t unused; - float rx_delay_base; - float tx_delay_factor; - char guest_password[16]; - float direct_tx_delay_factor; -}; - -class MyMesh : public mesh::Mesh { +class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RadioLibWrapper* my_radio; FILESYSTEM* _fs; mesh::MainBoard* _board; unsigned long next_local_advert; bool _logging; NodePrefs _prefs; + CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; ClientInfo known_clients[MAX_CLIENTS]; @@ -203,20 +177,6 @@ class MyMesh : public mesh::Mesh { return 0; // reply_len } - void checkAdvertInterval() { - if (_prefs.advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) { - _prefs.advert_interval = 0; // turn it off, now that device has been manually configured - } - } - - void updateAdvertTimer() { - if (_prefs.advert_interval > 0) { // schedule local advert timer - next_local_advert = futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); - } else { - next_local_advert = 0; // stop the timer - } - } - mesh::Packet* createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; @@ -460,7 +420,7 @@ protected: } uint8_t temp[166]; - handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); int text_len = strlen((char *) &temp[5]); if (text_len > 0) { uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); @@ -507,7 +467,8 @@ protected: public: MyMesh(mesh::MainBoard& board, RadioLibWrapper& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables) - : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _board(&board) + : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), + _board(&board), _cli(board, this, &_prefs, this) { my_radio = &radio; memset(known_clients, 0, sizeof(known_clients)); @@ -531,6 +492,8 @@ public: float getFreqPref() const { return _prefs.freq; } uint8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } + CommonCLI* getCLI() { return &_cli; } + void begin(FILESYSTEM* fs) { mesh::Mesh::begin(); _fs = fs; @@ -546,7 +509,9 @@ public: updateAdvertTimer(); } - void savePrefs() { + const char* getFirmwareVer() override { return FIRMWARE_VER_TEXT; } + + void savePrefs() override { #if defined(NRF52_PLATFORM) File file = _fs->open("/node_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } @@ -559,7 +524,18 @@ public: } } - void sendSelfAdvertisement(int delay_millis) { + bool formatFileSystem() override { +#if defined(NRF52_PLATFORM) + return InternalFS.format(); +#elif defined(ESP32) + return SPIFFS.format(); +#else + #error "need to implement file system erase" + return false; +#endif + } + + void sendSelfAdvertisement(int delay_millis) override { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { sendFlood(pkt, delay_millis); @@ -568,6 +544,32 @@ public: } } + void updateAdvertTimer() override { + if (_prefs.advert_interval > 0) { // schedule local advert timer + next_local_advert = futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); + } else { + next_local_advert = 0; // stop the timer + } + } + + void setLoggingOn(bool enable) override { _logging = enable; } + + void eraseLogFile() override { + _fs->remove(PACKET_LOG_FILE); + } + + void dumpLogFile() override { + File f = _fs->open(PACKET_LOG_FILE); + if (f) { + while (f.available()) { + int c = f.read(); + if (c < 0) break; + Serial.print((char)c); + } + f.close(); + } + } + void loop() { mesh::Mesh::loop(); @@ -580,168 +582,6 @@ public: updateAdvertTimer(); // schedule next local advert } } - - void handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { - while (*command == ' ') command++; // skip leading spaces - - if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) - memcpy(reply, command, 3); // reflect the prefix back - reply += 3; - command += 3; - } - - if (memcmp(command, "reboot", 6) == 0) { - board.reboot(); // doesn't return - } else if (memcmp(command, "advert", 6) == 0) { - sendSelfAdvertisement(400); - strcpy(reply, "OK - Advert sent"); - } else if (memcmp(command, "clock sync", 10) == 0) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (sender_timestamp > curr) { - getRTCClock()->setCurrentTime(sender_timestamp + 1); - strcpy(reply, "OK - clock set"); - } else { - strcpy(reply, "ERR: clock cannot go backwards"); - } - } else if (memcmp(command, "start ota", 9) == 0) { - if (_board->startOTAUpdate()) { - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error"); - } - } else if (memcmp(command, "clock", 5) == 0) { - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds) - uint32_t secs = _atoi(&command[5]); - uint32_t curr = getRTCClock()->getCurrentTime(); - if (secs > curr) { - getRTCClock()->setCurrentTime(secs); - strcpy(reply, "(OK - clock set!)"); - } else { - strcpy(reply, "(ERR: clock cannot go backwards)"); - } - } else if (memcmp(command, "password ", 9) == 0) { - // change admin password - StrHelper::strncpy(_prefs.password, &command[9], sizeof(_prefs.password)); - checkAdvertInterval(); - savePrefs(); - sprintf(reply, "password now: %s", _prefs.password); // echo back just to let admin know for sure!! - } else if (memcmp(command, "set ", 4) == 0) { - const char* config = &command[4]; - if (memcmp(config, "af ", 3) == 0) { - _prefs.airtime_factor = atof(&config[3]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "advert.interval ", 16) == 0) { - int mins = _atoi(&config[16]); - if (mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) { - sprintf(reply, "Error: min is %d mins", MIN_LOCAL_ADVERT_INTERVAL); - } else if (mins > 240) { - strcpy(reply, "Error: max is 240 mins"); - } else { - _prefs.advert_interval = (uint8_t)(mins / 2); - updateAdvertTimer(); - savePrefs(); - strcpy(reply, "OK"); - } - } else if (memcmp(config, "guest.password ", 15) == 0) { - StrHelper::strncpy(_prefs.guest_password, &config[15], sizeof(_prefs.guest_password)); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "name ", 5) == 0) { - StrHelper::strncpy(_prefs.node_name, &config[5], sizeof(_prefs.node_name)); - checkAdvertInterval(); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "repeat ", 7) == 0) { - _prefs.disable_fwd = memcmp(&config[7], "off", 3) == 0; - savePrefs(); - strcpy(reply, _prefs.disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); - } else if (memcmp(config, "lat ", 4) == 0) { - _prefs.node_lat = atof(&config[4]); - checkAdvertInterval(); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "lon ", 4) == 0) { - _prefs.node_lon = atof(&config[4]); - checkAdvertInterval(); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "rxdelay ", 8) == 0) { - float db = atof(&config[8]); - if (db >= 0) { - _prefs.rx_delay_base = db; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "txdelay ", 8) == 0) { - float f = atof(&config[8]); - if (f >= 0) { - _prefs.tx_delay_factor = f; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "direct.txdelay ", 15) == 0) { - float f = atof(&config[15]); - if (f >= 0) { - _prefs.direct_tx_delay_factor = f; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "tx ", 3) == 0) { - _prefs.tx_power_dbm = atoi(&config[3]); - savePrefs(); - strcpy(reply, "OK - reboot to apply"); - } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { - _prefs.freq = atof(&config[5]); - savePrefs(); - strcpy(reply, "OK - reboot to apply"); - } else { - sprintf(reply, "unknown config: %s", config); - } - } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) { - #if defined(NRF52_PLATFORM) - bool s = InternalFS.format(); - #elif defined(ESP32) - bool s = SPIFFS.format(); - #else - #error "need to implement file system erase" - #endif - sprintf(reply, "File system erase: %s", s ? "OK" : "Err"); - } else if (memcmp(command, "ver", 3) == 0) { - strcpy(reply, FIRMWARE_VER_TEXT); - } else if (memcmp(command, "log start", 9) == 0) { - _logging = true; - strcpy(reply, " logging on"); - } else if (memcmp(command, "log stop", 8) == 0) { - _logging = false; - strcpy(reply, " logging off"); - } else if (memcmp(command, "log erase", 9) == 0) { - _fs->remove(PACKET_LOG_FILE); - strcpy(reply, " log erased"); - } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) { - File f = _fs->open(PACKET_LOG_FILE); - if (f) { - while (f.available()) { - int c = f.read(); - if (c < 0) break; - Serial.print((char)c); - } - f.close(); - } - strcpy(reply, " EOF"); - } else { - sprintf(reply, "Unknown: %s (commands: reboot, advert, clock, set, ver, password, start ota)", command); - } - } }; #if defined(NRF52_PLATFORM) @@ -866,7 +706,7 @@ void loop() { if (len > 0 && command[len - 1] == '\r') { // received complete line command[len - 1] = 0; // replace newline with C string null terminator char reply[160]; - the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! + the_mesh.getCLI()->handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! if (reply[0]) { Serial.print(" -> "); Serial.println(reply); } diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index f653d4cb..2e9333bb 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include /* ------------------------------ Config -------------------------------- */ @@ -60,9 +61,6 @@ #define MAX_UNSYNCED_POSTS 16 #endif -#define MIN_LOCAL_ADVERT_INTERVAL 8 - - #if defined(HELTEC_LORA_V3) #include #include @@ -86,16 +84,6 @@ /* ------------------------------ Code -------------------------------- */ -// Believe it or not, this std C function is busted on some platforms! -static uint32_t _atoi(const char* sp) { - uint32_t n = 0; - while (*sp && *sp >= '0' && *sp <= '9') { - n *= 10; - n += (*sp++ - '0'); - } - return n; -} - struct ClientInfo { mesh::Identity id; uint32_t last_timestamp; // by THEIR clock @@ -133,28 +121,13 @@ struct PostInfo { #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ -struct NodePrefs { // persisted to file - float airtime_factor; - char node_name[32]; - double node_lat, node_lon; - char password[16]; - float freq; - uint8_t tx_power_dbm; - uint8_t disable_fwd; - uint8_t advert_interval; // minutes - uint8_t unused; - float rx_delay_base; - float tx_delay_factor; - char guest_password[16]; - float direct_tx_delay_factor; -}; - -class MyMesh : public mesh::Mesh { +class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RadioLibWrapper* my_radio; FILESYSTEM* _fs; mesh::MainBoard* _board; unsigned long next_local_advert; NodePrefs _prefs; + CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; int num_clients; ClientInfo known_clients[MAX_CLIENTS]; @@ -250,20 +223,6 @@ class MyMesh : public mesh::Mesh { return false; } - void checkAdvertInterval() { - if (_prefs.advert_interval < MIN_LOCAL_ADVERT_INTERVAL) { - _prefs.advert_interval = 0; // turn it off, now that device has been manually configured - } - } - - void updateAdvertTimer() { - if (_prefs.advert_interval > 0) { // schedule local advert timer - next_local_advert = futureMillis(_prefs.advert_interval * 60 * 1000); - } else { - next_local_advert = 0; // stop the timer - } - } - mesh::Packet* createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; @@ -422,7 +381,7 @@ protected: bool send_ack; if (flags == TXT_TYPE_CLI_DATA) { if (client->is_admin) { - handleAdminCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); + _cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]); temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN) send_ack = true; } else { @@ -535,7 +494,8 @@ protected: public: MyMesh(mesh::MainBoard& board, RadioLibWrapper& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) - : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _board(&board) + : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), + _board(&board), _cli(board, this, &_prefs, this) { my_radio = &radio; next_local_advert = 0; @@ -552,7 +512,7 @@ public: _prefs.freq = LORA_FREQ; _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.disable_fwd = 1; - _prefs.advert_interval = 2; // default to 2 minutes for NEW installs + _prefs.advert_interval = 1; // default to 2 minutes for NEW installs #ifdef ROOM_PASSWORD StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif @@ -567,6 +527,8 @@ public: float getFreqPref() const { return _prefs.freq; } uint8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } + CommonCLI* getCLI() { return &_cli; } + void begin(FILESYSTEM* fs) { mesh::Mesh::begin(); _fs = fs; @@ -582,7 +544,9 @@ public: updateAdvertTimer(); } - void savePrefs() { + const char* getFirmwareVer() override { return FIRMWARE_VER_TEXT; } + + void savePrefs() override { #if defined(NRF52_PLATFORM) File file = _fs->open("/node_prefs", FILE_O_WRITE); if (file) { file.seek(0); file.truncate(); } @@ -595,7 +559,18 @@ public: } } - void sendSelfAdvertisement(int delay_millis) { + bool formatFileSystem() override { + #if defined(NRF52_PLATFORM) + return InternalFS.format(); + #elif defined(ESP32) + return SPIFFS.format(); + #else + #error "need to implement file system erase" + return false; + #endif + } + + void sendSelfAdvertisement(int delay_millis) override { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { sendFlood(pkt, delay_millis); @@ -604,135 +579,18 @@ public: } } - void handleAdminCommand(uint32_t sender_timestamp, const char* command, char* reply) { - while (*command == ' ') command++; // skip leading spaces - - if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) - memcpy(reply, command, 3); // reflect the prefix back - reply += 3; - command += 3; - } - - if (memcmp(command, "reboot", 6) == 0) { - board.reboot(); // doesn't return - } else if (memcmp(command, "advert", 6) == 0) { - sendSelfAdvertisement(400); - strcpy(reply, "OK - Advert sent"); - } else if (memcmp(command, "clock sync", 10) == 0) { - uint32_t curr = getRTCClock()->getCurrentTime(); - if (sender_timestamp > curr) { - getRTCClock()->setCurrentTime(sender_timestamp + 1); - strcpy(reply, "OK - clock set"); - } else { - strcpy(reply, "ERR: clock cannot go backwards"); - } - } else if (memcmp(command, "start ota", 9) == 0) { - if (_board->startOTAUpdate()) { - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error"); - } - } else if (memcmp(command, "clock", 5) == 0) { - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds) - uint32_t secs = _atoi(&command[5]); - uint32_t curr = getRTCClock()->getCurrentTime(); - if (secs > curr) { - getRTCClock()->setCurrentTime(secs); - strcpy(reply, "(OK - clock set!)"); - } else { - strcpy(reply, "(ERR: clock cannot go backwards)"); - } - } else if (memcmp(command, "password ", 9) == 0) { - // change admin password - StrHelper::strncpy(_prefs.password, &command[9], sizeof(_prefs.password)); - savePrefs(); - sprintf(reply, "password now: %s", _prefs.password); // echo back just to let admin know for sure!! - } else if (memcmp(command, "set ", 4) == 0) { - const char* config = &command[4]; - if (memcmp(config, "af ", 3) == 0) { - _prefs.airtime_factor = atof(&config[3]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "advert.interval ", 16) == 0) { - int mins = _atoi(&config[16]); - if (mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) { - sprintf(reply, "Error: min is %d mins", MIN_LOCAL_ADVERT_INTERVAL); - } else if (mins > 120) { - strcpy(reply, "Error: max is 120 mins"); - } else { - _prefs.advert_interval = (uint8_t)mins; - updateAdvertTimer(); - savePrefs(); - strcpy(reply, "OK"); - } - } else if (memcmp(config, "guest.password ", 15) == 0) { - StrHelper::strncpy(_prefs.guest_password, &config[15], sizeof(_prefs.guest_password)); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "name ", 5) == 0) { - StrHelper::strncpy(_prefs.node_name, &config[5], sizeof(_prefs.node_name)); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "repeat ", 7) == 0) { - _prefs.disable_fwd = memcmp(&config[7], "off", 3) == 0; - savePrefs(); - strcpy(reply, _prefs.disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); - } else if (memcmp(config, "lat ", 4) == 0) { - _prefs.node_lat = atof(&config[4]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "lon ", 4) == 0) { - _prefs.node_lon = atof(&config[4]); - savePrefs(); - strcpy(reply, "OK"); - } else if (memcmp(config, "rxdelay ", 8) == 0) { - float db = atof(&config[8]); - if (db >= 0) { - _prefs.rx_delay_base = db; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "txdelay ", 8) == 0) { - float f = atof(&config[8]); - if (f >= 0) { - _prefs.tx_delay_factor = f; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "direct.txdelay ", 15) == 0) { - float f = atof(&config[15]); - if (f >= 0) { - _prefs.direct_tx_delay_factor = f; - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error, cannot be negative"); - } - } else if (memcmp(config, "tx ", 3) == 0) { - _prefs.tx_power_dbm = atoi(&config[3]); - savePrefs(); - strcpy(reply, "OK - reboot to apply"); - } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { - _prefs.freq = atof(&config[5]); - savePrefs(); - strcpy(reply, "OK - reboot to apply"); - } else { - sprintf(reply, "unknown config: %s", config); - } - } else if (memcmp(command, "ver", 3) == 0) { - strcpy(reply, FIRMWARE_VER_TEXT); + void updateAdvertTimer() override { + if (_prefs.advert_interval > 0) { // schedule local advert timer + next_local_advert = futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); } else { - strcpy(reply, "?"); // unknown command + next_local_advert = 0; // stop the timer } } + void setLoggingOn(bool enable) override { /* no-op */ } + void eraseLogFile() override { /* no-op */ } + void dumpLogFile() override { /* no-op */ } + void loop() { mesh::Mesh::loop(); @@ -902,7 +760,7 @@ void loop() { if (len > 0 && command[len - 1] == '\r') { // received complete line command[len - 1] = 0; // replace newline with C string null terminator char reply[160]; - the_mesh.handleAdminCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! + the_mesh.getCLI()->handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! if (reply[0]) { Serial.print(" -> "); Serial.println(reply); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp new file mode 100644 index 00000000..8e861a62 --- /dev/null +++ b/src/helpers/CommonCLI.cpp @@ -0,0 +1,170 @@ +#include +#include "CommonCLI.h" +#include "TxtDataHelpers.h" +#include + +// Believe it or not, this std C function is busted on some platforms! +static uint32_t _atoi(const char* sp) { + uint32_t n = 0; + while (*sp && *sp >= '0' && *sp <= '9') { + n *= 10; + n += (*sp++ - '0'); + } + return n; +} + +#define MIN_LOCAL_ADVERT_INTERVAL 60 + +void CommonCLI::checkAdvertInterval() { + if (_prefs->advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) { + _prefs->advert_interval = 0; // turn it off, now that device has been manually configured + } +} + +void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { + while (*command == ' ') command++; // skip leading spaces + + if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) + memcpy(reply, command, 3); // reflect the prefix back + reply += 3; + command += 3; + } + + if (memcmp(command, "reboot", 6) == 0) { + _board->reboot(); // doesn't return + } else if (memcmp(command, "advert", 6) == 0) { + _callbacks->sendSelfAdvertisement(400); + strcpy(reply, "OK - Advert sent"); + } else if (memcmp(command, "clock sync", 10) == 0) { + uint32_t curr = getRTCClock()->getCurrentTime(); + if (sender_timestamp > curr) { + getRTCClock()->setCurrentTime(sender_timestamp + 1); + strcpy(reply, "OK - clock set"); + } else { + strcpy(reply, "ERR: clock cannot go backwards"); + } + } else if (memcmp(command, "start ota", 9) == 0) { + if (_board->startOTAUpdate()) { + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error"); + } + } else if (memcmp(command, "clock", 5) == 0) { + uint32_t now = getRTCClock()->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(reply, "%02d:%02d - %d/%d/%d UTC", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); + } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds) + uint32_t secs = _atoi(&command[5]); + uint32_t curr = getRTCClock()->getCurrentTime(); + if (secs > curr) { + getRTCClock()->setCurrentTime(secs); + strcpy(reply, "(OK - clock set!)"); + } else { + strcpy(reply, "(ERR: clock cannot go backwards)"); + } + } else if (memcmp(command, "password ", 9) == 0) { + // change admin password + StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password)); + checkAdvertInterval(); + savePrefs(); + sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!! + } else if (memcmp(command, "set ", 4) == 0) { + const char* config = &command[4]; + if (memcmp(config, "af ", 3) == 0) { + _prefs->airtime_factor = atof(&config[3]); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "advert.interval ", 16) == 0) { + int mins = _atoi(&config[16]); + if (mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) { + sprintf(reply, "Error: min is %d mins", MIN_LOCAL_ADVERT_INTERVAL); + } else if (mins > 240) { + strcpy(reply, "Error: max is 240 mins"); + } else { + _prefs->advert_interval = (uint8_t)(mins / 2); + _callbacks->updateAdvertTimer(); + savePrefs(); + strcpy(reply, "OK"); + } + } else if (memcmp(config, "guest.password ", 15) == 0) { + StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "name ", 5) == 0) { + StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); + checkAdvertInterval(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "repeat ", 7) == 0) { + _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; + savePrefs(); + strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); + } else if (memcmp(config, "lat ", 4) == 0) { + _prefs->node_lat = atof(&config[4]); + checkAdvertInterval(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "lon ", 4) == 0) { + _prefs->node_lon = atof(&config[4]); + checkAdvertInterval(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "rxdelay ", 8) == 0) { + float db = atof(&config[8]); + if (db >= 0) { + _prefs->rx_delay_base = db; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, cannot be negative"); + } + } else if (memcmp(config, "txdelay ", 8) == 0) { + float f = atof(&config[8]); + if (f >= 0) { + _prefs->tx_delay_factor = f; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, cannot be negative"); + } + } else if (memcmp(config, "direct.txdelay ", 15) == 0) { + float f = atof(&config[15]); + if (f >= 0) { + _prefs->direct_tx_delay_factor = f; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, cannot be negative"); + } + } else if (memcmp(config, "tx ", 3) == 0) { + _prefs->tx_power_dbm = atoi(&config[3]); + savePrefs(); + strcpy(reply, "OK - reboot to apply"); + } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { + _prefs->freq = atof(&config[5]); + savePrefs(); + strcpy(reply, "OK - reboot to apply"); + } else { + sprintf(reply, "unknown config: %s", config); + } + } else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) { + bool s = _callbacks->formatFileSystem(); + sprintf(reply, "File system erase: %s", s ? "OK" : "Err"); + } else if (memcmp(command, "ver", 3) == 0) { + strcpy(reply, _callbacks->getFirmwareVer()); + } else if (memcmp(command, "log start", 9) == 0) { + _callbacks->setLoggingOn(true); + strcpy(reply, " logging on"); + } else if (memcmp(command, "log stop", 8) == 0) { + _callbacks->setLoggingOn(false); + strcpy(reply, " logging off"); + } else if (memcmp(command, "log erase", 9) == 0) { + _callbacks->eraseLogFile(); + strcpy(reply, " log erased"); + } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) { + _callbacks->dumpLogFile(); + strcpy(reply, " EOF"); + } else { + sprintf(reply, "Unknown: %s", command); + } +} diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h new file mode 100644 index 00000000..c74a46b8 --- /dev/null +++ b/src/helpers/CommonCLI.h @@ -0,0 +1,49 @@ +#pragma once + +#include "Mesh.h" + +struct NodePrefs { // persisted to file + float airtime_factor; + char node_name[32]; + double node_lat, node_lon; + char password[16]; + float freq; + uint8_t tx_power_dbm; + uint8_t disable_fwd; + uint8_t advert_interval; // minutes + uint8_t unused; + float rx_delay_base; + float tx_delay_factor; + char guest_password[16]; + float direct_tx_delay_factor; +}; + +class CommonCLICallbacks { +public: + virtual void savePrefs() = 0; + virtual const char* getFirmwareVer() = 0; + virtual bool formatFileSystem() = 0; + virtual void sendSelfAdvertisement(int delay_millis) = 0; + virtual void updateAdvertTimer() = 0; + virtual void setLoggingOn(bool enable) = 0; + virtual void eraseLogFile() = 0; + virtual void dumpLogFile() = 0; +}; + +class CommonCLI { + mesh::Mesh* _mesh; + NodePrefs* _prefs; + CommonCLICallbacks* _callbacks; + mesh::MainBoard* _board; + + mesh::RTCClock* getRTCClock() { return _mesh->getRTCClock(); } + void savePrefs() { _callbacks->savePrefs(); } + + void checkAdvertInterval(); + +public: + CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks) + : _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { } + + void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); +};