mirror of
https://github.com/vk496/MeshCore.git
synced 2026-09-07 07:54:13 +00:00
FolderMotaStore: an OtaStore that streams an in-transit .mota straight to the host folder over the seeder link (OP_BEGIN/WRITE/SREAD/STAT/FIN) instead of RAM/flash — the device holds no local staging for it. Wired as a selectable pull destination: - OtaContext gains a folder_dest (registered by the app while a motatool `serve` link is up) + its human id (e.g. "tcp 192.168.4.5"). - `ota pull <#> <dest>` now takes a MANDATORY destination; `ota pull <#>` with none lists the choices (flash always; folder + its link id when connected). - The ESP32 WiFi seeder registers/clears the folder destination on connect/close — the same connection both serves the folder and accepts pulls into it. Captures an exact copy of a device's firmware over the mesh to build a delta against. Pause/resume on a mid-pull disconnect follows.
365 lines
12 KiB
C++
365 lines
12 KiB
C++
#include <Arduino.h> // needed for PlatformIO
|
|
#include <Mesh.h>
|
|
#include "MyMesh.h"
|
|
|
|
// Believe it or not, this std C function is busted on some platforms!
|
|
static uint32_t _atoi(const char* sp) {
|
|
uint32_t n = 0;
|
|
while (*sp && *sp >= '0' && *sp <= '9') {
|
|
n *= 10;
|
|
n += (*sp++ - '0');
|
|
}
|
|
return n;
|
|
}
|
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
#include <InternalFileSystem.h>
|
|
#if defined(QSPIFLASH)
|
|
#include <CustomLFS_QSPIFlash.h>
|
|
DataStore store(InternalFS, QSPIFlash, rtc_clock);
|
|
#else
|
|
#if defined(EXTRAFS)
|
|
#include <CustomLFS.h>
|
|
CustomLFS ExtraFS(0xD4000, 0x19000, 128);
|
|
DataStore store(InternalFS, ExtraFS, rtc_clock);
|
|
#else
|
|
DataStore store(InternalFS, rtc_clock);
|
|
#endif
|
|
#endif
|
|
#elif defined(RP2040_PLATFORM)
|
|
#include <LittleFS.h>
|
|
DataStore store(LittleFS, rtc_clock);
|
|
#elif defined(ESP32)
|
|
#include <SPIFFS.h>
|
|
DataStore store(SPIFFS, rtc_clock);
|
|
#endif
|
|
|
|
#ifdef ESP32
|
|
#ifdef WIFI_SSID
|
|
#include <helpers/esp32/SerialWifiInterface.h>
|
|
SerialWifiInterface serial_interface;
|
|
#ifndef TCP_PORT
|
|
#define TCP_PORT 5000
|
|
#endif
|
|
#elif defined(BLE_PIN_CODE)
|
|
#include <helpers/esp32/SerialBLEInterface.h>
|
|
SerialBLEInterface serial_interface;
|
|
#elif defined(SERIAL_RX)
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
HardwareSerial companion_serial(1);
|
|
#else
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#endif
|
|
#elif defined(RP2040_PLATFORM)
|
|
//#ifdef WIFI_SSID
|
|
// #include <helpers/rp2040/SerialWifiInterface.h>
|
|
// SerialWifiInterface serial_interface;
|
|
// #ifndef TCP_PORT
|
|
// #define TCP_PORT 5000
|
|
// #endif
|
|
// #elif defined(BLE_PIN_CODE)
|
|
// #include <helpers/rp2040/SerialBLEInterface.h>
|
|
// SerialBLEInterface serial_interface;
|
|
#if defined(SERIAL_RX)
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
HardwareSerial companion_serial(1);
|
|
#else
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#endif
|
|
#elif defined(NRF52_PLATFORM)
|
|
#ifdef BLE_PIN_CODE
|
|
#include <helpers/nrf52/SerialBLEInterface.h>
|
|
SerialBLEInterface serial_interface;
|
|
#else
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#endif
|
|
#elif defined(STM32_PLATFORM)
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#else
|
|
#error "need to define a serial interface"
|
|
#endif
|
|
|
|
/* GLOBAL OBJECTS */
|
|
#ifdef DISPLAY_CLASS
|
|
#include "UITask.h"
|
|
UITask ui_task(&board, &serial_interface);
|
|
#endif
|
|
|
|
StdRNG fast_rng;
|
|
SimpleMeshTables tables;
|
|
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
|
|
#ifdef DISPLAY_CLASS
|
|
, &ui_task
|
|
#endif
|
|
);
|
|
|
|
/* END GLOBAL OBJECTS */
|
|
|
|
void halt() {
|
|
while (1) ;
|
|
}
|
|
|
|
/* WIFI RECONNECT TRACKERS */
|
|
#if defined(ESP32) && defined(WIFI_SSID)
|
|
bool wifi_needs_reconnect = false;
|
|
unsigned long last_wifi_reconnect_attempt = 0;
|
|
#endif
|
|
|
|
/* WIFI OTA SEEDER — relay a host folder of .mota over WiFi (motatool `serve --tcp`), on a DEDICATED port
|
|
separate from the companion (TCP_PORT), so a phone app stays connected while motatool feeds updates. */
|
|
#if defined(ESP32) && defined(WIFI_SSID) && defined(ENABLE_OTA)
|
|
#include <helpers/ota/OtaContext.h>
|
|
#include <helpers/ota/MotaSourceSerial.h>
|
|
#include <helpers/ota/FolderMotaStore.h> // `ota pull <#> folder` destination over this same connection
|
|
#ifndef OTA_SEEDER_TCP_PORT
|
|
#define OTA_SEEDER_TCP_PORT 5001
|
|
#endif
|
|
static WiFiServer ota_seeder_server(OTA_SEEDER_TCP_PORT);
|
|
static WiFiClient ota_seeder_client; // the live seeder connection (reused)
|
|
static mesh::ota::SerialMotaSource ota_seeder_source(ota_seeder_client, 3000); // SERVE: read folder -> relay
|
|
static mesh::ota::FolderMotaStore ota_folder_store(ota_seeder_client, 3000); // PULL: capture .mota -> folder
|
|
static bool ota_seeder_attached = false;
|
|
|
|
// Accept one motatool connection at a time. While connected, the same link both SERVES the host folder
|
|
// over LoRa (register it as a source) and is offered as a `folder` PULL destination (`ota pull <#> folder`
|
|
// captures a fetched .mota back to that folder). Drop both the moment the connection closes.
|
|
static void ota_seeder_loop() {
|
|
if (ota_seeder_client && ota_seeder_client.connected()) return; // still serving the current client
|
|
if (ota_seeder_attached) { // previous client just disconnected
|
|
mesh::ota::ota_ctx().detach_folder();
|
|
mesh::ota::ota_ctx().clear_folder_dest(); // the `folder` pull destination is gone too
|
|
mesh::ota::ota_ctx().manager.announce(); // served set shrank back to our own fw -> re-advertise
|
|
ota_seeder_attached = false;
|
|
WIFI_DEBUG_PRINTLN("OTA seeder: client disconnected, relay stopped");
|
|
}
|
|
WiFiClient c = ota_seeder_server.available();
|
|
if (c) {
|
|
ota_seeder_client = c; // rebind the persistent Stream to it
|
|
if (mesh::ota::ota_ctx().manager.add_source(&ota_seeder_source)) {
|
|
ota_seeder_attached = true;
|
|
char di[24]; snprintf(di, sizeof di, "tcp %s", ota_seeder_client.remoteIP().toString().c_str());
|
|
mesh::ota::ota_ctx().set_folder_dest(&ota_folder_store, di); // offer `ota pull <#> folder`
|
|
mesh::ota::ota_ctx().manager.announce(); // new served set -> advertise the folder's fw to peers
|
|
WIFI_DEBUG_PRINTLN("OTA seeder: client connected (%s) — relay + folder pull-dest ready", di);
|
|
} else {
|
|
ota_seeder_client.stop(); // no free source slot
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* WIFI OTA CONSOLE — a tiny text CLI for OTA over WiFi. A WiFi companion has no serial text console, so
|
|
without this its OTA is only reachable through the phone app. Connect with e.g. `nc <ip> 5002` and type
|
|
`ota status` / `ota ls` / `ota announce` / ... — one client at a time, on a DEDICATED port separate from
|
|
the companion (5000) and the seeder (5001). */
|
|
#if defined(ESP32) && defined(WIFI_SSID) && defined(ENABLE_OTA)
|
|
#include <helpers/ota/OtaCli.h> // mesh::ota::handle_ota_command(line, reply, board)
|
|
#ifndef OTA_CONSOLE_TCP_PORT
|
|
#define OTA_CONSOLE_TCP_PORT 5002
|
|
#endif
|
|
static WiFiServer ota_console_server(OTA_CONSOLE_TCP_PORT);
|
|
static WiFiClient ota_console_client;
|
|
static char ota_console_line[128];
|
|
static uint8_t ota_console_len = 0;
|
|
|
|
static void ota_console_loop() {
|
|
if (!ota_console_client || !ota_console_client.connected()) {
|
|
WiFiClient c = ota_console_server.available();
|
|
if (c) { ota_console_client = c; ota_console_len = 0;
|
|
ota_console_client.print("OTA console — type `ota ...` (e.g. ota status / ota ls / ota announce)\r\n> "); }
|
|
return;
|
|
}
|
|
while (ota_console_client.available()) {
|
|
char ch = (char)ota_console_client.read();
|
|
if (ch == '\r' || ch == '\n') {
|
|
if (ota_console_len == 0) continue; // ignore blanks / the CRLF pair
|
|
ota_console_line[ota_console_len] = 0;
|
|
char reply[160]; reply[0] = 0;
|
|
if (!mesh::ota::handle_ota_command(ota_console_line, reply, board))
|
|
strcpy(reply, "only `ota ...` commands are supported on this console");
|
|
ota_console_client.print(" -> "); ota_console_client.print(reply); ota_console_client.print("\r\n> ");
|
|
ota_console_len = 0;
|
|
} else if (ota_console_len < sizeof(ota_console_line) - 1) {
|
|
ota_console_line[ota_console_len++] = ch;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
board.begin();
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
DisplayDriver* disp = NULL;
|
|
if (display.begin()) {
|
|
disp = &display;
|
|
disp->startFrame();
|
|
#ifdef ST7789
|
|
disp->setTextSize(2);
|
|
#endif
|
|
disp->drawTextCentered(disp->width() / 2, 28, "Loading...");
|
|
disp->endFrame();
|
|
}
|
|
#endif
|
|
|
|
if (!radio_init()) { halt(); }
|
|
|
|
fast_rng.begin(radio_driver.getRngSeed());
|
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
InternalFS.begin();
|
|
#if defined(QSPIFLASH)
|
|
if (!QSPIFlash.begin()) {
|
|
// debug output might not be available at this point, might be too early. maybe should fall back to InternalFS here?
|
|
MESH_DEBUG_PRINTLN("CustomLFS_QSPIFlash: failed to initialize");
|
|
} else {
|
|
MESH_DEBUG_PRINTLN("CustomLFS_QSPIFlash: initialized successfully");
|
|
}
|
|
#else
|
|
#if defined(EXTRAFS)
|
|
ExtraFS.begin();
|
|
#endif
|
|
#endif
|
|
store.begin();
|
|
the_mesh.begin(
|
|
#ifdef DISPLAY_CLASS
|
|
disp != NULL
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
|
|
#ifdef BLE_PIN_CODE
|
|
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
|
|
#else
|
|
serial_interface.begin(Serial);
|
|
#endif
|
|
the_mesh.startInterface(serial_interface);
|
|
#elif defined(RP2040_PLATFORM)
|
|
LittleFS.begin();
|
|
store.begin();
|
|
the_mesh.begin(
|
|
#ifdef DISPLAY_CLASS
|
|
disp != NULL
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
|
|
//#ifdef WIFI_SSID
|
|
// WiFi.begin(WIFI_SSID, WIFI_PWD);
|
|
// serial_interface.begin(TCP_PORT);
|
|
// #elif defined(BLE_PIN_CODE)
|
|
// char dev_name[32+16];
|
|
// sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
|
|
// serial_interface.begin(dev_name, the_mesh.getBLEPin());
|
|
#if defined(SERIAL_RX)
|
|
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
|
companion_serial.begin(115200);
|
|
serial_interface.begin(companion_serial);
|
|
#else
|
|
serial_interface.begin(Serial);
|
|
#endif
|
|
the_mesh.startInterface(serial_interface);
|
|
#elif defined(ESP32)
|
|
SPIFFS.begin(true);
|
|
store.begin();
|
|
the_mesh.begin(
|
|
#ifdef DISPLAY_CLASS
|
|
disp != NULL
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
|
|
#ifdef WIFI_SSID
|
|
board.setInhibitSleep(true); // prevent sleep when WiFi is active
|
|
WiFi.setAutoReconnect(true);
|
|
|
|
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
|
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
|
|
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
|
|
wifi_needs_reconnect = true;
|
|
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
|
|
WIFI_DEBUG_PRINTLN("connected! IP %s (companion app on :%d)",
|
|
WiFi.localIP().toString().c_str(), TCP_PORT);
|
|
wifi_needs_reconnect = false;
|
|
}
|
|
});
|
|
|
|
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
|
// Disable WiFi modem power-save: its periodic modem/light-sleep stalls the SX1262 SPI+DIO servicing,
|
|
// which makes the LoRa radio go deaf (no TX/RX) while WiFi is associated. Required for LoRa+WiFi to
|
|
// coexist on this ESP32 — the small extra idle current is well worth a working radio.
|
|
WiFi.setSleep(false);
|
|
serial_interface.begin(TCP_PORT);
|
|
#ifdef ENABLE_OTA
|
|
ota_seeder_server.begin(); // dedicated OTA seeder port for `motatool serve --tcp` (relay over LoRa)
|
|
WIFI_DEBUG_PRINTLN("OTA seeder listening on :%d (motatool serve --tcp)", OTA_SEEDER_TCP_PORT);
|
|
ota_console_server.begin(); // dedicated OTA text-console port (`nc <ip> 5002` -> `ota ...`)
|
|
WIFI_DEBUG_PRINTLN("OTA console listening on :%d (nc <ip> %d, type `ota ...`)", OTA_CONSOLE_TCP_PORT, OTA_CONSOLE_TCP_PORT);
|
|
#endif
|
|
#elif defined(BLE_PIN_CODE)
|
|
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
|
|
#elif defined(SERIAL_RX)
|
|
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
|
companion_serial.begin(115200);
|
|
serial_interface.begin(companion_serial);
|
|
#else
|
|
serial_interface.begin(Serial);
|
|
#endif
|
|
the_mesh.startInterface(serial_interface);
|
|
#else
|
|
#error "need to define filesystem"
|
|
#endif
|
|
|
|
sensors.begin();
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
the_mesh.applyGpsPrefs();
|
|
#endif
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
|
#endif
|
|
|
|
board.onBootComplete();
|
|
}
|
|
|
|
void loop() {
|
|
the_mesh.loop();
|
|
sensors.loop();
|
|
#ifdef DISPLAY_CLASS
|
|
ui_task.loop();
|
|
#endif
|
|
rtc_clock.tick();
|
|
|
|
if (!the_mesh.hasPendingWork()) {
|
|
#if defined(NRF52_PLATFORM)
|
|
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
|
|
#endif
|
|
}
|
|
|
|
#if defined(ESP32) && defined(WIFI_SSID)
|
|
#ifdef ENABLE_OTA
|
|
ota_seeder_loop(); // accept/drop a motatool `serve --tcp` connection on the dedicated seeder port
|
|
ota_console_loop(); // service the OTA text console (port 5002)
|
|
#endif
|
|
// Safely attempt to reconnect every 10 seconds if flagged
|
|
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
|
|
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect...");
|
|
WiFi.disconnect();
|
|
WiFi.reconnect();
|
|
last_wifi_reconnect_attempt = millis();
|
|
}
|
|
#endif
|
|
}
|