Skip to content

Commit 6850428

Browse files
author
Tom Mackintosh
committed
merged camp mode base with new autonomous firmware
2 parents 1f10672 + c266a07 commit 6850428

5 files changed

Lines changed: 254 additions & 2 deletions

File tree

examples/companion_radio/DataStore.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,26 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
230230
file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 90
231231
file.read((uint8_t *)&_prefs.flood_max, sizeof(_prefs.flood_max)); // 91
232232

233+
// Optional appended fields for newer firmware versions.
234+
// Keep backward compatibility with older prefs files.
235+
_prefs.autonomous_enabled = 0;
236+
_prefs.autonomous_channel_hash = 0;
237+
_prefs.autonomous_interval_sec = 30;
238+
_prefs.autonomous_min_distance_m = 0;
239+
240+
if (file.available() >= (int)sizeof(_prefs.autonomous_enabled)) {
241+
file.read((uint8_t *)&_prefs.autonomous_enabled, sizeof(_prefs.autonomous_enabled));
242+
}
243+
if (file.available() >= (int)sizeof(_prefs.autonomous_channel_hash)) {
244+
file.read((uint8_t *)&_prefs.autonomous_channel_hash, sizeof(_prefs.autonomous_channel_hash));
245+
}
246+
if (file.available() >= (int)sizeof(_prefs.autonomous_interval_sec)) {
247+
file.read((uint8_t *)&_prefs.autonomous_interval_sec, sizeof(_prefs.autonomous_interval_sec));
248+
}
249+
if (file.available() >= (int)sizeof(_prefs.autonomous_min_distance_m)) {
250+
file.read((uint8_t *)&_prefs.autonomous_min_distance_m, sizeof(_prefs.autonomous_min_distance_m));
251+
}
252+
233253
file.close();
234254
}
235255
}
@@ -266,6 +286,12 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
266286
file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 90
267287
file.write((uint8_t *)&_prefs.flood_max, sizeof(_prefs.flood_max)); // 91
268288

289+
// Appended autonomous tracker fields.
290+
file.write((uint8_t *)&_prefs.autonomous_enabled, sizeof(_prefs.autonomous_enabled));
291+
file.write((uint8_t *)&_prefs.autonomous_channel_hash, sizeof(_prefs.autonomous_channel_hash));
292+
file.write((uint8_t *)&_prefs.autonomous_interval_sec, sizeof(_prefs.autonomous_interval_sec));
293+
file.write((uint8_t *)&_prefs.autonomous_min_distance_m, sizeof(_prefs.autonomous_min_distance_m));
294+
269295
file.close();
270296
}
271297
}

examples/companion_radio/MyMesh.cpp

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
#define CMD_GET_RADIO_SETTINGS 61
6565
#define CMD_SET_MAX_HOPS 62 // Adaptive forwarding control
6666

67+
// Custom TEAM extensions (kept out of stock command space)
68+
#define CMD_SET_FORWARD_LIST 74 // [count][6-byte pubkey prefix] * count
69+
#define CMD_GET_AUTONOMOUS_SETTINGS 75 // returns persisted autonomous settings
70+
#define CMD_SET_AUTONOMOUS_SETTINGS 76 // set persisted autonomous settings
71+
6772
// Stats sub-types for CMD_GET_STATS
6873
#define STATS_TYPE_CORE 0
6974
#define STATS_TYPE_RADIO 1
@@ -96,6 +101,7 @@
96101
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
97102
#define RESP_CODE_AUTOADD_CONFIG 25
98103
#define RESP_ALLOWED_REPEAT_FREQ 26
104+
#define RESP_CODE_AUTONOMOUS_SETTINGS 27
99105

100106
#define SEND_TIMEOUT_BASE_MILLIS 500
101107
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
@@ -463,9 +469,122 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
463469
return false;
464470
}
465471

472+
bool MyMesh::extractSenderNameFromGroupPayload(const mesh::Packet* packet, char* sender_name, size_t max_len) {
473+
if (!packet || !sender_name || max_len < 2 || packet->payload_len <= 6) return false;
474+
475+
// Group text payload format starts at payload[5], usually: "SenderName: message"
476+
const uint8_t* text_data = &packet->payload[5];
477+
size_t text_len = packet->payload_len - 5;
478+
size_t out_idx = 0;
479+
480+
for (size_t i = 0; i < text_len && out_idx < max_len - 1; i++) {
481+
char c = (char)text_data[i];
482+
if (c == '\0') break;
483+
if (c == ':') {
484+
sender_name[out_idx] = 0;
485+
return out_idx > 0;
486+
}
487+
sender_name[out_idx++] = c;
488+
}
489+
490+
sender_name[out_idx] = 0;
491+
return false;
492+
}
493+
494+
bool MyMesh::lookupContactPrefixByName(const char* sender_name, uint8_t out_pub_key_prefix[6]) {
495+
if (!sender_name || !sender_name[0] || !out_pub_key_prefix) return false;
496+
497+
ContactInfo contact;
498+
const int total = getNumContacts();
499+
for (int i = 0; i < total; i++) {
500+
if (getContactByIdx(i, contact) && strcmp(contact.name, sender_name) == 0) {
501+
memcpy(out_pub_key_prefix, contact.id.pub_key, 6);
502+
return true;
503+
}
504+
}
505+
return false;
506+
}
507+
508+
bool MyMesh::isInForwardList(const uint8_t* pub_key_prefix) const {
509+
if (!pub_key_prefix) return false;
510+
for (uint8_t i = 0; i < forward_list_count; i++) {
511+
if (memcmp(forward_list[i], pub_key_prefix, 6) == 0) {
512+
return true;
513+
}
514+
}
515+
return false;
516+
}
517+
518+
void MyMesh::updateForwardListPolicyState() {
519+
if (forward_list_updated_at == 0) return;
520+
521+
const unsigned long now = millis();
522+
const unsigned long age = now - forward_list_updated_at;
523+
524+
// 10 min: list expires (fallback to flood_max behavior)
525+
if (age >= 10UL * 60UL * 1000UL && forward_list_count > 0) {
526+
forward_list_count = 0;
527+
memset(forward_list, 0, sizeof(forward_list));
528+
MESH_DEBUG_PRINTLN("FORWARD: whitelist expired (10m) -> reverting to flood_max behavior");
529+
}
530+
531+
// 60 min: disable forwarding entirely until app refreshes policy
532+
if (age >= 60UL * 60UL * 1000UL && !forwarding_hard_disabled) {
533+
forwarding_hard_disabled = true;
534+
MESH_DEBUG_PRINTLN("FORWARD: hard-disabled (60m stale policy)");
535+
}
536+
}
537+
538+
bool MyMesh::shouldSendAutonomousUpdate() {
539+
if (_prefs.autonomous_enabled == 0) return false;
540+
if (_serial && _serial->isConnected()) return false; // autonomous mode is for disconnected operation
541+
542+
unsigned long interval_ms = (unsigned long)_prefs.autonomous_interval_sec * 1000UL;
543+
if (interval_ms < 10000UL) interval_ms = 10000UL;
544+
545+
const unsigned long now = millis();
546+
if (autonomous_last_sent_at != 0 && (now - autonomous_last_sent_at) < interval_ms) {
547+
return false;
548+
}
549+
550+
if (_prefs.autonomous_min_distance_m == 0 || !autonomous_has_last_fix) {
551+
return true;
552+
}
553+
554+
const double dLat = (sensors.node_lat - autonomous_last_lat) * DEG_TO_RAD;
555+
const double dLon = (sensors.node_lon - autonomous_last_lon) * DEG_TO_RAD;
556+
const double a = sin(dLat / 2.0) * sin(dLat / 2.0)
557+
+ cos(autonomous_last_lat * DEG_TO_RAD) * cos(sensors.node_lat * DEG_TO_RAD)
558+
* sin(dLon / 2.0) * sin(dLon / 2.0);
559+
const double c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
560+
const double distance_m = 6371000.0 * c;
561+
562+
return distance_m >= _prefs.autonomous_min_distance_m;
563+
}
564+
565+
void MyMesh::runAutonomousMode() {
566+
if (!shouldSendAutonomousUpdate()) return;
567+
568+
if (advert()) {
569+
autonomous_last_sent_at = millis();
570+
autonomous_last_lat = sensors.node_lat;
571+
autonomous_last_lon = sensors.node_lon;
572+
autonomous_has_last_fix = true;
573+
MESH_DEBUG_PRINTLN("AUTO: autonomous advert sent (interval=%lus, min_dist=%um)",
574+
(unsigned long)_prefs.autonomous_interval_sec,
575+
(unsigned int)_prefs.autonomous_min_distance_m);
576+
}
577+
}
578+
466579
bool MyMesh::allowPacketForward(const mesh::Packet* packet) {
467580
// Adaptive forwarding control for companion radios
468581
// Respects flood_max set by app via CMD_SET_MAX_HOPS (default 0 = disabled)
582+
updateForwardListPolicyState();
583+
584+
if (forwarding_hard_disabled) {
585+
MESH_DEBUG_PRINTLN("FORWARD: Blocked - hard disabled by stale whitelist policy");
586+
return false;
587+
}
469588

470589
if (_prefs.flood_max == 0) {
471590
MESH_DEBUG_PRINTLN("FORWARD: Blocked - forwarding disabled (flood_max=0)");
@@ -493,6 +612,20 @@ bool MyMesh::allowPacketForward(const mesh::Packet* packet) {
493612
return false;
494613
}
495614
}
615+
616+
// If whitelist is active, only forward messages from senders in list (when resolvable).
617+
if (forward_list_count > 0 && payload_type == PAYLOAD_TYPE_GRP_TXT) {
618+
char sender_name[32];
619+
uint8_t sender_prefix[6];
620+
621+
if (extractSenderNameFromGroupPayload(packet, sender_name, sizeof(sender_name)) &&
622+
lookupContactPrefixByName(sender_name, sender_prefix)) {
623+
if (!isInForwardList(sender_prefix)) {
624+
MESH_DEBUG_PRINTLN("FORWARD: Blocked by whitelist (sender=%s)", sender_name);
625+
return false;
626+
}
627+
}
628+
}
496629
}
497630

498631
// Allow forwarding for:
@@ -840,6 +973,14 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
840973
dirty_contacts_expiry = 0;
841974
memset(advert_paths, 0, sizeof(advert_paths));
842975
memset(send_scope.key, 0, sizeof(send_scope.key));
976+
memset(forward_list, 0, sizeof(forward_list));
977+
forward_list_count = 0;
978+
forward_list_updated_at = 0;
979+
forwarding_hard_disabled = false;
980+
autonomous_last_sent_at = 0;
981+
autonomous_last_lat = 0.0;
982+
autonomous_last_lon = 0.0;
983+
autonomous_has_last_fix = false;
843984

844985
// defaults
845986
memset(&_prefs, 0, sizeof(_prefs));
@@ -852,6 +993,10 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
852993
_prefs.tx_power_dbm = LORA_TX_POWER;
853994
_prefs.gps_enabled = 0; // GPS disabled by default
854995
_prefs.gps_interval = 0; // No automatic GPS updates by default
996+
_prefs.autonomous_enabled = 0;
997+
_prefs.autonomous_channel_hash = 0;
998+
_prefs.autonomous_interval_sec = 30;
999+
_prefs.autonomous_min_distance_m = 0;
8551000
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
8561001
}
8571002

@@ -891,6 +1036,9 @@ void MyMesh::begin(bool has_display) {
8911036
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER);
8921037
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
8931038
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
1039+
_prefs.autonomous_enabled = constrain(_prefs.autonomous_enabled, 0, 1);
1040+
_prefs.autonomous_interval_sec = constrain(_prefs.autonomous_interval_sec, 10, 3600);
1041+
_prefs.autonomous_min_distance_m = constrain(_prefs.autonomous_min_distance_m, 0, 5000);
8941042

8951043
#ifdef BLE_PIN_CODE // 123456 by default
8961044
if (_prefs.ble_pin == 0) {
@@ -1026,7 +1174,7 @@ void MyMesh::handleCmdFrame(size_t len) {
10261174
out_frame[i++] = 0; // Null terminator for device name
10271175

10281176
// Firmware capability flags - tells app what features this firmware supports
1029-
out_frame[i++] = CAPABILITY_FORWARDING; // Currently supports adaptive forwarding
1177+
out_frame[i++] = CAPABILITY_FORWARDING | CAPABILITY_AUTONOMOUS;
10301178

10311179
_serial->writeFrame(out_frame, i);
10321180
} else if (cmd_frame[0] == CMD_SEND_TXT_MSG && len >= 14) {
@@ -1884,6 +2032,56 @@ void MyMesh::handleCmdFrame(size_t len) {
18842032
memcpy(&out_frame[i], &r->upper_freq, 4); i += 4;
18852033
}
18862034
_serial->writeFrame(out_frame, i);
2035+
} else if (cmd_frame[0] == CMD_SET_FORWARD_LIST && len >= 2) {
2036+
uint8_t count = cmd_frame[1];
2037+
if (count > FORWARD_LIST_MAX) count = FORWARD_LIST_MAX;
2038+
2039+
const int needed = 2 + (count * 6);
2040+
if (len < needed) {
2041+
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
2042+
} else {
2043+
for (uint8_t i = 0; i < count; i++) {
2044+
memcpy(forward_list[i], &cmd_frame[2 + (i * 6)], 6);
2045+
}
2046+
for (uint8_t i = count; i < FORWARD_LIST_MAX; i++) {
2047+
memset(forward_list[i], 0, 6);
2048+
}
2049+
2050+
forward_list_count = count;
2051+
forward_list_updated_at = millis();
2052+
forwarding_hard_disabled = false;
2053+
2054+
MESH_DEBUG_PRINTLN("FORWARD: whitelist updated (%u entries)", (unsigned int)forward_list_count);
2055+
writeOKFrame();
2056+
}
2057+
} else if (cmd_frame[0] == CMD_GET_AUTONOMOUS_SETTINGS) {
2058+
int i = 0;
2059+
out_frame[i++] = RESP_CODE_AUTONOMOUS_SETTINGS;
2060+
out_frame[i++] = _prefs.autonomous_enabled;
2061+
out_frame[i++] = _prefs.autonomous_channel_hash;
2062+
memcpy(&out_frame[i], &_prefs.autonomous_interval_sec, sizeof(_prefs.autonomous_interval_sec));
2063+
i += sizeof(_prefs.autonomous_interval_sec);
2064+
memcpy(&out_frame[i], &_prefs.autonomous_min_distance_m, sizeof(_prefs.autonomous_min_distance_m));
2065+
i += sizeof(_prefs.autonomous_min_distance_m);
2066+
_serial->writeFrame(out_frame, i);
2067+
} else if (cmd_frame[0] == CMD_SET_AUTONOMOUS_SETTINGS && len >= 7) {
2068+
uint8_t enabled = constrain(cmd_frame[1], 0, 1);
2069+
uint8_t channel_hash = cmd_frame[2];
2070+
uint16_t interval_sec = 0;
2071+
uint16_t min_distance_m = 0;
2072+
memcpy(&interval_sec, &cmd_frame[3], sizeof(interval_sec));
2073+
memcpy(&min_distance_m, &cmd_frame[5], sizeof(min_distance_m));
2074+
2075+
interval_sec = constrain(interval_sec, 10, 3600);
2076+
min_distance_m = constrain(min_distance_m, 0, 5000);
2077+
2078+
_prefs.autonomous_enabled = enabled;
2079+
_prefs.autonomous_channel_hash = channel_hash;
2080+
_prefs.autonomous_interval_sec = interval_sec;
2081+
_prefs.autonomous_min_distance_m = min_distance_m;
2082+
2083+
savePrefs();
2084+
writeOKFrame();
18872085
} else {
18882086
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
18892087
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
@@ -2098,6 +2296,9 @@ void MyMesh::checkSerialInterface() {
20982296
void MyMesh::loop() {
20992297
BaseChatMesh::loop();
21002298

2299+
updateForwardListPolicyState();
2300+
runAutonomousMode();
2301+
21012302
if (_cli_rescue) {
21022303
checkCLIRescueCmd();
21032304
} else {

examples/companion_radio/MyMesh.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
161161
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
162162

163163
private:
164+
static const uint8_t FORWARD_LIST_MAX = 20;
165+
164166
void writeOKFrame();
165167
void writeErrFrame(uint8_t err_code);
166168
void writeDisabledFrame();
@@ -179,6 +181,13 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
179181
void checkSerialInterface();
180182
bool isValidClientRepeatFreq(uint32_t f) const;
181183

184+
bool extractSenderNameFromGroupPayload(const mesh::Packet* packet, char* sender_name, size_t max_len);
185+
bool lookupContactPrefixByName(const char* sender_name, uint8_t out_pub_key_prefix[6]);
186+
bool isInForwardList(const uint8_t* pub_key_prefix) const;
187+
void updateForwardListPolicyState();
188+
bool shouldSendAutonomousUpdate();
189+
void runAutonomousMode();
190+
182191
// helpers, short-cuts
183192
void saveChannels() { _store->saveChannels(this); }
184193
void saveContacts() { _store->saveContacts(this); }
@@ -205,6 +214,16 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
205214
uint32_t sign_data_len;
206215
unsigned long dirty_contacts_expiry;
207216

217+
uint8_t forward_list[FORWARD_LIST_MAX][6];
218+
uint8_t forward_list_count;
219+
unsigned long forward_list_updated_at;
220+
bool forwarding_hard_disabled;
221+
222+
unsigned long autonomous_last_sent_at;
223+
double autonomous_last_lat;
224+
double autonomous_last_lon;
225+
bool autonomous_has_last_fix;
226+
208227
TransportKey send_scope;
209228

210229
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];

examples/companion_radio/NodePrefs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ struct NodePrefs { // persisted to file
3030
uint8_t autoadd_config; // bitmask for auto-add contacts config
3131
uint8_t flood_max; // Max hops for packet forwarding (0=disabled, 1-127=max hops)
3232
uint8_t client_repeat;
33+
34+
// Autonomous tracker settings
35+
uint8_t autonomous_enabled; // 0=disabled, 1=enabled
36+
uint8_t autonomous_channel_hash; // reserved for channel-targeted autonomous telemetry
37+
uint16_t autonomous_interval_sec; // autonomous interval (seconds)
38+
uint16_t autonomous_min_distance_m; // min movement trigger in meters (0=disabled)
3339
};

variants/heltec_t114/platformio.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ build_flags =
108108
; -D BLE_DEBUG_LOGGING=1
109109
-D OFFLINE_QUEUE_SIZE=256
110110
; -D MESH_PACKET_LOGGING=1
111-
; -D MESH_DEBUG=1
111+
-D MESH_DEBUG=1
112112
build_src_filter = ${Heltec_t114.build_src_filter}
113113
+<helpers/nrf52/SerialBLEInterface.cpp>
114114
+<../examples/companion_radio/*.cpp>

0 commit comments

Comments
 (0)