flash cleaning improvements

This commit is contained in:
liquidraver
2026-08-28 09:41:43 +02:00
parent 8adbe2a648
commit 27ea7e41a5
13 changed files with 432 additions and 109 deletions
+11
View File
@@ -1114,6 +1114,17 @@ Codes `0x80``0x90` (`PUSH_CODE_*` in `app/CompanionMesh.h`). Most used:
| `/lfs/repeater/regions2` | Region map | Header + 164B × N entries |
| `storage_partition` (NVS, 0xD0000 nRF52) | BLE bonds + Zephyr settings | NVS settings backend (≥1.16.2; old `/lfs/settings` file detected by self-heal) |
> **Roles are not interchangeable.** Each role formats the whole volume on its first boot if the
> volume holds no data for that role: the companion checks `/lfs/new_prefs`
> (`ZephyrDataStore::hasPrefs()`), the repeater/room-server/observer check `/lfs/repeater/prefs`
> and `/lfs/repeater/_main.id` (`RepeaterDataStore::hasRoleData()`). So flashing a repeater over
> a companion — or the reverse — erases the previous role's identity, prefs and contacts, plus
> `storage_partition` and QSPI. Export your identity before switching roles. The roles' files
> never overlap physically (one LittleFS volume, one allocator); the reason for the wipe is that
> they share 128 KB and the other role's data crowds out writes. Repeater, room server and
> observer share `/lfs/repeater/` and the same prefs layout, so switching among *those three*
> preserves the identity.
### Preferences Binary Layouts
Two distinct field-by-field serializations (NOT raw struct dumps), both Arduino-compatible
+11 -1
View File
@@ -127,7 +127,17 @@ Regions control which flood packets the repeater forwards. The region tree is hi
| `log stop` | Disable packet logging |
| `log erase` | Erase the log file |
| `log` | *(USB only)* Dump the full log file to USB serial |
| `erase` | *(USB only)* Format the entire filesystem |
| `erase` | *(USB only)* Factory reset: erase the entire LittleFS volume, the BLE-bond NVS, and external QSPI flash, then reboot |
> **`erase` is a true factory reset.** It flattens `lfs_partition` (identity, prefs, ACL,
> region map, logs), `storage_partition` (BLE bonds) and `qspi_storage_partition` where
> present — not just the files under `/lfs/repeater/`. The node comes back with a new
> identity and default prefs. Erasing the volume rather than unlinking files is what makes
> it able to recover a volume another firmware has written into: on nRF52840 the Adafruit
> core's filesystem (used by Arduino MeshCore and Meshtastic) sits at 0xED000, inside our
> `lfs_partition`, and its format scribbles the top 7 blocks of our volume. Switching
> between Arduino-core firmware and ZephCore on nRF52840 needs an erase in **both**
> directions — `tools/formatter` or a full chip erase.
---
@@ -0,0 +1,107 @@
# ZephCore 1.17.4-zephcore
Storage housekeeping. The repeater's `erase` command never actually erased anything, a node flashed
from another firmware could start out with somebody else's leftovers underneath it, and switching a
node between companion and repeater firmware quietly let the two share the same 128 KB. All three are
fixed, and the last one is now deliberate and loud rather than quiet.
> [!IMPORTANT]
> **Read the role-switching section before you flash a different role onto an existing node.** A
> companion that gets repeater firmware — or the reverse — now erases itself on first boot. That is
> intentional, but it is new. Export your identity first if you want to keep it.
> [!NOTE]
> A normal upgrade is unaffected. Repeater to repeater, or companion to companion, keeps your
> identity, settings, contacts and phone pairing exactly as before.
---
## `erase` now erases
On a repeater or room server, `erase` promised to format the entire filesystem. It did not. It deleted
the handful of files it had put in its own folder and cleared the phone pairings, and left everything
else exactly where it was.
Most of the time nobody noticed, because on a healthy node those files *are* everything that matters.
It mattered when the node was not healthy — which is precisely when somebody reaches for `erase`. If
anything had written into the storage area from outside, deleting our own files could not undo it, and
the command reported success while the problem stayed.
`erase` now wipes the storage area itself, the phone-pairing store, and external flash where a board has
it, then reboots. The node comes back with a new identity and default settings, exactly like a node out
of the box.
> [!IMPORTANT]
> **This is a genuine factory reset now, and it takes the identity with it.** Anyone who had your node
> in their contacts will need to add it again, and an admin password, ACL and region map all go too.
> That was always what the command claimed to do; it is now what it does.
The companion's `erase` already worked this way. Repeater, room server and observer share one
implementation with it now, so there is one behaviour to remember instead of two.
---
## Switching a node between roles now wipes it
Companion firmware and repeater firmware kept their files in separate folders, and until now each left
the other's alone. Flashing back and forth preserved both sets.
That sounds generous and was not. The two roles share a single 128 KB storage area. A companion that has
collected a few hundred contacts and a full advert cache leaves noticeably less room for a repeater's
region map and access list, and a write that no longer fits simply fails. The node is not corrupted —
the two roles' files never sit on top of each other — it just runs out of space for reasons its owner
cannot see, because half of what is stored belongs to firmware that is not running.
They are also two quite different kinds of node, and treating one machine as quietly holding both was
never worth the space it cost.
From 1.17.4, each role checks on first boot whether the storage belongs to it, and formats everything if
not. So a repeater flashed onto a former companion starts empty, and a companion flashed onto a former
repeater starts empty.
> [!IMPORTANT]
> **Export your identity before switching roles.** The node's identity, settings, contacts, channels,
> access list, region map and phone pairings all go. There is no undo and no warning prompt — the first
> boot on the new firmware has already done it by the time you see anything.
> [!NOTE]
> **Repeater, room server and observer still share.** Those three keep their files in the same place and
> use the same settings layout, so moving between them keeps the node's identity and configuration. It
> is the companion that is now separate.
---
## A node coming from other firmware starts clean
Flashing ZephCore onto hardware that was running something else is a normal thing to do, and it used to
leave more behind than anyone expected.
Nothing about installing firmware erases storage. Dragging a UF2 file writes the program and nothing
else, and each firmware only ever clears the piece of flash it believes is its own. On the nRF52840
boards, Arduino MeshCore's storage sits inside the same region ZephCore uses, so the two overlap — and
whichever one boots first tidies up its own corner and leaves the rest of the other's files sitting
there. The result on one Seeed Solar Node was a repeater that came up looking perfectly healthy and
silently refused to forward anything, because a single setting deep inside its configuration had been
overwritten by bytes that belonged to a different firmware.
Each role now checks on first boot whether the storage is its own and, if not, clears the whole lot —
the storage area, the phone-pairing store, and external flash — before writing anything. A node arriving
from another firmware, or from a factory-fresh chip, starts from a known state instead of an inherited
one.
> [!IMPORTANT]
> **Moving between Arduino MeshCore and ZephCore still needs an erase in both directions.** ZephCore now
> cleans up on the way in, but it cannot clean up on the way out — going back to Arduino MeshCore leaves
> ZephCore's files inside the area Arduino will use. Run the formatter UF2, or a full chip erase, when
> you switch either way. This is not new advice; it is now written down.
---
## Also in this release
Nothing here changes how a node behaves.
- **A wasted erase on the companion.** Running `erase` from the companion's USB console formatted the
storage, then formatted it a second time on the reboot that followed, because the marker saying "this
node has been set up" went out with everything else. The pairing-based factory reset never had this
problem. Both paths behave the same way now.
+4
View File
@@ -629,6 +629,10 @@ target_sources(app PRIVATE
helpers/buzzer_gate.c
# QSPI bring-up probe. Self-stubs unless CONFIG_ZEPHCORE_QSPI_RDID_PROBE.
helpers/qspi_probe.c
# Factory format of every storage region. Compiled for all roles: the
# companion builds ZephyrDataStore, the other three build
# RepeaterDataStore, and both call into this.
adapters/datastore/ZephyrFsFormat.c
)
# Boot-time hardware-RTC auto-discovery (compact raw-I2C). Always compiled so
@@ -7,6 +7,7 @@
*/
#include "ZephyrDataStore.h"
#include "ZephyrFsFormat.h"
#include <AdvertDataHelpers.h> // ADV_TYPE_NONE (transient/anon contacts)
#include <zephyr/fs/fs.h>
#include <zephyr/fs/littlefs.h>
@@ -371,84 +372,14 @@ void ZephyrDataStore::checkAdvBlobFile()
bool ZephyrDataStore::formatFileSystem()
{
LOG_INF("formatFileSystem: starting...");
/* The erase/remount itself lives in ZephyrFsFormat.c so the repeater,
* room server and observer which build RepeaterDataStore and never
* compile this file get the identical implementation. */
bool ext_mounted = false;
bool mounted = zephcore_fs_format_all(&ext_mounted);
/* Properly unmount from Zephyr's VFS before erasing flash.
* The old unmount() only cleared flags Zephyr still held /lfs mounted,
* so flash_area_flatten silently destroyed the on-flash superblock while
* LittleFS considered itself active. Every subsequent file op then hit
* the erased blocks and logged "Corrupted dir pair at {0x0, 0x1}".
* FS_FSTAB_DECLARE_ENTRY exposes the non-static mount struct generated
* from the DTS fstab; fs_mount() on a blank partition auto-formats
* (littlefs_fs.c: lfs_mount fail lfs_format lfs_mount). */
FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(lfs));
fs_unmount(&FS_FSTAB_ENTRY(DT_NODELABEL(lfs)));
lfs_mounted = false;
#if DT_NODE_EXISTS(DT_NODELABEL(qspi_lfs))
FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(qspi_lfs));
fs_unmount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs)));
#endif
ext_lfs_mounted = false;
const struct flash_area *fap;
int rc;
#if FIXED_PARTITION_EXISTS(lfs_partition)
rc = flash_area_open(PARTITION_ID(lfs_partition), &fap);
if (rc == 0) {
LOG_INF("Formatting LFS partition (%u bytes)", (unsigned)fap->fa_size);
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
#endif
#if FIXED_PARTITION_EXISTS(storage_partition)
rc = flash_area_open(PARTITION_ID(storage_partition), &fap);
if (rc == 0) {
LOG_INF("Formatting NVS storage (%u bytes)", (unsigned)fap->fa_size);
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
#endif
#if FIXED_PARTITION_EXISTS(qspi_storage_partition)
/* QSPI if present (any platform) */
rc = flash_area_open(PARTITION_ID(qspi_storage_partition), &fap);
if (rc == 0) {
LOG_INF("Formatting QSPI (%u bytes, may take a while)", (unsigned)fap->fa_size);
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
#endif
/* Remount: littlefs_mount() auto-formats on blank flash, then mounts. */
rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(lfs)));
bool mounted = (rc == 0);
if (mounted) {
lfs_mounted = true;
}
#if DT_NODE_EXISTS(DT_NODELABEL(qspi_lfs))
/* Remount external QSPI too. We unmounted it above and flattened its
* partition, so it must be re-mounted here otherwise a runtime format
* (factory reset, or the first-boot "no prefs" auto-format) leaves /ext
* unmounted for the rest of the session. begin() then reads
* ext_lfs_mounted=false and the store falls back to internal /lfs, so
* contacts/channels save to /lfs and get needlessly migrated back to /ext
* on the next boot ("Migrating contacts to external storage" churn). */
{
FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(qspi_lfs));
int ext_rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs)));
if (is_mounted(extMountPoint())) {
ext_lfs_mounted = true;
LOG_INF("formatFileSystem: /ext remounted (rc=%d)", ext_rc);
} else {
ext_lfs_mounted = false;
LOG_ERR("formatFileSystem: /ext remount failed (rc=%d)", ext_rc);
}
}
#endif
lfs_mounted = mounted;
ext_lfs_mounted = ext_mounted;
LOG_INF("formatFileSystem: mount() returned %d", mounted ? 1 : 0);
return mounted;
@@ -0,0 +1,117 @@
/*
* SPDX-License-Identifier: MIT
* ZephyrFsFormat - shared factory-format of every ZephCore storage region.
*/
#include "ZephyrFsFormat.h"
#include <zephyr/devicetree.h>
#include <zephyr/fs/fs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephcore_fs_format, CONFIG_ZEPHCORE_DATASTORE_LOG_LEVEL);
#define LFS_MNT_POINT "/lfs"
#define EXT_MNT_POINT "/ext"
/* fs_mount() can return 0 having mounted nothing useful, and a remount that
* silently failed would leave the caller reporting a healthy store over an
* unmounted volume. Ask the VFS instead of trusting the return code. */
static bool is_mounted(const char *mount_point)
{
struct fs_statvfs stat;
return fs_statvfs(mount_point, &stat) == 0;
}
static void flatten(uint8_t id, const char *tag)
{
const struct flash_area *fap;
int rc = flash_area_open(id, &fap);
if (rc != 0) {
LOG_WRN("format: flash_area_open(%s) failed: %d", tag, rc);
return;
}
LOG_INF("format: erasing %s (%u bytes)", tag, (unsigned)fap->fa_size);
rc = flash_area_flatten(fap, 0, fap->fa_size);
if (rc != 0) {
LOG_ERR("format: flatten(%s) failed: %d", tag, rc);
}
flash_area_close(fap);
}
bool zephcore_fs_format_all(bool *out_ext_mounted)
{
LOG_INF("zephcore_fs_format_all: starting...");
if (out_ext_mounted) {
*out_ext_mounted = false;
}
/* Properly unmount from Zephyr's VFS before erasing flash. Clearing a
* local "mounted" flag is not enough Zephyr would still hold /lfs
* mounted, so flash_area_flatten destroys the on-flash superblock while
* LittleFS considers itself active. Every subsequent file op then hits
* the erased blocks and logs "Corrupted dir pair at {0x0, 0x1}".
* FS_FSTAB_DECLARE_ENTRY exposes the non-static mount struct generated
* from the DTS fstab; fs_mount() on a blank partition auto-formats
* (littlefs_fs.c: lfs_mount fail -> lfs_format -> lfs_mount). */
FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(lfs));
fs_unmount(&FS_FSTAB_ENTRY(DT_NODELABEL(lfs)));
#if DT_NODE_EXISTS(DT_NODELABEL(qspi_lfs))
FS_FSTAB_DECLARE_ENTRY(DT_NODELABEL(qspi_lfs));
fs_unmount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs)));
#endif
#if FIXED_PARTITION_EXISTS(lfs_partition)
flatten(PARTITION_ID(lfs_partition), "lfs_partition");
#endif
#if FIXED_PARTITION_EXISTS(storage_partition)
/* BLE bonds (NVS). A factory reset should clear them too; the caller
* reboots so NVS and the BT stack re-init clean. */
flatten(PARTITION_ID(storage_partition), "storage_partition");
#endif
#if FIXED_PARTITION_EXISTS(qspi_storage_partition)
flatten(PARTITION_ID(qspi_storage_partition), "qspi_storage_partition");
#endif
/* Remount: littlefs_mount() auto-formats blank flash, then mounts. */
int rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(lfs)));
bool mounted = is_mounted(LFS_MNT_POINT);
if (mounted) {
LOG_INF("format: %s remounted (rc=%d)", LFS_MNT_POINT, rc);
} else {
LOG_ERR("format: %s remount FAILED (rc=%d)", LFS_MNT_POINT, rc);
}
#if DT_NODE_EXISTS(DT_NODELABEL(qspi_lfs))
/* Remount external QSPI too. We unmounted it above and flattened its
* partition, so it must be re-mounted here otherwise a runtime format
* (factory reset, or the first-boot "no prefs" auto-format) leaves /ext
* unmounted for the rest of the session. begin() then reads
* ext_lfs_mounted=false and the store falls back to internal /lfs, so
* contacts/channels save to /lfs and get needlessly migrated back to
* /ext on the next boot ("Migrating contacts to external storage"). */
{
int ext_rc = fs_mount(&FS_FSTAB_ENTRY(DT_NODELABEL(qspi_lfs)));
bool ext_mounted = is_mounted(EXT_MNT_POINT);
if (ext_mounted) {
LOG_INF("format: %s remounted (rc=%d)", EXT_MNT_POINT, ext_rc);
} else {
LOG_ERR("format: %s remount failed (rc=%d)", EXT_MNT_POINT, ext_rc);
}
if (out_ext_mounted) {
*out_ext_mounted = ext_mounted;
}
}
#endif
return mounted;
}
@@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: MIT
* ZephyrFsFormat - shared factory-format of every ZephCore storage region.
*
* Lives outside ZephyrDataStore because the repeater, room server and observer
* build RepeaterDataStore instead (see CMakeLists role blocks) and must not
* each grow their own half-implementation of this.
*/
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Erase every ZephCore storage region and remount the filesystems.
*
* Unmounts /lfs (and /ext when the board has QSPI), flattens lfs_partition,
* storage_partition (BLE bonds NVS) and qspi_storage_partition where each
* exists, then remounts. A blank LittleFS partition is auto-formatted by
* fs_mount(), so the volume comes back empty rather than corrupt.
*
* This is the only path that erases the LittleFS *volume*. Deleting files
* cannot recover a volume another firmware has written into on nRF52840 the
* Adafruit core's InternalFileSystem lives at 0xED000, inside our 0xD4000
* lfs_partition, and its format() scribbles our top 7 blocks.
*
* @param out_ext_mounted optional; receives whether /ext came back mounted.
* Set to false on boards with no QSPI.
* @return true if /lfs is mounted afterwards.
*/
bool zephcore_fs_format_all(bool *out_ext_mounted);
#ifdef __cplusplus
}
#endif
+51 -28
View File
@@ -4,8 +4,9 @@
*/
#include "RepeaterDataStore.h"
#include "../adapters/datastore/ZephyrFsFormat.h"
#include <zephyr/fs/fs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <string.h>
#include <stdio.h>
@@ -37,6 +38,37 @@ bool RepeaterDataStore::begin() {
const char* RepeaterDataStore::getBasePath() const { return BASE_PATH; }
static bool fileExists(const char* path) {
struct fs_dirent entry;
return fs_stat(path, &entry) == 0;
}
bool RepeaterDataStore::hasRoleData() const {
char path[64];
/* Only THIS role's files count. A companion volume does not: the roles
* are deliberately not interchangeable, and a companion's contacts and
* blob cache would eat into the same 128 KB the repeater needs, so a
* repeater booting onto a companion volume formats it. The reverse
* already happens ZephyrDataStore::hasPrefs() tests /lfs/new_prefs,
* which a repeater volume never has.
*
* Repeater, room server and observer DO share this store and base path;
* they use the same prefs layout, so switching among them keeps the
* node's identity, which is what an operator wants.
*
* Self-limiting: loadPrefs() persists defaults on boot 1 and main_*.cpp
* saves a generated identity on the same boot, so after one successful
* boot at least one of these exists and the check never fires again. */
static const char* const ours[] = { "prefs", "_main.id" };
for (size_t i = 0; i < ARRAY_SIZE(ours); i++) {
snprintf(path, sizeof(path), "%s/%s", BASE_PATH, ours[i]);
if (fileExists(path)) return true;
}
return false;
}
const char* RepeaterDataStore::getAclPath() const {
static char buf[48];
snprintf(buf, sizeof(buf), "%s/acl", BASE_PATH);
@@ -412,37 +444,28 @@ bool RepeaterDataStore::savePrefs(const NodePrefs& prefs) {
}
bool RepeaterDataStore::formatFileSystem() {
LOG_WRN("Factory reset: erasing repeater data at %s", BASE_PATH);
LOG_WRN("Factory reset: erasing all storage");
struct fs_dir_t dir;
fs_dir_t_init(&dir);
int ret = fs_opendir(&dir, BASE_PATH);
if (ret < 0) {
LOG_WRN("No repeater directory to erase");
return true;
/* Erase the LittleFS *volume*, not just our files. The old loop walked
* /lfs/repeater/ with fs_unlink, which left the volume itself untouched:
* it could not recover a volume another firmware had written into (on
* nRF52840 the Adafruit core's filesystem overlaps the top of ours), and
* it left /lfs/settings, stale companion files and all of /ext behind.
* Shared with the companion so all four roles erase the same regions. */
bool mounted = zephcore_fs_format_all(nullptr);
if (!mounted) {
LOG_ERR("Factory reset: /lfs did not remount");
return false;
}
struct fs_dirent entry;
char path[280];
while (fs_readdir(&dir, &entry) == 0 && entry.name[0] != '\0') {
snprintf(path, sizeof(path), "%s/%s", BASE_PATH, entry.name);
LOG_INF("Deleting %s", path);
fs_unlink(path);
/* The format took /lfs/repeater with it. Re-create it now rather than
* relying on the reboot: the CLI defers the reset so the reply can be
* transmitted, and anything that saves in that window needs the dir. */
_initialized = false;
if (!begin()) {
LOG_ERR("Factory reset: could not re-create %s", BASE_PATH);
return false;
}
fs_closedir(&dir);
#if FIXED_PARTITION_EXISTS(storage_partition)
/* Erase the NVS bonds partition too — a factory reset should clear BLE
* bonds, not just repeater files. Caller reboots so NVS re-inits clean. */
const struct flash_area *fap;
if (flash_area_open(PARTITION_ID(storage_partition), &fap) == 0) {
LOG_INF("Formatting NVS storage (%u bytes)", (unsigned)fap->fa_size);
flash_area_flatten(fap, 0, fap->fa_size);
flash_area_close(fap);
}
#endif
LOG_INF("Repeater data erased");
return true;
+16 -2
View File
@@ -2,8 +2,15 @@
* SPDX-License-Identifier: MIT
* RepeaterDataStore - Filesystem storage for repeater
*
* Uses /lfs/repeater/ prefix to keep data separate from companion.
* This allows flashing back and forth between roles without corruption.
* Uses /lfs/repeater/ prefix to keep data separate from companion, so the
* two prefs layouts (301 B here, 163 B there, different field order) can
* never be read through each other.
*
* The roles are NOT interchangeable: booting a repeater onto a companion
* volume formats it, and vice versa. They are different kinds of node, and
* they share one 128 KB LittleFS volume - a companion's contacts and blob
* cache would crowd out repeater writes (-ENOSPC), not corrupt them. Save
* your identity before switching roles.
*/
#pragma once
@@ -39,6 +46,13 @@ public:
/* Factory reset - erase all repeater data */
bool formatFileSystem();
/* True if this LittleFS volume already holds THIS role's data. False
* means the volume belongs to something else - a fresh chip, a companion,
* or a node that was running Arduino MeshCore, whose nRF52 filesystems
* overlap our lfs_partition (devdocs/HANDOVER_lfs_arduino_overlap.md).
* Callers format on false; see the note on BASE_PATH below. */
bool hasRoleData() const;
/* Get base path for repeater storage */
const char* getBasePath() const;
+19
View File
@@ -32,6 +32,7 @@
LOG_MODULE_REGISTER(zephcore_observer_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <app/RepeaterDataStore.h>
#include "../adapters/datastore/ZephyrFsFormat.h"
#include <app/ObserverMesh.h>
#include <adapters/clock/ZephyrRTCClock.h>
#include <mesh/RadioIncludes.h>
@@ -306,6 +307,24 @@ int main(void)
}
#endif
/* First boot on a volume that is not this role's - a fresh chip, a
* companion, or a node that was running Arduino MeshCore, whose nRF52
* filesystems overlap our lfs_partition
* (devdocs/HANDOVER_lfs_arduino_overlap.md). Erase everything so we
* start from a known state: Zephyr's automount only
* auto-formats the LittleFS volume when it fails to mount, and never
* touches storage_partition (BLE bonds NVS) or QSPI.
*
* Self-limiting, so it needs no "done" marker: the identity is generated
* and saved a few lines below, and loadPrefs() persists defaults on the
* same boot, so the next boot sees this role's data and skips this. */
if (!data_store.hasRoleData()) {
LOG_WRN("Volume holds no data for this role - formatting before first boot");
if (!zephcore_fs_format_all(nullptr)) {
LOG_ERR("First-boot format failed - /lfs is not mounted");
}
}
/* Initialize LittleFS data store */
if (!data_store.begin()) {
LOG_ERR("RepeaterDataStore init failed");
+11 -1
View File
@@ -708,7 +708,17 @@ public:
const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
const char* getRole() override { return "companion"; }
bool formatFileSystem() override { return data_store.formatFileSystem(); }
/* CLI `erase`. Re-stamp the init marker on success, exactly as
* factoryReset() does for the BLE opcode path: the format takes
* /lfs/_zc_init with it, so without this the post-reboot first-boot check
* sees "no marker, no prefs" and runs a second, pointless full format. */
bool formatFileSystem() override {
bool ok = data_store.formatFileSystem();
if (ok) {
data_store.writeInitMarker();
}
return ok;
}
/* Advert — the companion can originate its own self-advert. delay_millis is
* unused (companion sends flood at 0 ms / zero-hop immediately, matching the
+19
View File
@@ -50,6 +50,7 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
#endif
#include <app/RepeaterDataStore.h>
#include "../adapters/datastore/ZephyrFsFormat.h"
#include <app/RepeaterMesh.h>
#include <adapters/clock/ZephyrRTCClock.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
@@ -643,6 +644,24 @@ int main(void)
}
#endif
/* First boot on a volume that is not this role's - a fresh chip, a
* companion, or a node that was running Arduino MeshCore, whose nRF52
* filesystems overlap our lfs_partition
* (devdocs/HANDOVER_lfs_arduino_overlap.md). Erase everything so we
* start from a known state: Zephyr's automount only
* auto-formats the LittleFS volume when it fails to mount, and never
* touches storage_partition (BLE bonds NVS) or QSPI.
*
* Self-limiting, so it needs no "done" marker: the identity is generated
* and saved a few lines below, and loadPrefs() persists defaults on the
* same boot, so the next boot sees this role's data and skips this. */
if (!data_store.hasRoleData()) {
LOG_WRN("Volume holds no data for this role - formatting before first boot");
if (!zephcore_fs_format_all(nullptr)) {
LOG_ERR("First-boot format failed - /lfs is not mounted");
}
}
/* Initialize repeater data store */
if (!data_store.begin()) {
LOG_ERR("RepeaterDataStore init failed");
+19
View File
@@ -50,6 +50,7 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
#endif
#include <app/RepeaterDataStore.h>
#include "../adapters/datastore/ZephyrFsFormat.h"
#include <app/RoomServerMesh.h>
#include <adapters/clock/ZephyrRTCClock.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
@@ -529,6 +530,24 @@ int main(void)
}
#endif
/* First boot on a volume that is not this role's - a fresh chip, a
* companion, or a node that was running Arduino MeshCore, whose nRF52
* filesystems overlap our lfs_partition
* (devdocs/HANDOVER_lfs_arduino_overlap.md). Erase everything so we
* start from a known state: Zephyr's automount only
* auto-formats the LittleFS volume when it fails to mount, and never
* touches storage_partition (BLE bonds NVS) or QSPI.
*
* Self-limiting, so it needs no "done" marker: the identity is generated
* and saved a few lines below, and loadPrefs() persists defaults on the
* same boot, so the next boot sees this role's data and skips this. */
if (!data_store.hasRoleData()) {
LOG_WRN("Volume holds no data for this role - formatting before first boot");
if (!zephcore_fs_format_all(nullptr)) {
LOG_ERR("First-boot format failed - /lfs is not mounted");
}
}
/* Initialize repeater data store */
if (!data_store.begin()) {
LOG_ERR("RepeaterDataStore init failed");