From 6e0b505a2a83d628f11efc2052f52ab6ddc4af49 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 6 Jun 2025 15:30:35 +1000 Subject: [PATCH 01/10] * companion: refactor of all filesystem access to new DataStore module --- examples/companion_radio/DataStore.cpp | 306 ++++++++++++++++++++++ examples/companion_radio/DataStore.h | 36 +++ examples/companion_radio/MyMesh.cpp | 344 ++----------------------- examples/companion_radio/MyMesh.h | 32 +-- examples/companion_radio/main.cpp | 15 +- src/helpers/BaseChatMesh.cpp | 7 + src/helpers/BaseChatMesh.h | 20 +- src/helpers/ChannelDetails.h | 9 + src/helpers/ContactInfo.h | 18 ++ 9 files changed, 433 insertions(+), 354 deletions(-) create mode 100644 examples/companion_radio/DataStore.cpp create mode 100644 examples/companion_radio/DataStore.h create mode 100644 src/helpers/ChannelDetails.h create mode 100644 src/helpers/ContactInfo.h diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp new file mode 100644 index 00000000..fa848c05 --- /dev/null +++ b/examples/companion_radio/DataStore.cpp @@ -0,0 +1,306 @@ +#include +#include "DataStore.h" + +DataStore::DataStore(FILESYSTEM& fs) : _fs(&fs), +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + identity_store(fs, "") +#elif defined(RP2040_PLATFORM) + identity_store(fs, "/identity") +#else + identity_store(fs, "/identity") +#endif +{ +} + +void DataStore::begin() { +#if defined(RP2040_PLATFORM) + identity_store.begin(); +#endif + + // init 'blob store' support + _fs->mkdir("/bl"); +} + +#if defined(ESP32) + #include +#elif defined(RP2040_PLATFORM) + #include +#endif + +bool DataStore::formatFileSystem() { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + return _fs->format(); +#elif defined(RP2040_PLATFORM) + return LittleFS.format(); +#elif defined(ESP32) + return ((fs::SPIFFSFS *)_fs)->format(); +#else + #error "need to implement format()" +#endif +} + +bool DataStore::loadMainIdentity(mesh::LocalIdentity &identity) { + return identity_store.load("_main", identity); +} + +bool DataStore::saveMainIdentity(const mesh::LocalIdentity &identity) { + return identity_store.save("_main", identity); +} + +void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) { + if (_fs->exists("/new_prefs")) { + loadPrefsInt("/new_prefs", prefs, node_lat, node_lon); // new filename + } else if (_fs->exists("/node_prefs")) { + loadPrefsInt("/node_prefs", prefs, node_lat, node_lon); + savePrefs(prefs, node_lat, node_lon); // save to new filename + _fs->remove("/node_prefs"); // remove old + } +} + +void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) { +#if defined(RP2040_PLATFORM) + File file = _fs->open(filename, "r"); +#else + File file = _fs->open(filename); +#endif + if (file) { + uint8_t pad[8]; + + file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 + file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 + file.read(pad, 4); // 36 + file.read((uint8_t *)&node_lat, sizeof(node_lat)); // 40 + file.read((uint8_t *)&node_lon, sizeof(node_lon)); // 48 + file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 + file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 + file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 + file.read((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 + file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 + file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 + file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 + file.read((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 + file.read((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 + file.read((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 + file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 + file.read(pad, 4); // 76 + file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + + file.close(); + } +} + +void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_lon) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove("/new_prefs"); + File file = _fs->open("/new_prefs", FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/new_prefs", "w"); +#else + File file = _fs->open("/new_prefs", "w", true); +#endif + if (file) { + uint8_t pad[8]; + memset(pad, 0, sizeof(pad)); + + file.write((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 + file.write((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 + file.write(pad, 4); // 36 + file.write((uint8_t *)&node_lat, sizeof(node_lat)); // 40 + file.write((uint8_t *)&node_lon, sizeof(node_lon)); // 48 + file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 + file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 + file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 + file.write((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 + file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 + file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 + file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 + file.write((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 + file.write((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 + file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 + file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 + file.write(pad, 4); // 76 + file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + + file.close(); + } +} + +void DataStore::loadContacts(DataStoreHost* host) { + if (_fs->exists("/contacts3")) { +#if defined(RP2040_PLATFORM) + File file = _fs->open("/contacts3", "r"); +#else + File file = _fs->open("/contacts3"); +#endif + if (file) { + bool full = false; + while (!full) { + ContactInfo c; + uint8_t pub_key[32]; + uint8_t unused; + + bool success = (file.read(pub_key, 32) == 32); + success = success && (file.read((uint8_t *)&c.name, 32) == 32); + success = success && (file.read(&c.type, 1) == 1); + success = success && (file.read(&c.flags, 1) == 1); + success = success && (file.read(&unused, 1) == 1); + success = success && (file.read((uint8_t *)&c.sync_since, 4) == 4); // was 'reserved' + success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); + success = success && (file.read((uint8_t *)&c.last_advert_timestamp, 4) == 4); + success = success && (file.read(c.out_path, 64) == 64); + success = success && (file.read((uint8_t *)&c.lastmod, 4) == 4); + success = success && (file.read((uint8_t *)&c.gps_lat, 4) == 4); + success = success && (file.read((uint8_t *)&c.gps_lon, 4) == 4); + + if (!success) break; // EOF + + c.id = mesh::Identity(pub_key); + if (!host->onContactLoaded(c)) full = true; + } + file.close(); + } + } +} + +void DataStore::saveContacts(DataStoreHost* host) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove("/contacts3"); + File file = _fs->open("/contacts3", FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/contacts3", "w"); +#else + File file = _fs->open("/contacts3", "w", true); +#endif + if (file) { + uint32_t idx = 0; + ContactInfo c; + uint8_t unused = 0; + + while (host->getContactForSave(idx, c)) { + bool success = (file.write(c.id.pub_key, 32) == 32); + success = success && (file.write((uint8_t *)&c.name, 32) == 32); + success = success && (file.write(&c.type, 1) == 1); + success = success && (file.write(&c.flags, 1) == 1); + success = success && (file.write(&unused, 1) == 1); + success = success && (file.write((uint8_t *)&c.sync_since, 4) == 4); + success = success && (file.write((uint8_t *)&c.out_path_len, 1) == 1); + success = success && (file.write((uint8_t *)&c.last_advert_timestamp, 4) == 4); + success = success && (file.write(c.out_path, 64) == 64); + success = success && (file.write((uint8_t *)&c.lastmod, 4) == 4); + success = success && (file.write((uint8_t *)&c.gps_lat, 4) == 4); + success = success && (file.write((uint8_t *)&c.gps_lon, 4) == 4); + + if (!success) break; // write failed + + idx++; // advance to next contact + } + file.close(); + } +} + +void DataStore::loadChannels(DataStoreHost* host) { + if (_fs->exists("/channels2")) { +#if defined(RP2040_PLATFORM) + File file = _fs->open("/channels2", "r"); +#else + File file = _fs->open("/channels2"); +#endif + if (file) { + bool full = false; + uint8_t channel_idx = 0; + while (!full) { + ChannelDetails ch; + uint8_t unused[4]; + + bool success = (file.read(unused, 4) == 4); + success = success && (file.read((uint8_t *)ch.name, 32) == 32); + success = success && (file.read((uint8_t *)ch.channel.secret, 32) == 32); + + if (!success) break; // EOF + + if (host->onChannelLoaded(channel_idx, ch)) { + channel_idx++; + } else { + full = true; + } + } + file.close(); + } + } +} + +void DataStore::saveChannels(DataStoreHost* host) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove("/channels2"); + File file = _fs->open("/channels2", FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File file = _fs->open("/channels2", "w"); +#else + File file = _fs->open("/channels2", "w", true); +#endif + if (file) { + uint8_t channel_idx = 0; + ChannelDetails ch; + uint8_t unused[4]; + memset(unused, 0, 4); + + while (host->getChannelForSave(channel_idx, ch)) { + bool success = (file.write(unused, 4) == 4); + success = success && (file.write((uint8_t *)ch.name, 32) == 32); + success = success && (file.write((uint8_t *)ch.channel.secret, 32) == 32); + + if (!success) break; // write failed + channel_idx++; + } + file.close(); + } +} + +int DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { + char path[64]; + char fname[18]; + + if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) + mesh::Utils::toHex(fname, key, key_len); + sprintf(path, "/bl/%s", fname); + + if (_fs->exists(path)) { +#if defined(RP2040_PLATFORM) + File f = _fs->open(path, "r"); +#else + File f = _fs->open(path); +#endif + if (f) { + int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!! + f.close(); + return len; + } + } + return 0; // not found +} + +bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { + char path[64]; + char fname[18]; + + if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) + mesh::Utils::toHex(fname, key, key_len); + sprintf(path, "/bl/%s", fname); + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(path); + File f = _fs->open(path, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File f = _fs->open(path, "w"); +#else + File f = _fs->open(path, "w", true); +#endif + if (f) { + int n = f.write(src_buf, len); + f.close(); + if (n == len) return true; // success! + + _fs->remove(path); // blob was only partially written! + } + return false; // error +} diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h new file mode 100644 index 00000000..d1ed19bf --- /dev/null +++ b/examples/companion_radio/DataStore.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include "NodePrefs.h" + +class DataStoreHost { +public: + virtual bool onContactLoaded(const ContactInfo& contact) =0; + virtual bool getContactForSave(uint32_t idx, ContactInfo& contact) =0; + virtual bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) =0; + virtual bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) =0; +}; + +class DataStore { + FILESYSTEM* _fs; + IdentityStore identity_store; + + void loadPrefsInt(const char *filename, NodePrefs& prefs, double& node_lat, double& node_lon); + + public: + DataStore(FILESYSTEM& fs); + void begin(); + bool formatFileSystem(); + bool loadMainIdentity(mesh::LocalIdentity &identity); + bool saveMainIdentity(const mesh::LocalIdentity &identity); + void loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon); + void savePrefs(const NodePrefs& prefs, double node_lat, double node_lon); + void loadContacts(DataStoreHost* host); + void saveContacts(DataStoreHost* host); + void loadChannels(DataStoreHost* host); + void saveChannels(DataStoreHost* host); + int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); + bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len); +}; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 730747e1..4c7b1618 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -103,200 +103,6 @@ #include "UITask.h" #endif -void MyMesh::loadMainIdentity() { - if (!_identity_store->load("_main", self_id)) { - self_id = radio_new_identity(); // create new random identity - int count = 0; - while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes - self_id = radio_new_identity(); - count++; - } - saveMainIdentity(self_id); - } -} - -bool MyMesh::saveMainIdentity(const mesh::LocalIdentity &identity) { - return _identity_store->save("_main", identity); -} - -void MyMesh::loadContacts() { - if (_fs->exists("/contacts3")) { -#if defined(RP2040_PLATFORM) - File file = _fs->open("/contacts3", "r"); -#else - File file = _fs->open("/contacts3"); -#endif - if (file) { - bool full = false; - while (!full) { - ContactInfo c; - uint8_t pub_key[32]; - uint8_t unused; - - bool success = (file.read(pub_key, 32) == 32); - success = success && (file.read((uint8_t *)&c.name, 32) == 32); - success = success && (file.read(&c.type, 1) == 1); - success = success && (file.read(&c.flags, 1) == 1); - success = success && (file.read(&unused, 1) == 1); - success = success && (file.read((uint8_t *)&c.sync_since, 4) == 4); // was 'reserved' - success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); - success = success && (file.read((uint8_t *)&c.last_advert_timestamp, 4) == 4); - success = success && (file.read(c.out_path, 64) == 64); - success = success && (file.read((uint8_t *)&c.lastmod, 4) == 4); - success = success && (file.read((uint8_t *)&c.gps_lat, 4) == 4); - success = success && (file.read((uint8_t *)&c.gps_lon, 4) == 4); - - if (!success) break; // EOF - - c.id = mesh::Identity(pub_key); - if (!addContact(c)) full = true; - } - file.close(); - } - } -} - -void MyMesh::saveContacts() { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove("/contacts3"); - File file = _fs->open("/contacts3", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File file = _fs->open("/contacts3", "w"); -#else - File file = _fs->open("/contacts3", "w", true); -#endif - if (file) { - ContactsIterator iter; - ContactInfo c; - uint8_t unused = 0; - - while (iter.hasNext(this, c)) { - bool success = (file.write(c.id.pub_key, 32) == 32); - success = success && (file.write((uint8_t *)&c.name, 32) == 32); - success = success && (file.write(&c.type, 1) == 1); - success = success && (file.write(&c.flags, 1) == 1); - success = success && (file.write(&unused, 1) == 1); - success = success && (file.write((uint8_t *)&c.sync_since, 4) == 4); - success = success && (file.write((uint8_t *)&c.out_path_len, 1) == 1); - success = success && (file.write((uint8_t *)&c.last_advert_timestamp, 4) == 4); - success = success && (file.write(c.out_path, 64) == 64); - success = success && (file.write((uint8_t *)&c.lastmod, 4) == 4); - success = success && (file.write((uint8_t *)&c.gps_lat, 4) == 4); - success = success && (file.write((uint8_t *)&c.gps_lon, 4) == 4); - - if (!success) break; // write failed - } - file.close(); - } -} - -void MyMesh::loadChannels() { - if (_fs->exists("/channels2")) { -#if defined(RP2040_PLATFORM) - File file = _fs->open("/channels2", "r"); -#else - File file = _fs->open("/channels2"); -#endif - if (file) { - bool full = false; - uint8_t channel_idx = 0; - while (!full) { - ChannelDetails ch; - uint8_t unused[4]; - - bool success = (file.read(unused, 4) == 4); - success = success && (file.read((uint8_t *)ch.name, 32) == 32); - success = success && (file.read((uint8_t *)ch.channel.secret, 32) == 32); - - if (!success) break; // EOF - - if (setChannel(channel_idx, ch)) { - channel_idx++; - } else { - full = true; - } - } - file.close(); - } - } -} - -void MyMesh::saveChannels() { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove("/channels2"); - File file = _fs->open("/channels2", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File file = _fs->open("/channels2", "w"); -#else - File file = _fs->open("/channels2", "w", true); -#endif - if (file) { - uint8_t channel_idx = 0; - ChannelDetails ch; - uint8_t unused[4]; - memset(unused, 0, 4); - - while (getChannel(channel_idx, ch)) { - bool success = (file.write(unused, 4) == 4); - success = success && (file.write((uint8_t *)ch.name, 32) == 32); - success = success && (file.write((uint8_t *)ch.channel.secret, 32) == 32); - - if (!success) break; // write failed - channel_idx++; - } - file.close(); - } -} - -int MyMesh::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { - char path[64]; - char fname[18]; - - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) - mesh::Utils::toHex(fname, key, key_len); - sprintf(path, "/bl/%s", fname); - - if (_fs->exists(path)) { -#if defined(RP2040_PLATFORM) - File f = _fs->open(path, "r"); -#else - File f = _fs->open(path); -#endif - if (f) { - int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!! - f.close(); - return len; - } - } - return 0; // not found -} - -bool MyMesh::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { - char path[64]; - char fname[18]; - - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) - mesh::Utils::toHex(fname, key, key_len); - sprintf(path, "/bl/%s", fname); - -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove(path); - File f = _fs->open(path, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File f = _fs->open(path, "w"); -#else - File f = _fs->open(path, "w", true); -#endif - if (f) { - int n = f.write(src_buf, len); - f.close(); - if (n == len) return true; // success! - - _fs->remove(path); // blob was only partially written! - } - return false; // error -} - void MyMesh::writeOKFrame() { uint8_t buf[1]; buf[0] = RESP_CODE_OK; @@ -724,14 +530,13 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} -MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables) +MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store) : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), - _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4) { + _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store) { _iter_started = false; _cli_rescue = false; offline_queue_len = 0; app_target_ver = 0; - _identity_store = NULL; pending_login = pending_status = pending_telemetry = 0; next_ack_idx = 0; sign_data = NULL; @@ -749,62 +554,18 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe //_prefs.rx_delay_base = 10.0f; enable once new algo fixed } -void MyMesh::loadPrefsInt(const char *filename) { -#if defined(RP2040_PLATFORM) - File file = _fs->open(filename, "r"); -#else - File file = _fs->open(filename); -#endif - if (file) { - uint8_t pad[8]; - - file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 - file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 - file.read(pad, 4); // 36 - file.read((uint8_t *)&sensors.node_lat, sizeof(sensors.node_lat)); // 40 - file.read((uint8_t *)&sensors.node_lon, sizeof(sensors.node_lon)); // 48 - file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 - file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 - file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.read((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 - file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 - file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 - file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 - file.read((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 - file.read((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 - file.read((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 - file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 - file.read(pad, 4); // 76 - file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 - - // sanitise bad pref values - _prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f); - _prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f); - _prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f); - _prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f); - _prefs.sf = constrain(_prefs.sf, 7, 12); - _prefs.cr = constrain(_prefs.cr, 5, 8); - _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); - - file.close(); - } -} - -void MyMesh::begin(FILESYSTEM &fs, bool has_display) { - _fs = &fs; - +void MyMesh::begin(bool has_display) { BaseChatMesh::begin(); -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _identity_store = new IdentityStore(fs, ""); -#elif defined(RP2040_PLATFORM) - _identity_store = new IdentityStore(fs, "/identity"); - _identity_store->begin(); -#else - _identity_store = new IdentityStore(fs, "/identity"); -#endif - - loadMainIdentity(); + if (!_store->loadMainIdentity(self_id)) { + self_id = radio_new_identity(); // create new random identity + int count = 0; + while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes + self_id = radio_new_identity(); + count++; + } + _store->saveMainIdentity(self_id); + } // use hex of first 4 bytes of identity public key as default node name char pub_key_hex[10]; @@ -817,13 +578,16 @@ void MyMesh::begin(FILESYSTEM &fs, bool has_display) { #endif // load persisted prefs - if (_fs->exists("/new_prefs")) { - loadPrefsInt("/new_prefs"); // new filename - } else if (_fs->exists("/node_prefs")) { - loadPrefsInt("/node_prefs"); - savePrefs(); // save to new filename - _fs->remove("/node_prefs"); // remove old - } + _store->loadPrefs(_prefs, sensors.node_lat, sensors.node_lon); + + // sanitise bad pref values + _prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f); + _prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f); + _prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f); + _prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f); + _prefs.sf = constrain(_prefs.sf, 7, 12); + _prefs.cr = constrain(_prefs.cr, 5, 8); + _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); #ifdef BLE_PIN_CODE if (_prefs.ble_pin == 0) { @@ -844,12 +608,9 @@ void MyMesh::begin(FILESYSTEM &fs, bool has_display) { _active_ble_pin = 0; #endif - // init 'blob store' support - _fs->mkdir("/bl"); - - loadContacts(); + _store->loadContacts(this); addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel - loadChannels(); + _store->loadChannels(this); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); @@ -870,42 +631,6 @@ void MyMesh::startInterface(BaseSerialInterface &serial) { serial.enable(); } -void MyMesh::savePrefs() { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove("/new_prefs"); - File file = _fs->open("/new_prefs", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File file = _fs->open("/new_prefs", "w"); -#else - File file = _fs->open("/new_prefs", "w", true); -#endif - if (file) { - uint8_t pad[8]; - memset(pad, 0, sizeof(pad)); - - file.write((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 - file.write((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 - file.write(pad, 4); // 36 - file.write((uint8_t *)&sensors.node_lat, sizeof(sensors.node_lat)); // 40 - file.write((uint8_t *)&sensors.node_lon, sizeof(sensors.node_lon)); // 48 - file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 - file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 - file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.write((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62 - file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 - file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 - file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 - file.write((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 - file.write((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 - file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 - file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 - file.write(pad, 4); // 76 - file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 - - file.close(); - } -} - void MyMesh::handleCmdFrame(size_t len) { if (cmd_frame[0] == CMD_DEVICE_QEURY && len >= 2) { // sent when app establishes connection app_target_ver = cmd_frame[1]; // which version of protocol does app understand @@ -1286,7 +1011,7 @@ void MyMesh::handleCmdFrame(size_t len) { #if ENABLE_PRIVATE_KEY_IMPORT mesh::LocalIdentity identity; identity.readFrom(&cmd_frame[1], 64); - if (saveMainIdentity(identity)) { + if (_store->saveMainIdentity(identity)) { self_id = identity; writeOKFrame(); } else { @@ -1536,19 +1261,6 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } -bool MyMesh::formatFileSystem() { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - return InternalFS.format(); -#elif defined(RP2040_PLATFORM) - return LittleFS.format(); -#elif defined(ESP32) - return SPIFFS.format(); -#else - #error "need to implement file system erase" - return false; -#endif -} - void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { @@ -1576,16 +1288,16 @@ void MyMesh::checkCLIRescueCmd() { Serial.printf(" Error: unknown config: %s\n", config); } } else if (strcmp(cli_command, "rebuild") == 0) { - bool success = formatFileSystem(); + bool success = _store->formatFileSystem(); if (success) { - saveMainIdentity(self_id); + _store->saveMainIdentity(self_id); saveContacts(); Serial.println(" > erase and rebuild done"); } else { Serial.println(" Error: erase failed"); } } else if (strcmp(cli_command, "erase") == 0) { - bool success = formatFileSystem(); + bool success = _store->formatFileSystem(); if (success) { Serial.println(" > erase done"); } else { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 92da8c9d..6ac030d9 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -25,6 +25,7 @@ #include #endif +#include "DataStore.h" #include "NodePrefs.h" #include @@ -76,14 +77,12 @@ #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 -class MyMesh : public BaseChatMesh { +class MyMesh : public BaseChatMesh, public DataStoreHost { public: - MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables); + MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store); - void begin(FILESYSTEM &fs, bool has_display); + void begin(bool has_display); void startInterface(BaseSerialInterface &serial); - void loadPrefsInt(const char *filename); - void savePrefs(); const char *getNodeName(); NodePrefs *getNodePrefs(); @@ -127,6 +126,12 @@ protected: uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override; void onSendTimeout() override; + // DataStoreHost methods + bool onContactLoaded(const ContactInfo& contact) override { return addContact(contact); } + bool getContactForSave(uint32_t idx, ContactInfo& contact) override { return getContactByIdx(idx, contact); } + bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); } + bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); } + private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); @@ -135,22 +140,17 @@ private: void updateContactFromFrame(ContactInfo &contact, const uint8_t *frame, int len); void addToOfflineQueue(const uint8_t frame[], int len); int getFromOfflineQueue(uint8_t frame[]); - void loadMainIdentity(); - bool saveMainIdentity(const mesh::LocalIdentity &identity); - void loadContacts(); - void saveContacts(); - void loadChannels(); - void saveChannels(); - int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) override; - bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) override; void checkCLIRescueCmd(); void checkSerialInterface(); - bool formatFileSystem(); + + // helpers, short-cuts + void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } + void saveChannels() { _store->saveChannels(this); } + void saveContacts() { _store->saveContacts(this); } private: - FILESYSTEM *_fs; - IdentityStore *_identity_store; + DataStore* _store; NodePrefs _prefs; uint32_t pending_login; uint32_t pending_status; diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index a276a2b5..b463dcbe 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -14,10 +14,13 @@ static uint32_t _atoi(const char* sp) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include + DataStore store(InternalFS); #elif defined(RP2040_PLATFORM) #include + DataStore store(LittleFS); #elif defined(ESP32) #include + DataStore store(SPIFFS); #endif #ifdef ESP32 @@ -74,7 +77,7 @@ static uint32_t _atoi(const char* sp) { /* GLOBAL OBJECTS */ StdRNG fast_rng; SimpleMeshTables tables; -MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables); +MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store); #ifdef DISPLAY_CLASS #include "UITask.h" @@ -82,7 +85,6 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables); #endif /* END GLOBAL OBJECTS */ - void halt() { while (1) ; } @@ -108,7 +110,8 @@ void setup() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); - the_mesh.begin(InternalFS, + store.begin(); + the_mesh.begin( #ifdef DISPLAY_CLASS disp != NULL #else @@ -126,7 +129,8 @@ void setup() { the_mesh.startInterface(serial_interface); #elif defined(RP2040_PLATFORM) LittleFS.begin(); - the_mesh.begin(LittleFS, + store.begin(); + the_mesh.begin( #ifdef DISPLAY_CLASS disp != NULL #else @@ -151,7 +155,8 @@ void setup() { the_mesh.startInterface(serial_interface); #elif defined(ESP32) SPIFFS.begin(true); - the_mesh.begin(SPIFFS, + store.begin(); + the_mesh.begin( #ifdef DISPLAY_CLASS disp != NULL #else diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index ba8c3e28..7efd4735 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -699,6 +699,13 @@ int BaseChatMesh::findChannelIdx(const mesh::GroupChannel& ch) { } #endif +bool BaseChatMesh::getContactByIdx(uint32_t idx, ContactInfo& contact) { + if (idx >= num_contacts) return false; + + contact = contacts[idx]; + return true; +} + ContactsIterator BaseChatMesh::startContactsIterator() { return ContactsIterator(); } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 2222bf4e..83ad2a89 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -7,19 +7,7 @@ #define MAX_TEXT_LEN (10*CIPHER_BLOCK_SIZE) // must be LESS than (MAX_PACKET_PAYLOAD - 4 - CIPHER_MAC_SIZE - 1) -struct ContactInfo { - mesh::Identity id; - char name[32]; - uint8_t type; // on of ADV_TYPE_* - uint8_t flags; - int8_t out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; - uint32_t last_advert_timestamp; // by THEIR clock - uint8_t shared_secret[PUB_KEY_SIZE]; - uint32_t lastmod; // by OUR clock - int32_t gps_lat, gps_lon; // 6 dec places - uint32_t sync_since; -}; +#include "ContactInfo.h" #define MAX_SEARCH_RESULTS 8 @@ -61,10 +49,7 @@ struct ConnectionInfo { uint32_t expected_ack; }; -struct ChannelDetails { - mesh::GroupChannel channel; - char name[32]; -}; +#include "ChannelDetails.h" /** * \brief abstract Mesh class for common 'chat' client @@ -158,6 +143,7 @@ public: bool removeContact(ContactInfo& contact); bool addContact(const ContactInfo& contact); int getNumContacts() const { return num_contacts; } + bool getContactByIdx(uint32_t idx, ContactInfo& contact); ContactsIterator startContactsIterator(); ChannelDetails* addChannel(const char* name, const char* psk_base64); bool getChannel(int idx, ChannelDetails& dest); diff --git a/src/helpers/ChannelDetails.h b/src/helpers/ChannelDetails.h new file mode 100644 index 00000000..b9d38d4f --- /dev/null +++ b/src/helpers/ChannelDetails.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +struct ChannelDetails { + mesh::GroupChannel channel; + char name[32]; +}; diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h new file mode 100644 index 00000000..4a8038d3 --- /dev/null +++ b/src/helpers/ContactInfo.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +struct ContactInfo { + mesh::Identity id; + char name[32]; + uint8_t type; // on of ADV_TYPE_* + uint8_t flags; + int8_t out_path_len; + uint8_t out_path[MAX_PATH_SIZE]; + uint32_t last_advert_timestamp; // by THEIR clock + uint8_t shared_secret[PUB_KEY_SIZE]; + uint32_t lastmod; // by OUR clock + int32_t gps_lat, gps_lon; // 6 dec places + uint32_t sync_since; +}; From dd808ee6c741367f57010e2e56fd1db8fb9a35a7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 6 Jun 2025 19:50:51 +1000 Subject: [PATCH 02/10] * new nRF52 impl for advert blobs --- examples/companion_radio/DataStore.cpp | 128 ++++++++++++++++++------- examples/companion_radio/DataStore.h | 9 +- examples/companion_radio/MyMesh.cpp | 2 + 3 files changed, 102 insertions(+), 37 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index fa848c05..8e0d119a 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -12,13 +12,28 @@ DataStore::DataStore(FILESYSTEM& fs) : _fs(&fs), { } +static File openWrite(FILESYSTEM* _fs, const char* filename) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(filename); + return _fs->open(filename, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + return _fs->open(filename, "w"); +#else + return _fs->open(filename, "w", true); +#endif +} + void DataStore::begin() { #if defined(RP2040_PLATFORM) identity_store.begin(); #endif +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + checkAdvBlobFile(); +#else // init 'blob store' support _fs->mkdir("/bl"); +#endif } #if defined(ESP32) @@ -90,14 +105,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no } void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_lon) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove("/new_prefs"); - File file = _fs->open("/new_prefs", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File file = _fs->open("/new_prefs", "w"); -#else - File file = _fs->open("/new_prefs", "w", true); -#endif + File file = openWrite(_fs, "/new_prefs"); if (file) { uint8_t pad[8]; memset(pad, 0, sizeof(pad)); @@ -163,14 +171,7 @@ void DataStore::loadContacts(DataStoreHost* host) { } void DataStore::saveContacts(DataStoreHost* host) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove("/contacts3"); - File file = _fs->open("/contacts3", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File file = _fs->open("/contacts3", "w"); -#else - File file = _fs->open("/contacts3", "w", true); -#endif + File file = openWrite(_fs, "/contacts3"); if (file) { uint32_t idx = 0; ContactInfo c; @@ -230,14 +231,7 @@ void DataStore::loadChannels(DataStoreHost* host) { } void DataStore::saveChannels(DataStoreHost* host) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove("/channels2"); - File file = _fs->open("/channels2", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File file = _fs->open("/channels2", "w"); -#else - File file = _fs->open("/channels2", "w", true); -#endif + File file = openWrite(_fs, "/channels2"); if (file) { uint8_t channel_idx = 0; ChannelDetails ch; @@ -256,7 +250,79 @@ void DataStore::saveChannels(DataStoreHost* host) { } } -int DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + +#define MAX_ADVERT_PKT_LEN (PUB_KEY_SIZE + 4 + SIGNATURE_SIZE + MAX_ADVERT_DATA_SIZE) + +void DataStore::checkAdvBlobFile() { + if (!_fs->exists("/adv_blobs")) { + File file = openWrite(_fs, "/adv_blobs"); + if (file) { + uint8_t zeroes[1 + MAX_ADVERT_PKT_LEN]; + memset(zeroes, 0, sizeof(zeroes)); + for (int i = 0; i < 24; i++) { // pre-allocate to fixed size + file.write(zeroes, sizeof(zeroes)); + } + file.close(); + } + } +} + +uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { + File file = _fs->open("/adv_blobs"); + uint8_t len = 0; // 0 = not found + if (file) { + uint8_t tmp[1 + MAX_ADVERT_PKT_LEN]; + while (file.read(tmp, sizeof(tmp)) == sizeof(tmp)) { + if (memcmp(key, &tmp[1], PUB_KEY_SIZE) == 0) { // public key is first 32 bytes of advert blob + len = tmp[0]; + memcpy(dest_buf, &tmp[1], len); + break; + } + } + file.close(); + } + return len; +} + +bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { + if (len < PUB_KEY_SIZE+4+SIGNATURE_SIZE || len > MAX_ADVERT_PKT_LEN) return false; + + checkAdvBlobFile(); + + File file = _fs->open("/adv_blobs", FILE_O_WRITE); + if (file) { + uint32_t pos = 0, found_pos = 0; + uint32_t min_timestamp = 0xFFFFFFFF; + + // search for matching key OR evict by oldest timestmap + uint8_t tmp[1 + MAX_ADVERT_PKT_LEN]; + while (file.read(tmp, sizeof(tmp)) == sizeof(tmp)) { + if (memcmp(src_buf, &tmp[1], PUB_KEY_SIZE) == 0) { // public key is first 32 bytes of advert blob + found_pos = pos; + break; + } + uint32_t timestamp; + memcpy(×tamp, &tmp[1 + PUB_KEY_SIZE], 4); + if (timestamp < min_timestamp) { + min_timestamp = timestamp; + found_pos = pos; + } + + pos += sizeof(tmp); + } + + file.seek(found_pos); + file.write(&len, 1); + file.write(src_buf, len); + + file.close(); + return true; + } + return false; // error +} +#else +uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { char path[64]; char fname[18]; @@ -279,7 +345,7 @@ int DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[] return 0; // not found } -bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { +bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { char path[64]; char fname[18]; @@ -287,14 +353,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src mesh::Utils::toHex(fname, key, key_len); sprintf(path, "/bl/%s", fname); -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove(path); - File f = _fs->open(path, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - File f = _fs->open(path, "w"); -#else - File f = _fs->open(path, "w", true); -#endif + File f = openWrite(_fs, path); if (f) { int n = f.write(src_buf, len); f.close(); @@ -304,3 +363,4 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } return false; // error } +#endif diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index d1ed19bf..540bc9cd 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -18,8 +18,11 @@ class DataStore { IdentityStore identity_store; void loadPrefsInt(const char *filename, NodePrefs& prefs, double& node_lat, double& node_lon); +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + void checkAdvBlobFile(); +#endif - public: +public: DataStore(FILESYSTEM& fs); void begin(); bool formatFileSystem(); @@ -31,6 +34,6 @@ class DataStore { void saveContacts(DataStoreHost* host); void loadChannels(DataStoreHost* host); void saveChannels(DataStoreHost* host); - int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); - bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len); + uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); + bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len); }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 4c7b1618..054d0cba 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1291,7 +1291,9 @@ void MyMesh::checkCLIRescueCmd() { bool success = _store->formatFileSystem(); if (success) { _store->saveMainIdentity(self_id); + savePrefs(); saveContacts(); + saveChannels(); Serial.println(" > erase and rebuild done"); } else { Serial.println(" Error: erase failed"); From 9c833486bf3c25b730fd81fc2d7f190f58d1f74c Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 6 Jun 2025 21:35:54 +1000 Subject: [PATCH 03/10] * DataStore, advert blob record format change --- examples/companion_radio/DataStore.cpp | 51 ++++++++++++++++---------- examples/companion_radio/DataStore.h | 3 +- examples/companion_radio/MyMesh.h | 6 +++ examples/companion_radio/main.cpp | 6 +-- variants/t114/platformio.ini | 2 +- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 8e0d119a..a37cfa49 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -1,7 +1,7 @@ #include #include "DataStore.h" -DataStore::DataStore(FILESYSTEM& fs) : _fs(&fs), +DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _clock(&clock), #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) identity_store(fs, "") #elif defined(RP2040_PLATFORM) @@ -252,16 +252,23 @@ void DataStore::saveChannels(DataStoreHost* host) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) -#define MAX_ADVERT_PKT_LEN (PUB_KEY_SIZE + 4 + SIGNATURE_SIZE + MAX_ADVERT_DATA_SIZE) +#define MAX_ADVERT_PKT_LEN (2 + 32 + PUB_KEY_SIZE + 4 + SIGNATURE_SIZE + MAX_ADVERT_DATA_SIZE) + +struct BlobRec { + uint32_t timestamp; + uint8_t key[7]; + uint8_t len; + uint8_t data[MAX_ADVERT_PKT_LEN]; +}; void DataStore::checkAdvBlobFile() { if (!_fs->exists("/adv_blobs")) { File file = openWrite(_fs, "/adv_blobs"); if (file) { - uint8_t zeroes[1 + MAX_ADVERT_PKT_LEN]; - memset(zeroes, 0, sizeof(zeroes)); - for (int i = 0; i < 24; i++) { // pre-allocate to fixed size - file.write(zeroes, sizeof(zeroes)); + BlobRec zeroes; + memset(&zeroes, 0, sizeof(zeroes)); + for (int i = 0; i < 20; i++) { // pre-allocate to fixed size + file.write((uint8_t *) &zeroes, sizeof(zeroes)); } file.close(); } @@ -271,12 +278,13 @@ void DataStore::checkAdvBlobFile() { uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { File file = _fs->open("/adv_blobs"); uint8_t len = 0; // 0 = not found + if (file) { - uint8_t tmp[1 + MAX_ADVERT_PKT_LEN]; - while (file.read(tmp, sizeof(tmp)) == sizeof(tmp)) { - if (memcmp(key, &tmp[1], PUB_KEY_SIZE) == 0) { // public key is first 32 bytes of advert blob - len = tmp[0]; - memcpy(dest_buf, &tmp[1], len); + BlobRec tmp; + while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) { + if (memcmp(key, tmp.key, sizeof(tmp.key)) == 0) { // only match by 7 byte prefix + len = tmp.len; + memcpy(dest_buf, tmp.data, len); break; } } @@ -296,25 +304,28 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src uint32_t min_timestamp = 0xFFFFFFFF; // search for matching key OR evict by oldest timestmap - uint8_t tmp[1 + MAX_ADVERT_PKT_LEN]; - while (file.read(tmp, sizeof(tmp)) == sizeof(tmp)) { - if (memcmp(src_buf, &tmp[1], PUB_KEY_SIZE) == 0) { // public key is first 32 bytes of advert blob + BlobRec tmp; + file.seek(0); + while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) { + if (memcmp(key, tmp.key, sizeof(tmp.key)) == 0) { // only match by 7 byte prefix found_pos = pos; break; } - uint32_t timestamp; - memcpy(×tamp, &tmp[1 + PUB_KEY_SIZE], 4); - if (timestamp < min_timestamp) { - min_timestamp = timestamp; + if (tmp.timestamp < min_timestamp) { + min_timestamp = tmp.timestamp; found_pos = pos; } pos += sizeof(tmp); } + memcpy(tmp.key, key, sizeof(tmp.key)); // just record 7 byte prefix of key + memcpy(tmp.data, src_buf, len); + tmp.len = len; + tmp.timestamp = _clock->getCurrentTime(); + file.seek(found_pos); - file.write(&len, 1); - file.write(src_buf, len); + file.write((uint8_t *) &tmp, sizeof(tmp)); file.close(); return true; diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index 540bc9cd..139131e1 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -15,6 +15,7 @@ public: class DataStore { FILESYSTEM* _fs; + mesh::RTCClock* _clock; IdentityStore identity_store; void loadPrefsInt(const char *filename, NodePrefs& prefs, double& node_lat, double& node_lon); @@ -23,7 +24,7 @@ class DataStore { #endif public: - DataStore(FILESYSTEM& fs); + DataStore(FILESYSTEM& fs, mesh::RTCClock& clock); void begin(); bool formatFileSystem(); bool loadMainIdentity(mesh::LocalIdentity &identity); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 6ac030d9..43194f09 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -140,6 +140,12 @@ private: void updateContactFromFrame(ContactInfo &contact, const uint8_t *frame, int len); void addToOfflineQueue(const uint8_t frame[], int len); int getFromOfflineQueue(uint8_t frame[]); + int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) override { + return _store->getBlobByKey(key, key_len, dest_buf); + } + bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) override { + return _store->putBlobByKey(key, key_len, src_buf, len); + } void checkCLIRescueCmd(); void checkSerialInterface(); diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index b463dcbe..b46cdaab 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -14,13 +14,13 @@ static uint32_t _atoi(const char* sp) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include - DataStore store(InternalFS); + DataStore store(InternalFS, rtc_clock); #elif defined(RP2040_PLATFORM) #include - DataStore store(LittleFS); + DataStore store(LittleFS, rtc_clock); #elif defined(ESP32) #include - DataStore store(SPIFFS); + DataStore store(SPIFFS, rtc_clock); #endif #ifdef ESP32 diff --git a/variants/t114/platformio.ini b/variants/t114/platformio.ini index fd9d6f34..5343e5ad 100644 --- a/variants/t114/platformio.ini +++ b/variants/t114/platformio.ini @@ -70,7 +70,7 @@ build_flags = -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 - -D BLE_DEBUG_LOGGING=1 +; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D ENABLE_PRIVATE_KEY_IMPORT=1 ; -D ENABLE_PRIVATE_KEY_EXPORT=1 From 0f601752e4e9089a866c2e6d70c439e709c93b94 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 15:23:55 +1200 Subject: [PATCH 04/10] implement ls and cat commands for rescue mode --- examples/companion_radio/DataStore.cpp | 10 ++++++ examples/companion_radio/DataStore.h | 1 + examples/companion_radio/MyMesh.cpp | 48 ++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index a37cfa49..795e38cc 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -42,6 +42,16 @@ void DataStore::begin() { #include #endif +File DataStore::openRead(const char* filename) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + return _fs->open(filename, FILE_O_READ); +#elif defined(RP2040_PLATFORM) + return _fs->open(filename, "r"); +#else + return _fs->open(filename, "r", true); +#endif +} + bool DataStore::formatFileSystem() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return _fs->format(); diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index 139131e1..201dac01 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -37,4 +37,5 @@ public: void saveChannels(DataStoreHost* host); uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len); + File openRead(const char* filename); }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 054d0cba..79910aee 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1305,6 +1305,54 @@ void MyMesh::checkCLIRescueCmd() { } else { Serial.println(" Error: erase failed"); } + } else if (memcmp(cli_command, "ls", 2) == 0) { + + // get path from command e.g: "ls /adafruit" + const char *path = &cli_command[3]; + + // log each file and directory + File root = _store->openRead(path); + File file = root.openNextFile(); + while (file) { + + if (file.isDirectory()) { + Serial.print("[dir] "); + Serial.println(file.name()); + } else { + Serial.print("[file] "); + Serial.print(file.name()); + Serial.print(" ("); + Serial.print(file.size()); + Serial.println(" bytes)"); + } + + // move to next file + file = root.openNextFile(); + + } + + } else if (memcmp(cli_command, "cat", 3) == 0) { + + // get path from command e.g: "cat /contacts3" + const char *path = &cli_command[4]; + + // log file content as hex + File file = _store->openRead(path); + if(file){ + + // get file content + int file_size = file.available(); + uint8_t buffer[file_size]; + file.read(buffer, file_size); + + // print hex + mesh::Utils::printHex(Serial, buffer, file_size); + Serial.print("\n"); + + file.close(); + + } + } else if (strcmp(cli_command, "reboot") == 0) { board.reboot(); // doesn't return } else { From a22c176d45123361523c9d0209056e78dae8ef3b Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 15:44:36 +1200 Subject: [PATCH 05/10] add rm command to remove file --- examples/companion_radio/DataStore.cpp | 4 ++++ examples/companion_radio/DataStore.h | 1 + examples/companion_radio/MyMesh.cpp | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 795e38cc..508b270e 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -52,6 +52,10 @@ File DataStore::openRead(const char* filename) { #endif } +bool DataStore::removeFile(const char* filename) { + return _fs->remove(filename); +} + bool DataStore::formatFileSystem() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return _fs->format(); diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index 201dac01..32ccd196 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -38,4 +38,5 @@ public: uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len); File openRead(const char* filename); + bool removeFile(const char* filename); }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 79910aee..63fbf142 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1353,6 +1353,19 @@ void MyMesh::checkCLIRescueCmd() { } + } else if (memcmp(cli_command, "rm ", 3) == 0) { + + // get path from command e.g: "rm /adv_blobs" + const char *path = &cli_command[4]; + + // remove file + bool removed = _store->removeFile(path); + if(removed){ + Serial.println("File removed"); + } else { + Serial.println("Failed to remove file"); + } + } else if (strcmp(cli_command, "reboot") == 0) { board.reboot(); // doesn't return } else { From 9d574b2de0a3ba9cd2ef27359d745343c02420fc Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 16:03:04 +1200 Subject: [PATCH 06/10] ensure user isn't removing invalid path --- examples/companion_radio/MyMesh.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 63fbf142..4d3860c3 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1357,13 +1357,20 @@ void MyMesh::checkCLIRescueCmd() { // get path from command e.g: "rm /adv_blobs" const char *path = &cli_command[4]; - - // remove file - bool removed = _store->removeFile(path); - if(removed){ - Serial.println("File removed"); + + // ensure path is not empty, or root dir + if(!path || strlen(path) == 0 || strcmp(path, "/") == 0){ + Serial.println("Invalid path provided"); } else { - Serial.println("Failed to remove file"); + + // remove file + bool removed = _store->removeFile(path); + if(removed){ + Serial.println("File removed"); + } else { + Serial.println("Failed to remove file"); + } + } } else if (strcmp(cli_command, "reboot") == 0) { From a814bfb00be1fabb164c1986fc5c8ab850b7bbeb Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 16:17:45 +1200 Subject: [PATCH 07/10] don't create file when trying to open for read --- examples/companion_radio/DataStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 508b270e..2ba5ccfb 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -48,7 +48,7 @@ File DataStore::openRead(const char* filename) { #elif defined(RP2040_PLATFORM) return _fs->open(filename, "r"); #else - return _fs->open(filename, "r", true); + return _fs->open(filename, "r", false); #endif } From a50f89f16f1a67b066e3414ae2e6fff4184ecc8b Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 17:38:22 +1200 Subject: [PATCH 08/10] ensure root path is usable --- examples/companion_radio/MyMesh.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 4d3860c3..68e9d615 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1312,8 +1312,9 @@ void MyMesh::checkCLIRescueCmd() { // log each file and directory File root = _store->openRead(path); - File file = root.openNextFile(); - while (file) { + if(root){ + File file = root.openNextFile(); + while (file) { if (file.isDirectory()) { Serial.print("[dir] "); @@ -1329,6 +1330,7 @@ void MyMesh::checkCLIRescueCmd() { // move to next file file = root.openNextFile(); + } } } else if (memcmp(cli_command, "cat", 3) == 0) { From 28edff43fd91038456046a095e2a399a1c78dab6 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 17:42:18 +1200 Subject: [PATCH 09/10] simplify serial print --- examples/companion_radio/MyMesh.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 68e9d615..9c8b96b1 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1317,14 +1317,9 @@ void MyMesh::checkCLIRescueCmd() { while (file) { if (file.isDirectory()) { - Serial.print("[dir] "); - Serial.println(file.name()); + Serial.printf("[dir] %s\n", file.name()); } else { - Serial.print("[file] "); - Serial.print(file.name()); - Serial.print(" ("); - Serial.print(file.size()); - Serial.println(" bytes)"); + Serial.printf("[file] %s (%d bytes)\n", file.name(), file.size()); } // move to next file From 7f79d0c5142a9b43e87d29d1e865c368ca3bf934 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 7 Jun 2025 17:56:20 +1200 Subject: [PATCH 10/10] close roor dir after listing files --- examples/companion_radio/MyMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9c8b96b1..2b98ec04 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1326,6 +1326,7 @@ void MyMesh::checkCLIRescueCmd() { file = root.openNextFile(); } + root.close(); } } else if (memcmp(cli_command, "cat", 3) == 0) {