From 73222600b258e7acad424918d8360d03f764b966 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:41:57 +0100 Subject: [PATCH] merge everything important up until d05d6abab8b52c0f20a0f85a0939c74bc762b4ad meschore/dev --- .gitignore | 3 ++ zephcore/adapters/board/ZephyrBoard.cpp | 29 +++++++++++++++ zephcore/adapters/board/ZephyrBoard.h | 1 + zephcore/adapters/ota/wifi_ota.c | 2 +- zephcore/app/CompanionMesh.cpp | 12 +++++-- zephcore/app/CompanionMesh.h | 1 + zephcore/app/RepeaterMesh.cpp | 46 +++++++++++++++++++++++- zephcore/app/RepeaterMesh.h | 7 ++-- zephcore/boards/common/logging.conf | 3 +- zephcore/boards/common/prod.conf | 2 -- zephcore/boards/common/wifi_ota.conf | 3 -- zephcore/helpers/CommonCLI.cpp | 17 ++++++--- zephcore/helpers/CommonCLI.h | 2 +- zephcore/helpers/NodePrefs.h | 2 +- zephcore/include/mesh/Board.h | 2 ++ zephcore/src/StaticPoolPacketManager.cpp | 13 ++++--- 16 files changed, 120 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 2ba9592..faaf13e 100644 --- a/.gitignore +++ b/.gitignore @@ -79,5 +79,8 @@ core meshcore/src/eraser_main.cpp zephcore/AUDIT_FINDINGS.md +# Upstream tracking (local only) +UPSTREAM_TRACKER.md + # Side projects (not part of main firmware) /doom/ diff --git a/zephcore/adapters/board/ZephyrBoard.cpp b/zephcore/adapters/board/ZephyrBoard.cpp index 0586091..e08d56d 100644 --- a/zephcore/adapters/board/ZephyrBoard.cpp +++ b/zephcore/adapters/board/ZephyrBoard.cpp @@ -175,6 +175,35 @@ bool ZephyrBoard::startOTAUpdate(const char *id, char reply[]) #endif } +bool ZephyrBoard::getBootloaderVersion(char *out, size_t max_len) +{ +#if defined(CONFIG_SOC_SERIES_NRF52X) || defined(CONFIG_SOC_SERIES_NRF52) + /* Scan flash for UF2 bootloader version string. + * info.txt lives somewhere in the 0xFB000-0xFE000 range depending + * on SoftDevice version and bootloader build. */ + static const char MARKER[] = "UF2 Bootloader "; + const uint8_t *flash = (const uint8_t *)0x000FB000; + + for (uint32_t i = 0; i < 0x3000 - (sizeof(MARKER) - 1); i++) { + if (memcmp(&flash[i], MARKER, sizeof(MARKER) - 1) == 0) { + const char *ver = (const char *)&flash[i + sizeof(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; + } + } +#else + (void)out; + (void)max_len; +#endif + return false; +} + void ZephyrBoard::clearBootloaderMagic() { #ifdef NRF52_GPREGRET diff --git a/zephcore/adapters/board/ZephyrBoard.h b/zephcore/adapters/board/ZephyrBoard.h index c43b9bc..c4c2eab 100644 --- a/zephcore/adapters/board/ZephyrBoard.h +++ b/zephcore/adapters/board/ZephyrBoard.h @@ -16,6 +16,7 @@ public: const char *getManufacturerName() const override; void reboot() override; void rebootToBootloader(); /* Reboot into UF2 mass storage bootloader */ + bool getBootloaderVersion(char *version, size_t max_len) override; bool startOTAUpdate(const char *id, char reply[]) override; /* Reboot into BLE OTA DFU */ void clearBootloaderMagic(); /* Clear stale GPREGRET values at startup */ uint8_t getStartupReason() const override; diff --git a/zephcore/adapters/ota/wifi_ota.c b/zephcore/adapters/ota/wifi_ota.c index ef83ecc..0d547b2 100644 --- a/zephcore/adapters/ota/wifi_ota.c +++ b/zephcore/adapters/ota/wifi_ota.c @@ -29,7 +29,7 @@ #include #include -LOG_MODULE_REGISTER(wifi_ota, LOG_LEVEL_DBG); +LOG_MODULE_REGISTER(wifi_ota, LOG_LEVEL_INF); /* ========== Configuration ========== */ diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index 59305e9..3776978 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -1095,10 +1095,16 @@ void CompanionMesh::onRawDataRecv(mesh::Packet *packet) sendPush(buf[0], &buf[1], i - 1); } -/* Dispatcher tuning - uses prefs for configurable behavior */ +/* Dispatcher tuning - hard-coded for companion repeat mode (matches Arduino) */ +uint32_t CompanionMesh::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 CompanionMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.3f); + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.2f); return getRNG()->nextInt(0, 5 * t + 1); } @@ -1752,7 +1758,7 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len) if (len >= 2) { int8_t power = (int8_t)data[1]; if (power >= -9 && power <= MAX_LORA_TX_POWER) { - prefs.tx_power_dbm = (uint8_t)power; + prefs.tx_power_dbm = power; _store->savePrefs(prefs); if (_radio_reconfig_cb) _radio_reconfig_cb(); sendPacketOk(); diff --git a/zephcore/app/CompanionMesh.h b/zephcore/app/CompanionMesh.h index b1fbb34..3eda585 100644 --- a/zephcore/app/CompanionMesh.h +++ b/zephcore/app/CompanionMesh.h @@ -214,6 +214,7 @@ protected: void sendFloodScoped(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t delay_millis = 0) override; /* Dispatcher tuning (uses prefs) */ + uint32_t getRetransmitDelay(const mesh::Packet *packet) override; uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override; uint8_t getDutyCyclePercent() const override; uint8_t getExtraAckTransmitCount() const override; diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index 8cc9d66..0d7ae09 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -731,6 +731,39 @@ void RepeaterMesh::onControlDataRecv(mesh::Packet* packet) { sendZeroHop(resp, getRetransmitDelay(resp) * 4); } } + } 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) return; + + /* Only accept responses matching our pending discover tag */ + 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, getRTCClock()->getCurrentTime(), packet->getSNR()); + } +} + +void RepeaterMesh::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(30000); + uint32_t since = 0; + memcpy(&data[6], &since, 4); + + auto pkt = createControlData(data, sizeof(data)); + if (pkt) { + sendZeroHop(pkt); } } @@ -752,6 +785,8 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil _logging = false; region_load_active = false; recv_pkt_region = nullptr; + pending_discover_tag = 0; + pending_discover_until = 0; #if MAX_NEIGHBOURS > 0 for (int i = 0; i < MAX_NEIGHBOURS; i++) { @@ -850,7 +885,7 @@ void RepeaterMesh::dumpLogFile() { LOG_INF("Log dump not implemented"); } -void RepeaterMesh::setTxPower(uint8_t power_dbm) { +void RepeaterMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } @@ -1122,6 +1157,15 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char* } 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); } diff --git a/zephcore/app/RepeaterMesh.h b/zephcore/app/RepeaterMesh.h index a92f6f0..0c0b718 100644 --- a/zephcore/app/RepeaterMesh.h +++ b/zephcore/app/RepeaterMesh.h @@ -25,7 +25,7 @@ #include "RepeaterDataStore.h" #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0-zephyr" + #define FIRMWARE_VERSION "v1.13.0-zephyr" #endif #ifndef FIRMWARE_BUILD_DATE @@ -92,6 +92,8 @@ class RepeaterMesh : 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 > 0 @@ -105,6 +107,7 @@ class RepeaterMesh : public mesh::Mesh, public CommonCLICallbacks { int matching_peer_indexes[MAX_CLIENTS]; 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); @@ -171,7 +174,7 @@ public: void setLoggingOn(bool enable) override { _logging = enable; } void eraseLogFile() override; void dumpLogFile() override; - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char* reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; void formatStatsReply(char* reply) override; diff --git a/zephcore/boards/common/logging.conf b/zephcore/boards/common/logging.conf index e08143b..badc2f7 100644 --- a/zephcore/boards/common/logging.conf +++ b/zephcore/boards/common/logging.conf @@ -7,8 +7,7 @@ # Avoid DBG level — it causes system workqueue stack overflow. CONFIG_ZEPHCORE_BLE_LOG_LEVEL_INF=y -# Deferred mode (non-blocking) — this is the Kconfig default, but explicit for clarity -CONFIG_LOG_MODE_DEFERRED=y +# Deferred mode (non-blocking) — buffer/thread config CONFIG_LOG_BUFFER_SIZE=8192 CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=1 CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 diff --git a/zephcore/boards/common/prod.conf b/zephcore/boards/common/prod.conf index 85f8d66..f5ffb8f 100644 --- a/zephcore/boards/common/prod.conf +++ b/zephcore/boards/common/prod.conf @@ -7,8 +7,6 @@ # Disable logging subsystem (saves ~62KB flash, ~2-5mA power) CONFIG_LOG=n -CONFIG_LOG_BACKEND_UART=n -CONFIG_LOG_BACKEND_RTT=n # Disable asserts (saves flash, removes CMake warning) CONFIG_ASSERT=n diff --git a/zephcore/boards/common/wifi_ota.conf b/zephcore/boards/common/wifi_ota.conf index 9f6660e..ab23380 100644 --- a/zephcore/boards/common/wifi_ota.conf +++ b/zephcore/boards/common/wifi_ota.conf @@ -49,9 +49,6 @@ CONFIG_STREAM_FLASH=y CONFIG_IMG_BLOCK_BUF_SIZE=4096 CONFIG_IMG_ERASE_PROGRESSIVELY=y -# ========== Debug logging (temporary) ========== -CONFIG_NET_LOG=y - # ========== Heap (WiFi + TCP/IP + HTTP needs more than default) ========== CONFIG_HEAP_MEM_POOL_SIZE=65536 diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index a3afa58..016b937 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -115,10 +115,10 @@ void CommonCLI::loadPrefs(const char* path) { _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); _prefs->sf = constrain(_prefs->sf, (uint8_t)5, (uint8_t)12); _prefs->cr = constrain(_prefs->cr, (uint8_t)5, (uint8_t)8); - _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, (uint8_t)1, (uint8_t)30); + _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, (int8_t)-9, (int8_t)30); #ifdef CONFIG_ZEPHCORE_MAX_TX_POWER_DBM if (_prefs->tx_power_dbm > CONFIG_ZEPHCORE_MAX_TX_POWER_DBM) { - _prefs->tx_power_dbm = (uint8_t)CONFIG_ZEPHCORE_MAX_TX_POWER_DBM; + _prefs->tx_power_dbm = (int8_t)CONFIG_ZEPHCORE_MAX_TX_POWER_DBM; } #endif _prefs->multi_acks = constrain(_prefs->multi_acks, (uint8_t)0, (uint8_t)1); @@ -404,7 +404,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } *reply = 0; } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { - snprintf(reply, CLI_REPLY_SIZE, "> %u", (uint32_t)_prefs->tx_power_dbm); + snprintf(reply, CLI_REPLY_SIZE, "> %d", (int)_prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { snprintf(reply, CLI_REPLY_SIZE, "> %.3f", (double)_prefs->freq); } else if (memcmp(config, "public.key", 10) == 0) { @@ -412,6 +412,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { snprintf(reply, CLI_REPLY_SIZE, "> %s", _callbacks->getRole()); + } else if (memcmp(config, "bootloader.ver", 14) == 0) { + char ver[32]; + if (_board->getBootloaderVersion(ver, sizeof(ver))) { + snprintf(reply, CLI_REPLY_SIZE, "> %s", ver); + } else { + strcpy(reply, "> unknown"); + } } else if (memcmp(config, "adc.multiplier", 14) == 0) { float adc_mult = _board->getAdcMultiplier(); if (adc_mult == 0.0f) { @@ -581,10 +588,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch val = CONFIG_ZEPHCORE_MAX_TX_POWER_DBM; } #endif - _prefs->tx_power_dbm = (uint8_t)val; + _prefs->tx_power_dbm = (int8_t)val; savePrefs(); _callbacks->setTxPower(_prefs->tx_power_dbm); - snprintf(reply, CLI_REPLY_SIZE, "OK - tx power=%u dBm", (uint32_t)_prefs->tx_power_dbm); + snprintf(reply, CLI_REPLY_SIZE, "OK - tx power=%d dBm", (int)_prefs->tx_power_dbm); } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { _prefs->freq = atof(&config[5]); savePrefs(); diff --git a/zephcore/helpers/CommonCLI.h b/zephcore/helpers/CommonCLI.h index 85d600e..3676a7b 100644 --- a/zephcore/helpers/CommonCLI.h +++ b/zephcore/helpers/CommonCLI.h @@ -34,7 +34,7 @@ public: virtual void setLoggingOn(bool enable) = 0; virtual void eraseLogFile() = 0; virtual void dumpLogFile() = 0; - virtual void setTxPower(uint8_t power_dbm) = 0; + virtual void setTxPower(int8_t power_dbm) = 0; virtual void formatNeighborsReply(char* reply) = 0; virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { // no-op by default diff --git a/zephcore/helpers/NodePrefs.h b/zephcore/helpers/NodePrefs.h index 71e09cc..81b2f31 100644 --- a/zephcore/helpers/NodePrefs.h +++ b/zephcore/helpers/NodePrefs.h @@ -30,7 +30,7 @@ struct NodePrefs { double node_lat, node_lon; char password[16]; float freq; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t disable_fwd; // repeater: disable forwarding uint8_t advert_interval; // minutes / 2 uint8_t flood_advert_interval; // hours diff --git a/zephcore/include/mesh/Board.h b/zephcore/include/mesh/Board.h index 56a77c6..4968126 100644 --- a/zephcore/include/mesh/Board.h +++ b/zephcore/include/mesh/Board.h @@ -6,6 +6,7 @@ #pragma once #include +#include #include namespace mesh { @@ -28,6 +29,7 @@ public: virtual uint32_t getGpio() { return 0; } virtual void setGpio(uint32_t values) { (void)values; } virtual uint8_t getStartupReason() const = 0; + virtual bool getBootloaderVersion(char *version, size_t max_len) { (void)version; (void)max_len; return false; } virtual bool startOTAUpdate(const char *id, char reply[]) { (void)id; (void)reply; return false; } virtual bool isExternalPowered() { return false; } diff --git a/zephcore/src/StaticPoolPacketManager.cpp b/zephcore/src/StaticPoolPacketManager.cpp index 6bbd4f3..31bf1d1 100644 --- a/zephcore/src/StaticPoolPacketManager.cpp +++ b/zephcore/src/StaticPoolPacketManager.cpp @@ -65,12 +65,13 @@ struct PacketQueue { return item; } - void add(Packet *packet, uint8_t priority, uint32_t scheduled_for) { - if (_num >= QUEUE_SIZE) return; + bool add(Packet *packet, uint8_t priority, uint32_t scheduled_for) { + if (_num >= QUEUE_SIZE) return false; _table[_num] = packet; _pri_table[_num] = priority; _schedule_table[_num] = scheduled_for; _num++; + return true; } int count() const { return _num; } @@ -104,7 +105,9 @@ void StaticPoolPacketManager::free(Packet *packet) void StaticPoolPacketManager::queueOutbound(Packet *packet, uint8_t priority, uint32_t scheduled_for) { - _send_queue.add(packet, priority, scheduled_for); + if (!_send_queue.add(packet, priority, scheduled_for)) { + free(packet); + } } Packet *StaticPoolPacketManager::getNextOutbound(uint32_t now) @@ -134,7 +137,9 @@ Packet *StaticPoolPacketManager::removeOutboundByIdx(int i) void StaticPoolPacketManager::queueInbound(Packet *packet, uint32_t scheduled_for) { - _rx_queue.add(packet, 0, scheduled_for); + if (!_rx_queue.add(packet, 0, scheduled_for)) { + free(packet); + } } Packet *StaticPoolPacketManager::getNextInbound(uint32_t now)