diff --git a/zephcore/ARCHITECTURE.md b/zephcore/ARCHITECTURE.md index d4e906d..0f783b5 100644 --- a/zephcore/ARCHITECTURE.md +++ b/zephcore/ARCHITECTURE.md @@ -1109,15 +1109,17 @@ Codes `0x80`–`0x90` (`PUSH_CODE_*` in `app/CompanionMesh.h`). Most used: Two distinct field-by-field serializations (NOT raw struct dumps), both Arduino-compatible in their shared base fields: -**Companion `/lfs/new_prefs` (152 bytes)** — `adapters/datastore/ZephyrDataStore.cpp` +**Companion `/lfs/new_prefs` (168 bytes)** — `adapters/datastore/ZephyrDataStore.cpp` `loadPrefs()`/`savePrefs()` (offset comments inline). Arduino companion layout (name, lat/lon, radio params, telemetry modes, BLE pin, GPS, autoadd) plus ZephCore extensions from offset 92: rx_boost(92), leds_disabled(93), reserved(94-95, was APC), default flood scope name/key(96-142), ble_disabled(143), display/wake/screen-off/auto-shutdown(144-149), rx_duty_cycle(150), meshtimesync(151). -**Repeater/room-server `/lfs/repeater/prefs` (297 bytes)** — `app/RepeaterDataStore.cpp` -`loadPrefs()`/`savePrefs()` (same field order as `helpers/CommonCLI.cpp`; offset comments inline). +**Repeater/room-server `/lfs/repeater/prefs` (305 bytes)** — `app/RepeaterDataStore.cpp` +`loadPrefs()`/`savePrefs()` (offset comments inline). This is the only serializer for the +repeater layout; `helpers/CommonCLI.cpp` carried a second, unreachable copy of it until it was +removed — do not add prefs fields anywhere but the two files named in this section. Key ranges: name(4-36), radio(72-119), adaptive-delay(80-111, ignored at runtime), leds_disabled(120, magic-encoded `0xA0`/`0xA1` — the byte formerly held `agc_reset_interval`, which stored seconds/4, so any other value is a legacy interval and decodes to "LEDs on"), diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index fe4216a..b7b86e2 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -47,231 +46,6 @@ static bool isValidName(const char* n) { return true; } -// Constrain helper -template -static T constrain(T value, T min_val, T max_val) { - if (value < min_val) return min_val; - if (value > max_val) return max_val; - return value; -} - -/* Read exactly 'len' bytes; short/error read stops the chain via ok flag */ -static inline bool prefs_read(struct fs_file_t *f, void *dest, size_t len) { - return fs_read(f, dest, len) == (ssize_t)len; -} - -void CommonCLI::loadPrefs(const char* path) { - struct fs_file_t file; - fs_file_t_init(&file); - - if (fs_open(&file, path, FS_O_READ) < 0) { - LOG_DBG("No prefs file at %s, using defaults", path); - return; - } - - uint8_t pad[8]; - uint8_t leds_byte = 0; - bool ok = true; - - /* Read fields in Arduino-compatible binary order. - * On truncated file, short-circuit at first failure - * so remaining fields keep their default values. */ - ok = ok && prefs_read(&file, &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 - ok = ok && prefs_read(&file, &_prefs->node_name, sizeof(_prefs->node_name)); // 4 - ok = ok && prefs_read(&file, pad, 4); // 36 - ok = ok && prefs_read(&file, &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 - ok = ok && prefs_read(&file, &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 - ok = ok && prefs_read(&file, &_prefs->password[0], sizeof(_prefs->password)); // 56 - ok = ok && prefs_read(&file, &_prefs->freq, sizeof(_prefs->freq)); // 72 - ok = ok && prefs_read(&file, &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 - ok = ok && prefs_read(&file, &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 - ok = ok && prefs_read(&file, &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 - ok = ok && prefs_read(&file, pad, 1); // 79 - ok = ok && prefs_read(&file, &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 - ok = ok && prefs_read(&file, &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 - ok = ok && prefs_read(&file, &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 - ok = ok && prefs_read(&file, &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 - ok = ok && prefs_read(&file, &_prefs->backoff_multiplier, sizeof(_prefs->backoff_multiplier)); // 108 - ok = ok && prefs_read(&file, &_prefs->sf, sizeof(_prefs->sf)); // 112 - ok = ok && prefs_read(&file, &_prefs->cr, sizeof(_prefs->cr)); // 113 - ok = ok && prefs_read(&file, &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 - ok = ok && prefs_read(&file, &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 - ok = ok && prefs_read(&file, &_prefs->bw, sizeof(_prefs->bw)); // 116 - /* 120: leds_disabled, magic-encoded. Formerly agc_reset_interval — see the - * LEDS_PREF_* comment in NodePrefs.h for why this is not a bare 0/1. */ - ok = ok && prefs_read(&file, &leds_byte, sizeof(leds_byte)); // 120 - ok = ok && prefs_read(&file, &_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 - ok = ok && prefs_read(&file, &_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122 - ok = ok && prefs_read(&file, pad, 1); // 123 - ok = ok && prefs_read(&file, &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 - ok = ok && prefs_read(&file, &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 - ok = ok && prefs_read(&file, &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 - ok = ok && prefs_read(&file, pad, 1); // skip bridge_enabled // 127 - ok = ok && prefs_read(&file, pad, 2); // skip bridge_delay // 128 - ok = ok && prefs_read(&file, pad, 1); // skip bridge_pkt_src // 130 - ok = ok && prefs_read(&file, pad, 4); // skip bridge_baud // 131 - ok = ok && prefs_read(&file, pad, 1); // skip bridge_channel // 135 - ok = ok && prefs_read(&file, pad, 16); // skip bridge_secret // 136 - ok = ok && prefs_read(&file, &_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 - ok = ok && prefs_read(&file, pad, 3); // 153 - ok = ok && prefs_read(&file, &_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 - ok = ok && prefs_read(&file, &_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 - ok = ok && prefs_read(&file, &_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 - ok = ok && prefs_read(&file, &_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - ok = ok && prefs_read(&file, &_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - ok = ok && prefs_read(&file, _prefs->owner_info, sizeof(_prefs->owner_info)); // 170 - ok = ok && prefs_read(&file, &_prefs->rx_boost, sizeof(_prefs->rx_boost)); // 290 - ok = ok && prefs_read(&file, &_prefs->rx_duty_cycle, sizeof(_prefs->rx_duty_cycle)); // 291 - /* 292-293: RESERVED — formerly apc_enabled / apc_margin (APC, removed in - * 1.16.6). Still read so offset 294 onward stays where deployed nodes - * wrote it; the values are ignored. */ - ok = ok && prefs_read(&file, &_prefs->_reserved_apc_enabled, sizeof(_prefs->_reserved_apc_enabled)); // 292 - ok = ok && prefs_read(&file, &_prefs->_reserved_apc_margin, sizeof(_prefs->_reserved_apc_margin)); // 293 - ok = ok && prefs_read(&file, &_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 294 - ok = ok && prefs_read(&file, &_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 295 - ok = ok && prefs_read(&file, &_prefs->meshtimesync, sizeof(_prefs->meshtimesync)); // 296 - ok = ok && prefs_read(&file, &_prefs->cad_auto, sizeof(_prefs->cad_auto)); // 297 - ok = ok && prefs_read(&file, &_prefs->cad_offset, sizeof(_prefs->cad_offset)); // 298 - ok = ok && prefs_read(&file, &_prefs->probe_interval, sizeof(_prefs->probe_interval)); // 299 - ok = ok && prefs_read(&file, &_prefs->cad_busycap, sizeof(_prefs->cad_busycap)); // 300 - ok = ok && prefs_read(&file, _prefs->extra_sf, sizeof(_prefs->extra_sf)); // 301-303 - - if (!ok) { - LOG_WRN("Prefs file %s truncated, some fields use defaults", path); - } - - fs_close(&file); - - /* Only the explicit "off" magic disables LEDs; a legacy AGC interval, an - * unwritten byte, or a truncated file all mean "on". */ - _prefs->leds_disabled = (leds_byte == LEDS_PREF_OFF) ? 1 : 0; - - // Sanitise bad pref values - _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0.0f, 20.0f); - _prefs->tx_delay_factor = constrain(_prefs->tx_delay_factor, 0.0f, 2.0f); - _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0.0f, 2.0f); - /* Migrate uninitialized pad bytes: NaN or out-of-range → default 0.2. - * 0.0 is valid (disables reactive backoff). Old firmware upgrading - * with zeroed pad bytes will get 0.0 = disabled; user can set explicitly. */ - if (_prefs->backoff_multiplier != _prefs->backoff_multiplier || - _prefs->backoff_multiplier < 0.0f || _prefs->backoff_multiplier > 10.0f) { - _prefs->backoff_multiplier = 0.2f; - } - _prefs->backoff_multiplier = constrain(_prefs->backoff_multiplier, 0.0f, 2.0f); - /* af is the Arduino airtime budget factor: duty% = 100 / (af + 1). - * Range matches upstream (0..9). Values >9 (from a previous build that - * stored af as a percentage) get clamped to 9 → 10% effective. */ - _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0.0f, 9.0f); - _prefs->freq = constrain(_prefs->freq, 150.0f, 2500.0f); - _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); - _prefs->sf = constrain(_prefs->sf, (uint8_t)5, (uint8_t)12); - _prefs->cr = constrain(_prefs->cr, (uint8_t)5, (uint8_t)8); - _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, (int8_t)-9, (int8_t)30); -#ifdef CONFIG_ZEPHCORE_MAX_TX_POWER_DBM - if (_prefs->tx_power_dbm > CONFIG_ZEPHCORE_MAX_TX_POWER_DBM) { - _prefs->tx_power_dbm = (int8_t)CONFIG_ZEPHCORE_MAX_TX_POWER_DBM; - } -#endif - _prefs->multi_acks = constrain(_prefs->multi_acks, (uint8_t)0, (uint8_t)1); - _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 30000.0f); - _prefs->path_hash_mode = constrain(_prefs->path_hash_mode, (uint8_t)0, (uint8_t)2); - _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, (uint8_t)0, (uint8_t)1); - _prefs->gps_enabled = constrain(_prefs->gps_enabled, (uint8_t)0, (uint8_t)1); - _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, (uint8_t)0, (uint8_t)2); - _prefs->rx_boost = constrain(_prefs->rx_boost, (uint8_t)0, (uint8_t)1); - _prefs->rx_duty_cycle = constrain(_prefs->rx_duty_cycle, (uint8_t)0, (uint8_t)1); - _prefs->flood_max_unscoped = constrain(_prefs->flood_max_unscoped, (uint8_t)0, (uint8_t)64); - _prefs->flood_max_advert = constrain(_prefs->flood_max_advert, (uint8_t)0, (uint8_t)64); - _prefs->meshtimesync = constrain(_prefs->meshtimesync, (uint8_t)0, (uint8_t)1); - _prefs->cad_auto = constrain(_prefs->cad_auto, (uint8_t)0, (uint8_t)1); - _prefs->cad_offset = constrain(_prefs->cad_offset, (int8_t)CAD_OFFSET_MIN, (int8_t)CAD_OFFSET_MAX); - if (_prefs->probe_interval != 0 && _prefs->probe_interval < 10) { - _prefs->probe_interval = 10; - } - _prefs->cad_busycap = constrain(_prefs->cad_busycap, (uint8_t)0, (uint8_t)90); - - LOG_INF("Loaded prefs from %s", path); -} - -void CommonCLI::savePrefs(const char* path) { - // Remove old file first - fs_unlink(path); - - struct fs_file_t file; - fs_file_t_init(&file); - - if (fs_open(&file, path, FS_O_CREATE | FS_O_WRITE) < 0) { - LOG_ERR("Failed to open %s for write", path); - return; - } - - uint8_t pad[16]; - memset(pad, 0, sizeof(pad)); - - fs_write(&file, &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); - fs_write(&file, &_prefs->node_name, sizeof(_prefs->node_name)); - fs_write(&file, pad, 4); - fs_write(&file, &_prefs->node_lat, sizeof(_prefs->node_lat)); - fs_write(&file, &_prefs->node_lon, sizeof(_prefs->node_lon)); - fs_write(&file, &_prefs->password[0], sizeof(_prefs->password)); - fs_write(&file, &_prefs->freq, sizeof(_prefs->freq)); - fs_write(&file, &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); - fs_write(&file, &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); - fs_write(&file, &_prefs->advert_interval, sizeof(_prefs->advert_interval)); - fs_write(&file, pad, 1); - fs_write(&file, &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); - fs_write(&file, &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); - fs_write(&file, &_prefs->guest_password[0], sizeof(_prefs->guest_password)); - fs_write(&file, &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); - fs_write(&file, &_prefs->backoff_multiplier, sizeof(_prefs->backoff_multiplier)); - fs_write(&file, &_prefs->sf, sizeof(_prefs->sf)); - fs_write(&file, &_prefs->cr, sizeof(_prefs->cr)); - fs_write(&file, &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); - fs_write(&file, &_prefs->multi_acks, sizeof(_prefs->multi_acks)); - fs_write(&file, &_prefs->bw, sizeof(_prefs->bw)); - /* 120: leds_disabled, magic-encoded (was agc_reset_interval). */ - { - uint8_t leds_byte = _prefs->leds_disabled ? LEDS_PREF_OFF : LEDS_PREF_ON; - fs_write(&file, &leds_byte, sizeof(leds_byte)); - } - fs_write(&file, &_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); - fs_write(&file, &_prefs->loop_detect, sizeof(_prefs->loop_detect)); - fs_write(&file, pad, 1); - fs_write(&file, &_prefs->flood_max, sizeof(_prefs->flood_max)); - fs_write(&file, &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); - fs_write(&file, &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); - fs_write(&file, pad, 1); // bridge_enabled - fs_write(&file, pad, 2); // bridge_delay - fs_write(&file, pad, 1); // bridge_pkt_src - fs_write(&file, pad, 4); // bridge_baud - fs_write(&file, pad, 1); // bridge_channel - fs_write(&file, pad, 16); // bridge_secret - fs_write(&file, &_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); - fs_write(&file, pad, 3); - fs_write(&file, &_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); - fs_write(&file, &_prefs->gps_interval, sizeof(_prefs->gps_interval)); - fs_write(&file, &_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); - fs_write(&file, &_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); - fs_write(&file, &_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); - fs_write(&file, _prefs->owner_info, sizeof(_prefs->owner_info)); - fs_write(&file, &_prefs->rx_boost, sizeof(_prefs->rx_boost)); - fs_write(&file, &_prefs->rx_duty_cycle, sizeof(_prefs->rx_duty_cycle)); - /* 292-293: RESERVED — formerly APC, written back unchanged. */ - fs_write(&file, &_prefs->_reserved_apc_enabled, sizeof(_prefs->_reserved_apc_enabled)); - fs_write(&file, &_prefs->_reserved_apc_margin, sizeof(_prefs->_reserved_apc_margin)); - fs_write(&file, &_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); - fs_write(&file, &_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); - fs_write(&file, &_prefs->meshtimesync, sizeof(_prefs->meshtimesync)); - fs_write(&file, &_prefs->cad_auto, sizeof(_prefs->cad_auto)); - fs_write(&file, &_prefs->cad_offset, sizeof(_prefs->cad_offset)); - fs_write(&file, &_prefs->probe_interval, sizeof(_prefs->probe_interval)); - fs_write(&file, &_prefs->cad_busycap, sizeof(_prefs->cad_busycap)); - fs_write(&file, _prefs->extra_sf, sizeof(_prefs->extra_sf)); - - fs_close(&file); - LOG_INF("Saved prefs to %s", path); -} - #define MIN_LOCAL_ADVERT_INTERVAL 60 void CommonCLI::savePrefs() { diff --git a/zephcore/helpers/CommonCLI.h b/zephcore/helpers/CommonCLI.h index b614763..44d4963 100644 --- a/zephcore/helpers/CommonCLI.h +++ b/zephcore/helpers/CommonCLI.h @@ -147,8 +147,6 @@ public: k_work_init_delayable(&_reboot_work, rebootWorkHandler); } - void loadPrefs(const char* path); - void savePrefs(const char* path); void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data); };