mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-07-28 07:09:21 +00:00
Harden nRF52 persistence, watchdog, and build variants
This commit is contained in:
@@ -31,7 +31,7 @@ PARSED_COMMAND_ARGS=()
|
||||
FIRMWARE_VERSION_EXPLICIT=0
|
||||
OUTPUT_POLICY_EXPLICIT=0
|
||||
|
||||
ENV_VARIANT_SUFFIX_PATTERN='companion_radio_(wifi_mqtt|serial|wifi|usb|ble)(_ps)?(_fem(on|off))?|companion_radio_ethernet|comp_radio_usb|companion_usb|companion_ble|repeater_lora_ota_no_external_sensors|repeater_bridge_rs232_serial1|repeater_bridge_rs232_serial2|repeater_bridge_rs232|repeater_bridge_espnow|repeater_observer_mqtt|repeater_ethernet|room_server_observer_mqtt|room_server_ethernet|terminal_chat|room_server|room_svr|kiss_modem|sensor|repeatr|repeater'
|
||||
ENV_VARIANT_SUFFIX_PATTERN='companion_radio_(wifi_mqtt|serial|wifi|usb|ble)(_ps)?(_fem(on|off))?|companion_radio_ethernet|comp_radio_usb|companion_usb|companion_ble|repeater_bridge_rs232_serial1_lora_ota_no_external_sensors|repeater_bridge_rs232_serial2_lora_ota_no_external_sensors|repeater_bridge_rs232_lora_ota_no_external_sensors|room_server_lora_ota_no_external_sensors|repeater_lora_ota_no_external_sensors|repeater_bridge_rs232_serial1|repeater_bridge_rs232_serial2|repeater_bridge_rs232|repeater_bridge_espnow|repeater_observer_mqtt|repeater_ethernet|room_server_observer_mqtt|room_server_ethernet|terminal_chat|room_server|room_svr|kiss_modem|sensor|repeatr|repeater'
|
||||
BOARD_MODIFIER_WITHOUT_DISPLAY="_without_display"
|
||||
BOARD_MODIFIER_LOGGING="_logging"
|
||||
BOARD_MODIFIER_TFT="_tft"
|
||||
|
||||
@@ -567,6 +567,22 @@ send text.flood checking ridge link
|
||||
|
||||
---
|
||||
|
||||
#### Enable or disable the nRF52 system watchdog
|
||||
**Usage:**
|
||||
- `get system.watchdog`
|
||||
- `set system.watchdog on`
|
||||
- `set system.watchdog off`
|
||||
|
||||
**Default:** `on`
|
||||
|
||||
**Notes:**
|
||||
- This nRF52-only hardware watchdog resets the device if the application loop stops for 60 seconds, including an indefinite SoftDevice flash-write wait.
|
||||
- Enabling takes effect without a reboot.
|
||||
- The nRF52 hardware cannot stop a watchdog after it has started. Disabling is persisted immediately, then the current firmware stops feeding it so the board performs one watchdog restart within 60 seconds. It remains off after that restart.
|
||||
- This setting is nRF52-only. The SoftDevice flash deadlock does not apply to ESP32, whose existing watchdog behavior is unchanged.
|
||||
|
||||
---
|
||||
|
||||
#### Estimate and correct repeater time after startup
|
||||
|
||||
**Usage:**
|
||||
|
||||
@@ -503,6 +503,9 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog();
|
||||
#endif
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -124,6 +124,9 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog();
|
||||
#endif
|
||||
modem->loop();
|
||||
|
||||
if (!modem->isActuallyTransmitting()) {
|
||||
|
||||
@@ -148,6 +148,9 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
|
||||
#endif
|
||||
// Handle Serial CLI
|
||||
int len = strlen(command);
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
|
||||
@@ -107,6 +107,9 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
|
||||
#endif
|
||||
int len = strlen(command);
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
|
||||
@@ -597,6 +597,9 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog();
|
||||
#endif
|
||||
the_mesh.loop();
|
||||
rtc_clock.tick();
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
|
||||
@@ -121,6 +121,9 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
|
||||
#endif
|
||||
int len = strlen(command);
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
|
||||
+17
-4
@@ -201,13 +201,26 @@ lib_deps =
|
||||
adafruit/Adafruit BME680 Library @ ^2.0.4
|
||||
adafruit/Adafruit BMP085 Library @ ^1.2.4
|
||||
|
||||
; The VL53L0X and BME680/BSEC stacks prevent some nRF52840 repeater images from fitting
|
||||
; the safe in-place LoRa OTA window. Space-constrained targets retain GPS, the other
|
||||
; environmental sensors, and board-native telemetry while omitting these optional sensors.
|
||||
[nrf52_ota_sensor_trim]
|
||||
; Lean nRF52 LoRa OTA roles use this complete external-sensor trim. Board-native
|
||||
; radio, display, buttons, battery monitoring, and other board telemetry remain.
|
||||
[nrf52_no_external_sensors]
|
||||
build_flags =
|
||||
-UENV_INCLUDE_GPS
|
||||
-UENV_INCLUDE_AHTX0
|
||||
-UENV_INCLUDE_BME280
|
||||
-UENV_INCLUDE_BMP280
|
||||
-UENV_INCLUDE_SHTC3
|
||||
-UENV_INCLUDE_SHT4X
|
||||
-UENV_INCLUDE_LPS22HB
|
||||
-UENV_INCLUDE_INA3221
|
||||
-UENV_INCLUDE_INA219
|
||||
-UENV_INCLUDE_INA226
|
||||
-UENV_INCLUDE_INA260
|
||||
-UENV_INCLUDE_MLX90614
|
||||
-UENV_INCLUDE_VL53L0X
|
||||
-UENV_INCLUDE_BME680
|
||||
-UENV_INCLUDE_BMP085
|
||||
-UENV_INCLUDE_RAK12035
|
||||
-UENV_INCLUDE_BME680_BSEC
|
||||
|
||||
; ----------------- TESTING ---------------------
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "IdentityStore.h"
|
||||
|
||||
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#include "AtomicFileWriter.h"
|
||||
@@ -0,0 +1,181 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
/**
|
||||
* Power-loss-safe file replacement for the nRF52 LittleFS backend.
|
||||
*
|
||||
* Data is written to a sibling .tmp file. commit() flushes and closes that
|
||||
* file, reads it back while checking its size and CRC32, and only then uses
|
||||
* LittleFS rename() to atomically replace the live file. A failed or abandoned
|
||||
* write removes the temporary file and leaves the previous live file intact.
|
||||
*/
|
||||
class AtomicFileWriter {
|
||||
static const size_t TEMP_PATH_CAPACITY = 96;
|
||||
|
||||
FILESYSTEM* _fs;
|
||||
const char* _target_path;
|
||||
char _temp_path[TEMP_PATH_CAPACITY];
|
||||
File _file;
|
||||
size_t _bytes_written;
|
||||
uint32_t _crc;
|
||||
bool _opened;
|
||||
bool _write_ok;
|
||||
bool _finished;
|
||||
|
||||
static uint32_t updateCRC32(uint32_t crc, const uint8_t* data, size_t len);
|
||||
void removeTempFile();
|
||||
bool validateTempFile();
|
||||
|
||||
public:
|
||||
AtomicFileWriter(FILESYSTEM* fs, const char* target_path);
|
||||
~AtomicFileWriter();
|
||||
AtomicFileWriter(const AtomicFileWriter&) = delete;
|
||||
AtomicFileWriter& operator=(const AtomicFileWriter&) = delete;
|
||||
|
||||
operator bool() const { return _opened && _write_ok; }
|
||||
|
||||
size_t write(uint8_t value);
|
||||
size_t write(const uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* Commit the staged file. content_valid lets callers include format-specific
|
||||
* checks in the same decision; false always preserves the previous file.
|
||||
*/
|
||||
bool commit(bool content_valid = true);
|
||||
|
||||
size_t bytesWritten() const { return _bytes_written; }
|
||||
const char* tempPath() const { return _temp_path; }
|
||||
};
|
||||
|
||||
} // namespace mesh
|
||||
|
||||
#if defined(ATOMIC_FILE_WRITER_IMPLEMENTATION)
|
||||
|
||||
namespace mesh {
|
||||
|
||||
uint32_t AtomicFileWriter::updateCRC32(uint32_t crc, const uint8_t* data, size_t len) {
|
||||
while (len-- > 0) {
|
||||
crc ^= *data++;
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320UL : 0);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void AtomicFileWriter::removeTempFile() {
|
||||
if (_temp_path[0] != 0 && _fs->exists(_temp_path)) {
|
||||
_fs->remove(_temp_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool AtomicFileWriter::validateTempFile() {
|
||||
File verify(*_fs);
|
||||
if (!verify.open(_temp_path, FILE_O_READ)) return false;
|
||||
|
||||
bool valid = verify.size() == _bytes_written;
|
||||
uint32_t read_crc = 0xFFFFFFFFUL;
|
||||
size_t remaining = _bytes_written;
|
||||
uint8_t buf[64];
|
||||
|
||||
while (valid && remaining > 0) {
|
||||
size_t chunk = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
||||
int count = verify.read(buf, (uint16_t)chunk);
|
||||
if (count != (int)chunk) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
read_crc = updateCRC32(read_crc, buf, chunk);
|
||||
remaining -= chunk;
|
||||
}
|
||||
|
||||
verify.close();
|
||||
return valid && remaining == 0 && read_crc == _crc;
|
||||
}
|
||||
|
||||
AtomicFileWriter::AtomicFileWriter(FILESYSTEM* fs, const char* target_path)
|
||||
: _fs(fs), _target_path(target_path), _file(*fs), _bytes_written(0),
|
||||
_crc(0xFFFFFFFFUL), _opened(false), _write_ok(true), _finished(false) {
|
||||
_temp_path[0] = 0;
|
||||
if (target_path == NULL) {
|
||||
_write_ok = false;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t target_len = strlen(target_path);
|
||||
if (target_len + sizeof(".tmp") > sizeof(_temp_path)) {
|
||||
_write_ok = false;
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(_temp_path, target_path, target_len);
|
||||
memcpy(_temp_path + target_len, ".tmp", sizeof(".tmp"));
|
||||
|
||||
// A reset before a previous commit may leave a harmless stale temp file.
|
||||
// Never append to it: remove it before opening the new transaction.
|
||||
removeTempFile();
|
||||
_opened = _file.open(_temp_path, FILE_O_WRITE);
|
||||
_write_ok = _opened;
|
||||
}
|
||||
|
||||
AtomicFileWriter::~AtomicFileWriter() {
|
||||
if (_finished) return;
|
||||
if (_opened) {
|
||||
_file.close();
|
||||
_opened = false;
|
||||
}
|
||||
removeTempFile();
|
||||
}
|
||||
|
||||
size_t AtomicFileWriter::write(uint8_t value) {
|
||||
return write(&value, 1);
|
||||
}
|
||||
|
||||
size_t AtomicFileWriter::write(const uint8_t* data, size_t len) {
|
||||
if (!_opened || !_write_ok || (data == NULL && len != 0)) {
|
||||
_write_ok = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t written = _file.write(data, len);
|
||||
if (written != len) {
|
||||
_write_ok = false;
|
||||
return written;
|
||||
}
|
||||
|
||||
_bytes_written += written;
|
||||
_crc = updateCRC32(_crc, data, written);
|
||||
return written;
|
||||
}
|
||||
|
||||
bool AtomicFileWriter::commit(bool content_valid) {
|
||||
if (_finished) return false;
|
||||
|
||||
bool success = _opened && _write_ok && content_valid;
|
||||
if (_opened) {
|
||||
_file.flush();
|
||||
if (_file.size() != _bytes_written) success = false;
|
||||
_file.close();
|
||||
_opened = false;
|
||||
}
|
||||
|
||||
if (success) success = validateTempFile();
|
||||
if (success) success = _fs->rename(_temp_path, _target_path);
|
||||
if (!success) removeTempFile();
|
||||
|
||||
_finished = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
|
||||
#endif // ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
|
||||
#endif // NRF52_PLATFORM
|
||||
@@ -1,9 +1,13 @@
|
||||
#include "ClientACL.h"
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include "AtomicFileWriter.h"
|
||||
#endif
|
||||
|
||||
static const uint8_t CONTACT_RECORD_VERSION_ALT_PATH = 1;
|
||||
|
||||
#if !defined(NRF52_PLATFORM)
|
||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#if defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
return _fs->open(filename, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
@@ -12,6 +16,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
return _fs->open(filename, "w", true);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
|
||||
_fs = fs;
|
||||
@@ -61,17 +66,22 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
|
||||
|
||||
void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) {
|
||||
_fs = fs;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
mesh::AtomicFileWriter file(_fs, "/s_contacts");
|
||||
#else
|
||||
File file = openWrite(_fs, "/s_contacts");
|
||||
#endif
|
||||
if (file) {
|
||||
uint8_t unused[2];
|
||||
unused[0] = CONTACT_RECORD_VERSION_ALT_PATH;
|
||||
unused[1] = 0;
|
||||
|
||||
for (int i = 0; i < num_clients; i++) {
|
||||
bool success = true;
|
||||
for (int i = 0; success && i < num_clients; i++) {
|
||||
auto c = &clients[i];
|
||||
if (c->permissions == 0 || (filter && !filter(c))) continue; // skip deleted entries, or by filter function
|
||||
|
||||
bool success = (file.write(c->id.pub_key, 32) == 32);
|
||||
success = (file.write(c->id.pub_key, 32) == 32);
|
||||
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
||||
success = success && (file.write((uint8_t *) &c->extra.room.sync_since, 4) == 4);
|
||||
success = success && (file.write(unused, 2) == 2);
|
||||
@@ -83,7 +93,13 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) {
|
||||
|
||||
if (!success) break; // write failed
|
||||
}
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (!file.commit(success)) {
|
||||
MESH_DEBUG_PRINTLN("ERROR: ClientACL::save atomic commit failed");
|
||||
}
|
||||
#else
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +108,11 @@ bool ClientACL::clear() {
|
||||
if (_fs->exists("/s_contacts")) {
|
||||
_fs->remove("/s_contacts");
|
||||
}
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (_fs->exists("/s_contacts.tmp")) {
|
||||
_fs->remove("/s_contacts.tmp");
|
||||
}
|
||||
#endif
|
||||
memset(clients, 0, sizeof(clients));
|
||||
num_clients = 0;
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include "AlertReporter.h" // for alertReporterBannedChannelMatch()
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include "AtomicFileWriter.h"
|
||||
#endif
|
||||
#include <RTClib.h>
|
||||
#include <Utils.h>
|
||||
#include <stddef.h>
|
||||
@@ -804,6 +807,10 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
bool is_upgrade = false;
|
||||
bool loaded = false;
|
||||
|
||||
// The hardware main-loop watchdog is opt-out. Older preference files do not
|
||||
// contain its appended byte, so they safely inherit the enabled default.
|
||||
_prefs->system_watchdog_enabled = 1;
|
||||
|
||||
if (fs->exists("/com_prefs")) {
|
||||
loadPrefsInt(fs, "/com_prefs"); loaded = true; // new filename
|
||||
} else if (fs->exists("/node_prefs")) {
|
||||
@@ -943,6 +950,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->telemetry_access = TELEMETRY_ACCESS_ALL;
|
||||
_prefs->flood_retry_group_max_path = FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT;
|
||||
_prefs->rx_watchdog_enabled = 0;
|
||||
_prefs->system_watchdog_enabled = 1;
|
||||
// A remainder larger than the smallest legacy MQTT gap (864) means an old fork
|
||||
// file with the zero-filled gap; detect and recover it below. Anything smaller
|
||||
// (upstream/flex 5-byte tails, or the ~384-byte keymind retry tail) takes the
|
||||
@@ -1198,6 +1206,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->rx_watchdog_enabled,
|
||||
sizeof(_prefs->rx_watchdog_enabled));
|
||||
}
|
||||
if (file.available() >= (int)sizeof(_prefs->system_watchdog_enabled)) {
|
||||
file.read((uint8_t *)&_prefs->system_watchdog_enabled,
|
||||
sizeof(_prefs->system_watchdog_enabled));
|
||||
}
|
||||
}
|
||||
|
||||
// sanitise bad pref values
|
||||
@@ -1290,6 +1302,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
#endif
|
||||
_prefs->rx_powersaving_enabled = constrain(_prefs->rx_powersaving_enabled, 0, 1);
|
||||
_prefs->rx_watchdog_enabled = constrain(_prefs->rx_watchdog_enabled, 0, 1);
|
||||
_prefs->system_watchdog_enabled = constrain(_prefs->system_watchdog_enabled, 0, 1);
|
||||
_prefs->rx_ps_level = constrain(_prefs->rx_ps_level, 0, 10);
|
||||
if (_prefs->rx_ps_preamble != 16 && _prefs->rx_ps_preamble != 32) {
|
||||
_prefs->rx_ps_preamble = 0; // 0 = auto (derive from SF)
|
||||
@@ -1302,7 +1315,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
}
|
||||
|
||||
void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#if defined(NRF52_PLATFORM)
|
||||
mesh::AtomicFileWriter file(fs, "/com_prefs");
|
||||
#elif defined(STM32_PLATFORM)
|
||||
fs->remove("/com_prefs");
|
||||
File file = fs->open("/com_prefs", FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
@@ -1428,9 +1443,17 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
sizeof(_prefs->flood_retry_group_max_path)); // 853
|
||||
file.write((uint8_t *)&_prefs->rx_watchdog_enabled,
|
||||
sizeof(_prefs->rx_watchdog_enabled)); // 854
|
||||
// next: 855
|
||||
file.write((uint8_t *)&_prefs->system_watchdog_enabled,
|
||||
sizeof(_prefs->system_watchdog_enabled)); // 855
|
||||
// next: 856
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (!file.commit()) {
|
||||
MESH_DEBUG_PRINTLN("ERROR: savePrefs atomic commit failed");
|
||||
}
|
||||
#else
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Observer config (MQTT/WiFi/timezone/SNMP/alert) is persisted separately. The
|
||||
@@ -1702,7 +1725,9 @@ void CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) {
|
||||
MESH_DEBUG_PRINTLN("MQTT: /mqtt_prefs from newer firmware, not overwriting");
|
||||
return;
|
||||
}
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#if defined(NRF52_PLATFORM)
|
||||
mesh::AtomicFileWriter file(fs, "/mqtt_prefs");
|
||||
#elif defined(STM32_PLATFORM)
|
||||
fs->remove("/mqtt_prefs");
|
||||
File file = fs->open("/mqtt_prefs", FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
@@ -1718,7 +1743,13 @@ void CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) {
|
||||
hdr.payload_len = (uint16_t)sizeof(_mqtt_prefs);
|
||||
file.write((uint8_t *)&hdr, sizeof(hdr));
|
||||
file.write((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs));
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (!file.commit()) {
|
||||
MESH_DEBUG_PRINTLN("ERROR: saveMQTTPrefs atomic commit failed");
|
||||
}
|
||||
#else
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2219,6 +2250,23 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
int a_frac = (int)((actual - a_int) * 10.0f + 0.5f);
|
||||
sprintf(reply, "OK - %d.%d%%", a_int, a_frac);
|
||||
}
|
||||
} else if (memcmp(config, "system.watchdog ", 16) == 0) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
const char* value = &config[16];
|
||||
if (strcmp(value, "on") == 0) {
|
||||
_prefs->system_watchdog_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - system watchdog enabled");
|
||||
} else if (strcmp(value, "off") == 0) {
|
||||
_prefs->system_watchdog_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - disabled; restarting within 60s");
|
||||
} else {
|
||||
strcpy(reply, "Error: use set system.watchdog on|off");
|
||||
}
|
||||
#else
|
||||
strcpy(reply, "Error: unsupported on this platform");
|
||||
#endif
|
||||
} else if (memcmp(config, "af ", 3) == 0) {
|
||||
_prefs->airtime_factor = atof(&config[3]);
|
||||
savePrefs();
|
||||
@@ -3035,6 +3083,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
int dc_int = (int)dc;
|
||||
int dc_frac = (int)((dc - dc_int) * 10.0f + 0.5f);
|
||||
sprintf(reply, "> %d.%d%%", dc_int, dc_frac);
|
||||
} else if (strcmp(config, "system.watchdog") == 0) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
sprintf(reply, "> %s", _prefs->system_watchdog_enabled ? "on" : "off");
|
||||
#else
|
||||
strcpy(reply, "Error: unsupported on this platform");
|
||||
#endif
|
||||
} else if (memcmp(config, "af", 2) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
|
||||
} else if (memcmp(config, "int.thresh", 10) == 0) {
|
||||
|
||||
@@ -219,6 +219,7 @@ struct NodePrefs { // persisted to file
|
||||
char battery_alert_region[31]; // named scope for low-battery floods; empty = no alert scope
|
||||
uint8_t flood_retry_group_max_path; // PAYLOAD_TYPE_GRP_DATA retry path gate; 0xFF = use only the general gate
|
||||
uint8_t rx_watchdog_enabled; // repeater RX-inactivity reboot watchdog (boolean)
|
||||
uint8_t system_watchdog_enabled; // nRF52 main-loop hardware watchdog (boolean; default on)
|
||||
};
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <bluefruit.h>
|
||||
#include "ble_gap.h"
|
||||
#include "ble_hci.h"
|
||||
#include <nrf.h>
|
||||
#include <nrf_soc.h>
|
||||
#ifdef USE_TINYUSB
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
@@ -15,6 +16,19 @@ static uint16_t ota_conn_handle = BLE_CONN_HANDLE_INVALID;
|
||||
static bool ota_active = false;
|
||||
static bool ota_ble_started = false;
|
||||
|
||||
// A normal internal-flash operation completes in milliseconds. One minute is
|
||||
// deliberately generous for other legitimate application work while still
|
||||
// recovering an indefinitely blocked SoftDevice flash wait without requiring
|
||||
// a physical power cycle. Builds can override this, or set it to 0 to disable
|
||||
// the watchdog for diagnostics.
|
||||
#ifndef NRF52_WATCHDOG_TIMEOUT_SECONDS
|
||||
#define NRF52_WATCHDOG_TIMEOUT_SECONDS 60UL
|
||||
#endif
|
||||
|
||||
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 131071UL
|
||||
#error "NRF52_WATCHDOG_TIMEOUT_SECONDS exceeds the nRF52 WDT counter range"
|
||||
#endif
|
||||
|
||||
static void format_ota_reply(char reply[]) {
|
||||
uint8_t mac_addr[6];
|
||||
memset(mac_addr, 0, sizeof(mac_addr));
|
||||
@@ -41,9 +55,42 @@ void NRF52Board::begin() {
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
}
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
#include "nrf.h"
|
||||
void NRF52Board::feedWatchdog(bool enabled) {
|
||||
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
|
||||
// The nRF52 watchdog cannot be stopped after it starts. When the persisted
|
||||
// setting is turned off, deliberately stop reloading it; the resulting
|
||||
// watchdog reset is the only software-only way to return it to the stopped
|
||||
// state. On that next boot the disabled preference prevents it starting.
|
||||
if (!enabled) return;
|
||||
|
||||
const bool running = NRF_WDT->RUNSTATUS != 0;
|
||||
if (!running) {
|
||||
// Keep running during CPU sleep: the flash-driver failure this protects
|
||||
// against sleeps in sd_app_evt_wait(). Pause while halted so breakpoints
|
||||
// do not reset a board being debugged.
|
||||
NRF_WDT->CONFIG =
|
||||
(WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos) |
|
||||
(WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos);
|
||||
NRF_WDT->CRV = (uint32_t)(NRF52_WATCHDOG_TIMEOUT_SECONDS * 32768UL);
|
||||
NRF_WDT->RREN = WDT_RREN_RR0_Msk;
|
||||
NRF_WDT->TASKS_START = 1;
|
||||
}
|
||||
|
||||
// Ordinarily only RR0 is enabled. If a bootloader left the watchdog running
|
||||
// with a different reload channel, service every enabled channel rather
|
||||
// than assuming ownership of RR0 and immediately entering a reset loop.
|
||||
const uint32_t enabled_channels = NRF_WDT->RREN & 0xFFUL;
|
||||
for (uint8_t channel = 0; channel < 8; channel++) {
|
||||
if (enabled_channels & (1UL << channel)) {
|
||||
NRF_WDT->RR[channel] = WDT_RR_RR_Reload;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)enabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
// Power Management global variables
|
||||
uint32_t g_nrf52_reset_reason = 0; // Reset/Startup reason
|
||||
uint8_t g_nrf52_shutdown_reason = 0; // Shutdown reason
|
||||
|
||||
@@ -47,6 +47,11 @@ protected:
|
||||
public:
|
||||
NRF52Board(char *otaname) : ota_name(otaname) {}
|
||||
virtual void begin();
|
||||
// Feed the hardware main-loop watchdog. The first call starts it, so normal
|
||||
// setup (including filesystem mount and radio initialization) is not timed.
|
||||
// A loop blocked forever in SoftDevice-backed flash I/O will stop feeding it
|
||||
// and be reset automatically.
|
||||
void feedWatchdog(bool enabled = true);
|
||||
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
||||
virtual float getMCUTemperature() override;
|
||||
virtual void reboot() override { NVIC_SystemReset(); }
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "RegionMap.h"
|
||||
#include <helpers/RegionNameUtils.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <helpers/AtomicFileWriter.h>
|
||||
#endif
|
||||
#include <SHA256.h>
|
||||
|
||||
// helper class for region map exporter, we emulate Stream with a safe buffer writer.
|
||||
@@ -59,8 +62,9 @@ static const char* skip_hash(const char* name) {
|
||||
return RegionNameUtils::canonical(name);
|
||||
}
|
||||
|
||||
#if !defined(NRF52_PLATFORM)
|
||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#if defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
return _fs->open(filename, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
@@ -69,6 +73,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
return _fs->open(filename, "w", true);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
|
||||
if (_fs->exists(path ? path : "/regions2")) {
|
||||
@@ -185,7 +190,12 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
|
||||
}
|
||||
|
||||
bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
|
||||
File file = openWrite(_fs, path ? path : "/regions2");
|
||||
const char* target_path = path ? path : "/regions2";
|
||||
#if defined(NRF52_PLATFORM)
|
||||
mesh::AtomicFileWriter file(_fs, target_path);
|
||||
#else
|
||||
File file = openWrite(_fs, target_path);
|
||||
#endif
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
@@ -208,7 +218,11 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
|
||||
if (!success) break; // write failed
|
||||
}
|
||||
}
|
||||
#if defined(NRF52_PLATFORM)
|
||||
success = file.commit(success);
|
||||
#else
|
||||
file.close();
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
return false; // failed
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class FakeFilesystem;
|
||||
|
||||
class FakeFile {
|
||||
FakeFilesystem* _fs;
|
||||
std::string _path;
|
||||
size_t _position;
|
||||
bool _open;
|
||||
bool _write_mode;
|
||||
|
||||
public:
|
||||
explicit FakeFile(FakeFilesystem& fs);
|
||||
|
||||
bool open(const char* path, uint8_t mode);
|
||||
size_t write(const uint8_t* data, size_t len);
|
||||
int read(void* data, uint16_t len);
|
||||
uint32_t size() const;
|
||||
void flush() {}
|
||||
void close() { _open = false; }
|
||||
explicit operator bool() const { return _open; }
|
||||
};
|
||||
|
||||
class FakeFilesystem {
|
||||
public:
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> files;
|
||||
size_t max_write = std::numeric_limits<size_t>::max();
|
||||
bool fail_read_open = false;
|
||||
bool fail_rename = false;
|
||||
unsigned rename_calls = 0;
|
||||
|
||||
bool exists(const char* path) const {
|
||||
return files.find(path) != files.end();
|
||||
}
|
||||
|
||||
bool remove(const char* path) {
|
||||
return files.erase(path) != 0;
|
||||
}
|
||||
|
||||
bool rename(const char* from, const char* to) {
|
||||
rename_calls++;
|
||||
if (fail_rename) return false;
|
||||
auto source = files.find(from);
|
||||
if (source == files.end()) return false;
|
||||
files[to] = source->second;
|
||||
files.erase(source);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
enum {
|
||||
FILE_O_READ = 0,
|
||||
FILE_O_WRITE = 1,
|
||||
};
|
||||
|
||||
FakeFile::FakeFile(FakeFilesystem& fs)
|
||||
: _fs(&fs), _position(0), _open(false), _write_mode(false) {}
|
||||
|
||||
bool FakeFile::open(const char* path, uint8_t mode) {
|
||||
_path = path;
|
||||
_write_mode = mode == FILE_O_WRITE;
|
||||
if (!_write_mode && (_fs->fail_read_open || !_fs->exists(path))) return false;
|
||||
if (_write_mode) {
|
||||
auto& contents = _fs->files[_path];
|
||||
_position = contents.size();
|
||||
} else {
|
||||
_position = 0;
|
||||
}
|
||||
_open = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t FakeFile::write(const uint8_t* data, size_t len) {
|
||||
if (!_open || !_write_mode) return 0;
|
||||
size_t written = std::min(len, _fs->max_write);
|
||||
auto& contents = _fs->files[_path];
|
||||
contents.insert(contents.end(), data, data + written);
|
||||
_position += written;
|
||||
return written;
|
||||
}
|
||||
|
||||
int FakeFile::read(void* data, uint16_t len) {
|
||||
if (!_open || _write_mode) return -1;
|
||||
const auto& contents = _fs->files[_path];
|
||||
size_t available = contents.size() - std::min(_position, contents.size());
|
||||
size_t count = std::min<size_t>(len, available);
|
||||
if (count > 0) memcpy(data, contents.data() + _position, count);
|
||||
_position += count;
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
uint32_t FakeFile::size() const {
|
||||
auto found = _fs->files.find(_path);
|
||||
return found == _fs->files.end() ? 0 : (uint32_t)found->second.size();
|
||||
}
|
||||
|
||||
#define NRF52_PLATFORM 1
|
||||
#define FILESYSTEM FakeFilesystem
|
||||
using File = FakeFile;
|
||||
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#include <helpers/AtomicFileWriter.h>
|
||||
|
||||
static std::vector<uint8_t> bytes(const char* text) {
|
||||
return std::vector<uint8_t>(text, text + strlen(text));
|
||||
}
|
||||
|
||||
TEST(AtomicFileWriter, ReplacesLiveFileOnlyAfterVerifiedCommit) {
|
||||
FakeFilesystem fs;
|
||||
fs.files["/prefs"] = bytes("old");
|
||||
|
||||
mesh::AtomicFileWriter writer(&fs, "/prefs");
|
||||
ASSERT_TRUE(writer);
|
||||
const uint8_t replacement[] = {'n', 'e', 'w'};
|
||||
EXPECT_EQ(writer.write(replacement, sizeof(replacement)), sizeof(replacement));
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("old"));
|
||||
|
||||
EXPECT_TRUE(writer.commit());
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("new"));
|
||||
EXPECT_FALSE(fs.exists("/prefs.tmp"));
|
||||
EXPECT_EQ(fs.rename_calls, 1u);
|
||||
}
|
||||
|
||||
TEST(AtomicFileWriter, PartialWritePreservesLiveFile) {
|
||||
FakeFilesystem fs;
|
||||
fs.files["/prefs"] = bytes("old");
|
||||
fs.max_write = 2;
|
||||
|
||||
mesh::AtomicFileWriter writer(&fs, "/prefs");
|
||||
const uint8_t replacement[] = {'n', 'e', 'w'};
|
||||
EXPECT_EQ(writer.write(replacement, sizeof(replacement)), 2u);
|
||||
EXPECT_FALSE(writer.commit());
|
||||
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("old"));
|
||||
EXPECT_FALSE(fs.exists("/prefs.tmp"));
|
||||
EXPECT_EQ(fs.rename_calls, 0u);
|
||||
}
|
||||
|
||||
TEST(AtomicFileWriter, ReadbackMismatchPreservesLiveFile) {
|
||||
FakeFilesystem fs;
|
||||
fs.files["/prefs"] = bytes("old");
|
||||
|
||||
mesh::AtomicFileWriter writer(&fs, "/prefs");
|
||||
const uint8_t replacement[] = {'n', 'e', 'w'};
|
||||
ASSERT_EQ(writer.write(replacement, sizeof(replacement)), sizeof(replacement));
|
||||
fs.files["/prefs.tmp"][1] ^= 0x01;
|
||||
|
||||
EXPECT_FALSE(writer.commit());
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("old"));
|
||||
EXPECT_FALSE(fs.exists("/prefs.tmp"));
|
||||
EXPECT_EQ(fs.rename_calls, 0u);
|
||||
}
|
||||
|
||||
TEST(AtomicFileWriter, CallerValidationFailurePreservesLiveFile) {
|
||||
FakeFilesystem fs;
|
||||
fs.files["/prefs"] = bytes("old");
|
||||
|
||||
mesh::AtomicFileWriter writer(&fs, "/prefs");
|
||||
const uint8_t replacement[] = {'n', 'e', 'w'};
|
||||
ASSERT_EQ(writer.write(replacement, sizeof(replacement)), sizeof(replacement));
|
||||
|
||||
EXPECT_FALSE(writer.commit(false));
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("old"));
|
||||
EXPECT_FALSE(fs.exists("/prefs.tmp"));
|
||||
EXPECT_EQ(fs.rename_calls, 0u);
|
||||
}
|
||||
|
||||
TEST(AtomicFileWriter, RenameFailurePreservesLiveFileAndCleansTemp) {
|
||||
FakeFilesystem fs;
|
||||
fs.files["/prefs"] = bytes("old");
|
||||
fs.fail_rename = true;
|
||||
|
||||
mesh::AtomicFileWriter writer(&fs, "/prefs");
|
||||
const uint8_t replacement[] = {'n', 'e', 'w'};
|
||||
ASSERT_EQ(writer.write(replacement, sizeof(replacement)), sizeof(replacement));
|
||||
|
||||
EXPECT_FALSE(writer.commit());
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("old"));
|
||||
EXPECT_FALSE(fs.exists("/prefs.tmp"));
|
||||
EXPECT_EQ(fs.rename_calls, 1u);
|
||||
}
|
||||
|
||||
TEST(AtomicFileWriter, AbandonedWriteAndStaleTempAreCleaned) {
|
||||
FakeFilesystem fs;
|
||||
fs.files["/prefs"] = bytes("old");
|
||||
fs.files["/prefs.tmp"] = bytes("stale");
|
||||
|
||||
{
|
||||
mesh::AtomicFileWriter writer(&fs, "/prefs");
|
||||
ASSERT_TRUE(writer);
|
||||
EXPECT_TRUE(fs.files["/prefs.tmp"].empty());
|
||||
const uint8_t replacement[] = {'n', 'e', 'w'};
|
||||
ASSERT_EQ(writer.write(replacement, sizeof(replacement)), sizeof(replacement));
|
||||
}
|
||||
|
||||
EXPECT_EQ(fs.files["/prefs"], bytes("old"));
|
||||
EXPECT_FALSE(fs.exists("/prefs.tmp"));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -36,14 +36,8 @@ lib_deps =
|
||||
extends = GAT562_30S_Mesh_Kit
|
||||
build_flags =
|
||||
${GAT562_30S_Mesh_Kit.build_flags}
|
||||
; Keep the repeater image inside the nRF52 in-place LoRa OTA workspace. The
|
||||
; room-server and companion targets retain the complete optional sensor set.
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENV_INCLUDE_MLX90614
|
||||
-UENV_INCLUDE_BMP085
|
||||
-UENV_INCLUDE_INA219
|
||||
-UENV_INCLUDE_INA226
|
||||
-UENV_INCLUDE_INA260
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"GAT562 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -56,6 +50,25 @@ build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA repeater above.
|
||||
[env:GAT562_30S_Mesh_Kit_repeater_lora_ota_no_external_sensors]
|
||||
extends = GAT562_30S_Mesh_Kit
|
||||
build_flags =
|
||||
${GAT562_30S_Mesh_Kit.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"GAT562 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
|
||||
[env:GAT562_30S_Mesh_Kit_room_server]
|
||||
extends = GAT562_30S_Mesh_Kit
|
||||
|
||||
@@ -32,6 +32,8 @@ lib_deps =
|
||||
extends = GAT562_Mesh_Tracker_Pro
|
||||
build_flags =
|
||||
${GAT562_Mesh_Tracker_Pro.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"GAT562 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -44,6 +46,25 @@ build_src_filter = ${GAT562_Mesh_Tracker_Pro.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA repeater above.
|
||||
[env:GAT562_Mesh_Tracker_Pro_repeater_lora_ota_no_external_sensors]
|
||||
extends = GAT562_Mesh_Tracker_Pro
|
||||
build_flags =
|
||||
${GAT562_Mesh_Tracker_Pro.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"GAT562 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
build_src_filter = ${GAT562_Mesh_Tracker_Pro.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
|
||||
[env:GAT562_Mesh_Tracker_Pro_room_server]
|
||||
extends = GAT562_Mesh_Tracker_Pro
|
||||
|
||||
@@ -67,16 +67,8 @@ build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
; Keep the repeater image inside the nRF52 in-place LoRa OTA workspace. The
|
||||
; sensor and companion targets retain the complete optional sensor set.
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENV_INCLUDE_BMP085
|
||||
-UENV_INCLUDE_MLX90614
|
||||
-UENV_INCLUDE_INA226
|
||||
-UENV_INCLUDE_INA260
|
||||
-UENV_INCLUDE_INA219
|
||||
-UENV_INCLUDE_LPS22HB
|
||||
-UENV_INCLUDE_BMP280
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D ADVERT_NAME='"Heltec_t096 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -85,19 +77,29 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA repeater above.
|
||||
[env:Heltec_t096_repeater_lora_ota_no_external_sensors]
|
||||
extends = Heltec_t096
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_t096 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
bodmer/TFT_eSPI @ ^2.4.31
|
||||
|
||||
[env:Heltec_t096_repeater_bridge_rs232]
|
||||
extends = Heltec_t096
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
; The RS232 bridge shares the repeater's strict in-place LoRa OTA budget.
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENV_INCLUDE_BMP085
|
||||
-UENV_INCLUDE_MLX90614
|
||||
-UENV_INCLUDE_INA226
|
||||
-UENV_INCLUDE_INA260
|
||||
-UENV_INCLUDE_INA219
|
||||
-UENV_INCLUDE_LPS22HB
|
||||
-UENV_INCLUDE_BMP280
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D ADVERT_NAME='"RS232 Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -113,6 +115,27 @@ build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA RS232 bridge above.
|
||||
[env:Heltec_t096_repeater_bridge_rs232_lora_ota_no_external_sensors]
|
||||
extends = Heltec_t096
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D ADVERT_NAME='"RS232 Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_RS232_BRIDGE=Serial2
|
||||
-D WITH_RS232_BRIDGE_RX=9
|
||||
-D WITH_RS232_BRIDGE_TX=10
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
bodmer/TFT_eSPI @ ^2.4.31
|
||||
|
||||
[env:Heltec_t096_room_server]
|
||||
extends = Heltec_t096
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
|
||||
@@ -36,16 +36,26 @@ build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
|
||||
build_flags =
|
||||
${Heltec_t1.build_flags}
|
||||
; Keep the repeater image inside the nRF52 in-place LoRa OTA workspace. The
|
||||
; room-server and companion targets retain the complete optional sensor set.
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENV_INCLUDE_BMP085
|
||||
-UENV_INCLUDE_MLX90614
|
||||
-UENV_INCLUDE_INA226
|
||||
-UENV_INCLUDE_INA260
|
||||
-UENV_INCLUDE_INA219
|
||||
-UENV_INCLUDE_LPS22HB
|
||||
-UENV_INCLUDE_BMP280
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D ADVERT_NAME='"Heltec_t1 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA repeater above.
|
||||
[env:Heltec_t1_repeater_lora_ota_no_external_sensors]
|
||||
extends = Heltec_t1
|
||||
extra_scripts = ${nrf52_lora_ota.extra_scripts}
|
||||
build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
+<helpers/ota/*.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
build_flags =
|
||||
${Heltec_t1.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_t1 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -54,8 +64,9 @@ build_flags =
|
||||
-D ENABLE_OTA=1
|
||||
-D OTA_FLASH_STORE=1
|
||||
-D OTA_FOLDER_SERIAL
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
bodmer/TFT_eSPI @ ^2.4.31
|
||||
|
||||
[env:Heltec_t1_room_server]
|
||||
extends = Heltec_t1
|
||||
|
||||
@@ -164,7 +164,8 @@ build_src_filter = ${Heltec_t114_with_display.build_src_filter}
|
||||
|
||||
build_flags =
|
||||
${Heltec_t114_with_display.build_flags}
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D ADVERT_NAME='"Heltec_T114 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -173,6 +174,23 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA repeater above.
|
||||
[env:Heltec_t114_repeater_lora_ota_no_external_sensors]
|
||||
extends = Heltec_t114_with_display
|
||||
build_src_filter = ${Heltec_t114_with_display.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
build_flags =
|
||||
${Heltec_t114_with_display.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_T114 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Heltec_t114_repeater_bridge_rs232]
|
||||
extends = Heltec_t114
|
||||
build_flags =
|
||||
|
||||
@@ -30,6 +30,28 @@ extends = rak3401
|
||||
extra_scripts = ${nrf52_lora_ota.extra_scripts}
|
||||
build_flags =
|
||||
${rak3401.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RAK3401 1W Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
;-D MESH_PACKET_LOGGING=1
|
||||
;-D MESH_DEBUG=1
|
||||
build_src_filter = ${rak3401.build_src_filter}
|
||||
+<helpers/ota/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA repeater above.
|
||||
[env:RAK_3401_repeater_lora_ota_no_external_sensors]
|
||||
extends = rak3401
|
||||
extra_scripts = ${nrf52_lora_ota.extra_scripts}
|
||||
build_flags =
|
||||
${rak3401.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RAK3401 1W Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -39,12 +61,13 @@ build_flags =
|
||||
-D ENABLE_OTA=1
|
||||
-D OTA_FLASH_STORE=1
|
||||
-D OTA_FOLDER_SERIAL
|
||||
;-D MESH_PACKET_LOGGING=1
|
||||
;-D MESH_DEBUG=1
|
||||
build_src_filter = ${rak3401.build_src_filter}
|
||||
+<helpers/ota/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:RAK_3401_room_server]
|
||||
extends = rak3401
|
||||
|
||||
@@ -72,23 +72,7 @@ extends = rak4631
|
||||
extra_scripts = ${nrf52_lora_ota.extra_scripts}
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-UENV_INCLUDE_GPS
|
||||
-UENV_INCLUDE_AHTX0
|
||||
-UENV_INCLUDE_BME280
|
||||
-UENV_INCLUDE_BMP280
|
||||
-UENV_INCLUDE_SHTC3
|
||||
-UENV_INCLUDE_SHT4X
|
||||
-UENV_INCLUDE_LPS22HB
|
||||
-UENV_INCLUDE_INA3221
|
||||
-UENV_INCLUDE_INA219
|
||||
-UENV_INCLUDE_INA226
|
||||
-UENV_INCLUDE_INA260
|
||||
-UENV_INCLUDE_MLX90614
|
||||
-UENV_INCLUDE_VL53L0X
|
||||
-UENV_INCLUDE_BME680
|
||||
-UENV_INCLUDE_BMP085
|
||||
-UENV_INCLUDE_RAK12035
|
||||
-UENV_INCLUDE_BME680_BSEC
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RAK4631 LoRa OTA No Ext Sensors"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -130,7 +114,8 @@ lib_deps =
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RS232 Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -150,11 +135,35 @@ build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA Serial1 bridge above.
|
||||
[env:RAK_4631_repeater_bridge_rs232_serial1_lora_ota_no_external_sensors]
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RS232 Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_RS232_BRIDGE=Serial1
|
||||
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX
|
||||
-D WITH_RS232_BRIDGE_TX=PIN_SERIAL1_TX
|
||||
build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:RAK_4631_repeater_bridge_rs232_serial2]
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
${nrf52_ota_sensor_trim.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RS232 Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -174,10 +183,35 @@ build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA Serial2 bridge above.
|
||||
[env:RAK_4631_repeater_bridge_rs232_serial2_lora_ota_no_external_sensors]
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RS232 Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_RS232_BRIDGE=Serial2
|
||||
-D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX
|
||||
-D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX
|
||||
build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:RAK_4631_room_server]
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full external-sensor build exceeds the in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Test Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
@@ -190,6 +224,25 @@ build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
|
||||
; Explicit lean alternative to the full-sensor, non-OTA room server above.
|
||||
[env:RAK_4631_room_server_lora_ota_no_external_sensors]
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
${nrf52_no_external_sensors.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Test Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${nrf52_lora_ota.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:RAK_4631_room_server_ethernet]
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
@@ -309,6 +362,8 @@ lib_deps =
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-UENABLE_OTA
|
||||
-D DISABLE_LORA_OTA=1 ; Full sensor image exceeds the safe nRF52 in-place OTA ceiling
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"RAK4631 Sensor"'
|
||||
-D ADVERT_LAT=0.0
|
||||
|
||||
Reference in New Issue
Block a user