Merge upstream/dev into mqtt-bridge-implementation

Resolve conflicts: keep MQTT/WiFi/timezone/analyzer defaults and get
handlers, add upstream neighbour sort guard, adc_multiplier and
pending_discover, and bootloader.ver get handler.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
agessamanandCursor committed 2026-02-24 16:23:11 -08:00
commit 397a48fc22
15 files changed
+187 -22

No files matched your search

+1 -1
View File
@@ -41,7 +41,7 @@
"name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_ram_size": 8388608,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
+9
View File
@@ -257,6 +257,15 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
}
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.5f);
return getRNG()->nextInt(0, 5*t + 1);
}
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.2f);
return getRNG()->nextInt(0, 5*t + 1);
}
uint8_t MyMesh::getExtraAckTransmitCount() const {
return _prefs.multi_acks;
}
+2
View File
@@ -106,6 +106,8 @@ protected:
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
uint8_t getExtraAckTransmitCount() const override;
bool filterRecvFloodPacket(mesh::Packet* packet) override;
bool allowPacketForward(const mesh::Packet* packet) override;
+71 -11
View File
@@ -326,6 +326,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
// Early exit if no neighbours to avoid unnecessary processing
int16_t neighbours_count = 0;
#if MAX_NEIGHBOURS
NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS];
#endif
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
if (neighbours[i].heard_timestamp > 0) {
neighbours_count++;
@@ -341,7 +344,6 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
}
// create copy of neighbours list, skipping empty entries so we can sort it separately from main list
NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS];
int16_t sorted_idx = 0;
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
auto neighbour = &neighbours[i];
@@ -366,6 +368,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
// sort by weakest to strongest
qsort(sorted_neighbours, neighbours_count, sizeof(NeighbourInfo*), cmp_neighbours_weakest_to_strongest);
}
#endif
// build results buffer
int results_count = 0;
@@ -380,6 +383,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
break;
}
#if MAX_NEIGHBOURS
// add next neighbour to results
auto neighbour = sorted_neighbours[index + offset];
uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp;
@@ -387,6 +391,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4;
memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1;
results_count++;
#endif
}
@@ -782,6 +787,47 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
}
}
} else if (type == CTL_TYPE_NODE_DISCOVER_RESP && packet->payload_len >= 6) {
uint8_t node_type = packet->payload[0] & 0x0F;
if (node_type != ADV_TYPE_REPEATER) {
return;
}
if (packet->payload_len < 6 + PUB_KEY_SIZE) {
MESH_DEBUG_PRINTLN("onControlDataRecv: DISCOVER_RESP pubkey too short: %d", (uint32_t)packet->payload_len);
return;
}
if (pending_discover_tag == 0 || millisHasNowPassed(pending_discover_until)) {
pending_discover_tag = 0;
return;
}
uint32_t tag;
memcpy(&tag, &packet->payload[2], 4);
if (tag != pending_discover_tag) {
return;
}
mesh::Identity id(&packet->payload[6]);
if (id.matches(self_id)) {
return;
}
putNeighbour(id, rtc_clock.getCurrentTime(), packet->getSNR());
}
}
void MyMesh::sendNodeDiscoverReq() {
uint8_t data[10];
data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0
data[1] = (1 << ADV_TYPE_REPEATER);
getRNG()->random(&data[2], 4); // tag
memcpy(&pending_discover_tag, &data[2], 4);
pending_discover_until = futureMillis(60000);
uint32_t since = 0;
memcpy(&data[6], &since, 4);
auto pkt = createControlData(data, sizeof(data));
if (pkt) {
sendZeroHop(pkt);
}
}
@@ -816,7 +862,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.airtime_factor = 1.0; // one half
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
_prefs.tx_delay_factor = 0.5f; // was 0.25f
_prefs.direct_tx_delay_factor = 0.2f; // was zero
_prefs.direct_tx_delay_factor = 0.3f; // was 0.2
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
_prefs.node_lat = ADVERT_LAT;
_prefs.node_lon = ADVERT_LON;
@@ -853,18 +899,23 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.mqtt_raw_enabled = 0; // disabled
_prefs.mqtt_tx_enabled = 0; // disabled (RX only for now)
_prefs.mqtt_status_interval = 300000; // 5 minutes
// WiFi defaults
StrHelper::strncpy(_prefs.wifi_ssid, "ssid_here", sizeof(_prefs.wifi_ssid));
StrHelper::strncpy(_prefs.wifi_password, "password_here", sizeof(_prefs.wifi_password));
// Timezone defaults (Pacific Time with DST support)
StrHelper::strncpy(_prefs.timezone_string, "America/Los_Angeles", sizeof(_prefs.timezone_string));
_prefs.timezone_offset = -8; // fallback
// Let's Mesh Analyzer defaults (both enabled by default)
_prefs.mqtt_analyzer_us_enabled = 1; // enabled
_prefs.mqtt_analyzer_eu_enabled = 1; // enabled
// Timezone defaults (Pacific Time with DST support)
StrHelper::strncpy(_prefs.timezone_string, "America/Los_Angeles", sizeof(_prefs.timezone_string));
_prefs.timezone_offset = -8; // fallback
// Let's Mesh Analyzer defaults (both enabled by default)
_prefs.mqtt_analyzer_us_enabled = 1; // enabled
_prefs.mqtt_analyzer_eu_enabled = 1; // enabled
_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier
pending_discover_tag = 0;
pending_discover_until = 0;
}
void MyMesh::begin(FILESYSTEM *fs) {
@@ -1265,6 +1316,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
} else {
strcpy(reply, "Err - ??");
}
} else if (memcmp(command, "discover.neighbors", 18) == 0) {
const char* sub = command + 18;
while (*sub == ' ') sub++;
if (*sub != 0) {
strcpy(reply, "Err - discover.neighbors has no options");
} else {
sendNodeDiscoverReq();
strcpy(reply, "OK - Discover sent");
}
} else{
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
}
+3
View File
@@ -100,6 +100,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
RegionEntry* load_stack[8];
RegionEntry* recv_pkt_region;
RateLimiter discover_limiter, anon_limiter;
uint32_t pending_discover_tag;
unsigned long pending_discover_until;
bool region_load_active;
unsigned long dirty_contacts_expiry;
#if MAX_NEIGHBOURS
@@ -121,6 +123,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
#endif
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
void sendNodeDiscoverReq();
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
+1
View File
@@ -55,6 +55,7 @@ public:
virtual uint32_t getGpio() { return 0; }
virtual void setGpio(uint32_t values) {}
virtual uint8_t getStartupReason() const = 0;
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
// Power management interface (boards with power management override these)
+11
View File
@@ -730,6 +730,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
bool valid = MQTTBridge::isConfigValid(_prefs);
sprintf(reply, "> %s", valid ? "valid" : "invalid");
#endif
} else if (memcmp(config, "bootloader.ver", 14) == 0) {
#ifdef NRF52_PLATFORM
char ver[32];
if (_board->getBootloaderVersion(ver, sizeof(ver))) {
sprintf(reply, "> %s", ver);
} else {
strcpy(reply, "> unknown");
}
#else
strcpy(reply, "ERROR: unsupported");
#endif
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
+19
View File
@@ -297,6 +297,25 @@ float NRF52Board::getMCUTemperature() {
return temp * 0.25f; // Convert to *C
}
bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
static const char BOOTLOADER_MARKER[] = "UF2 Bootloader ";
const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B
for (uint32_t i = 0; i < 0x3000 - (sizeof(BOOTLOADER_MARKER) - 1); i++) {
if (memcmp(&flash[i], BOOTLOADER_MARKER, sizeof(BOOTLOADER_MARKER) - 1) == 0) {
const char* ver = (const char*)&flash[i + sizeof(BOOTLOADER_MARKER) - 1];
size_t len = 0;
while (len < max_len - 1 && ver[len] != '\0' && ver[len] != ' ' && ver[len] != '\n' && ver[len] != '\r') {
out[len] = ver[len];
len++;
}
out[len] = '\0';
return len > 0; // bootloader string is non-empty
}
}
return false;
}
bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
+1
View File
@@ -50,6 +50,7 @@ public:
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
virtual void reboot() override { NVIC_SystemReset(); }
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) override;
+13 -7
View File
@@ -11,7 +11,7 @@ PacketQueue::PacketQueue(int max_entries) {
int PacketQueue::countBefore(uint32_t now) const {
int n = 0;
for (int j = 0; j < _num; j++) {
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now
n++;
}
return n;
@@ -21,7 +21,7 @@ mesh::Packet* PacketQueue::get(uint32_t now) {
uint8_t min_pri = 0xFF;
int best_idx = -1;
for (int j = 0; j < _num; j++) {
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now
if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries
min_pri = _pri_table[j];
best_idx = j;
@@ -55,15 +55,15 @@ mesh::Packet* PacketQueue::removeByIdx(int i) {
return item;
}
void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
bool PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
if (_num == _size) {
// TODO: log "FATAL: queue is full!"
return;
return false;
}
_table[_num] = packet;
_pri_table[_num] = priority;
_schedule_table[_num] = scheduled_for;
_num++;
return true;
}
StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size), rx_queue(pool_size) {
@@ -82,7 +82,10 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) {
}
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
send_queue.add(packet, priority, scheduled_for);
if (!send_queue.add(packet, priority, scheduled_for)) {
MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet");
free(packet);
}
}
mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
@@ -106,7 +109,10 @@ mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) {
}
void StaticPoolPacketManager::queueInbound(mesh::Packet* packet, uint32_t scheduled_for) {
rx_queue.add(packet, 0, scheduled_for);
if (!rx_queue.add(packet, 0, scheduled_for)) {
MESH_DEBUG_PRINTLN("queueInbound: rx queue full, dropping packet");
free(packet);
}
}
mesh::Packet* StaticPoolPacketManager::getNextInbound(uint32_t now) {
return rx_queue.get(now);
+1 -1
View File
@@ -11,7 +11,7 @@ class PacketQueue {
public:
PacketQueue(int max_entries);
mesh::Packet* get(uint32_t now);
void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
bool add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
int count() const { return _num; }
int countBefore(uint32_t now) const;
mesh::Packet* itemAt(int i) const { return _table[i]; }
+25
View File
@@ -43,6 +43,31 @@ lib_deps =
stevemarple/MicroNMEA @ ^2.0.6
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
[env:Heltec_Wireless_Tracker_companion_radio_usb]
extends = Heltec_tracker_base
build_flags =
${Heltec_tracker_base.build_flags}
-I src/helpers/ui
-I examples/companion_radio/ui-new
-D DISPLAY_ROTATION=1
-D DISPLAY_CLASS=ST7735Display
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
; -D BLE_PIN_CODE=123456 ; HWT will use display for pin
; -D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_tracker_base.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
+<helpers/ui/ST7735Display.cpp>
lib_deps =
${Heltec_tracker_base.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_Wireless_Tracker_companion_radio_ble]
extends = Heltec_tracker_base
build_flags =
@@ -26,7 +26,9 @@ build_src_filter = ${esp32_base.build_src_filter}
+<helpers/ui/SH1106Display.cpp>
+<helpers/esp32/TBeamBoard.cpp>
+<helpers/sensors>
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
board_build.partitions = default_8MB.csv
board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
lib_deps =
${esp32_base.lib_deps}
lewisxhe/XPowersLib @ ^0.2.7
@@ -199,3 +201,27 @@ build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter}
lib_deps =
${T_Beam_S3_Supreme_SX1262.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:T_Beam_S3_Supreme_SX1262_companion_radio_wifi]
extends = T_Beam_S3_Supreme_SX1262
build_flags =
${T_Beam_S3_Supreme_SX1262.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
-D WIFI_SSID='"WIFI_SSID"'
-D WIFI_PWD='"Password"'
; -D WIFI_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=8
; -D MESH_DEBUG=1
; -D ARDUHAL_LOG_LEVEL=4
; -D CORE_DEBUG_LEVEL=4
build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${T_Beam_S3_Supreme_SX1262.lib_deps}
densaugeo/base64 @ ~1.4.0
+1 -1
View File
@@ -65,7 +65,7 @@ build_flags =
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
+<../examples/simple_repeater>
+<../examples/simple_secure_chat/main.cpp>
lib_deps =
${LilyGo_TLora_V2_1_1_6.lib_deps}
densaugeo/base64 @ ~1.4.0
+2
View File
@@ -94,6 +94,8 @@ build_flags = ${M5Stack_Unit_C6L.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
-D ARDUINO_USB_CDC_ON_BOOT=1
-D ARDUINO_USB_MODE=1
build_src_filter = ${M5Stack_Unit_C6L.build_src_filter}
+<helpers/esp32/*.cpp>
-<helpers/esp32/ESPNOWRadio.cpp>