From 466caebd9a7cd985037832a21c709d7f3146b918 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 3 Feb 2025 13:56:57 +1100 Subject: [PATCH] * Terminal Chat: "set ..." commands, save to "node_prefs" file, consistent with the other firmwares --- examples/simple_secure_chat/main.cpp | 124 +++++++++++++++++++++++---- src/Utils.cpp | 4 + src/Utils.h | 2 + src/helpers/BaseChatMesh.cpp | 4 +- src/helpers/BaseChatMesh.h | 2 +- 5 files changed, 116 insertions(+), 20 deletions(-) diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 41faa7314..43712e65e 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -18,6 +18,8 @@ /* ---------------------------------- CONFIGURATION ------------------------------------- */ +#define FIRMWARE_VER_TEXT "v2 (build: 3 Feb 2025)" + #ifndef LORA_FREQ #define LORA_FREQ 915.0 #endif @@ -80,8 +82,18 @@ static uint32_t _atoi(const char* sp) { /* -------------------------------------------------------------------------------------- */ +struct NodePrefs { // persisted to file + float airtime_factor; + char node_name[32]; + double node_lat, node_lon; + float freq; + uint8_t tx_power_dbm; + uint8_t unused[3]; +}; + class MyMesh : public BaseChatMesh, ContactVisitor { FILESYSTEM* _fs; + NodePrefs _prefs; uint32_t expected_ack_crc; mesh::GroupChannel* _public; unsigned long last_msg_sent; @@ -174,6 +186,12 @@ class MyMesh : public BaseChatMesh, ContactVisitor { while (*command == ' ') command++; // skip leading spaces if (memcmp(command, "meshcore://", 11) == 0) { command += 11; // skip the prefix + char *ep = strchr(command, 0); // find end of string + while (ep > command) { + ep--; + if (mesh::Utils::isHexChar(*ep)) break; // found tail end of card + *ep = 0; // remove trailing spaces and other junk + } int len = strlen(command); if (len % 2 == 0) { len >>= 1; // halve, for num bytes @@ -196,6 +214,10 @@ class MyMesh : public BaseChatMesh, ContactVisitor { } protected: + float getAirtimeBudgetFactor() const override { + return _prefs.airtime_factor; + } + void onDiscoveredContact(ContactInfo& contact, bool is_new) override { // TODO: if not in favs, prompt to add as fav(?) @@ -256,39 +278,76 @@ protected: } public: - char self_name[sizeof(ContactInfo::name)]; MyMesh(RadioLibWrapper& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables) : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables) { + // defaults + memset(&_prefs, 0, sizeof(_prefs)); + _prefs.airtime_factor = 2.0; // one third + strcpy(_prefs.node_name, "NONAME"); + _prefs.freq = LORA_FREQ; + _prefs.tx_power_dbm = LORA_TX_POWER; + command[0] = 0; curr_recipient = NULL; } + float getFreqPref() const { return _prefs.freq; } + uint8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } + void begin(FILESYSTEM& fs) { _fs = &fs; BaseChatMesh::begin(); - strcpy(self_name, "UNSET"); IdentityStore store(fs, "/identity"); - if (!store.load("_main", self_id, self_name, sizeof(self_name))) { + if (!store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))) { // legacy: node_name was from identity file self_id = mesh::LocalIdentity(getRNG()); // create new random identity store.save("_main", self_id); } + // load persisted prefs + if (_fs->exists("/node_prefs")) { + File file = _fs->open("/node_prefs"); + if (file) { + file.read((uint8_t *) &_prefs, sizeof(_prefs)); + file.close(); + } + } + loadContacts(); _public = addChannel(PUBLIC_GROUP_PSK); // pre-configure Andy's public channel } + void savePrefs() { +#if defined(NRF52_PLATFORM) + File file = _fs->open("/node_prefs", FILE_O_WRITE); + if (file) { file.seek(0); file.truncate(); } +#else + File file = _fs->open("/node_prefs", "w", true); +#endif + if (file) { + file.write((const uint8_t *)&_prefs, sizeof(_prefs)); + file.close(); + } + } + void showWelcome() { Serial.println("===== MeshCore Chat Terminal ====="); Serial.println(); - Serial.printf("WELCOME %s\n", self_name); + Serial.printf("WELCOME %s\n", _prefs.node_name); Serial.println(" (enter 'help' for basic commands)"); Serial.println(); } + void sendSelfAdvert(int delay_millis) { + auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon); + if (pkt) { + sendFlood(pkt, delay_millis); + } + } + // ContactVisitor void onContactVisit(const ContactInfo& contact) override { Serial.printf(" %s - ", contact.name); @@ -322,7 +381,7 @@ public: memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = 0; // attempt and flags - sprintf((char *) &temp[5], "%s: %s", self_name, &command[7]); // : + sprintf((char *) &temp[5], "%s: %s", _prefs.node_name, &command[7]); // : temp[5 + MAX_TEXT_LEN] = 0; // truncate if too long int len = strlen((char *) &temp[5]); @@ -343,11 +402,6 @@ public: uint32_t now = getRTCClock()->getCurrentTime(); DateTime dt = DateTime(now); Serial.printf( "%02d:%02d - %d/%d/%d UTC\n", dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year()); - } else if (memcmp(command, "name ", 5) == 0) { // set name - strncpy(self_name, &command[5], sizeof(self_name)-1); - self_name[sizeof(self_name)-1] = 0; - IdentityStore store(*_fs, "/identity"); // update IdentityStore - store.save("_main", self_id, self_name); } else if (memcmp(command, "time ", 5) == 0) { // set time (to epoch seconds) uint32_t secs = _atoi(&command[5]); setClock(secs); @@ -365,7 +419,7 @@ public: Serial.println(" Err: no recipient selected"); } } else if (strcmp(command, "advert") == 0) { - auto pkt = createSelfAdvert(self_name); + auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon); if (pkt) { sendZeroHop(pkt); Serial.println(" (advert sent, zero hop)."); @@ -379,8 +433,8 @@ public: Serial.println(" Done."); } } else if (memcmp(command, "card", 4) == 0) { - Serial.printf("Hello %s\n", self_name); - auto pkt = createSelfAdvert(self_name); + Serial.printf("Hello %s\n", _prefs.node_name); + auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon); if (pkt) { uint8_t len = pkt->writeTo(tmp_buf); releasePacket(pkt); // undo the obtainNewPacket() @@ -394,6 +448,38 @@ public: } } else if (memcmp(command, "import ", 7) == 0) { importCard(&command[7]); + } 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(); + Serial.println(" OK"); + } else if (memcmp(config, "name ", 5) == 0) { + strncpy(_prefs.node_name, &config[5], sizeof(_prefs.node_name)-1); + _prefs.node_name[sizeof(_prefs.node_name)-1] = 0; // truncate if nec + savePrefs(); + Serial.println(" OK"); + } else if (memcmp(config, "lat ", 4) == 0) { + _prefs.node_lat = atof(&config[4]); + savePrefs(); + Serial.println(" OK"); + } else if (memcmp(config, "lon ", 4) == 0) { + _prefs.node_lon = atof(&config[4]); + savePrefs(); + Serial.println(" OK"); + } else if (memcmp(config, "tx ", 3) == 0) { + _prefs.tx_power_dbm = atoi(&config[3]); + savePrefs(); + Serial.println(" OK - reboot to apply"); + } else if (memcmp(config, "freq ", 5) == 0) { + _prefs.freq = atof(&config[5]); + savePrefs(); + Serial.println(" OK - reboot to apply"); + } else { + Serial.printf(" ERROR: unknown config: %s\n", config); + } + } else if (memcmp(command, "ver", 3) == 0) { + Serial.println(FIRMWARE_VER_TEXT); } else if (memcmp(command, "help", 4) == 0) { Serial.println("Commands:"); Serial.println(" name "); @@ -499,13 +585,17 @@ void setup() { #error "need to define filesystem" #endif + if (LORA_FREQ != the_mesh.getFreqPref()) { + radio.setFrequency(the_mesh.getFreqPref()); + } + if (LORA_TX_POWER != the_mesh.getTxPowerPref()) { + radio.setOutputPower(the_mesh.getTxPowerPref()); + } + the_mesh.showWelcome(); // send out initial Advertisement to the mesh - auto pkt = the_mesh.createSelfAdvert(the_mesh.self_name); - if (pkt) { - the_mesh.sendFlood(pkt, 1200); // add slight delay - } + the_mesh.sendSelfAdvert(1200); // add slight delay } void loop() { diff --git a/src/Utils.cpp b/src/Utils.cpp index 60f8f0a44..186c8720a 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -115,6 +115,10 @@ static uint8_t hexVal(char c) { return 0; } +bool Utils::isHexChar(char c) { + return c == '0' || hexVal(c) > 0; +} + bool Utils::fromHex(uint8_t* dest, int dest_size, const char *src_hex) { int len = strlen(src_hex); if (len != dest_size*2) return false; // incorrect length diff --git a/src/Utils.h b/src/Utils.h index 9d662f6f8..5736b8747 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -80,6 +80,8 @@ public: * \returns the number of parts parsed (in 'parts') */ static int parseTextParts(char* text, const char* parts[], int max_num, char separator=','); + + static bool isHexChar(char c); }; } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 6f1bf6f75..26b3928db 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -1,11 +1,11 @@ #include #include -mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) { +mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, double lon) { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_CHAT, name); + AdvertDataBuilder builder(ADV_TYPE_CHAT, name, lat, lon); app_data_len = builder.encodeTo(app_data); } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 3c696e7ac..74ec9adab 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -96,7 +96,7 @@ protected: void onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) override; public: - mesh::Packet* createSelfAdvert(const char* name); + mesh::Packet* createSelfAdvert(const char* name, double lat=0.0, double lon=0.0); int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout); void resetPathTo(ContactInfo& recipient); void scanRecentContacts(int last_n, ContactVisitor* visitor);