Files
HaloKeymind/examples/simple_sensor/main.cpp
T
mikecarper b847b46ef7 Retry identity startup and harden replay state and permissions
Read identities up to three times before allowing replacement, publish only complete reads, and require new startup keys to be saved with bounded retries.

Use authoritative replay-file metadata throughout persistence and clock recovery. Validate setperm input before narrowing or mutating roles, and add production-path regression coverage.
2026-09-15 17:33:50 -07:00

234 lines
6.6 KiB
C++

#include "SensorMesh.h"
#include <helpers/IdentityGeneration.h>
#include <helpers/UsbLogging.h>
#if defined(ESP32_PLATFORM)
#include <helpers/ESP32TrueRandom.h>
#endif
#ifdef DISPLAY_CLASS
#include "UITask.h"
static UITask ui_task(display);
#endif
class MyMesh : public SensorMesh {
public:
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: SensorMesh(board, radio, ms, rng, rtc, tables),
battery_data(12*24, 5*60) // 24 hours worth of battery data, every 5 minutes
{
}
protected:
/* ========================== custom logic here ========================== */
Trigger low_batt, critical_batt;
TimeSeriesData battery_data;
void onSensorDataRead() override {
float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);
battery_data.recordData(getRTCClock(), batt_voltage); // record battery
alertIf(batt_voltage < 3.4f, critical_batt, HIGH_PRI_ALERT, "Battery is critical!");
alertIf(batt_voltage < 3.6f, low_batt, LOW_PRI_ALERT, "Battery is low");
}
int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) override {
battery_data.calcMinMaxAvg(getRTCClock(), start_secs_ago, end_secs_ago, &dest[0], TELEM_CHANNEL_SELF, LPP_VOLTAGE);
return 1;
}
bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) override {
if (strcmp(command, "magic") == 0) { // example 'custom' command handling
strcpy(reply, "**Magic now done**");
return true; // handled
}
return false; // not handled
}
/* ======================================================================= */
};
StdRNG fast_rng;
SimpleMeshTables tables;
MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables);
void halt() {
while (1) ;
}
static char command[160];
static const unsigned long POWERSAVING_FIRST_SLEEP_SECS = 120;
void setup() {
mesh::prepareUsbLoggingPort();
Serial.begin(115200);
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
mesh::beginUsbLoggingPort();
#endif
delay(1000);
board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS
if (display.begin()) {
display.turnOff();
// Saved display policy is applied by UITask::begin().
}
#endif
int radioinit_attempts = 0;
while (!radio_init()) {
++radioinit_attempts;
MESH_DEBUG_PRINTLN("Radio init failed! (attempt %d)", radioinit_attempts);
if (radioinit_attempts >= 3) {
MESH_DEBUG_PRINTLN("Radio init failed 3x - rebooting");
board.reboot();
}
delay(500);
}
fast_rng.begin(radio_driver.getRngSeed());
FILESYSTEM* fs;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
InternalFS.begin();
fs = &InternalFS;
IdentityStore store(InternalFS, "");
#elif defined(ESP32)
SPIFFS.begin(true);
fs = &SPIFFS;
IdentityStore store(SPIFFS, "/identity");
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
fs = &LittleFS;
IdentityStore store(LittleFS, "/identity");
store.begin();
#else
#error "need to define filesystem"
#endif
const bool needs_identity = !store.load("_main", the_mesh.self_id)
|| mesh::hasReservedIdentityPrefix(the_mesh.self_id);
bool identity_ready = true;
if (needs_identity) {
MESH_DEBUG_PRINTLN("Generating new keypair");
identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity);
if (identity_ready) identity_ready = store.saveWithRetry("_main", the_mesh.self_id);
}
#if defined(ESP32_PLATFORM)
mesh::discardESP32TrueRandom();
#endif
if (!identity_ready) {
MESH_DEBUG_PRINTLN("Identity generation or persistence failed after retries; rebooting");
board.reboot();
return;
}
Stream& console = mesh::usbConsolePort();
console.print("Sensor ID: ");
mesh::Utils::printHex(console, the_mesh.self_id.pub_key, PUB_KEY_SIZE);
console.println();
command[0] = 0;
sensors.begin();
the_mesh.begin(fs);
#ifdef DISPLAY_CLASS
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
// send out initial zero hop Advertisement to the mesh
#if ENABLE_ADVERT_ON_BOOT == 1
the_mesh.sendSelfAdvertisement(16000, false);
#endif
board.onBootComplete();
}
void loop() {
#if defined(NRF52_PLATFORM)
board.feedWatchdog(the_mesh.getNodePrefs()->system_watchdog_enabled != 0);
#endif
bool usb_ready = true;
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
mesh::serviceUsbLoggingPort();
mesh::serviceUsbTerminalPort();
if (mesh::takeUsbTerminalSessionReset()) command[0] = 0;
usb_ready = mesh::tryCompleteUsbTerminalSessionReset()
&& mesh::canAcceptUsbConsoleCommand();
#endif
Stream& console = mesh::usbConsolePort();
int len = strlen(command);
while (usb_ready && console.available() && len < sizeof(command)-1) {
char c = console.read();
if (c != '\n') {
command[len++] = c;
command[len] = 0;
}
console.print(c);
}
if (len == sizeof(command)-1) { // command buffer full
command[sizeof(command)-1] = '\r';
}
if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
reply[0] = 0;
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
if (reply[0]) {
console.print(" -> ");
console.println(reply);
}
command[0] = 0; // reset command buffer
}
the_mesh.loop();
sensors.loop();
#ifdef DISPLAY_CLASS
ui_task.loop();
#endif
rtc_clock.tick();
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
mesh::serviceUsbTerminalPort();
#endif
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
bool can_power_save = the_mesh.getNodePrefs()->powersaving_enabled
&& !board.isUsbDataConnected();
#if defined(MOMENTARY_BUTTON_WAKE_FROM_SLEEP) \
&& MOMENTARY_BUTTON_WAKE_FROM_SLEEP \
&& defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
can_power_save = can_power_save && !user_btn.needsPolling();
#endif
if (can_power_save) {
uint32_t sleep_secs = the_mesh.getPowerSaveSleepSeconds(30);
#ifdef HAS_EXTERNAL_WATCHDOG
if (sleep_secs > 0) external_watchdog.feed();
#endif
#if defined(NRF52_PLATFORM)
if (sleep_secs > 0) board.sleep(0);
#else
if (sleep_secs > 0
&& the_mesh.millisHasNowPassed(
POWERSAVING_FIRST_SLEEP_SECS * 1000UL)) {
board.sleep(sleep_secs);
}
#endif
}
#if defined(ESP32_PLATFORM)
if (!can_power_save && the_mesh.getNodePrefs()->powersaving_enabled) {
delay(1); // Keep USB and the button wake interval serviced while idle.
}
#endif
}