From 5fb83c9bf71e924a0007a564da12b2e571c51a42 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 8 Feb 2025 11:44:09 +1100 Subject: [PATCH] * repeater and room server: new CLI command: "set advert.interval {mins}" --- examples/simple_repeater/main.cpp | 75 ++++++++++++++++++++++++---- examples/simple_room_server/main.cpp | 75 ++++++++++++++++++++++------ 2 files changed, 126 insertions(+), 24 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 8331c790..4632a0a5 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -19,7 +19,7 @@ /* ------------------------------ Config -------------------------------- */ -#define FIRMWARE_VER_TEXT "v3 (build: 4 Feb 2025)" +#define FIRMWARE_VER_TEXT "v3 (build: 8 Feb 2025)" #ifndef LORA_FREQ #define LORA_FREQ 915.0 @@ -51,6 +51,8 @@ #define ADMIN_PASSWORD "password" #endif +#define MIN_LOCAL_ADVERT_INTERVAL 8 + #if defined(HELTEC_LORA_V3) #include #include @@ -121,13 +123,15 @@ struct NodePrefs { // persisted to file float freq; uint8_t tx_power_dbm; uint8_t disable_fwd; - uint8_t unused[2]; + uint8_t advert_interval; // minutes + uint8_t unused; }; class MyMesh : public mesh::Mesh { RadioLibWrapper* my_radio; FILESYSTEM* _fs; mesh::MainBoard* _board; + unsigned long next_local_advert; NodePrefs _prefs; uint8_t reply_data[MAX_PACKET_PAYLOAD]; int num_clients; @@ -185,6 +189,31 @@ class MyMesh : public mesh::Mesh { return 0; // reply_len } + 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; + { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } + + return createAdvert(self_id, app_data, app_data_len); + } + protected: float getAirtimeBudgetFactor() const override { return _prefs.airtime_factor; @@ -369,6 +398,7 @@ public: { my_radio = &radio; num_clients = 0; + next_local_advert = 0; // defaults memset(&_prefs, 0, sizeof(_prefs)); @@ -381,6 +411,7 @@ public: _prefs.password[sizeof(_prefs.password)-1] = 0; // truncate if necessary _prefs.freq = LORA_FREQ; _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.advert_interval = 2; // default to 2 minutes for NEW installs } float getFreqPref() const { return _prefs.freq; } @@ -397,6 +428,8 @@ public: file.close(); } } + + updateAdvertTimer(); } void savePrefs() { @@ -413,14 +446,7 @@ public: } void sendSelfAdvertisement(int delay_millis) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - - mesh::Packet* pkt = createAdvert(self_id, app_data, app_data_len); + mesh::Packet* pkt = createSelfAdvert(); if (pkt) { sendFlood(pkt, delay_millis); } else { @@ -428,6 +454,19 @@ public: } } + void loop() { + mesh::Mesh::loop(); + + if (next_local_advert && millisHasNowPassed(next_local_advert)) { + mesh::Packet* pkt = createSelfAdvert(); + if (pkt) { + sendZeroHop(pkt); + } + + updateAdvertTimer(); // schedule next local advert + } + } + void handleCommand(uint32_t sender_timestamp, const char* command, char reply[]) { while (*command == ' ') command++; // skip leading spaces @@ -467,6 +506,7 @@ public: // change admin password strncpy(_prefs.password, &command[9], sizeof(_prefs.password)-1); _prefs.password[sizeof(_prefs.password)-1] = 0; // truncate if necesary + 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) { @@ -475,9 +515,22 @@ public: _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, "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 + checkAdvertInterval(); savePrefs(); strcpy(reply, "OK"); } else if (memcmp(config, "repeat ", 7) == 0) { @@ -486,10 +539,12 @@ public: 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, "tx ", 3) == 0) { diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index f956ab85..533b3cd9 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -19,7 +19,7 @@ /* ------------------------------ Config -------------------------------- */ -#define FIRMWARE_VER_TEXT "v4 (build: 4 Feb 2025)" +#define FIRMWARE_VER_TEXT "v4 (build: 8 Feb 2025)" #ifndef LORA_FREQ #define LORA_FREQ 915.0 @@ -59,6 +59,8 @@ #define MAX_UNSYNCED_POSTS 16 #endif +#define MIN_LOCAL_ADVERT_INTERVAL 8 + #if defined(HELTEC_LORA_V3) #include @@ -138,12 +140,14 @@ struct NodePrefs { // persisted to file float freq; uint8_t tx_power_dbm; uint8_t disable_fwd; - uint8_t unused[2]; + uint8_t advert_interval; // minutes + uint8_t unused; }; class MyMesh : public mesh::Mesh { RadioLibWrapper* my_radio; FILESYSTEM* _fs; + unsigned long next_local_advert; NodePrefs _prefs; uint8_t reply_data[MAX_PACKET_PAYLOAD]; int num_clients; @@ -242,6 +246,31 @@ 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; + { + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } + + return createAdvert(self_id, app_data, app_data_len); + } + protected: float getAirtimeBudgetFactor() const override { return _prefs.airtime_factor; @@ -480,6 +509,7 @@ public: : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables) { my_radio = &radio; + next_local_advert = 0; // defaults memset(&_prefs, 0, sizeof(_prefs)); @@ -493,6 +523,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 num_clients = 0; next_post_idx = 0; @@ -515,6 +546,8 @@ public: file.close(); } } + + updateAdvertTimer(); } void savePrefs() { @@ -530,17 +563,10 @@ public: } } - void sendSelfAdvertisement() { - uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - - mesh::Packet* pkt = createAdvert(self_id, app_data, app_data_len); + void sendSelfAdvertisement(int delay_millis) { + mesh::Packet* pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, 1200); // add slight delay + sendFlood(pkt, delay_millis); } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } @@ -552,7 +578,7 @@ public: if (memcmp(command, "reboot", 6) == 0) { board.reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { - sendSelfAdvertisement(); + sendSelfAdvertisement(400); strcpy(reply, "OK - Advert sent"); } else if (memcmp(command, "clock sync", 10) == 0) { uint32_t curr = getRTCClock()->getCurrentTime(); @@ -587,6 +613,18 @@ public: _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, "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 @@ -657,6 +695,15 @@ public: next_push = futureMillis(SYNC_PUSH_INTERVAL); } + if (next_local_advert && millisHasNowPassed(next_local_advert)) { + mesh::Packet* pkt = createSelfAdvert(); + if (pkt) { + sendZeroHop(pkt); + } + + updateAdvertTimer(); // schedule next local advert + } + // TODO: periodically check for OLD/inactive entries in known_clients[], and evict } }; @@ -760,7 +807,7 @@ void setup() { } // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(); + the_mesh.sendSelfAdvertisement(2000); } void loop() {