mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 16:12:38 +00:00
Share bounded STM32 Companion preference wire metadata
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <initializer_list>
|
||||
#include "DataStore.h"
|
||||
#include "PrefsStorageLayout.h"
|
||||
#include <helpers/FileRead.h>
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#if defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
@@ -1094,6 +1095,19 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
return false;
|
||||
}
|
||||
bool success = true;
|
||||
#if defined(STM32_PLATFORM) && defined(__GNUC__)
|
||||
using namespace mesh::companion_prefs;
|
||||
uint8_t pad[4];
|
||||
for (size_t i = 0; i < FIELD_COUNT; ++i) {
|
||||
const PrefField& field = PREF_FIELDS[i];
|
||||
if (i >= MANDATORY_FIELDS && file.available() == 0) continue;
|
||||
// Only this normally constructed, mutable snapshot is a destination.
|
||||
void* destination = const_cast<void*>(prefValue(field, loaded_prefs,
|
||||
loaded_lat, loaded_lon, pad));
|
||||
if (success && file.read(static_cast<uint8_t*>(destination), field.length)
|
||||
!= field.length) success = false;
|
||||
}
|
||||
#else
|
||||
auto readField = [&file, &success](void* dest, size_t size) -> bool {
|
||||
if (!success
|
||||
|| file.read(static_cast<uint8_t*>(dest), size) != size) {
|
||||
@@ -1226,6 +1240,7 @@ bool DataStore::loadPrefsInt(const char *filename,
|
||||
sizeof(loaded_prefs.gps_sync_interval_hours));
|
||||
readOptionalField(&loaded_prefs.usb_debug_enabled,
|
||||
sizeof(loaded_prefs.usb_debug_enabled));
|
||||
#endif
|
||||
|
||||
// Any bytes left over form only part of a historically appended field.
|
||||
// Preserve the file and defaults rather than treating that tail as EOF.
|
||||
@@ -1260,6 +1275,24 @@ bool DataStore::savePrefs(const CompanionNodePrefs& _prefs, double node_lat, dou
|
||||
File file = openWrite(_fs, "/new_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
#if defined(STM32_PLATFORM) && defined(__GNUC__)
|
||||
using namespace mesh::companion_prefs;
|
||||
uint8_t image[235];
|
||||
const uint8_t pad[4] = {};
|
||||
size_t used = 0;
|
||||
for (const PrefField& field : PREF_FIELDS) {
|
||||
if (field.length > sizeof(image) - used) {
|
||||
// Latch failure and stop before any out-of-bounds pointer arithmetic.
|
||||
used = sizeof(image) + 1;
|
||||
break;
|
||||
}
|
||||
memcpy(image + used, prefValue(field, _prefs, node_lat, node_lon, pad),
|
||||
field.length);
|
||||
used += field.length;
|
||||
}
|
||||
bool success = used == sizeof(image)
|
||||
&& file.write(image, sizeof(image)) == sizeof(image);
|
||||
#else
|
||||
#if defined(STM32_PLATFORM)
|
||||
// Keep the explicit wire field list, but make one bounded atomic write.
|
||||
// Other platforms retain direct writes and their existing stack footprint.
|
||||
@@ -1406,6 +1439,7 @@ bool DataStore::savePrefs(const CompanionNodePrefs& _prefs, double node_lat, dou
|
||||
success = success && image_length == sizeof(image)
|
||||
&& file.write(image, sizeof(image)) == sizeof(image);
|
||||
#endif
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
success = file.commit(success);
|
||||
if (!success) MESH_DEBUG_PRINTLN("DataStore: atomic preferences write failed");
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(STM32_PLATFORM) && defined(__GNUC__)
|
||||
#include "NodePrefs.h"
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#if defined(TBEAM_1W) || defined(RP2040_PLATFORM)
|
||||
#error "The compact STM32 preference table needs an explicit board layout"
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
namespace companion_prefs {
|
||||
|
||||
struct PrefField { uint8_t offset, length; };
|
||||
enum : uint8_t { PrefsLat = 253, PrefsLon = 254, PrefsPad = 255 };
|
||||
static constexpr size_t MANDATORY_FIELDS = 21;
|
||||
|
||||
// GNU supports offsetof for this adapter-bearing, non-standard-layout class.
|
||||
// Use it only on the STM32 GNU ABI, and verify every individual value field;
|
||||
// neither the class padding nor either runtime adapter is a wire image.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
template<typename Value, size_t Offset, size_t Length>
|
||||
constexpr PrefField prefField() {
|
||||
static_assert(std::is_trivially_copyable<Value>::value,
|
||||
"only scalar/array preferences are persisted");
|
||||
static_assert(sizeof(Value) == Length, "published preference size");
|
||||
static_assert(Offset < PrefsLat && Length <= UINT8_MAX,
|
||||
"compact preference field bounds");
|
||||
static_assert(Offset + Length <=
|
||||
__builtin_offsetof(CompanionNodePrefs, usb_debug_enabled)
|
||||
+ sizeof(CompanionNodePrefs::usb_debug_enabled),
|
||||
"preference field stays before runtime adapters");
|
||||
static_assert(Offset + Length <= sizeof(CompanionNodePrefs),
|
||||
"preference field stays inside its object");
|
||||
return {uint8_t(Offset), uint8_t(Length)};
|
||||
}
|
||||
template<uint8_t Source, size_t Length>
|
||||
constexpr PrefField prefSpecial() {
|
||||
static_assert(Source == PrefsLat || Source == PrefsLon || Source == PrefsPad,
|
||||
"known auxiliary preference source");
|
||||
static_assert(Source == PrefsPad ? Length <= 4 : Length == sizeof(double),
|
||||
"coordinate/padding source bounds");
|
||||
return {Source, uint8_t(Length)};
|
||||
}
|
||||
#define PREF_FIELD(name, length) prefField<decltype(CompanionNodePrefs::name), \
|
||||
__builtin_offsetof(CompanionNodePrefs, name), length>()
|
||||
// Explicit published wire order, deliberately independent of member order.
|
||||
static constexpr PrefField PREF_FIELDS[] = {
|
||||
PREF_FIELD(airtime_factor, 4),
|
||||
PREF_FIELD(node_name, 32),
|
||||
prefSpecial<PrefsPad, 4>(),
|
||||
prefSpecial<PrefsLat, 8>(),
|
||||
prefSpecial<PrefsLon, 8>(),
|
||||
PREF_FIELD(freq, 4),
|
||||
PREF_FIELD(sf, 1),
|
||||
PREF_FIELD(cr, 1),
|
||||
PREF_FIELD(client_repeat, 1),
|
||||
PREF_FIELD(manual_add_contacts, 1),
|
||||
PREF_FIELD(bw, 4),
|
||||
PREF_FIELD(tx_power_dbm, 1),
|
||||
PREF_FIELD(telemetry_mode_base, 1),
|
||||
PREF_FIELD(telemetry_mode_loc, 1),
|
||||
PREF_FIELD(telemetry_mode_env, 1),
|
||||
PREF_FIELD(rx_delay_base, 4),
|
||||
PREF_FIELD(advert_loc_policy, 1),
|
||||
PREF_FIELD(multi_acks, 1),
|
||||
PREF_FIELD(path_hash_mode, 1),
|
||||
prefSpecial<PrefsPad, 1>(),
|
||||
PREF_FIELD(ble_pin, 4),
|
||||
PREF_FIELD(buzzer_quiet, 1),
|
||||
PREF_FIELD(gps_enabled, 1),
|
||||
PREF_FIELD(gps_interval, 4),
|
||||
PREF_FIELD(autoadd_config, 1),
|
||||
PREF_FIELD(autoadd_max_hops, 1),
|
||||
PREF_FIELD(rx_boosted_gain, 1),
|
||||
PREF_FIELD(default_scope_name, 31),
|
||||
PREF_FIELD(default_scope_key, 16),
|
||||
PREF_FIELD(radio_fem_rxgain, 1),
|
||||
PREF_FIELD(radio_fem_rxgain_override, 1),
|
||||
PREF_FIELD(vibe_quiet, 1),
|
||||
PREF_FIELD(radio_fem_txgain, 1),
|
||||
PREF_FIELD(rx_powersaving_enabled, 1),
|
||||
PREF_FIELD(rx_ps_rx_us, 4),
|
||||
PREF_FIELD(rx_ps_sleep_us, 4),
|
||||
PREF_FIELD(rx_ps_level, 1),
|
||||
PREF_FIELD(rx_ps_preamble, 1),
|
||||
PREF_FIELD(powersaving_enabled, 1),
|
||||
PREF_FIELD(wifi_enabled, 1),
|
||||
PREF_FIELD(powersaving_policy_version, 1),
|
||||
PREF_FIELD(usb_logging_enabled, 1),
|
||||
PREF_FIELD(bluetooth_name, 32),
|
||||
PREF_FIELD(display_rotation_degrees, 2),
|
||||
PREF_FIELD(cad_enabled, 1),
|
||||
PREF_FIELD(cad_scan_timeout_ms, 2),
|
||||
PREF_FIELD(cad_retry_delay_ms, 2),
|
||||
PREF_FIELD(cad_max_duration_ms, 2),
|
||||
PREF_FIELD(bluetooth_mac_mode, 1),
|
||||
PREF_FIELD(bluetooth_mac, 6),
|
||||
PREF_FIELD(bluetooth_stealth_peer_type, 1),
|
||||
PREF_FIELD(bluetooth_stealth_peer, 6),
|
||||
PREF_FIELD(bluetooth_stealth_mode, 1),
|
||||
PREF_FIELD(tx_delay_factor, 4),
|
||||
PREF_FIELD(direct_tx_delay_factor, 4),
|
||||
PREF_FIELD(interference_threshold, 1),
|
||||
PREF_FIELD(agc_reset_interval, 1),
|
||||
PREF_FIELD(tz_offset, 1),
|
||||
PREF_FIELD(flood_retry_attempts, 1),
|
||||
PREF_FIELD(flood_retry_max_path, 1),
|
||||
PREF_FIELD(flood_retry_group_max_path, 1),
|
||||
PREF_FIELD(flood_retry_advert_enabled, 1),
|
||||
PREF_FIELD(one_key_dm_enabled, 1),
|
||||
PREF_FIELD(bluetooth_enabled, 1),
|
||||
PREF_FIELD(gps_sync_interval_hours, 2),
|
||||
PREF_FIELD(usb_debug_enabled, 1),
|
||||
};
|
||||
#undef PREF_FIELD
|
||||
#pragma GCC diagnostic pop
|
||||
static constexpr size_t FIELD_COUNT = sizeof(PREF_FIELDS) / sizeof(PREF_FIELDS[0]);
|
||||
constexpr size_t wireSize(size_t count) {
|
||||
return count == 0 ? 0 : PREF_FIELDS[count - 1].length + wireSize(count - 1);
|
||||
}
|
||||
static_assert(wireSize(MANDATORY_FIELDS) == 84, "published mandatory image size");
|
||||
static_assert(wireSize(FIELD_COUNT) == 235, "published STM32 preference image size");
|
||||
|
||||
static const void* prefValue(const PrefField& field,
|
||||
const CompanionNodePrefs& values,
|
||||
const double& lat, const double& lon,
|
||||
const uint8_t pad[4]) {
|
||||
if (field.offset == PrefsLat) return ⪫
|
||||
if (field.offset == PrefsLon) return &lon;
|
||||
if (field.offset == PrefsPad) return pad;
|
||||
return reinterpret_cast<const uint8_t*>(&values) + field.offset;
|
||||
}
|
||||
|
||||
} // namespace companion_prefs
|
||||
} // namespace mesh
|
||||
#endif
|
||||
@@ -0,0 +1,246 @@
|
||||
// Frozen sequential preference serializer from 54e5b0b99.
|
||||
// Independent of the new storage table; keep its historical field order.
|
||||
bool DataStore::loadPrefsLegacy(const char *filename,
|
||||
CompanionNodePrefs& _prefs, double& node_lat,
|
||||
double& node_lon) {
|
||||
File file = openRead(_fs, filename);
|
||||
if (file) {
|
||||
CompanionNodePrefs loaded_prefs = _prefs;
|
||||
// Files written before this tail existed always started Bluetooth at boot.
|
||||
// Do not inherit a runtime off value when loading one of those images.
|
||||
loaded_prefs.bluetooth_enabled = 1;
|
||||
loaded_prefs.gps_sync_interval_hours = 0;
|
||||
// Older images have no independent debug preference. Never inherit a
|
||||
// runtime-on value when loading one of those images.
|
||||
loaded_prefs.usb_debug_enabled = 0;
|
||||
double loaded_lat = node_lat;
|
||||
double loaded_lon = node_lon;
|
||||
// The original image ended after ble_pin at byte 84. Later releases only
|
||||
// appended fields, sometimes as an indivisible group. Accept every format
|
||||
// actually emitted by those releases, but reject a truncated field/group
|
||||
// or an unknown tail before any value reaches the live preferences.
|
||||
static const uint32_t MIN_PREFS_SIZE = 84;
|
||||
static const uint16_t KNOWN_PREFS_SIZES[] = {
|
||||
84, 85, 90, 91, 92, 93, 140, 141, 142, 143, 144, 155,
|
||||
156, 157, 158, 159,
|
||||
159 + sizeof(loaded_prefs.bluetooth_name),
|
||||
161 + sizeof(loaded_prefs.bluetooth_name),
|
||||
168 + sizeof(loaded_prefs.bluetooth_name),
|
||||
168 + sizeof(loaded_prefs.bluetooth_name)
|
||||
+ sizeof(loaded_prefs.bluetooth_mac_mode)
|
||||
+ sizeof(loaded_prefs.bluetooth_mac),
|
||||
168 + sizeof(loaded_prefs.bluetooth_name)
|
||||
+ sizeof(loaded_prefs.bluetooth_mac_mode)
|
||||
+ sizeof(loaded_prefs.bluetooth_mac)
|
||||
+ sizeof(loaded_prefs.bluetooth_stealth_peer_type)
|
||||
+ sizeof(loaded_prefs.bluetooth_stealth_peer),
|
||||
168 + sizeof(loaded_prefs.bluetooth_name)
|
||||
+ sizeof(loaded_prefs.bluetooth_mac_mode)
|
||||
+ sizeof(loaded_prefs.bluetooth_mac)
|
||||
+ sizeof(loaded_prefs.bluetooth_stealth_peer_type)
|
||||
+ sizeof(loaded_prefs.bluetooth_stealth_peer)
|
||||
+ sizeof(loaded_prefs.bluetooth_stealth_mode),
|
||||
226, // prior 215-byte image plus radio timing, interference, AGC and timezone
|
||||
#ifdef TBEAM_1W
|
||||
233, // fan mode and low/high thermistor thresholds
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
323, // Pico W additionally persists its SSID/password (ESP32 uses NVS)
|
||||
#endif
|
||||
230, // Companion flood retry controls, appended after prior fields
|
||||
#ifdef TBEAM_1W
|
||||
237,
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
327,
|
||||
#endif
|
||||
231, // One-key DM consent policy, appended after flood retry controls
|
||||
#ifdef TBEAM_1W
|
||||
238,
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
328,
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
239, // Bluetooth enable preference, appended after one-key DM policy
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
329,
|
||||
#else
|
||||
232,
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
241, // GPS time-sync cadence in hours, appended after Bluetooth enable
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
331,
|
||||
#else
|
||||
234,
|
||||
#endif
|
||||
#ifdef TBEAM_1W
|
||||
242, // Independent USB debug preference, after the published GPS tail
|
||||
#elif defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
332,
|
||||
#else
|
||||
235,
|
||||
#endif
|
||||
};
|
||||
const uint32_t prefs_size = file.size();
|
||||
bool known_size = false;
|
||||
for (size_t i = 0;
|
||||
i < sizeof(KNOWN_PREFS_SIZES) / sizeof(KNOWN_PREFS_SIZES[0]); i++) {
|
||||
if (prefs_size == KNOWN_PREFS_SIZES[i]) {
|
||||
known_size = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prefs_size < MIN_PREFS_SIZE || !known_size) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
bool success = true;
|
||||
auto readField = [&file, &success](void* dest, size_t size) -> bool {
|
||||
if (!success
|
||||
|| file.read(static_cast<uint8_t*>(dest), size) != size) {
|
||||
success = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto readOptionalField = [&file, &readField](void* dest,
|
||||
size_t size) -> bool {
|
||||
return file.available() == 0 || readField(dest, size);
|
||||
};
|
||||
uint8_t pad[8];
|
||||
|
||||
readField(&loaded_prefs.airtime_factor, sizeof(float)); // 0
|
||||
readField(loaded_prefs.node_name, sizeof(loaded_prefs.node_name)); // 4
|
||||
readField(pad, 4); // 36
|
||||
readField(&loaded_lat, sizeof(loaded_lat)); // 40
|
||||
readField(&loaded_lon, sizeof(loaded_lon)); // 48
|
||||
readField(&loaded_prefs.freq, sizeof(loaded_prefs.freq)); // 56
|
||||
readField(&loaded_prefs.sf, sizeof(loaded_prefs.sf)); // 60
|
||||
readField(&loaded_prefs.cr, sizeof(loaded_prefs.cr)); // 61
|
||||
readField(&loaded_prefs.client_repeat, sizeof(loaded_prefs.client_repeat)); // 62
|
||||
readField(&loaded_prefs.manual_add_contacts, sizeof(loaded_prefs.manual_add_contacts)); // 63
|
||||
readField(&loaded_prefs.bw, sizeof(loaded_prefs.bw)); // 64
|
||||
readField(&loaded_prefs.tx_power_dbm, sizeof(loaded_prefs.tx_power_dbm)); // 68
|
||||
readField(&loaded_prefs.telemetry_mode_base, sizeof(loaded_prefs.telemetry_mode_base)); // 69
|
||||
readField(&loaded_prefs.telemetry_mode_loc, sizeof(loaded_prefs.telemetry_mode_loc)); // 70
|
||||
readField(&loaded_prefs.telemetry_mode_env, sizeof(loaded_prefs.telemetry_mode_env)); // 71
|
||||
readField(&loaded_prefs.rx_delay_base, sizeof(loaded_prefs.rx_delay_base)); // 72
|
||||
readField(&loaded_prefs.advert_loc_policy, sizeof(loaded_prefs.advert_loc_policy)); // 76
|
||||
readField(&loaded_prefs.multi_acks, sizeof(loaded_prefs.multi_acks)); // 77
|
||||
readField(&loaded_prefs.path_hash_mode, sizeof(loaded_prefs.path_hash_mode)); // 78
|
||||
readField(pad, 1); // 79
|
||||
readField(&loaded_prefs.ble_pin, sizeof(loaded_prefs.ble_pin)); // 80
|
||||
readOptionalField(&loaded_prefs.buzzer_quiet,
|
||||
sizeof(loaded_prefs.buzzer_quiet)); // 84
|
||||
readOptionalField(&loaded_prefs.gps_enabled,
|
||||
sizeof(loaded_prefs.gps_enabled)); // 85
|
||||
readOptionalField(&loaded_prefs.gps_interval,
|
||||
sizeof(loaded_prefs.gps_interval)); // 86
|
||||
readOptionalField(&loaded_prefs.autoadd_config,
|
||||
sizeof(loaded_prefs.autoadd_config)); // 90
|
||||
readOptionalField(&loaded_prefs.autoadd_max_hops,
|
||||
sizeof(loaded_prefs.autoadd_max_hops)); // 91
|
||||
readOptionalField(&loaded_prefs.rx_boosted_gain,
|
||||
sizeof(loaded_prefs.rx_boosted_gain)); // 92
|
||||
readOptionalField(loaded_prefs.default_scope_name,
|
||||
sizeof(loaded_prefs.default_scope_name)); // 93
|
||||
readOptionalField(loaded_prefs.default_scope_key,
|
||||
sizeof(loaded_prefs.default_scope_key)); // 124
|
||||
readOptionalField(&loaded_prefs.radio_fem_rxgain,
|
||||
sizeof(loaded_prefs.radio_fem_rxgain)); // 140
|
||||
readOptionalField(&loaded_prefs.radio_fem_rxgain_override,
|
||||
sizeof(loaded_prefs.radio_fem_rxgain_override)); // 141
|
||||
readOptionalField(&loaded_prefs.vibe_quiet,
|
||||
sizeof(loaded_prefs.vibe_quiet)); // 142
|
||||
readOptionalField(&loaded_prefs.radio_fem_txgain,
|
||||
sizeof(loaded_prefs.radio_fem_txgain)); // 143
|
||||
readOptionalField(&loaded_prefs.rx_powersaving_enabled,
|
||||
sizeof(loaded_prefs.rx_powersaving_enabled)); // 144
|
||||
readOptionalField(&loaded_prefs.rx_ps_rx_us,
|
||||
sizeof(loaded_prefs.rx_ps_rx_us)); // 145
|
||||
readOptionalField(&loaded_prefs.rx_ps_sleep_us,
|
||||
sizeof(loaded_prefs.rx_ps_sleep_us)); // 149
|
||||
readOptionalField(&loaded_prefs.rx_ps_level,
|
||||
sizeof(loaded_prefs.rx_ps_level)); // 153
|
||||
readOptionalField(&loaded_prefs.rx_ps_preamble,
|
||||
sizeof(loaded_prefs.rx_ps_preamble)); // 154
|
||||
readOptionalField(&loaded_prefs.powersaving_enabled,
|
||||
sizeof(loaded_prefs.powersaving_enabled)); // 155
|
||||
readOptionalField(&loaded_prefs.wifi_enabled,
|
||||
sizeof(loaded_prefs.wifi_enabled)); // 156
|
||||
readOptionalField(&loaded_prefs.powersaving_policy_version,
|
||||
sizeof(loaded_prefs.powersaving_policy_version)); // 157
|
||||
readOptionalField(&loaded_prefs.usb_logging_enabled,
|
||||
sizeof(loaded_prefs.usb_logging_enabled)); // 158
|
||||
readOptionalField(loaded_prefs.bluetooth_name,
|
||||
sizeof(loaded_prefs.bluetooth_name)); // 159
|
||||
readOptionalField(&loaded_prefs.display_rotation_degrees,
|
||||
sizeof(loaded_prefs.display_rotation_degrees)); // 191
|
||||
readOptionalField(&loaded_prefs.cad_enabled,
|
||||
sizeof(loaded_prefs.cad_enabled)); // 193
|
||||
readOptionalField(&loaded_prefs.cad_scan_timeout_ms,
|
||||
sizeof(loaded_prefs.cad_scan_timeout_ms)); // 194
|
||||
readOptionalField(&loaded_prefs.cad_retry_delay_ms,
|
||||
sizeof(loaded_prefs.cad_retry_delay_ms)); // 196
|
||||
readOptionalField(&loaded_prefs.cad_max_duration_ms,
|
||||
sizeof(loaded_prefs.cad_max_duration_ms)); // 198
|
||||
readOptionalField(&loaded_prefs.bluetooth_mac_mode,
|
||||
sizeof(loaded_prefs.bluetooth_mac_mode)); // 200
|
||||
readOptionalField(loaded_prefs.bluetooth_mac,
|
||||
sizeof(loaded_prefs.bluetooth_mac)); // 201
|
||||
readOptionalField(&loaded_prefs.bluetooth_stealth_peer_type,
|
||||
sizeof(loaded_prefs.bluetooth_stealth_peer_type)); // 207
|
||||
readOptionalField(loaded_prefs.bluetooth_stealth_peer,
|
||||
sizeof(loaded_prefs.bluetooth_stealth_peer)); // 208
|
||||
readOptionalField(&loaded_prefs.bluetooth_stealth_mode,
|
||||
sizeof(loaded_prefs.bluetooth_stealth_mode)); // 214
|
||||
readOptionalField(&loaded_prefs.tx_delay_factor, sizeof(loaded_prefs.tx_delay_factor));
|
||||
readOptionalField(&loaded_prefs.direct_tx_delay_factor, sizeof(loaded_prefs.direct_tx_delay_factor));
|
||||
readOptionalField(&loaded_prefs.interference_threshold, sizeof(loaded_prefs.interference_threshold));
|
||||
readOptionalField(&loaded_prefs.agc_reset_interval, sizeof(loaded_prefs.agc_reset_interval));
|
||||
readOptionalField(&loaded_prefs.tz_offset, sizeof(loaded_prefs.tz_offset));
|
||||
#ifdef TBEAM_1W
|
||||
readOptionalField(loaded_prefs.fan_mode, sizeof(loaded_prefs.fan_mode));
|
||||
readOptionalField(&loaded_prefs.fan_lo, sizeof(loaded_prefs.fan_lo));
|
||||
readOptionalField(&loaded_prefs.fan_hi, sizeof(loaded_prefs.fan_hi));
|
||||
loaded_prefs.fan_mode[sizeof(loaded_prefs.fan_mode) - 1] = 0;
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
readOptionalField(loaded_prefs.wifi_ssid, sizeof(loaded_prefs.wifi_ssid));
|
||||
readOptionalField(loaded_prefs.wifi_pwd, sizeof(loaded_prefs.wifi_pwd));
|
||||
loaded_prefs.wifi_ssid[sizeof(loaded_prefs.wifi_ssid) - 1] = 0;
|
||||
loaded_prefs.wifi_pwd[sizeof(loaded_prefs.wifi_pwd) - 1] = 0;
|
||||
#endif
|
||||
readOptionalField(&loaded_prefs.flood_retry_attempts,
|
||||
sizeof(loaded_prefs.flood_retry_attempts));
|
||||
readOptionalField(&loaded_prefs.flood_retry_max_path,
|
||||
sizeof(loaded_prefs.flood_retry_max_path));
|
||||
readOptionalField(&loaded_prefs.flood_retry_group_max_path,
|
||||
sizeof(loaded_prefs.flood_retry_group_max_path));
|
||||
readOptionalField(&loaded_prefs.flood_retry_advert_enabled,
|
||||
sizeof(loaded_prefs.flood_retry_advert_enabled));
|
||||
readOptionalField(&loaded_prefs.one_key_dm_enabled,
|
||||
sizeof(loaded_prefs.one_key_dm_enabled));
|
||||
readOptionalField(&loaded_prefs.bluetooth_enabled,
|
||||
sizeof(loaded_prefs.bluetooth_enabled));
|
||||
readOptionalField(&loaded_prefs.gps_sync_interval_hours,
|
||||
sizeof(loaded_prefs.gps_sync_interval_hours));
|
||||
readOptionalField(&loaded_prefs.usb_debug_enabled,
|
||||
sizeof(loaded_prefs.usb_debug_enabled));
|
||||
|
||||
// Any bytes left over form only part of a historically appended field.
|
||||
// Preserve the file and defaults rather than treating that tail as EOF.
|
||||
success = success && file.available() == 0;
|
||||
success = success && loaded_prefs.bluetooth_enabled <= 1;
|
||||
file.close();
|
||||
if (!success) return false;
|
||||
loaded_prefs.usb_debug_enabled = loaded_prefs.usb_debug_enabled == 1 ? 1 : 0;
|
||||
_prefs = loaded_prefs;
|
||||
node_lat = loaded_lat;
|
||||
node_lon = loaded_lon;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Frozen sequential preference serializer from 54e5b0b99.
|
||||
// Independent of the new storage table; keep its historical field order.
|
||||
bool DataStore::savePrefsLegacy(const CompanionNodePrefs& _prefs, double node_lat, double node_lon) {
|
||||
if (_prefs_load_incomplete) return false;
|
||||
#if defined(ESP32_PLATFORM)
|
||||
if (!promoteCompanionRecoveryFile(_fs, "/new_prefs", _prefs_recovery_source)) return false;
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (_primary_storage_unavailable) return false;
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
mesh::AtomicFileWriter file(_fs, "/new_prefs");
|
||||
#elif defined(ESP32_PLATFORM)
|
||||
mesh::ContactFileTransaction file(_fs, "/new_prefs", companionPathPresence);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
mesh::ContactFileTransaction file(_fs, "/new_prefs");
|
||||
#else
|
||||
File file = openWrite(_fs, "/new_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
const auto writeField = [&file](const void* data, size_t size) -> bool {
|
||||
return file.write(static_cast<const uint8_t*>(data), size) == size;
|
||||
};
|
||||
uint8_t pad[8];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
bool success = writeField((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0
|
||||
success = success && writeField((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4
|
||||
success = success && writeField(pad, 4); // 36
|
||||
success = success && writeField((uint8_t *)&node_lat, sizeof(node_lat)); // 40
|
||||
success = success && writeField((uint8_t *)&node_lon, sizeof(node_lon)); // 48
|
||||
success = success && writeField((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
success = success && writeField((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
success = success && writeField((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
success = success && writeField((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62
|
||||
success = success && writeField((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
success = success && writeField((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
success = success && writeField((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
success = success && writeField((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
|
||||
success = success && writeField((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
|
||||
success = success && writeField((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
||||
success = success && writeField((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76
|
||||
success = success && writeField((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
|
||||
success = success && writeField((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78
|
||||
success = success && writeField(pad, 1); // 79
|
||||
success = success && writeField((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
success = success && writeField((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||
success = success && writeField((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
success = success && writeField((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||
success = success && writeField((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||
success = success && writeField((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89
|
||||
success = success && writeField((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
|
||||
success = success && writeField((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
|
||||
success = success && writeField((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
|
||||
success = success && writeField((uint8_t *)&_prefs.radio_fem_rxgain_override,
|
||||
sizeof(_prefs.radio_fem_rxgain_override)); // 123
|
||||
success = success && writeField((uint8_t *)&_prefs.vibe_quiet,
|
||||
sizeof(_prefs.vibe_quiet)); // 124
|
||||
success = success && writeField((uint8_t *)&_prefs.radio_fem_txgain,
|
||||
sizeof(_prefs.radio_fem_txgain)); // 125
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_powersaving_enabled,
|
||||
sizeof(_prefs.rx_powersaving_enabled)); // 126
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_ps_rx_us,
|
||||
sizeof(_prefs.rx_ps_rx_us)); // 127
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_ps_sleep_us,
|
||||
sizeof(_prefs.rx_ps_sleep_us)); // 131
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_ps_level,
|
||||
sizeof(_prefs.rx_ps_level)); // 135
|
||||
success = success && writeField((uint8_t *)&_prefs.rx_ps_preamble,
|
||||
sizeof(_prefs.rx_ps_preamble)); // 136
|
||||
success = success && writeField((uint8_t *)&_prefs.powersaving_enabled,
|
||||
sizeof(_prefs.powersaving_enabled)); // 137
|
||||
success = success && writeField((uint8_t *)&_prefs.wifi_enabled,
|
||||
sizeof(_prefs.wifi_enabled)); // 138
|
||||
success = success && writeField((uint8_t *)&_prefs.powersaving_policy_version,
|
||||
sizeof(_prefs.powersaving_policy_version)); // 139
|
||||
success = success && writeField((uint8_t *)&_prefs.usb_logging_enabled,
|
||||
sizeof(_prefs.usb_logging_enabled)); // 140
|
||||
success = success && writeField((uint8_t *)_prefs.bluetooth_name,
|
||||
sizeof(_prefs.bluetooth_name)); // 141
|
||||
success = success && writeField(
|
||||
(uint8_t *)&_prefs.display_rotation_degrees,
|
||||
sizeof(_prefs.display_rotation_degrees));
|
||||
success = success && writeField((uint8_t *)&_prefs.cad_enabled,
|
||||
sizeof(_prefs.cad_enabled));
|
||||
success = success && writeField((uint8_t *)&_prefs.cad_scan_timeout_ms,
|
||||
sizeof(_prefs.cad_scan_timeout_ms));
|
||||
success = success && writeField((uint8_t *)&_prefs.cad_retry_delay_ms,
|
||||
sizeof(_prefs.cad_retry_delay_ms));
|
||||
success = success && writeField((uint8_t *)&_prefs.cad_max_duration_ms,
|
||||
sizeof(_prefs.cad_max_duration_ms));
|
||||
success = success && writeField((uint8_t *)&_prefs.bluetooth_mac_mode,
|
||||
sizeof(_prefs.bluetooth_mac_mode));
|
||||
success = success && writeField((uint8_t *)_prefs.bluetooth_mac,
|
||||
sizeof(_prefs.bluetooth_mac));
|
||||
success = success && writeField(
|
||||
(uint8_t *)&_prefs.bluetooth_stealth_peer_type,
|
||||
sizeof(_prefs.bluetooth_stealth_peer_type));
|
||||
success = success && writeField(
|
||||
(uint8_t *)_prefs.bluetooth_stealth_peer,
|
||||
sizeof(_prefs.bluetooth_stealth_peer));
|
||||
success = success && writeField(
|
||||
(uint8_t *)&_prefs.bluetooth_stealth_mode,
|
||||
sizeof(_prefs.bluetooth_stealth_mode));
|
||||
|
||||
success = success && writeField((uint8_t *)&_prefs.tx_delay_factor,
|
||||
sizeof(_prefs.tx_delay_factor));
|
||||
success = success && writeField((uint8_t *)&_prefs.direct_tx_delay_factor,
|
||||
sizeof(_prefs.direct_tx_delay_factor));
|
||||
success = success && writeField((uint8_t *)&_prefs.interference_threshold,
|
||||
sizeof(_prefs.interference_threshold));
|
||||
success = success && writeField((uint8_t *)&_prefs.agc_reset_interval,
|
||||
sizeof(_prefs.agc_reset_interval));
|
||||
success = success && writeField((uint8_t *)&_prefs.tz_offset,
|
||||
sizeof(_prefs.tz_offset));
|
||||
#ifdef TBEAM_1W
|
||||
success = success && writeField((uint8_t *)_prefs.fan_mode,
|
||||
sizeof(_prefs.fan_mode));
|
||||
success = success && writeField((uint8_t *)&_prefs.fan_lo,
|
||||
sizeof(_prefs.fan_lo));
|
||||
success = success && writeField((uint8_t *)&_prefs.fan_hi,
|
||||
sizeof(_prefs.fan_hi));
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
success = success && writeField((uint8_t *)_prefs.wifi_ssid,
|
||||
sizeof(_prefs.wifi_ssid));
|
||||
success = success && writeField((uint8_t *)_prefs.wifi_pwd,
|
||||
sizeof(_prefs.wifi_pwd));
|
||||
#endif
|
||||
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_attempts, 1) == 1;
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_max_path, 1) == 1;
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_group_max_path, 1) == 1;
|
||||
success = success && file.write((uint8_t *)&_prefs.flood_retry_advert_enabled, 1) == 1;
|
||||
success = success && file.write((uint8_t *)&_prefs.one_key_dm_enabled, 1) == 1;
|
||||
success = success && file.write((uint8_t *)&_prefs.bluetooth_enabled, 1) == 1;
|
||||
success = success && file.write((uint8_t *)&_prefs.gps_sync_interval_hours, 2) == 2;
|
||||
success = success && file.write((uint8_t *)&_prefs.usb_debug_enabled, 1) == 1;
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) || defined(ESP32_PLATFORM) || defined(RP2040_PLATFORM)
|
||||
success = file.commit(success);
|
||||
if (!success) MESH_DEBUG_PRINTLN("DataStore: atomic preferences write failed");
|
||||
#else
|
||||
file.close();
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -22,6 +22,7 @@ HARNESS = r'''
|
||||
#include <helpers/AtomicFileWriter.h>
|
||||
#endif
|
||||
#include "examples/companion_radio/NodePrefs.h"
|
||||
#include "examples/companion_radio/PrefsStorageLayout.h"
|
||||
#define MESH_DEBUG_PRINTLN(...) ((void)0)
|
||||
struct DataStore {
|
||||
MemoryFS fs;
|
||||
|
||||
@@ -132,18 +132,9 @@ class CompanionPrefsTailTests(unittest.TestCase):
|
||||
def test_full_image_parity_and_atomic_tail_faults(self):
|
||||
source = (ROOT / 'examples/companion_radio/DataStore.cpp').read_text()
|
||||
save = method(source, 'bool DataStore::savePrefs(')
|
||||
tail_start = save.index(' // Pack only the explicit persisted tail')
|
||||
tail_end = save.index('\n#if defined(NRF52_PLATFORM)', tail_start)
|
||||
legacy_save = save[:tail_start] + LEGACY_TAIL + save[tail_end:]
|
||||
legacy_save = legacy_save.replace('DataStore::savePrefs(', 'DataStore::savePrefsLegacy(', 1)
|
||||
# The golden tail serializer must still issue the original scalar
|
||||
# writes, including on STM32 where the production writer now stages
|
||||
# an image. Its prefix emitter forwards directly to the same file.
|
||||
emitter_start = legacy_save.index('#if defined(STM32_PLATFORM)\n // Keep the explicit wire field list')
|
||||
emitter_end = legacy_save.index('\n uint8_t pad[8];', emitter_start)
|
||||
legacy_save = legacy_save[:emitter_start] + ''' const auto writeField = [&file](const void* data, size_t size) -> bool {
|
||||
return file.write(static_cast<const uint8_t*>(data), size) == size;
|
||||
};''' + legacy_save[emitter_end:]
|
||||
# Keep the complete original scalar writer independent of any future
|
||||
# production field-table changes, including every native tail byte.
|
||||
legacy_save = (ROOT / 'test/fixtures/companion_prefs_storage/legacy_writer.inc').read_text()
|
||||
methods = esp_recovery_helpers(source) + '\n'.join(method(source, signature) for signature in (
|
||||
'bool DataStore::loadPrefs(', 'bool DataStore::loadPrefsInt('))
|
||||
methods += '\n' + save + '\n' + legacy_save
|
||||
|
||||
@@ -114,11 +114,35 @@ int main() {
|
||||
auto actual_adapters = adapterBytes(actual);
|
||||
auto legacy_adapters = adapterBytes(legacy);
|
||||
double lat = 1, lon = 2, legacy_lat = 1, legacy_lon = 2;
|
||||
READ_CALLS.clear(); AVAILABLE_CALLS = 0;
|
||||
const bool expected = legacy_store.loadPrefsLegacy(
|
||||
"/new_prefs", legacy, legacy_lat, legacy_lon);
|
||||
const auto expected_reads = READ_CALLS;
|
||||
const unsigned expected_available = AVAILABLE_CALLS;
|
||||
READ_CALLS.clear(); AVAILABLE_CALLS = 0;
|
||||
const bool got = actual_store.loadPrefsInt(
|
||||
"/new_prefs", actual, lat, lon);
|
||||
assert(got == expected);
|
||||
assert(READ_CALLS == expected_reads);
|
||||
assert(AVAILABLE_CALLS == expected_available);
|
||||
if (length == 84 && quota == 84) {
|
||||
// The original mandatory prefix has 21 reads, ending at ble_pin.
|
||||
// This order is an independent wire contract, not the new table.
|
||||
const std::vector<size_t> mandatory = {
|
||||
4, 32, 4, 8, 8, 4, 1, 1, 1, 1, 4, 1, 1, 1, 1,
|
||||
4, 1, 1, 1, 1, 4};
|
||||
assert(expected && READ_CALLS == mandatory);
|
||||
unsigned optional_fields = 45;
|
||||
#ifdef TBEAM_1W
|
||||
optional_fields += 3;
|
||||
#endif
|
||||
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
|
||||
optional_fields += 2;
|
||||
#endif
|
||||
// Every absent optional field observes EOF, then the completed
|
||||
// image receives the existing final trailing-byte check.
|
||||
assert(AVAILABLE_CALLS == optional_fields + 1);
|
||||
}
|
||||
equalValues(actual, legacy);
|
||||
assert(!memcmp(&lat, &legacy_lat, sizeof(lat)));
|
||||
assert(!memcmp(&lon, &legacy_lon, sizeof(lon)));
|
||||
@@ -145,15 +169,9 @@ class CompanionPrefsValuesTests(unittest.TestCase):
|
||||
def test_value_only_copy_and_full_loader_match_legacy(self):
|
||||
source = (ROOT / 'examples/companion_radio/DataStore.cpp').read_text()
|
||||
loader = method(source, 'bool DataStore::loadPrefsInt(')
|
||||
legacy = loader.replace('DataStore::loadPrefsInt(', 'DataStore::loadPrefsLegacy(', 1)
|
||||
legacy = legacy.replace(''' CompanionNodePrefs loaded_prefs;
|
||||
if (!loaded_prefs.copyPersistedValuesFrom(_prefs)) {
|
||||
file.close();
|
||||
return false;
|
||||
}''', ' CompanionNodePrefs loaded_prefs = _prefs;')
|
||||
legacy = legacy.replace(
|
||||
' if (!_prefs.copyPersistedValuesFrom(loaded_prefs)) return false;',
|
||||
' _prefs = loaded_prefs;')
|
||||
# A frozen sequential reader, not a clone of the current field table,
|
||||
# is the authority for all old/torn images and read failure traces.
|
||||
legacy = (ROOT / 'test/fixtures/companion_prefs_storage/legacy_loader.inc').read_text()
|
||||
self.assertNotIn('copyPersistedValuesFrom', legacy)
|
||||
methods = esp_recovery_helpers(source) + '\n'.join((loader, legacy,
|
||||
method(source, 'bool DataStore::savePrefs(')))
|
||||
@@ -211,6 +229,19 @@ inline char* utoa(unsigned int value,char* output,int base){
|
||||
transaction = (ROOT / 'src/helpers/ContactFileTransaction.h').read_text()
|
||||
(work / 'ContactFileTransaction.h').write_text(transaction.replace(
|
||||
'#include "IdentityStore.h"', '#include <helpers/IdentityStore.h>'))
|
||||
# Instrument only the existing filesystem boundary. Both actual
|
||||
# readers and the real transactional writer remain unchanged.
|
||||
fs = (ROOT / 'test/fixtures/radio_profiles/mocks/helpers/IdentityStore.h').read_text()
|
||||
fs = fs.replace('class MemoryFS;',
|
||||
'static std::vector<size_t> READ_CALLS;\n'
|
||||
'static unsigned AVAILABLE_CALLS = 0;\nclass MemoryFS;')
|
||||
fs = fs.replace('size_t available() const { return size() - cursor_; }',
|
||||
'size_t available() const { ++AVAILABLE_CALLS;return size() - cursor_; }')
|
||||
fs = fs.replace('inline int File::read(uint8_t* data, size_t size) {',
|
||||
'inline int File::read(uint8_t* data, size_t size) {\n'
|
||||
' READ_CALLS.push_back(size);')
|
||||
(work / 'helpers').mkdir()
|
||||
(work / 'helpers/IdentityStore.h').write_text(fs)
|
||||
cpp = work / 'test.cpp'
|
||||
cpp.write_text(code + tests)
|
||||
for platform in ('STM32_PLATFORM', 'NRF52_PLATFORM', 'ESP32_PLATFORM', 'RP2040_PLATFORM',
|
||||
|
||||
Reference in New Issue
Block a user