Update MeshCore features and OTA layout handling
This commit is contained in:
1 parent
0c4619bcc3
commit
47993d178e
74 files changed
+1696
-1169
No files matched your search
+12
-8
@@ -220,6 +220,7 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
void Dispatcher::logPacketStart(const char* direction, const Packet* packet, int len) {
|
||||
if (!isUsbLoggingEnabled()) return;
|
||||
#if defined(MESH_COMPACT_PACKET_LOGGING)
|
||||
(void)packet;
|
||||
Serial.printf("%s %d\n", direction, len);
|
||||
@@ -232,6 +233,7 @@ void Dispatcher::logPacketStart(const char* direction, const Packet* packet, int
|
||||
}
|
||||
|
||||
void Dispatcher::logPacketEnd(const Packet* packet) {
|
||||
if (!isUsbLoggingEnabled()) return;
|
||||
#if defined(MESH_COMPACT_PACKET_LOGGING)
|
||||
(void)packet;
|
||||
#else
|
||||
@@ -598,17 +600,19 @@ void Dispatcher::checkRecv() {
|
||||
}
|
||||
if (pkt) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
logPacketStart("RX", pkt, pkt->getRawLength());
|
||||
if (isUsbLoggingEnabled()) {
|
||||
logPacketStart("RX", pkt, pkt->getRawLength());
|
||||
#if !defined(MESH_COMPACT_PACKET_LOGGING)
|
||||
Serial.printf(" SNR=%d RSSI=%d score=%d time=%d", (int)pkt->getSNR(),
|
||||
(int)rssi, (int)(score * 1000), air_time);
|
||||
Serial.printf(" SNR=%d RSSI=%d score=%d time=%d", (int)pkt->getSNR(),
|
||||
(int)rssi, (int)(score * 1000), air_time);
|
||||
|
||||
static uint8_t packet_hash[MAX_HASH_SIZE];
|
||||
pkt->calculatePacketHash(packet_hash);
|
||||
Serial.print(" hash=");
|
||||
mesh::Utils::printHex(Serial, packet_hash, MAX_HASH_SIZE);
|
||||
logPacketEnd(pkt);
|
||||
static uint8_t packet_hash[MAX_HASH_SIZE];
|
||||
pkt->calculatePacketHash(packet_hash);
|
||||
Serial.print(" hash=");
|
||||
mesh::Utils::printHex(Serial, packet_hash, MAX_HASH_SIZE);
|
||||
logPacketEnd(pkt);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
logRx(pkt, pkt->getRawLength(), score); // hook for custom logging
|
||||
|
||||
|
||||
+15
-6
@@ -3,6 +3,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
#include "helpers/UsbLogging.h"
|
||||
|
||||
#define MAX_HASH_SIZE 8
|
||||
#define PUB_KEY_SIZE 32
|
||||
@@ -24,23 +25,23 @@
|
||||
|
||||
#if MESH_DEBUG && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define MESH_DEBUG_PRINT(F, ...) do { if (Serial.availableForWrite() > 0) { Serial.printf("DEBUG: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define MESH_DEBUG_PRINTLN(F, ...) do { if (Serial.availableForWrite() > 0) { Serial.printf("DEBUG: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#define MESH_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled() && Serial.availableForWrite() > 0) { Serial.printf("DEBUG: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define MESH_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled() && Serial.availableForWrite() > 0) { Serial.printf("DEBUG: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define MESH_DEBUG_PRINT(...) {}
|
||||
#define MESH_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
|
||||
#if BRIDGE_DEBUG && ARDUINO
|
||||
#define BRIDGE_DEBUG_PRINTLN(F, ...) do { if (Serial.availableForWrite() > 0) { Serial.printf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__); } } while(0)
|
||||
#define BRIDGE_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled() && Serial.availableForWrite() > 0) { Serial.printf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define BRIDGE_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
|
||||
#if POWERSAVING_DEBUG && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define POWERSAVING_DEBUG_PRINT(F, ...) Serial.printf("POWERSAVING: " F, ##__VA_ARGS__)
|
||||
#define POWERSAVING_DEBUG_PRINTLN(F, ...) Serial.printf("POWERSAVING: " F "\n", ##__VA_ARGS__)
|
||||
#define POWERSAVING_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("POWERSAVING: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define POWERSAVING_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("POWERSAVING: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define POWERSAVING_DEBUG_PRINT(...) {}
|
||||
#define POWERSAVING_DEBUG_PRINTLN(...) {}
|
||||
@@ -71,7 +72,15 @@ public:
|
||||
// Default no-op: boards that don't care need not implement anything.
|
||||
virtual void onBootComplete() { /* no op */ }
|
||||
virtual uint32_t getIRQGpio() { return -1; } // not supported. Returns DIO1 (SX1262) and DIO0 (SX127x)
|
||||
virtual void sleep(uint32_t secs) { /* no op */ }
|
||||
virtual void sleep(uint32_t secs) {
|
||||
(void)secs;
|
||||
#if defined(RP2040_PLATFORM) || defined(STM32_PLATFORM)
|
||||
// These platforms have no shared deep-sleep board implementation. WFI is
|
||||
// still a real CPU idle state and preserves all configured interrupt wake
|
||||
// sources, including the radio and USB.
|
||||
__asm volatile("wfi");
|
||||
#endif
|
||||
}
|
||||
virtual uint32_t getGpio() { return 0; }
|
||||
virtual void setGpio(uint32_t values) {}
|
||||
// Returns true only for physical MCU GPIOs that are safe for the user to
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#ifdef MQTT_DEBUG
|
||||
#include <Arduino.h>
|
||||
#define ALERT_DEBUG_PRINTLN(...) Serial.printf("Alert: " __VA_ARGS__); Serial.println()
|
||||
#define ALERT_DEBUG_PRINTLN(...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("Alert: " __VA_ARGS__); Serial.println(); } } while (0)
|
||||
#else
|
||||
#define ALERT_DEBUG_PRINTLN(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
+44
-518
@@ -2250,238 +2250,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
// Observer-only top-level commands (ota check/update, tls.bundletest, alert test)
|
||||
// live in CommonCLI_Observer.cpp.
|
||||
if (handleObserverCommand(sender_timestamp, command, reply)) return;
|
||||
#if defined(PORTABLE_ESP32_RADIO_CLI)
|
||||
// Portable WiFi-heavy images keep the commands needed to commission,
|
||||
// update, and diagnose the node. Their feature-complete FULL siblings keep
|
||||
// the complete administration, logging, and external-sensor command tree.
|
||||
if (strcmp(command, "poweroff") == 0 || strcmp(command, "shutdown") == 0) {
|
||||
_board->powerOff(); // doesn't return
|
||||
} else if (strcmp(command, "reboot") == 0) {
|
||||
_board->reboot(); // doesn't return
|
||||
} else if (strcmp(command, "advert.zerohop") == 0) {
|
||||
_callbacks->sendSelfAdvertisement(1500, false);
|
||||
strcpy(reply, "OK - zerohop advert sent");
|
||||
} else if (strcmp(command, "advert") == 0) {
|
||||
_callbacks->sendSelfAdvertisement(1500, true);
|
||||
strcpy(reply, "OK - Advert sent");
|
||||
} else if (strcmp(command, "clock sync") == 0) {
|
||||
uint32_t curr = getRTCClock()->getCurrentTime();
|
||||
if (sender_timestamp > curr) {
|
||||
getRTCClock()->setCurrentTime(sender_timestamp + 1);
|
||||
_callbacks->onManualClockSet();
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
DateTime dt = DateTime(now);
|
||||
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC",
|
||||
dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
|
||||
} else {
|
||||
strcpy(reply, "ERR: clock cannot go backwards");
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
} else if (strcmp(command, "memory") == 0) {
|
||||
sprintf(reply, "Free: %d, Min: %d, Max: %d, Queue: %d, IntFree: %d, IntMax: %d, PSRAM: %d/%d",
|
||||
ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(),
|
||||
_callbacks->getQueueSize(),
|
||||
(int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
(int)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
|
||||
(int)heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
||||
(int)heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
|
||||
#endif
|
||||
} else if (memcmp(command, "start ota", 9) == 0
|
||||
&& (command[9] == 0 || command[9] == ' ')) {
|
||||
const bool force_ap = command[9] == ' ' && strcmp(&command[10], "ap") == 0;
|
||||
if (command[9] == ' ' && !force_ap) {
|
||||
strcpy(reply, "ERR: usage start ota [ap]");
|
||||
} else if (!_board->startOTAUpdate(_prefs->node_name, reply, force_ap)) {
|
||||
strcpy(reply, "Error");
|
||||
}
|
||||
#if defined(WITH_MQTT_BRIDGE) && defined(LIGHTWEIGHT_WIFI_OTA)
|
||||
else {
|
||||
_callbacks->setBridgeState(false);
|
||||
}
|
||||
#endif
|
||||
} else if (strcmp(command, "stop ota") == 0) {
|
||||
if (!_board->stopOTAUpdate(reply)) {
|
||||
strcpy(reply, "Error");
|
||||
}
|
||||
#if defined(WITH_MQTT_BRIDGE) && defined(LIGHTWEIGHT_WIFI_OTA)
|
||||
else if (_prefs->bridge_enabled) {
|
||||
_callbacks->setBridgeState(true);
|
||||
}
|
||||
#endif
|
||||
} else if (strcmp(command, "clock") == 0) {
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
DateTime dt = DateTime(now);
|
||||
sprintf(reply, "%02d:%02d - %d/%d/%d UTC",
|
||||
dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
|
||||
} else if (memcmp(command, "time ", 5) == 0) {
|
||||
uint32_t secs = _atoi(&command[5]);
|
||||
uint32_t curr = getRTCClock()->getCurrentTime();
|
||||
if (secs > curr) {
|
||||
getRTCClock()->setCurrentTime(secs);
|
||||
_callbacks->onManualClockSet();
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
DateTime dt = DateTime(now);
|
||||
sprintf(reply, "OK - clock set: %02d:%02d - %d/%d/%d UTC",
|
||||
dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
|
||||
} else {
|
||||
strcpy(reply, "(ERR: clock cannot go backwards)");
|
||||
}
|
||||
} else if (strcmp(command, "neighbors") == 0) {
|
||||
_callbacks->formatNeighborsReply(reply);
|
||||
} else if (memcmp(command, "password ", 9) == 0 && command[9] != 0) {
|
||||
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
|
||||
savePrefs();
|
||||
sprintf(reply, "password now: %s", _prefs->password);
|
||||
} else if (memcmp(command, "get ", 4) == 0) {
|
||||
handleGetCmd(sender_timestamp, command, reply);
|
||||
} else if (memcmp(command, "set ", 4) == 0) {
|
||||
handleSetCmd(sender_timestamp, command, reply);
|
||||
} else if (sender_timestamp == 0 && strcmp(command, "erase") == 0) {
|
||||
bool success = _callbacks->formatFileSystem();
|
||||
sprintf(reply, "File system erase: %s", success ? "OK" : "Err");
|
||||
} else if (strcmp(command, "ver") == 0) {
|
||||
sprintf(reply, "%s (Build: %s)",
|
||||
_callbacks->getFirmwareVer(), _callbacks->getBuildDate());
|
||||
} else if (strcmp(command, "board") == 0) {
|
||||
sprintf(reply, "%s", _board->getManufacturerName());
|
||||
} else if (memcmp(command, "region", 6) == 0
|
||||
&& (command[6] == 0 || command[6] == ' ')) {
|
||||
handleRegionCmd(command, reply);
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (strcmp(command, "gps on") == 0) {
|
||||
if (_sensors->setSettingValue("gps", "1")) {
|
||||
_prefs->gps_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, _prefs->powersaving_enabled ? "on (powersaving)" : "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps toggle not found");
|
||||
}
|
||||
} else if (strcmp(command, "gps off") == 0) {
|
||||
if (_sensors->setSettingValue("gps", "0")) {
|
||||
_prefs->gps_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps toggle not found");
|
||||
}
|
||||
} else if (strcmp(command, "gps sync") == 0) {
|
||||
LocationProvider* location = _sensors->getLocationProvider();
|
||||
if (!_prefs->gps_enabled) {
|
||||
strcpy(reply, "gps is off");
|
||||
} else if (location != NULL) {
|
||||
location->syncTime();
|
||||
strcpy(reply, "scheduled");
|
||||
} else {
|
||||
strcpy(reply, "gps provider not found");
|
||||
}
|
||||
} else if (strcmp(command, "gps setloc") == 0) {
|
||||
_prefs->node_lat = _sensors->node_lat;
|
||||
_prefs->node_lon = _sensors->node_lon;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command, "gps advert", 10) == 0
|
||||
&& (command[10] == 0 || command[10] == ' ')) {
|
||||
if (command[10] == 0) {
|
||||
switch (_prefs->advert_loc_policy) {
|
||||
case ADVERT_LOC_NONE: strcpy(reply, "> none"); break;
|
||||
case ADVERT_LOC_PREFS: strcpy(reply, "> prefs"); break;
|
||||
case ADVERT_LOC_SHARE: strcpy(reply, "> share"); break;
|
||||
default: strcpy(reply, "error");
|
||||
}
|
||||
} else if (strcmp(&command[11], "none") == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_NONE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (strcmp(&command[11], "share") == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (strcmp(&command[11], "prefs") == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "error");
|
||||
}
|
||||
} else if (strcmp(command, "gps") == 0) {
|
||||
LocationProvider* location = _sensors->getLocationProvider();
|
||||
if (location == NULL) {
|
||||
strcpy(reply, "Can't find GPS");
|
||||
} else {
|
||||
bool enabled = location->isEnabled();
|
||||
bool fix = location->isValid();
|
||||
int sats = location->satellitesCount();
|
||||
const char* gps_setting = _sensors->getSettingByKey("gps");
|
||||
bool active = gps_setting != NULL && strcmp(gps_setting, "1") == 0;
|
||||
if (_prefs->powersaving_enabled && location->getGPSPowerSaving()) {
|
||||
unsigned long now = millis();
|
||||
unsigned long next_off = location->getNextGPSOff();
|
||||
unsigned long deadline =
|
||||
next_off != 0 ? next_off : location->getNextGPSOn();
|
||||
long remaining_ms = deadline == 0 ? 0 : (long)(deadline - now);
|
||||
unsigned long mins =
|
||||
remaining_ms > 0 ? (unsigned long)remaining_ms / 60000UL : 0;
|
||||
if (next_off != 0) {
|
||||
snprintf(reply, 160,
|
||||
"on (powersaving, sleep in %luh %lum), %s, %s, %d sats",
|
||||
mins / 60UL, mins % 60UL,
|
||||
active ? "active" : "deactivated",
|
||||
fix ? "fix" : "no fix", sats);
|
||||
} else {
|
||||
snprintf(reply, 160, "off (powersaving, wake in %luh %lum)",
|
||||
mins / 60UL, mins % 60UL);
|
||||
}
|
||||
unsigned long last_sync = location->getLastValidTimeSync();
|
||||
size_t used = strlen(reply);
|
||||
if (last_sync == 0) {
|
||||
snprintf(reply + used, 160 - used, ", last sync: none");
|
||||
} else {
|
||||
DateTime dt = DateTime(last_sync);
|
||||
snprintf(reply + used, 160 - used,
|
||||
", last sync: %02d:%02d - %d/%d/%d UTC",
|
||||
dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
|
||||
}
|
||||
} else if (enabled) {
|
||||
sprintf(reply, "on, %s, %s, %d sats",
|
||||
active ? "active" : "deactivated",
|
||||
fix ? "fix" : "no fix", sats);
|
||||
} else {
|
||||
strcpy(reply, "off");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else if (strcmp(command, "sensor") == 0) {
|
||||
#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL)
|
||||
sprintf(reply, "I2C Wire1: SDA=%s,SCL=%s\r\n",
|
||||
STR(ENV_PIN_SDA), STR(ENV_PIN_SCL));
|
||||
#elif defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
sprintf(reply, "I2C Wire: SDA=%s, SCL=%s\r\n",
|
||||
STR(PIN_BOARD_SDA), STR(PIN_BOARD_SCL));
|
||||
#elif defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||
sprintf(reply, "I2C Wire: SDA=%s, SCL=%s\r\n",
|
||||
STR(PIN_WIRE_SDA), STR(PIN_WIRE_SCL));
|
||||
#else
|
||||
sprintf(reply, "I2C GPIOs not defined\r\n");
|
||||
#endif
|
||||
#if defined(PIN_GPS_RX) && defined(PIN_GPS_TX)
|
||||
sprintf(reply + strlen(reply), "GPS Serial: RX=%s, TX=%s",
|
||||
STR(PIN_GPS_RX), STR(PIN_GPS_TX));
|
||||
#if defined(ENV_INCLUDE_GPS) && ENV_INCLUDE_GPS > 0
|
||||
sprintf(reply + strlen(reply), ". Configured");
|
||||
#else
|
||||
sprintf(reply + strlen(reply), ". Not configured");
|
||||
#endif
|
||||
#else
|
||||
sprintf(reply + strlen(reply), "GPS Serial not defined");
|
||||
#endif
|
||||
} else if (strcmp(command, "powerlog") == 0) {
|
||||
sprintf(reply, "Last reset reason: %s",
|
||||
_board->getResetReasonString(_board->getResetReason()));
|
||||
} else {
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
return;
|
||||
#else
|
||||
if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) {
|
||||
_board->powerOff(); // doesn't return
|
||||
} else if (memcmp(command, "reboot", 6) == 0) {
|
||||
@@ -2687,7 +2455,27 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' ');
|
||||
const char *key = (num > 0) ? parts[0] : "";
|
||||
const char *value = (num > 1) ? parts[1] : "null";
|
||||
if (_sensors->setSettingValue(key, value)) {
|
||||
uint32_t gps_interval = 0;
|
||||
const bool is_gps_interval = strcmp(key, "gps_interval") == 0;
|
||||
const bool is_gps_toggle = strcmp(key, "gps") == 0;
|
||||
const bool valid_gps_interval = !is_gps_interval
|
||||
|| (parseUint32Strict(value, gps_interval)
|
||||
&& gps_interval <= mesh::gps::MAX_UPDATE_INTERVAL_SEC);
|
||||
const bool valid_gps_toggle = !is_gps_toggle
|
||||
|| strcmp(value, "0") == 0 || strcmp(value, "1") == 0;
|
||||
|
||||
if (!valid_gps_interval) {
|
||||
strcpy(reply, "gps_interval must be 0..86400 seconds");
|
||||
} else if (!valid_gps_toggle) {
|
||||
strcpy(reply, "gps must be 0 or 1");
|
||||
} else if (_sensors->setSettingValue(key, value)) {
|
||||
if (is_gps_interval) {
|
||||
_prefs->gps_interval = gps_interval;
|
||||
savePrefs();
|
||||
} else if (is_gps_toggle) {
|
||||
_prefs->gps_enabled = strcmp(value, "1") == 0 ? 1 : 0;
|
||||
savePrefs();
|
||||
}
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "can't find custom var");
|
||||
@@ -2921,7 +2709,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
} else {
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_SD_STORE)
|
||||
@@ -3093,6 +2880,22 @@ bool CommonCLI::handleSdCardGetCmd(const char* config, char* reply) {
|
||||
|
||||
void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
const char* config = &command[4];
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
if (strncmp(config, "usb.logging", 11) == 0
|
||||
&& (config[11] == 0 || config[11] == ' ' || config[11] == '\t')) {
|
||||
const char* value = &config[11];
|
||||
while (*value == ' ' || *value == '\t') value++;
|
||||
if (strcmp(value, "on") == 0 || strcmp(value, "off") == 0) {
|
||||
const bool enabled = strcmp(value, "on") == 0;
|
||||
mesh::setUsbLoggingEnabled(enabled);
|
||||
snprintf(reply, 160, "OK - USB logging %s until reboot",
|
||||
enabled ? "on" : "off");
|
||||
} else {
|
||||
strcpy(reply, "Error: usage set usb.logging on|off");
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL)
|
||||
if (isGpioConfig(config)) {
|
||||
const UserGpio::SetResult result = _user_gpio.handleSet(
|
||||
@@ -3169,215 +2972,6 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(PORTABLE_ESP32_RADIO_CLI)
|
||||
// Portable WiFi-heavy images keep the controls needed to configure and
|
||||
// diagnose their radio. The full parser remains available in the FULL image.
|
||||
if (memcmp(config, "int.thresh ", 11) == 0) {
|
||||
_prefs->interference_threshold = atoi(&config[11]);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "cad ", 4) == 0) {
|
||||
const char* value = &config[4];
|
||||
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
strcpy(reply, "Error: use set cad on|off");
|
||||
} else {
|
||||
_prefs->cad_enabled = strcmp(value, "on") == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
|
||||
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
|
||||
savePrefs();
|
||||
sprintf(reply, "OK - interval rounded to %d",
|
||||
((uint32_t)_prefs->agc_reset_interval) * 4);
|
||||
} else if (memcmp(config, "repeat ", 7) == 0) {
|
||||
const char* value = &config[7];
|
||||
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
strcpy(reply, "Error, must be on or off");
|
||||
} else {
|
||||
_prefs->disable_fwd = strcmp(value, "off") == 0;
|
||||
savePrefs();
|
||||
_callbacks->onRetryConfigChanged();
|
||||
strcpy(reply, _prefs->disable_fwd
|
||||
? "OK - repeat is now OFF" : "OK - repeat is now ON");
|
||||
}
|
||||
} else if (memcmp(config, "radio.rxgain ", 13) == 0) {
|
||||
const char* value = &config[13];
|
||||
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
strcpy(reply, "Error: use set radio.rxgain on|off");
|
||||
} else {
|
||||
bool enabled = strcmp(value, "on") == 0;
|
||||
if (_callbacks->setRxBoostedGain(enabled)) {
|
||||
_prefs->rx_boosted_gain = enabled;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
}
|
||||
}
|
||||
} else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) {
|
||||
const char* value = &config[17];
|
||||
if (!_board->canControlLoRaFemLna()) {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
} else if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
strcpy(reply, "Error: state must be on or off");
|
||||
} else {
|
||||
bool enabled = strcmp(value, "on") == 0;
|
||||
bool changed = _board->isLoRaFemLnaEnabled() != enabled;
|
||||
if (_board->setLoRaFemLnaEnabled(enabled)) {
|
||||
if (changed) _callbacks->recalibrateNoiseFloor();
|
||||
_prefs->radio_fem_rxgain = enabled ? 1 : 0;
|
||||
savePrefs();
|
||||
strcpy(reply, enabled
|
||||
? "OK - LoRa FEM RX gain on" : "OK - LoRa FEM RX gain off");
|
||||
} else {
|
||||
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
|
||||
}
|
||||
}
|
||||
} else if (memcmp(config, "radio.fem.txgain ", 17) == 0) {
|
||||
const char* value = &config[17];
|
||||
if (!_board->canControlLoRaFemPaGain()) {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
} else if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
strcpy(reply, "Error: state must be on or off");
|
||||
} else {
|
||||
const bool enabled = strcmp(value, "on") == 0;
|
||||
if (_board->setLoRaFemPaGainEnabled(enabled)) {
|
||||
_prefs->radio_fem_txgain = enabled ? 1 : 0;
|
||||
savePrefs();
|
||||
strcpy(reply, enabled
|
||||
? "OK - LoRa FEM TX gain on" : "OK - LoRa FEM TX gain off");
|
||||
} else {
|
||||
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
|
||||
}
|
||||
}
|
||||
} else if (memcmp(config, "radio ", 6) == 0) {
|
||||
strcpy(tmp, &config[6]);
|
||||
const char* parts[4];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 4);
|
||||
float freq = 0.0f;
|
||||
float bw = 0.0f;
|
||||
uint8_t sf = 0;
|
||||
uint8_t cr = 0;
|
||||
if (num == 4
|
||||
&& mesh::cli::parseDecimalStrict(parts[0], freq)
|
||||
&& mesh::cli::parseDecimalStrict(parts[1], bw)
|
||||
&& parseUint8Strict(parts[2], 5, 12, sf)
|
||||
&& parseUint8Strict(parts[3], 5, 8, cr)
|
||||
&& freq >= 150.0f && freq <= 2500.0f
|
||||
&& sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8
|
||||
&& isValidLoRaBandwidth(bw)) {
|
||||
_prefs->sf = sf;
|
||||
_prefs->cr = cr;
|
||||
_prefs->freq = freq;
|
||||
_prefs->bw = bw;
|
||||
bool rxps_retuned = recalculateRxPowerSavingFromLevel(_prefs);
|
||||
_callbacks->savePrefs();
|
||||
strcpy(reply, rxps_retuned
|
||||
? "OK - reboot to apply (rxps retuned)" : "OK - reboot to apply");
|
||||
} else {
|
||||
strcpy(reply, "Error, invalid radio params");
|
||||
}
|
||||
} else if (memcmp(config, "tx ", 3) == 0) {
|
||||
_prefs->tx_power_dbm = atoi(&config[3]);
|
||||
savePrefs();
|
||||
_callbacks->setTxPower(_prefs->tx_power_dbm);
|
||||
strcpy(reply, "OK");
|
||||
} else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) {
|
||||
float freq = 0.0f;
|
||||
if (mesh::cli::parseDecimalStrict(&config[5], freq)
|
||||
&& freq >= 150.0f && freq <= 2500.0f) {
|
||||
_prefs->freq = freq;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - reboot to apply");
|
||||
} else {
|
||||
strcpy(reply, "Error, invalid frequency");
|
||||
}
|
||||
#ifdef WITH_BRIDGE
|
||||
} else if (memcmp(config, "rxdelay ", 8) == 0) {
|
||||
float delay = 0.0f;
|
||||
if (mesh::cli::parseDecimalStrict(&config[8], delay)
|
||||
&& delay >= 0.0f && delay <= 20.0f) {
|
||||
_prefs->rx_delay_base = delay;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be 0-20");
|
||||
}
|
||||
} else if (memcmp(config, "txdelay ", 8) == 0) {
|
||||
float factor = 0.0f;
|
||||
if (mesh::cli::parseDecimalStrict(&config[8], factor)
|
||||
&& factor >= 0.0f && factor <= 2.0f) {
|
||||
_prefs->tx_delay_factor = factor;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be 0-2");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.enabled ", 15) == 0) {
|
||||
const char* value = &config[15];
|
||||
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
strcpy(reply, "Error: use set bridge.enabled on|off");
|
||||
} else {
|
||||
_prefs->bridge_enabled = strcmp(value, "on") == 0;
|
||||
_callbacks->setBridgeState(_prefs->bridge_enabled);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.delay ", 13) == 0) {
|
||||
int delay = _atoi(&config[13]);
|
||||
if (delay >= 0 && delay <= 10000) {
|
||||
_prefs->bridge_delay = (uint16_t)delay;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: delay must be between 0-10000 ms");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.source ", 14) == 0) {
|
||||
const char* value = &config[14];
|
||||
if (strcmp(value, "rx") != 0 && strcmp(value, "tx") != 0) {
|
||||
strcpy(reply, "Error: use set bridge.source rx|tx");
|
||||
} else {
|
||||
_prefs->bridge_pkt_src = strcmp(value, "rx") == 0;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_mqtt_prefs.mqtt_rx_enabled = _prefs->bridge_pkt_src;
|
||||
_mqtt_prefs.mqtt_tx_enabled = !_prefs->bridge_pkt_src;
|
||||
savePrefs(PrefsSaveRouting::Scope::Both);
|
||||
#else
|
||||
savePrefs();
|
||||
#endif
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
} else if (memcmp(config, "bridge.channel ", 15) == 0) {
|
||||
int channel = atoi(&config[15]);
|
||||
if (channel >= 1 && channel <= 14) {
|
||||
_prefs->bridge_channel = (uint8_t)channel;
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: channel must be between 1-14");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.secret ", 14) == 0) {
|
||||
const char* secret = &config[14];
|
||||
if (secret[0] == 0 || strlen(secret) >= sizeof(_prefs->bridge_secret)) {
|
||||
sprintf(reply, "Error: secret must be 1-%u characters",
|
||||
(unsigned)(sizeof(_prefs->bridge_secret) - 1));
|
||||
} else {
|
||||
StrHelper::strncpy(_prefs->bridge_secret, secret,
|
||||
sizeof(_prefs->bridge_secret));
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "Unsupported in this firmware: %s", config);
|
||||
}
|
||||
return;
|
||||
#else
|
||||
if (isAdvancedRetryConfig(config) && !_callbacks->supportsAdvancedRetryConfig()) {
|
||||
strcpy(reply, "Error, unsupported on this role");
|
||||
return;
|
||||
@@ -4254,11 +3848,17 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
const char* config = &command[4];
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
if (strcmp(config, "usb.logging") == 0) {
|
||||
snprintf(reply, 160, "> %s",
|
||||
mesh::isUsbLoggingEnabled() ? "on" : "off");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL)
|
||||
if (isGpioConfig(config)) {
|
||||
_user_gpio.handleGet(config + 4, reply, 160);
|
||||
@@ -4319,79 +3919,6 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(PORTABLE_ESP32_RADIO_CLI)
|
||||
if (strcmp(config, "int.thresh") == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->interference_threshold);
|
||||
} else if (strcmp(config, "cad") == 0) {
|
||||
sprintf(reply, "> %s. # channel busy: %u",
|
||||
_prefs->cad_enabled ? "on" : "off", _board->n_cad_busy);
|
||||
} else if (strcmp(config, "agc.reset.interval") == 0) {
|
||||
sprintf(reply, "> %d", ((uint32_t)_prefs->agc_reset_interval) * 4);
|
||||
} else if (strcmp(config, "repeat") == 0) {
|
||||
sprintf(reply, "> %s", _prefs->disable_fwd ? "off" : "on");
|
||||
} else if (strcmp(config, "radio.rxgain") == 0) {
|
||||
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
|
||||
} else if (strcmp(config, "radio.fem.rxgain") == 0) {
|
||||
if (!_board->canControlLoRaFemLna()) {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
} else {
|
||||
sprintf(reply, "> %s",
|
||||
_board->isLoRaFemLnaEnabled() ? "on" : "off");
|
||||
}
|
||||
} else if (strcmp(config, "radio.fem.txgain") == 0) {
|
||||
if (!_board->canControlLoRaFemPaGain()) {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
} else {
|
||||
sprintf(reply, "> %s",
|
||||
_board->isLoRaFemPaGainEnabled() ? "on" : "off");
|
||||
}
|
||||
} else if (strcmp(config, "radio") == 0) {
|
||||
char freq[16], bw[16];
|
||||
strcpy(freq, StrHelper::ftoa(_prefs->freq));
|
||||
strcpy(bw, StrHelper::ftoa3(_prefs->bw));
|
||||
sprintf(reply, "> %s,%s,%d,%d",
|
||||
freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
|
||||
} else if (strcmp(config, "tx") == 0) {
|
||||
sprintf(reply, "> %d", (int32_t)_prefs->tx_power_dbm);
|
||||
} else if (strcmp(config, "freq") == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
|
||||
} else if (strcmp(config, "role") == 0) {
|
||||
sprintf(reply, "> %s", _callbacks->getRole());
|
||||
#ifdef WITH_BRIDGE
|
||||
} else if (strcmp(config, "rxdelay") == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
|
||||
} else if (strcmp(config, "txdelay") == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
|
||||
} else if (strcmp(config, "bridge.enabled") == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
|
||||
} else if (strcmp(config, "bridge.delay") == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
|
||||
} else if (strcmp(config, "bridge.source") == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
|
||||
} else if (strcmp(config, "bridge.type") == 0) {
|
||||
sprintf(reply, "> %s",
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
"rs232"
|
||||
#elif WITH_ESPNOW_BRIDGE
|
||||
"espnow"
|
||||
#elif WITH_MQTT_BRIDGE
|
||||
"mqtt"
|
||||
#else
|
||||
"none"
|
||||
#endif
|
||||
);
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
} else if (strcmp(config, "bridge.channel") == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
|
||||
} else if (strcmp(config, "bridge.secret") == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_secret);
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "Unsupported in this firmware: %s", config);
|
||||
}
|
||||
return;
|
||||
#else
|
||||
if (isAdvancedRetryConfig(config) && !_callbacks->supportsAdvancedRetryConfig()) {
|
||||
strcpy(reply, "Error, unsupported on this role");
|
||||
return;
|
||||
@@ -4686,7 +4213,6 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
mesh::cli::formatUnknownSetting(reply, 160, config);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommonCLI::handleDelCmd(char* command, char* reply) {
|
||||
|
||||
@@ -148,7 +148,7 @@ public:
|
||||
uint8_t reboot_interval = 0; // hours, 0-255 (default 0=disable)
|
||||
// Gps settings
|
||||
uint8_t gps_enabled = 0;
|
||||
uint32_t gps_interval = 0; // in seconds
|
||||
uint32_t gps_interval = 0; // seconds; 0 uses the 1-second default
|
||||
uint8_t advert_loc_policy = 0;
|
||||
uint32_t discovery_mod_timestamp = 0;
|
||||
float adc_multiplier = 0;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace gps {
|
||||
|
||||
static const uint32_t DEFAULT_UPDATE_INTERVAL_SEC = 1;
|
||||
static const uint32_t MAX_UPDATE_INTERVAL_SEC = 24UL * 60UL * 60UL;
|
||||
|
||||
inline bool parseUpdateInterval(const char* value, uint32_t& configured_sec) {
|
||||
if (value == nullptr || *value == 0) return false;
|
||||
|
||||
uint32_t parsed = 0;
|
||||
for (const char* p = value; *p; ++p) {
|
||||
if (*p < '0' || *p > '9') return false;
|
||||
uint32_t digit = (uint32_t)(*p - '0');
|
||||
if (parsed > (MAX_UPDATE_INTERVAL_SEC - digit) / 10UL) return false;
|
||||
parsed = parsed * 10UL + digit;
|
||||
}
|
||||
|
||||
configured_sec = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline uint32_t effectiveUpdateIntervalSec(uint32_t configured_sec) {
|
||||
return configured_sec == 0 ? DEFAULT_UPDATE_INTERVAL_SEC : configured_sec;
|
||||
}
|
||||
|
||||
inline uint32_t updateIntervalMillis(uint32_t configured_sec) {
|
||||
return effectiveUpdateIntervalSec(configured_sec) * 1000UL;
|
||||
}
|
||||
|
||||
} // namespace gps
|
||||
} // namespace mesh
|
||||
@@ -49,14 +49,10 @@ void SensorManager::updateGpsTelemetryCache(float lat, float lon, float altitude
|
||||
}
|
||||
|
||||
void SensorManager::maybeStopGpsForTelemetry(unsigned long now) {
|
||||
#if defined(PERSISTANT_GPS) || defined(FORCE_GPS_ALIVE)
|
||||
(void)now;
|
||||
#else
|
||||
if (telemetryGpsActive() && !gps_user_enabled && !gps_acquiring && !gpsTelemetryHoldActive(now)) {
|
||||
telemetryGpsStop();
|
||||
gps_next_cache_update_at = now + GPS_TELEMETRY_CACHE_INTERVAL_SEC * 1000UL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SensorManager::beginGpsTelemetryAcquisition(unsigned long now) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <CayenneLPP.h>
|
||||
#include "GpsPowerPolicy.h"
|
||||
#include "sensors/LocationProvider.h"
|
||||
#include <string.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
@@ -32,6 +34,7 @@ class SensorManager {
|
||||
unsigned long gps_hold_until = 0;
|
||||
unsigned long gps_acquire_started_at = 0;
|
||||
unsigned long gps_stable_started_at = 0;
|
||||
uint32_t gps_update_interval_sec = 0;
|
||||
|
||||
bool gpsTelemetryHoldActive(unsigned long now) const;
|
||||
bool gpsTelemetryCacheFresh(unsigned long now) const;
|
||||
@@ -50,6 +53,15 @@ protected:
|
||||
void loopGpsTelemetry(unsigned long now);
|
||||
void setGpsTelemetryUserEnabled(bool enabled);
|
||||
bool isGpsTelemetryUserEnabled() const { return gps_user_enabled; }
|
||||
bool gpsTelemetryReceiverRequired(unsigned long now) const {
|
||||
return gps_acquiring || gpsTelemetryHoldActive(now);
|
||||
}
|
||||
bool setGpsUpdateIntervalValue(const char* value) {
|
||||
return mesh::gps::parseUpdateInterval(value, gps_update_interval_sec);
|
||||
}
|
||||
uint32_t getGpsUpdateIntervalMillis() const {
|
||||
return mesh::gps::updateIntervalMillis(gps_update_interval_sec);
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
@@ -66,7 +78,17 @@ public:
|
||||
virtual int getNumSettings() const { return 0; }
|
||||
virtual const char* getSettingName(int i) const { return NULL; }
|
||||
virtual const char* getSettingValue(int i) const { return NULL; }
|
||||
virtual bool setSettingValue(const char* name, const char* value) { return false; }
|
||||
virtual bool setSettingValue(const char* name, const char* value) {
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (strcmp(name, "gps_interval") == 0) {
|
||||
return setGpsUpdateIntervalValue(value);
|
||||
}
|
||||
#else
|
||||
(void)name;
|
||||
(void)value;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
virtual LocationProvider* getLocationProvider() { return NULL; }
|
||||
virtual void setPowerSavingEnabled(bool enabled) { powersaving_enabled = enabled; }
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "UsbLogging.h"
|
||||
|
||||
#if defined(ARDUINO)
|
||||
#include <atomic>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
static std::atomic<bool> usb_logging_enabled{true};
|
||||
|
||||
bool isUsbLoggingEnabled() {
|
||||
return usb_logging_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void setUsbLoggingEnabled(bool enabled) {
|
||||
usb_logging_enabled.store(enabled, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// Logging artifacts compile at least one of these two diagnostics. Keep the
|
||||
// command surface out of ordinary images while providing one runtime gate for
|
||||
// every diagnostic category that writes to the USB Serial stream.
|
||||
#if defined(ARDUINO) && \
|
||||
((defined(MESH_DEBUG) && MESH_DEBUG) || \
|
||||
(defined(MESH_PACKET_LOGGING) && MESH_PACKET_LOGGING))
|
||||
#define MESH_USB_LOGGING_AVAILABLE 1
|
||||
#else
|
||||
#define MESH_USB_LOGGING_AVAILABLE 0
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO)
|
||||
namespace mesh {
|
||||
|
||||
// This is intentionally session-only. Logging artifacts start enabled after
|
||||
// every boot so a stale setting cannot make a diagnostic image look silent.
|
||||
bool isUsbLoggingEnabled();
|
||||
void setUsbLoggingEnabled(bool enabled);
|
||||
|
||||
} // namespace mesh
|
||||
#endif
|
||||
@@ -209,6 +209,7 @@ static unsigned long s_wifi_disconnect_time = 0;
|
||||
// #region agent log
|
||||
static void agentLogHeap(const char* location, const char* message, const char* hypothesisId,
|
||||
size_t free_h, size_t max_alloc, unsigned long internal_free, unsigned long spiram_free) {
|
||||
if (!mesh::isUsbLoggingEnabled()) return;
|
||||
char buf[320];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"{\"sessionId\":\"debug-session\",\"location\":\"%s\",\"message\":\"%s\",\"hypothesisId\":\"%s\","
|
||||
|
||||
@@ -31,8 +31,8 @@ class MeshSNMPAgent; // Forward declaration
|
||||
// USB CDC-aware debug macros: only print if Serial is ready (non-blocking check)
|
||||
// Serial.availableForWrite() returns bytes available in write buffer (>0 means ready)
|
||||
// This prevents hangs when USB CDC isn't ready yet (e.g., ESP32-S3 native USB)
|
||||
#define MQTT_DEBUG_PRINT(F, ...) do { if (Serial.availableForWrite() > 0) { Serial.printf("MQTT: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define MQTT_DEBUG_PRINTLN(F, ...) do { if (Serial.availableForWrite() > 0) { Serial.printf("MQTT: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#define MQTT_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled() && Serial.availableForWrite() > 0) { Serial.printf("MQTT: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define MQTT_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled() && Serial.availableForWrite() > 0) { Serial.printf("MQTT: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define MQTT_DEBUG_PRINT(...) {}
|
||||
#define MQTT_DEBUG_PRINTLN(...) {}
|
||||
|
||||
@@ -64,8 +64,8 @@ public:
|
||||
|
||||
#if ESPNOW_DEBUG_LOGGING && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define ESPNOW_DEBUG_PRINT(F, ...) Serial.printf("ESP-Now: " F, ##__VA_ARGS__)
|
||||
#define ESPNOW_DEBUG_PRINTLN(F, ...) Serial.printf("ESP-Now: " F "\n", ##__VA_ARGS__)
|
||||
#define ESPNOW_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("ESP-Now: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define ESPNOW_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("ESP-Now: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define ESPNOW_DEBUG_PRINT(...) {}
|
||||
#define ESPNOW_DEBUG_PRINTLN(...) {}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../BaseSerialInterface.h"
|
||||
#include "../BleTxStallWatchdog.h"
|
||||
#include "../UsbLogging.h"
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
@@ -114,8 +115,8 @@ public:
|
||||
|
||||
#if BLE_DEBUG_LOGGING && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define BLE_DEBUG_PRINT(F, ...) Serial.printf("BLE: " F, ##__VA_ARGS__)
|
||||
#define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__)
|
||||
#define BLE_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("BLE: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define BLE_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("BLE: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define BLE_DEBUG_PRINT(...) {}
|
||||
#define BLE_DEBUG_PRINTLN(...) {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../BaseSerialInterface.h"
|
||||
#include "../UsbLogging.h"
|
||||
#include <WiFi.h>
|
||||
|
||||
class SerialWifiInterface : public BaseSerialInterface {
|
||||
@@ -66,8 +67,8 @@ public:
|
||||
|
||||
#if WIFI_DEBUG_LOGGING && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define WIFI_DEBUG_PRINT(F, ...) Serial.printf("WiFi: " F, ##__VA_ARGS__)
|
||||
#define WIFI_DEBUG_PRINTLN(F, ...) Serial.printf("WiFi: " F "\n", ##__VA_ARGS__)
|
||||
#define WIFI_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("WiFi: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define WIFI_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("WiFi: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define WIFI_DEBUG_PRINT(...) {}
|
||||
#define WIFI_DEBUG_PRINTLN(...) {}
|
||||
|
||||
@@ -55,8 +55,9 @@ void TBeamBoard::scanDevices(TwoWire *w)
|
||||
uint8_t err, addr;
|
||||
int nDevices = 0;
|
||||
uint32_t start = 0;
|
||||
const bool usb_logging = mesh::isUsbLoggingEnabled();
|
||||
|
||||
Serial.println("Scanning I2C for Devices");
|
||||
if (usb_logging) Serial.println("Scanning I2C for Devices");
|
||||
for (addr = 1; addr < 127; addr++) {
|
||||
start = millis();
|
||||
w->beginTransmission(addr); delay(2);
|
||||
@@ -66,45 +67,49 @@ void TBeamBoard::scanDevices(TwoWire *w)
|
||||
switch (addr) {
|
||||
case 0x77:
|
||||
case 0x76:
|
||||
Serial.println("\tFound BME280 Sensor");
|
||||
if (usb_logging) Serial.println("\tFound BME280 Sensor");
|
||||
deviceOnline |= BME280_ONLINE;
|
||||
break;
|
||||
case 0x34:
|
||||
Serial.println("\tFound AXP192/AXP2101 PMU");
|
||||
if (usb_logging) Serial.println("\tFound AXP192/AXP2101 PMU");
|
||||
deviceOnline |= POWERMANAGE_ONLINE;
|
||||
break;
|
||||
case 0x3C:
|
||||
Serial.println("\tFound SSD1306/SH1106 display");
|
||||
if (usb_logging) Serial.println("\tFound SSD1306/SH1106 display");
|
||||
deviceOnline |= DISPLAY_ONLINE;
|
||||
break;
|
||||
case 0x51:
|
||||
Serial.println("\tFound PCF8563 RTC");
|
||||
if (usb_logging) Serial.println("\tFound PCF8563 RTC");
|
||||
deviceOnline |= PCF8563_ONLINE;
|
||||
break;
|
||||
case 0x1C:
|
||||
Serial.println("\tFound QMC6310 MAG Sensor");
|
||||
if (usb_logging) Serial.println("\tFound QMC6310 MAG Sensor");
|
||||
deviceOnline |= QMC6310_ONLINE;
|
||||
break;
|
||||
default:
|
||||
Serial.print("\tI2C device found at address 0x");
|
||||
if (addr < 16) {
|
||||
Serial.print("0");
|
||||
if (usb_logging) {
|
||||
Serial.print("\tI2C device found at address 0x");
|
||||
if (addr < 16) {
|
||||
Serial.print("0");
|
||||
}
|
||||
Serial.print(addr, HEX);
|
||||
Serial.println(" !");
|
||||
}
|
||||
Serial.print(addr, HEX);
|
||||
Serial.println(" !");
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (err == 4) {
|
||||
Serial.print("Unknow error at address 0x");
|
||||
if (addr < 16) {
|
||||
Serial.print("0");
|
||||
if (usb_logging) {
|
||||
Serial.print("Unknow error at address 0x");
|
||||
if (addr < 16) {
|
||||
Serial.print("0");
|
||||
}
|
||||
Serial.println(addr, HEX);
|
||||
}
|
||||
Serial.println(addr, HEX);
|
||||
}
|
||||
}
|
||||
if (nDevices == 0)
|
||||
Serial.println("No I2C devices found\n");
|
||||
if (!usb_logging) return;
|
||||
if (nDevices == 0) Serial.println("No I2C devices found\n");
|
||||
|
||||
Serial.println("Scan for devices is complete.");
|
||||
Serial.println("\n");
|
||||
@@ -115,6 +120,7 @@ void TBeamBoard::scanDevices(TwoWire *w)
|
||||
}
|
||||
void TBeamBoard::printPMU()
|
||||
{
|
||||
if (!mesh::isUsbLoggingEnabled()) return;
|
||||
Serial.print("isCharging:"); Serial.println(PMU->isCharging() ? "YES" : "NO");
|
||||
Serial.print("isDischarge:"); Serial.println(PMU->isDischarge() ? "YES" : "NO");
|
||||
Serial.print("isVbusIn:"); Serial.println(PMU->isVbusIn() ? "YES" : "NO");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../BaseSerialInterface.h"
|
||||
#include "../UsbLogging.h"
|
||||
|
||||
#ifndef ETHERNET_TCP_PORT
|
||||
#define ETHERNET_TCP_PORT 5000
|
||||
@@ -69,9 +70,9 @@ class SerialEthernetInterface : public BaseSerialInterface {
|
||||
|
||||
#if ETHERNET_DEBUG_LOGGING && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define ETHERNET_DEBUG_PRINT(F, ...) Serial.printf("ETH: " F, ##__VA_ARGS__)
|
||||
#define ETHERNET_DEBUG_PRINTLN(F, ...) Serial.printf("ETH: " F "\n", ##__VA_ARGS__)
|
||||
#define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf("ETH: " name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3])
|
||||
#define ETHERNET_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("ETH: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define ETHERNET_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("ETH: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#define ETHERNET_DEBUG_PRINT_IP(name, ip) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("ETH: " name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3]); } } while(0)
|
||||
#else
|
||||
#define ETHERNET_DEBUG_PRINT(...) {}
|
||||
#define ETHERNET_DEBUG_PRINTLN(...) {}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../BaseSerialInterface.h"
|
||||
#include "../BleTxStallWatchdog.h"
|
||||
#include "../UsbLogging.h"
|
||||
#include "SecuritySessionTimer.h"
|
||||
#include <bluefruit.h>
|
||||
#include <atomic>
|
||||
@@ -94,8 +95,8 @@ public:
|
||||
|
||||
#if BLE_DEBUG_LOGGING && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define BLE_DEBUG_PRINT(F, ...) Serial.printf("BLE: " F, ##__VA_ARGS__)
|
||||
#define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__)
|
||||
#define BLE_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("BLE: " F, ##__VA_ARGS__); } } while(0)
|
||||
#define BLE_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf("BLE: " F "\n", ##__VA_ARGS__); } } while(0)
|
||||
#else
|
||||
#define BLE_DEBUG_PRINT(...) {}
|
||||
#define BLE_DEBUG_PRINTLN(...) {}
|
||||
|
||||
@@ -416,6 +416,23 @@ void ota_reboot_to_apply() { esp_restart(); } // boots the slot armed by ota_ap
|
||||
|
||||
#elif defined(NRF52_PLATFORM) // single-slot: verify + mark APPROVED + hand off to the bootloader
|
||||
|
||||
struct InplacePatchDims {
|
||||
uint32_t memory = 0;
|
||||
uint32_t segment = 0;
|
||||
uint32_t shift = 0;
|
||||
uint32_t from = 0;
|
||||
uint32_t to = 0;
|
||||
};
|
||||
|
||||
static bool parse_inplace_patch_dims(const uint8_t* payload, uint32_t payload_len,
|
||||
InplacePatchDims& d) {
|
||||
if (!payload || payload_len < 2 || ((payload[0] >> 4) & 0x07u) != 1u) return false;
|
||||
ByteReader r(payload, payload_len);
|
||||
r.u8(); // patch type/compression header
|
||||
return r.detools_size(d.memory) && r.detools_size(d.segment) && r.detools_size(d.shift) &&
|
||||
r.detools_size(d.from) && r.detools_size(d.to) && r.ok;
|
||||
}
|
||||
|
||||
// ESP32 A/B-only entry points are unsupported on nRF52.
|
||||
bool ota_apply_slot_info(uint32_t*, uint32_t*) { return false; }
|
||||
bool ota_apply_set_manifest(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st) { st = ApplyState(); return false; }
|
||||
@@ -424,12 +441,20 @@ bool ota_apply_commit() { return false; }
|
||||
bool ota_apply_detools_mota(const uint8_t*, uint32_t, const SignerAllowlist&, ApplyState& st, char* msg) { st = ApplyState(); strcpy(msg, "use ota_apply_mota_nrf52"); return false; }
|
||||
|
||||
void ota_reboot_to_apply() { // public: set the apply magic + reset (does not return)
|
||||
uint8_t stage_handoff = GPREGRET2_OTA_STAGE_LEGACY;
|
||||
#if defined(OTA_FLASH_STORE)
|
||||
if (ota_nrf52_effective_stage_ceiling() == MOTA_NRF52_STAGE_CEILING_EXPANDED)
|
||||
stage_handoff = GPREGRET2_OTA_STAGE_EXPANDED;
|
||||
#endif
|
||||
uint8_t sd_en = 0;
|
||||
sd_softdevice_is_enabled(&sd_en);
|
||||
if (sd_en) { // POWER is SD-restricted while the SoftDevice runs
|
||||
sd_power_gpregret_clr(1, 0xFFFFFFFF);
|
||||
sd_power_gpregret_set(1, stage_handoff);
|
||||
sd_power_gpregret_clr(0, 0xFFFFFFFF);
|
||||
sd_power_gpregret_set(0, GPREGRET_OTA_APPLY);
|
||||
} else {
|
||||
NRF_POWER->GPREGRET2 = stage_handoff;
|
||||
NRF_POWER->GPREGRET = GPREGRET_OTA_APPLY;
|
||||
}
|
||||
NVIC_SystemReset(); // does not return
|
||||
@@ -510,6 +535,31 @@ static bool ota_apply_mota_nrf52_impl(const uint8_t* buf, uint32_t len,
|
||||
if (!vr.trusted) { strcpy(msg, "untrusted signer (pubkey not in allowlist)"); return false; }
|
||||
}
|
||||
|
||||
// 4) detools geometry. memory_size is selected by motatool for this exact staged address; reject a
|
||||
// mismatched/legacy package before writing APRV so the bootloader never starts a doomed in-place apply.
|
||||
{
|
||||
InplacePatchDims d;
|
||||
if (!parse_inplace_patch_dims(m.payload, m.payload_size, d)) {
|
||||
strcpy(msg, "bad in-place patch header"); return false;
|
||||
}
|
||||
if (d.memory == 0 || d.segment != MOTA_NRF52_FLASH_PAGE || d.shift > d.memory ||
|
||||
d.shift % d.segment != 0 || d.from > d.memory - d.shift || d.to > d.memory ||
|
||||
d.to != m.image_size || (self_valid && d.from != fi.image_len)) {
|
||||
strcpy(msg, "invalid in-place patch geometry"); return false;
|
||||
}
|
||||
#if defined(OTA_FLASH_STORE)
|
||||
const uint32_t app_base = mota_nrf52_app_base();
|
||||
const uint32_t mota_start = (uint32_t)(uintptr_t)buf;
|
||||
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
|
||||
if (mota_start < app_base || mota_start >= stage_ceiling ||
|
||||
d.memory > mota_start - app_base) {
|
||||
snprintf(msg, 159, "patch memory 0x%x exceeds staging at 0x%x",
|
||||
(unsigned)d.memory, (unsigned)mota_start);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// mark the staged manifest APPROVED in flash (buf is the memory-mapped staging region, so
|
||||
// m.approval is a real flash address). NOR-clear over the erased 0xFFFFFFFF -> "APRV".
|
||||
uint32_t approval_addr = (uint32_t)(uintptr_t)m.approval;
|
||||
|
||||
@@ -25,10 +25,11 @@ struct OtaBlCaps {
|
||||
bool present = false;
|
||||
uint16_t apply_abi = 0; // max .mota format_ver the bootloader can apply
|
||||
uint16_t codec_mask = 0; // bit i set => can apply codec_id i (in-place delta = bit 2)
|
||||
uint8_t storage_flags = 0; // bit 0 => raw-SD handoff/apply is supported
|
||||
uint8_t storage_flags = 0; // OTA_BL_STORAGE_* capability bits
|
||||
};
|
||||
|
||||
static const uint8_t OTA_BL_STORAGE_SD = 0x01;
|
||||
static const uint8_t OTA_BL_STORAGE_SD = 0x01;
|
||||
static const uint8_t OTA_BL_STORAGE_STAGE_CEILING = 0x02;
|
||||
|
||||
// Scan the bootloader flash region for the marker. Returns {present=false} if not found / non-nRF52.
|
||||
inline OtaBlCaps ota_bootloader_caps() {
|
||||
@@ -54,5 +55,23 @@ inline bool ota_bootloader_can_apply(uint8_t format_ver, uint8_t codec_id) {
|
||||
return c.present && c.apply_abi >= format_ver && (c.codec_mask & (1u << codec_id)) != 0;
|
||||
}
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
// Use the layout's larger ceiling only when the installed bootloader advertises the matching
|
||||
// GPREGRET2 handoff. New applications remain filesystem-safe with older OTAFIX bootloaders by retaining
|
||||
// the legacy 0xD4000 ceiling. Packages sized for the expanded window still require the newer bootloader.
|
||||
inline uint32_t ota_nrf52_effective_stage_ceiling(const OtaBlCaps& c) {
|
||||
const uint32_t desired = mota_nrf52_layout_stage_ceiling();
|
||||
if (desired == MOTA_NRF52_STAGE_CEILING_EXPANDED) {
|
||||
if (!c.present || !(c.storage_flags & OTA_BL_STORAGE_STAGE_CEILING))
|
||||
return MOTA_NRF52_STAGE_CEILING_LEGACY;
|
||||
}
|
||||
return desired;
|
||||
}
|
||||
|
||||
inline uint32_t ota_nrf52_effective_stage_ceiling() {
|
||||
return ota_nrf52_effective_stage_ceiling(ota_bootloader_caps());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ota
|
||||
} // namespace mesh
|
||||
@@ -52,6 +52,26 @@ struct ByteReader {
|
||||
const uint8_t* r = p + n; n += k; return r;
|
||||
}
|
||||
void skip(uint32_t k) { if (!fits(k)) { ok = false; return; } n += k; }
|
||||
|
||||
// Positive detools header size (signed varint encoding, but flash geometry never accepts negatives).
|
||||
bool detools_size(uint32_t& out) {
|
||||
if (!fits(1)) { ok = false; return false; }
|
||||
uint8_t byte = p[n++];
|
||||
if (byte & 0x40u) { ok = false; return false; }
|
||||
uint32_t value = byte & 0x3Fu;
|
||||
uint32_t shift = 6;
|
||||
while (byte & 0x80u) {
|
||||
if (!fits(1)) { ok = false; return false; }
|
||||
byte = p[n++];
|
||||
const uint32_t bits = byte & 0x7Fu;
|
||||
if (shift >= 32 || bits > (UINT32_MAX >> shift)) { ok = false; return false; }
|
||||
value |= bits << shift;
|
||||
shift += 7;
|
||||
}
|
||||
if (value > 0x7FFFFFFFu) { ok = false; return false; }
|
||||
out = value;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ota
|
||||
|
||||
@@ -523,7 +523,12 @@ bool handle_ota_command(const char* command, char* reply, mesh::MainBoard& board
|
||||
else
|
||||
snprintf(reply + n, 160 - n, " | bootloader: NO SD mota-apply support (install will refuse)");
|
||||
#else
|
||||
if (bl.present) snprintf(reply + n, 160 - n, " | bootloader: apply OK (abi=%u codecs=0x%x)", bl.apply_abi, bl.codec_mask);
|
||||
if (bl.present) {
|
||||
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling(bl);
|
||||
snprintf(reply + n, 160 - n,
|
||||
" | bootloader: apply OK (abi=%u codecs=0x%x stage=%05X)",
|
||||
bl.apply_abi, bl.codec_mask, (unsigned)stage_ceiling);
|
||||
}
|
||||
else snprintf(reply + n, 160 - n, " | bootloader: NO mota-apply support (delta install will refuse)");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
// touches a non-debug or test build.
|
||||
#if defined(OTA_DEBUG) && defined(ARDUINO)
|
||||
#include <Arduino.h>
|
||||
#define OTA_DBG(...) do { Serial.printf(__VA_ARGS__); } while (0)
|
||||
#include <helpers/UsbLogging.h>
|
||||
#define OTA_DBG(...) do { if (mesh::isUsbLoggingEnabled()) { Serial.printf(__VA_ARGS__); } } while (0)
|
||||
#else
|
||||
#define OTA_DBG(...) do {} while (0)
|
||||
#endif
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
// Shared OTA flash-layout constants for the nRF52840 single-slot delta-apply path.
|
||||
//
|
||||
// The running app occupies [APP_BASE, app_end]; the primary LittleFS (InternalFS) starts at FS_START.
|
||||
// MeshCore stages a verified+approved `.mota` in the free flash below FS_START (bottom-aligned), then
|
||||
// sets GPREGRET_OTA_APPLY and resets; the bootloader scans [APP_BASE, FS_START) for it and applies it
|
||||
// in place. APP_BASE is obtained from the linker so S140 v6 (0x26000) and v7 (0x27000) both work. The
|
||||
// bootloader independently uses DFU_BANK_0_REGION_START, which resolves to the same address.
|
||||
// The running app occupies [APP_BASE, app_end]. Internal ExtraFS, when actually used, starts at
|
||||
// 0xD4000; primary InternalFS starts at 0xED000. MeshCore derives the safe staging ceiling from the
|
||||
// linked application region and secondary-storage type, bottom-aligns the `.mota` below it, and passes
|
||||
// the choice to a capable bootloader in GPREGRET2. APP_BASE is obtained from the linker so S140 v6
|
||||
// (0x26000) and v7 (0x27000) both work. The bootloader independently uses DFU_BANK_0_REGION_START.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -17,6 +17,8 @@ static const uint32_t MOTA_NRF52_APP_BASE_S140_V6 = 0x00026000u;
|
||||
static const uint32_t MOTA_NRF52_APP_BASE_S140_V7 = 0x00027000u;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
extern "C" uint32_t __flash_arduino_start[]; // nrf52_common.ld: ORIGIN(FLASH)
|
||||
extern "C" uint32_t __flash_arduino_end[]; // nrf52_common.ld: ORIGIN(FLASH) + LENGTH(FLASH)
|
||||
extern "C" const uint8_t g_meshcore_internal_extrafs __attribute__((weak));
|
||||
inline uint32_t mota_nrf52_app_base() {
|
||||
return (uint32_t)(uintptr_t)__flash_arduino_start;
|
||||
}
|
||||
@@ -24,22 +26,22 @@ inline uint32_t mota_nrf52_app_base() {
|
||||
// Native geometry tests have no linker script; default their runtime helper to the v6 layout.
|
||||
inline uint32_t mota_nrf52_app_base() { return MOTA_NRF52_APP_BASE_S140_V6; }
|
||||
#endif
|
||||
// Staging ceiling: the lowest filesystem region above the app. nRF52840 ExtraFS linker scripts place
|
||||
// ExtraFS at 0xD4000..0xED000 (and InternalFS at 0xED000), while default scripts leave that range free.
|
||||
// Staging below 0xD4000 therefore never touches either filesystem.
|
||||
static const uint32_t MOTA_NRF52_FS_START = 0x000D4000u; // ExtraFS start (universal staging ceiling)
|
||||
// End of the normal nRF52840 application region. The SD-backed MeshTower V2
|
||||
// target can use this whole range because its staged container is off-chip.
|
||||
// InternalFS begins here and must never be erased by the bootloader.
|
||||
static const uint32_t MOTA_NRF52_APP_END = 0x000ED000u;
|
||||
static const uint32_t MOTA_NRF52_EXTRAFS_START = 0x000D4000u;
|
||||
static const uint32_t MOTA_NRF52_APP_END = 0x000ED000u;
|
||||
static const uint32_t MOTA_NRF52_STAGE_CEILING_LEGACY = MOTA_NRF52_EXTRAFS_START;
|
||||
static const uint32_t MOTA_NRF52_STAGE_CEILING_EXPANDED = MOTA_NRF52_APP_END;
|
||||
// Compatibility name for code that refers to the fixed lower boundary. New staging code must use an
|
||||
// explicit ceiling so it cannot accidentally cross an Internal ExtraFS.
|
||||
static const uint32_t MOTA_NRF52_FS_START = MOTA_NRF52_EXTRAFS_START;
|
||||
static const uint32_t MOTA_NRF52_FLASH_PAGE = 4096u;
|
||||
static const uint8_t GPREGRET_OTA_APPLY = 0x6Au; // distinct from DFU magics 0x57/0x4E/0xA8
|
||||
static const uint8_t GPREGRET2_OTA_STAGE_LEGACY = 0xD4u;
|
||||
static const uint8_t GPREGRET2_OTA_STAGE_EXPANDED = 0xEDu;
|
||||
|
||||
// In-place patches are built with --inplace-memory = this (the apply workspace, from APP_BASE up).
|
||||
// It must hold the new image (~520 KB) yet leave the staged mota room below FS_START: workspace ends
|
||||
// at APP_BASE+this = 0xBE000 (S140 v6) or 0xBF000 (v7), leaving 88/84 KB for the staged delta. The
|
||||
// bootloader also bounds writes to < the (scanned) mota start, so a mis-sized memory still fails safe.
|
||||
static const uint32_t MOTA_NRF52_INPLACE_MEMORY = 0x00098000u; // 608 KB
|
||||
// Firmware without a valid EndF and older host tooling fall back to this conservative apply workspace.
|
||||
// New motatool builds read the firmware's appended layout record and derive memory_size from the actual
|
||||
// staged-container start; the app and bootloader both validate that per-patch value before writing.
|
||||
static const uint32_t MOTA_NRF52_FALLBACK_INPLACE_MEMORY = 0x00098000u; // 608 KB
|
||||
|
||||
// Bootloader flash region (nRF52840: 39 KB ending just below the CF2/MBR-params pages). The app scans
|
||||
// this for the bootloader capability marker (OtaBlInfo.h) to know whether THIS device's bootloader can
|
||||
@@ -51,44 +53,93 @@ static const uint32_t MOTA_NRF52_BL_END = 0x000FE000u;
|
||||
// BUILD rather than silently letting a stage/apply corrupt the filesystem (user prefs) or the app.
|
||||
static_assert((MOTA_NRF52_APP_BASE_S140_V6 % MOTA_NRF52_FLASH_PAGE) == 0, "S140 v6 base must be page-aligned");
|
||||
static_assert((MOTA_NRF52_APP_BASE_S140_V7 % MOTA_NRF52_FLASH_PAGE) == 0, "S140 v7 base must be page-aligned");
|
||||
static_assert((MOTA_NRF52_FS_START % MOTA_NRF52_FLASH_PAGE) == 0, "FS_START must be page-aligned");
|
||||
static_assert(MOTA_NRF52_FS_START < MOTA_NRF52_BL_START, "staging (+FS) must end below the bootloader");
|
||||
static_assert((MOTA_NRF52_STAGE_CEILING_LEGACY % MOTA_NRF52_FLASH_PAGE) == 0,
|
||||
"legacy staging ceiling must be page-aligned");
|
||||
static_assert((MOTA_NRF52_STAGE_CEILING_EXPANDED % MOTA_NRF52_FLASH_PAGE) == 0,
|
||||
"expanded staging ceiling must be page-aligned");
|
||||
static_assert(MOTA_NRF52_STAGE_CEILING_LEGACY < MOTA_NRF52_STAGE_CEILING_EXPANDED,
|
||||
"legacy staging ceiling must precede expanded ceiling");
|
||||
static_assert(MOTA_NRF52_STAGE_CEILING_EXPANDED < MOTA_NRF52_BL_START,
|
||||
"staging and filesystems must end below the bootloader");
|
||||
static_assert(MOTA_NRF52_BL_START < MOTA_NRF52_BL_END, "bootloader region must be non-empty");
|
||||
// The in-place apply workspace [APP_BASE, APP_BASE+INPLACE_MEMORY) must end at/below the staging ceiling,
|
||||
// so an in-place apply never writes into ExtraFS/InternalFS (where user prefs live).
|
||||
static_assert(MOTA_NRF52_APP_BASE_S140_V7 + MOTA_NRF52_INPLACE_MEMORY <= MOTA_NRF52_FS_START,
|
||||
"in-place apply workspace must end at or below the staging ceiling");
|
||||
static_assert(MOTA_NRF52_APP_BASE_S140_V7 + MOTA_NRF52_FALLBACK_INPLACE_MEMORY <=
|
||||
MOTA_NRF52_STAGE_CEILING_LEGACY,
|
||||
"fallback workspace must end at or below the legacy staging ceiling");
|
||||
|
||||
inline bool mota_nrf52_layout_valid(uint32_t app_base) {
|
||||
return (app_base % MOTA_NRF52_FLASH_PAGE) == 0 && app_base < MOTA_NRF52_FS_START &&
|
||||
(uint64_t)app_base + MOTA_NRF52_INPLACE_MEMORY <= MOTA_NRF52_FS_START;
|
||||
// Pure policy helper. Actual internal ExtraFS use always wins and keeps the legacy ceiling. Otherwise
|
||||
// either supported linker region may safely reclaim through InternalFS at 0xED000. This handles
|
||||
// repeaters, XIAO/QSPI companions, and future boards without maintaining a board-name allowlist. Unknown
|
||||
// linker geometry falls back to the legacy ceiling.
|
||||
inline uint32_t mota_nrf52_stage_ceiling_for_layout(uint32_t linked_app_end,
|
||||
bool uses_internal_extrafs) {
|
||||
if (uses_internal_extrafs) return MOTA_NRF52_STAGE_CEILING_LEGACY;
|
||||
if (linked_app_end == MOTA_NRF52_APP_END || linked_app_end == MOTA_NRF52_EXTRAFS_START)
|
||||
return MOTA_NRF52_STAGE_CEILING_EXPANDED;
|
||||
return MOTA_NRF52_STAGE_CEILING_LEGACY;
|
||||
}
|
||||
|
||||
inline uint32_t mota_nrf52_stage_capacity(uint32_t app_base) {
|
||||
return mota_nrf52_layout_valid(app_base)
|
||||
? MOTA_NRF52_FS_START - (app_base + MOTA_NRF52_INPLACE_MEMORY) : 0;
|
||||
inline uint32_t mota_nrf52_layout_stage_ceiling() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
// DataStore.cpp defines this symbol only for a companion that really constructs internal ExtraFS.
|
||||
// An undefined weak symbol has address zero, so other roles do not inherit nrf52_base's broad EXTRAFS
|
||||
// feature define as a false-positive storage reservation.
|
||||
const bool uses_internal_extrafs = (uintptr_t)&g_meshcore_internal_extrafs != 0u;
|
||||
return mota_nrf52_stage_ceiling_for_layout((uint32_t)(uintptr_t)__flash_arduino_end,
|
||||
uses_internal_extrafs);
|
||||
#else
|
||||
// Native callers use the explicit-ceiling helpers below; keep the no-argument fallback conservative.
|
||||
return MOTA_NRF52_STAGE_CEILING_LEGACY;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool mota_nrf52_stage_ceiling_valid(uint32_t stage_ceiling) {
|
||||
return stage_ceiling == MOTA_NRF52_STAGE_CEILING_LEGACY ||
|
||||
stage_ceiling == MOTA_NRF52_STAGE_CEILING_EXPANDED;
|
||||
}
|
||||
|
||||
inline bool mota_nrf52_layout_valid(uint32_t app_base, uint32_t stage_ceiling) {
|
||||
return mota_nrf52_stage_ceiling_valid(stage_ceiling) &&
|
||||
(app_base % MOTA_NRF52_FLASH_PAGE) == 0 && app_base < stage_ceiling;
|
||||
}
|
||||
|
||||
inline bool mota_nrf52_layout_valid(uint32_t app_base) {
|
||||
return mota_nrf52_layout_valid(app_base, mota_nrf52_layout_stage_ceiling());
|
||||
}
|
||||
|
||||
inline uint32_t mota_nrf52_stage_capacity(uint32_t app_base, uint32_t app_end,
|
||||
uint32_t stage_ceiling) {
|
||||
if (!mota_nrf52_layout_valid(app_base, stage_ceiling) || app_end < app_base ||
|
||||
app_end > stage_ceiling || app_end > UINT32_MAX - (MOTA_NRF52_FLASH_PAGE - 1u)) return 0;
|
||||
const uint32_t first_free_page =
|
||||
(app_end + MOTA_NRF52_FLASH_PAGE - 1u) & ~(MOTA_NRF52_FLASH_PAGE - 1u);
|
||||
return first_free_page <= stage_ceiling ? stage_ceiling - first_free_page : 0;
|
||||
}
|
||||
|
||||
// Plan where to stage a received `.mota` of `total_size` bytes. It is placed bottom-aligned within the
|
||||
// highest flash page below FS_START (the bootloader scans downward from there), and it must sit
|
||||
// ENTIRELY above both the running image and the in-place decoder workspace, and below the filesystem
|
||||
// region (ExtraFS/InternalFS - where user prefs live, assumed immutable). Reserving the full workspace
|
||||
// here prevents accepting a large container that the bootloader would later reject as overlapping it.
|
||||
// highest flash page below stage_ceiling (the bootloader scans downward from there), and it must sit
|
||||
// ENTIRELY above the running image and below the filesystem region (ExtraFS/InternalFS - where user prefs
|
||||
// live, assumed immutable). The apply path separately verifies the patch's own detools memory_size ends
|
||||
// at or below this planned start before it marks the package approved.
|
||||
// Returns false (and leaves out_start untouched) if it does not fit. This is the SINGLE place the FS
|
||||
// ceiling + app-collision bounds are enforced; begin()/reopen() both go through it. Pure - no flash I/O -
|
||||
// so it is unit-tested natively in test/test_ota/test_ota_flashplan.cpp.
|
||||
inline bool mota_nrf52_stage_plan(uint32_t total_size, uint32_t app_base, uint32_t app_end,
|
||||
uint32_t& out_start) {
|
||||
if (!mota_nrf52_layout_valid(app_base) || app_end < app_base ||
|
||||
app_end > app_base + MOTA_NRF52_INPLACE_MEMORY) return false;
|
||||
const uint32_t workspace_end = app_base + MOTA_NRF52_INPLACE_MEMORY;
|
||||
const uint32_t capacity = MOTA_NRF52_FS_START - workspace_end;
|
||||
if (total_size < 13 || total_size > capacity) return false; // 13 = header(8)+trailer(5); must fit below FS
|
||||
uint32_t start = (MOTA_NRF52_FS_START - total_size) & ~(MOTA_NRF52_FLASH_PAGE - 1); // bottom-align down
|
||||
if (start < app_end || start < workspace_end) return false; // would overlap app / decoder workspace
|
||||
uint32_t stage_ceiling, uint32_t& out_start) {
|
||||
if (!mota_nrf52_layout_valid(app_base, stage_ceiling) || app_end < app_base ||
|
||||
app_end > stage_ceiling) return false;
|
||||
const uint32_t capacity = stage_ceiling - app_base;
|
||||
if (total_size < 13 || total_size > capacity) return false; // 13 = header(8)+trailer(5)
|
||||
uint32_t start = (stage_ceiling - total_size) & ~(MOTA_NRF52_FLASH_PAGE - 1); // bottom-align down
|
||||
if (start < app_end) return false; // would overlap the running app
|
||||
out_start = start;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool mota_nrf52_stage_plan(uint32_t total_size, uint32_t app_base, uint32_t app_end,
|
||||
uint32_t& out_start) {
|
||||
return mota_nrf52_stage_plan(total_size, app_base, app_end,
|
||||
mota_nrf52_layout_stage_ceiling(), out_start);
|
||||
}
|
||||
|
||||
} // namespace ota
|
||||
} // namespace mesh
|
||||
@@ -68,14 +68,15 @@ bool ota_self_firmware(SelfFwInfo& out) {
|
||||
// firmware's own trailer), ignoring any staged `.mota` (which carries its own embedded EndF) higher up.
|
||||
bool ota_self_firmware(SelfFwInfo& out) {
|
||||
const uint32_t app_base = mota_nrf52_app_base();
|
||||
if (!mota_nrf52_layout_valid(app_base)) { out = SelfFwInfo(); return false; }
|
||||
const uint8_t* region = (const uint8_t*)(uintptr_t)app_base;
|
||||
uint32_t region_len =
|
||||
const uint32_t stage_ceiling =
|
||||
#if defined(OTA_SD_STORE)
|
||||
MOTA_NRF52_APP_END - app_base;
|
||||
MOTA_NRF52_APP_END;
|
||||
#else
|
||||
MOTA_NRF52_FS_START - app_base;
|
||||
mota_nrf52_layout_stage_ceiling();
|
||||
#endif
|
||||
if (!mota_nrf52_layout_valid(app_base, stage_ceiling)) { out = SelfFwInfo(); return false; }
|
||||
const uint8_t* region = (const uint8_t*)(uintptr_t)app_base;
|
||||
const uint32_t region_len = stage_ceiling - app_base;
|
||||
return find_self_firmware(region, region_len, out, /*verify_body=*/true);
|
||||
}
|
||||
#else
|
||||
@@ -97,7 +98,9 @@ bool ota_self_read(uint32_t off, uint8_t* buf, uint32_t len) {
|
||||
#if defined(OTA_SD_STORE)
|
||||
if (app_base >= MOTA_NRF52_APP_END || (uint64_t)app_base + off + len > MOTA_NRF52_APP_END) return false;
|
||||
#else
|
||||
if (!mota_nrf52_layout_valid(app_base) || (uint64_t)app_base + off + len > MOTA_NRF52_FS_START) return false;
|
||||
const uint32_t stage_ceiling = mota_nrf52_layout_stage_ceiling();
|
||||
if (!mota_nrf52_layout_valid(app_base, stage_ceiling) ||
|
||||
(uint64_t)app_base + off + len > stage_ceiling) return false;
|
||||
#endif
|
||||
memcpy(buf, (const uint8_t*)(uintptr_t)(app_base + off), len);
|
||||
return true;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#if defined(NRF52_PLATFORM) && defined(OTA_FLASH_STORE)
|
||||
|
||||
#include "OtaSelf.h"
|
||||
#include "OtaBlInfo.h"
|
||||
#include "OtaDebug.h"
|
||||
#include "OtaByteIO.h" // align_down / rd_u32le (flash-page geometry + header read)
|
||||
#include <string.h>
|
||||
@@ -11,10 +12,19 @@
|
||||
namespace mesh {
|
||||
namespace ota {
|
||||
|
||||
// A valid EndF gives the exact live-image extent. Rescue/bring-up builds without one retain the old
|
||||
// 608 KiB protection floor so staging cannot begin in the middle of a normally sized application.
|
||||
static bool protected_app_end(uint32_t app_base, uint32_t stage_ceiling, uint32_t& app_end) {
|
||||
if (!mota_nrf52_layout_valid(app_base, stage_ceiling)) return false;
|
||||
app_end = app_base + MOTA_NRF52_FALLBACK_INPLACE_MEMORY;
|
||||
SelfFwInfo fi;
|
||||
if (ota_self_firmware(fi) && fi.valid) app_end = app_base + fi.image_len;
|
||||
return app_end >= app_base && app_end <= stage_ceiling;
|
||||
}
|
||||
|
||||
// Write one whole 4 KB page from `buf` to flash (erase + program, ~85 ms). `buf` is PG bytes, 0xFF-padded
|
||||
// past the container, so the program is clean. The last container page ends exactly at FS_START (both
|
||||
// FS_START and _write_start are page-aligned and the container ends <= FS_START), so a full-page write
|
||||
// never reaches into ExtraFS.
|
||||
// past the container, so the program is clean. The selected ceiling and _write_start are page-aligned,
|
||||
// and the container ends below the ceiling, so a full-page write never reaches protected storage.
|
||||
static bool flash_matches(uint32_t addr, const uint8_t* expected, uint32_t n) {
|
||||
const volatile uint8_t* actual = (const volatile uint8_t*)(uintptr_t)addr;
|
||||
for (uint32_t i = 0; i < n; i++) if (actual[i] != expected[i]) return false;
|
||||
@@ -22,8 +32,8 @@ static bool flash_matches(uint32_t addr, const uint8_t* expected, uint32_t n) {
|
||||
}
|
||||
|
||||
bool OtaStoreFlashNrf52::flush_page(uint32_t page_idx, const uint8_t* buf) {
|
||||
if (!_io_ok || _write_start > MOTA_NRF52_FS_START - PG ||
|
||||
page_idx > (MOTA_NRF52_FS_START - PG - _write_start) / PG) {
|
||||
if (!_io_ok || _write_start > _stage_ceiling - PG ||
|
||||
page_idx > (_stage_ceiling - PG - _write_start) / PG) {
|
||||
_io_ok = false;
|
||||
return false;
|
||||
}
|
||||
@@ -35,6 +45,14 @@ bool OtaStoreFlashNrf52::flush_page(uint32_t page_idx, const uint8_t* buf) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t OtaStoreFlashNrf52::capacity() const {
|
||||
const uint32_t app_base = mota_nrf52_app_base();
|
||||
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
|
||||
uint32_t app_end;
|
||||
return protected_app_end(app_base, stage_ceiling, app_end)
|
||||
? mota_nrf52_stage_capacity(app_base, app_end, stage_ceiling) : 0;
|
||||
}
|
||||
|
||||
bool OtaStoreFlashNrf52::flush_pay() {
|
||||
return _pay_idx == 0 || flush_page(_pay_idx, _pay_page); // _pay_idx 0 == no payload page open
|
||||
}
|
||||
@@ -74,28 +92,25 @@ bool OtaStoreFlashNrf52::begin(uint32_t total_size) {
|
||||
|
||||
// never collide with the running application image (its extent comes from its EndF trailer)
|
||||
const uint32_t app_base = mota_nrf52_app_base();
|
||||
if (!mota_nrf52_layout_valid(app_base)) return false;
|
||||
uint32_t app_end = app_base;
|
||||
SelfFwInfo fi;
|
||||
if (ota_self_firmware(fi) && fi.valid) {
|
||||
if ((uint64_t)app_base + fi.image_len > MOTA_NRF52_FS_START) return false;
|
||||
app_end = app_base + fi.image_len;
|
||||
}
|
||||
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
|
||||
uint32_t app_end;
|
||||
if (!protected_app_end(app_base, stage_ceiling, app_end)) return false;
|
||||
|
||||
// Bottom-align below FS_START and reject unless it sits above the running image AND the full detools
|
||||
// workspace (the FS/prefs-safe bounds check; pure + unit-tested in the native OTA suite).
|
||||
// Bottom-align below the selected ceiling and reject unless it sits above the running image. The
|
||||
// approval path later verifies the patch's detools workspace ends at/below this exact start.
|
||||
uint32_t start;
|
||||
if (!mota_nrf52_stage_plan(total_size, app_base, app_end, start)) return false;
|
||||
if (!mota_nrf52_stage_plan(total_size, app_base, app_end, stage_ceiling, start)) return false;
|
||||
|
||||
_write_start = start;
|
||||
_stage_ceiling = stage_ceiling;
|
||||
_total = total_size;
|
||||
memset(_meta_page, 0xFF, PG); // assemble page 0 in RAM; 0xFF = erased sentinel (unfilled leaf slots)
|
||||
memset(_trailer, 0xFF, sizeof(_trailer));
|
||||
_pay_idx = 0;
|
||||
_flushed = false;
|
||||
_io_ok = true;
|
||||
OTA_DBG("OTA flash: begin total=%u start=%08x app_end=%08x\n",
|
||||
(unsigned)total_size, (unsigned)start, (unsigned)app_end);
|
||||
OTA_DBG("OTA flash: begin total=%u start=%08x app_end=%08x ceiling=%08x\n",
|
||||
(unsigned)total_size, (unsigned)start, (unsigned)app_end, (unsigned)stage_ceiling);
|
||||
return true; // no pre-erase: each page is erased by its own (single) flush
|
||||
}
|
||||
|
||||
@@ -156,33 +171,31 @@ void OtaStoreFlashNrf52::checkpoint() {
|
||||
}
|
||||
|
||||
// Re-attach to a container already staged in flash (after a reboot), without erasing. The container is
|
||||
// bottom-aligned (begin: start = (FS_START - total) & ~(PG-1)) and flash is memory-mapped, so scan page
|
||||
// starts from just below FS_START down to the app end for MOTA_MAGIC with a self-consistent total; adopt
|
||||
// bottom-aligned (begin: start = (ceiling - total) & ~(PG-1)) and flash is memory-mapped, so scan page
|
||||
// starts from just below that ceiling down to the app end for MOTA_MAGIC with a self-consistent total; adopt
|
||||
// the first match (highest address = most recent for the common single-container case). The manager then
|
||||
// parses the loaded manifest and validates geometry/root, so a stale leftover is rejected there.
|
||||
bool OtaStoreFlashNrf52::reopen() {
|
||||
const uint32_t app_base = mota_nrf52_app_base();
|
||||
if (!mota_nrf52_layout_valid(app_base)) return false;
|
||||
uint32_t app_end = app_base;
|
||||
SelfFwInfo fi;
|
||||
if (ota_self_firmware(fi) && fi.valid) {
|
||||
if ((uint64_t)app_base + fi.image_len > MOTA_NRF52_FS_START) return false;
|
||||
app_end = app_base + fi.image_len;
|
||||
}
|
||||
for (uint32_t start = align_down(MOTA_NRF52_FS_START - PG, PG); start >= app_end; start -= PG) {
|
||||
const uint32_t stage_ceiling = ota_nrf52_effective_stage_ceiling();
|
||||
uint32_t app_end;
|
||||
if (!protected_app_end(app_base, stage_ceiling, app_end)) return false;
|
||||
for (uint32_t start = align_down(stage_ceiling - PG, PG); start >= app_end; start -= PG) {
|
||||
const uint8_t* p = (const uint8_t*)(uintptr_t)start;
|
||||
if (memcmp(p, MOTA_MAGIC, 4) != 0) continue;
|
||||
uint32_t total = rd_u32le(p + 4);
|
||||
uint32_t want; // must be valid + placed exactly where begin() would have staged it (same bounds fn)
|
||||
if (!mota_nrf52_stage_plan(total, app_base, app_end, want) || want != start) continue;
|
||||
if (!mota_nrf52_stage_plan(total, app_base, app_end, stage_ceiling, want) || want != start) continue;
|
||||
_write_start = start;
|
||||
_stage_ceiling = stage_ceiling;
|
||||
_total = total;
|
||||
memcpy(_meta_page, p, PG); // load page 0 (header+manifest+leaves) into RAM to continue
|
||||
memcpy(_trailer, p + (total - 5), 5); // recover the trailer tail (flushed at last finalize, if any)
|
||||
_pay_idx = 0;
|
||||
_flushed = false;
|
||||
_io_ok = true;
|
||||
OTA_DBG("OTA flash: reopen total=%u start=%08x\n", (unsigned)total, (unsigned)start);
|
||||
OTA_DBG("OTA flash: reopen total=%u start=%08x ceiling=%08x\n",
|
||||
(unsigned)total, (unsigned)start, (unsigned)stage_ceiling);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include "OtaStore.h"
|
||||
#include "OtaFlashLayout_nrf52.h"
|
||||
|
||||
// Persistent flash-backed OtaStore for nRF52840. Stages the received `.mota` in the free flash
|
||||
// below the primary LittleFS (FS_START), bottom-aligned so its trailer ends at FS_START and the
|
||||
// bootloader can scan for it. Survives reboot - the whole point - so the bootloader can apply the
|
||||
// staged delta on the next boot.
|
||||
// Persistent flash-backed OtaStore for nRF52840. Stages the received `.mota` below the selected
|
||||
// filesystem-safe ceiling, bottom-aligned so the bootloader can find it. Internal ExtraFS roles stop
|
||||
// at 0xD4000; QSPI/no-ExtraFS roles can use the otherwise-free range through 0xED000. Survives reboot -
|
||||
// the whole point - so the bootloader can apply the staged delta on the next boot.
|
||||
//
|
||||
// RAM is bounded to O(one flash page), NEVER O(mota): a 100 KB+ delta must not live in RAM.
|
||||
// - On nRF52 the flash *erase* unit is one 4 KB page and the only SoftDevice-safe writer
|
||||
@@ -36,6 +36,7 @@ class OtaStoreFlashNrf52 : public OtaStore {
|
||||
static const uint32_t PG = MOTA_NRF52_FLASH_PAGE; // 4096
|
||||
|
||||
uint32_t _write_start = 0; // flash address of container offset 0 (page-aligned)
|
||||
uint32_t _stage_ceiling = MOTA_NRF52_STAGE_CEILING_LEGACY;
|
||||
uint32_t _total = 0; // staged container size (0 = none)
|
||||
bool _flushed = false; // finalize() committed everything to flash
|
||||
bool _io_ok = true; // cleared on a failed/out-of-bounds flash write or readback mismatch
|
||||
@@ -58,9 +59,12 @@ public:
|
||||
bool begin(uint32_t total_size) override;
|
||||
bool write(uint32_t offset, const uint8_t* data, uint32_t len) override;
|
||||
bool read(uint32_t offset, uint8_t* buf, uint32_t len) const override;
|
||||
uint32_t capacity() const override { return mota_nrf52_stage_capacity(mota_nrf52_app_base()); }
|
||||
uint32_t capacity() const override;
|
||||
uint32_t staged_size() const override { return _total; }
|
||||
void clear() override { _total = 0; _pay_idx = 0; _flushed = false; _io_ok = true; }
|
||||
void clear() override {
|
||||
_stage_ceiling = MOTA_NRF52_STAGE_CEILING_LEGACY;
|
||||
_total = 0; _pay_idx = 0; _flushed = false; _io_ok = true;
|
||||
}
|
||||
bool set_meta_size(uint32_t meta_bytes) override { return meta_bytes <= PG; } // leaves must fit page 0
|
||||
bool finalize() override;
|
||||
void checkpoint() override; // persist page 0 (leaves) + the open payload page so a reboot can resume
|
||||
|
||||
@@ -780,6 +780,20 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (gps_detected && strcmp(name, "gps") == 0) {
|
||||
bool enabled = strcmp(value, "0") != 0;
|
||||
#if defined(RAK_WISBLOCK_GPS) && defined(FORCE_GPS_ALIVE)
|
||||
// RAK3401 must keep 3V3_S on for its radio FEM. RAK12501/L76K has no
|
||||
// separately wired standby control, so claiming it is off would only stop
|
||||
// parsing while the receiver continued drawing full power.
|
||||
if (!enabled && serialGPSFlag) {
|
||||
setGpsTelemetryUserEnabled(true);
|
||||
return false;
|
||||
}
|
||||
if (enabled && serialGPSFlag) {
|
||||
_location->setGPSPowerSaving(false);
|
||||
setGpsTelemetryUserEnabled(true);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
bool was_active = gps_active;
|
||||
_location->setGPSPowerSaving(enabled && powersaving_enabled);
|
||||
setGpsTelemetryUserEnabled(enabled);
|
||||
@@ -788,13 +802,8 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "gps_interval") == 0) {
|
||||
uint32_t interval_seconds = atoi(value);
|
||||
gps_update_interval_sec = interval_seconds > 0 ? interval_seconds : 1;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false; // not supported
|
||||
return SensorManager::setSettingValue(name, value);
|
||||
}
|
||||
|
||||
void EnvironmentSensorManager::setPowerSavingEnabled(bool enabled) {
|
||||
@@ -804,6 +813,13 @@ void EnvironmentSensorManager::setPowerSavingEnabled(bool enabled) {
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (!gps_detected) return;
|
||||
bool gps_user_enabled = isGpsTelemetryUserEnabled();
|
||||
#if defined(RAK_WISBLOCK_GPS) && defined(FORCE_GPS_ALIVE)
|
||||
if (serialGPSFlag) {
|
||||
_location->setGPSPowerSaving(false);
|
||||
if (!gps_active) start_gps();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
_location->setGPSPowerSaving(enabled && gps_user_enabled);
|
||||
if (!gps_user_enabled) return;
|
||||
|
||||
@@ -886,8 +902,16 @@ void EnvironmentSensorManager::rakGPSInit() {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef FORCE_GPS_ALIVE // for use with repeaters, until GPS toggle is implimented
|
||||
// Now that GPS is found and set up, set to sleep for initial state
|
||||
#ifdef FORCE_GPS_ALIVE
|
||||
if (i2cGPSFlag) {
|
||||
// The u-blox receiver can sleep while the shared rail remains available.
|
||||
stop_gps();
|
||||
} else if (serialGPSFlag) {
|
||||
// The UART L76K cannot be isolated from the RAK3401 radio power rail.
|
||||
setGpsTelemetryUserEnabled(true);
|
||||
}
|
||||
#else
|
||||
// Now that GPS is found and set up, set it to sleep for the initial state.
|
||||
stop_gps();
|
||||
#endif
|
||||
}
|
||||
@@ -986,8 +1010,10 @@ void EnvironmentSensorManager::stop_gps() {
|
||||
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
#ifdef FORCE_GPS_ALIVE
|
||||
// Keep the shared rail alive. The UART L76K cannot be put to sleep here.
|
||||
if (i2cGPSFlag) ublox_GNSS.powerSaveMode(false);
|
||||
// Keep the shared rail alive for the RAK3401 radio FEM. The I2C u-blox can
|
||||
// still enter its internal power-save mode. UART L76K builds reject GPS off
|
||||
// above because that module has no independent standby connection.
|
||||
if (i2cGPSFlag) ublox_GNSS.powerSaveMode(true);
|
||||
#else
|
||||
pinMode(gpsResetPin, OUTPUT);
|
||||
digitalWrite(gpsResetPin, LOW); // WB_IO2
|
||||
@@ -1013,8 +1039,9 @@ void EnvironmentSensorManager::loop() {
|
||||
if (powersaving_enabled && gps_detected && _location->getGPSPowerSaving()) {
|
||||
unsigned long next_off = _location->getNextGPSOff();
|
||||
unsigned long next_on = _location->getNextGPSOn();
|
||||
if (gps_active && ((next_off != 0 && (long)(now - next_off) >= 0)
|
||||
|| !_location->waitingTimeSync())) {
|
||||
if (gps_active && !gpsTelemetryReceiverRequired(now)
|
||||
&& ((next_off != 0 && (long)(now - next_off) >= 0)
|
||||
|| !_location->waitingTimeSync())) {
|
||||
POWERSAVING_DEBUG_PRINTLN("GPS entering sleep");
|
||||
stop_gps();
|
||||
} else if (!gps_active && ((next_on != 0 && (long)(now - next_on) >= 0)
|
||||
@@ -1055,7 +1082,7 @@ void EnvironmentSensorManager::loop() {
|
||||
#endif
|
||||
|
||||
}
|
||||
next_gps_update = now + (gps_update_interval_sec * 1000);
|
||||
next_gps_update = now + getGpsUpdateIntervalMillis();
|
||||
}
|
||||
#endif
|
||||
#if ENV_INCLUDE_BME680_BSEC
|
||||
|
||||
@@ -21,7 +21,6 @@ protected:
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
uint32_t gps_update_interval_sec = 1;
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
LocationProvider* _location;
|
||||
|
||||
@@ -147,7 +147,7 @@ public :
|
||||
while (_gps_serial->available()) {
|
||||
char c = _gps_serial->read();
|
||||
#ifdef GPS_NMEA_DEBUG
|
||||
Serial.print(c);
|
||||
if (mesh::isUsbLoggingEnabled()) Serial.print(c);
|
||||
#endif
|
||||
nmea.process(c);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user