Files
MeshCore/examples/companion_radio/NodePrefs.h
T

349 lines
14 KiB
C++

#pragma once
#if defined(ENABLE_WIFI_INTERFACE) && !defined(WIFI_SSID)
#define WIFI_SSID ""
#endif
#include <Arduino.h>
#include <cstdint>
#include <helpers/BluetoothMac.h>
#include <helpers/CommonRadioPrefs.h>
#include <helpers/DynamicConfigSerializer.h>
#include <helpers/CompanionTrafficPrefs.h>
#include "BluetoothName.h"
#define TELEM_MODE_DENY 0
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
#define TELEM_MODE_ALLOW_ALL 2
#define ADVERT_LOC_NONE 0
#define ADVERT_LOC_SHARE 1
// Increment when a release must migrate the persisted Companion device-power
// policy. Version 1 corrects the short-lived default-off regression: old
// preference files are upgraded to power saving on exactly once, after which
// an explicit user choice remains persistent.
#define COMPANION_POWERSAVING_POLICY_VERSION 1
// Companion preferences remain stored by DataStore's explicit, append-only
// binary field list. The adapter objects are appended after every persisted
// field, and node latitude/longitude deliberately remain separate DataStore
// arguments, so the established field offsets and on-device format do not move.
class CompanionNodePrefs {
public:
float airtime_factor = 0;
char node_name[32] = {};
float freq = 0;
uint8_t sf = 0;
uint8_t cr = 0;
uint8_t multi_acks = 0;
uint8_t manual_add_contacts = 0;
float bw = 0;
int8_t tx_power_dbm = 0;
uint8_t telemetry_mode_base = 0;
uint8_t telemetry_mode_loc = 0;
uint8_t telemetry_mode_env = 0;
float rx_delay_base = 0;
uint32_t ble_pin = 0;
uint8_t advert_loc_policy = 0;
uint8_t buzzer_quiet = 0;
uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled)
uint32_t gps_interval = 0; // seconds; 0 uses the 1-second default
uint8_t autoadd_config = 0; // bitmask for auto-add contacts config
uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode
uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain
uint8_t client_repeat = 0;
uint8_t path_hash_mode = 0;
uint8_t autoadd_max_hops = 0;
char default_scope_name[31] = {};
uint8_t default_scope_key[16] = {};
// Append-only binary preference tail. Do not insert persisted fields above
// or between these entries without updating DataStore's compatibility map.
uint8_t radio_fem_rxgain_override = 0;
uint8_t vibe_quiet = 0;
uint8_t radio_fem_txgain = 0;
uint8_t rx_powersaving_enabled = 0;
uint32_t rx_ps_rx_us = 0;
uint32_t rx_ps_sleep_us = 0;
uint8_t rx_ps_level = 0;
uint8_t rx_ps_preamble = 0;
uint8_t powersaving_enabled = 0;
uint8_t wifi_enabled = 0;
uint8_t powersaving_policy_version = 0;
uint8_t usb_logging_enabled = 0; // 0=off, 1=separate where supported, 2=primary packet stream
char bluetooth_name[mesh::companion::BLUETOOTH_NAME_SIZE] = {};
uint16_t display_rotation_degrees = 0;
uint8_t cad_enabled = 0;
uint16_t cad_scan_timeout_ms = 0;
uint16_t cad_retry_delay_ms = 0;
uint16_t cad_max_duration_ms = 0;
uint8_t bluetooth_mac_mode = mesh::companion::BLUETOOTH_MAC_DEFAULT;
uint8_t bluetooth_mac[mesh::companion::BLUETOOTH_MAC_BYTES] = {};
uint8_t bluetooth_stealth_peer_type =
mesh::companion::BLUETOOTH_PEER_ADDRESS_NONE;
uint8_t bluetooth_stealth_peer[mesh::companion::BLUETOOTH_MAC_BYTES] = {};
uint8_t bluetooth_stealth_mode = mesh::companion::BLUETOOTH_STEALTH_OFF;
float tx_delay_factor = 0.5f;
float direct_tx_delay_factor = 0.2f;
uint8_t interference_threshold = 0;
uint8_t agc_reset_interval = 0; // seconds / 4
int8_t tz_offset = 0;
#ifdef TBEAM_1W
char fan_mode[5] = "auto";
uint8_t fan_lo = 30;
uint8_t fan_hi = 36;
#endif
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
char wifi_ssid[33] = {};
char wifi_pwd[64] = {};
#endif
// Keep these after every board-specific field: all existing prefs images
// must retain their exact offsets. Defaults reproduce the core Companion
// retry policy for older files that have no retry tail.
uint8_t flood_retry_attempts = 15;
uint8_t flood_retry_max_path = 1;
uint8_t flood_retry_group_max_path = 0xFF;
uint8_t flood_retry_advert_enabled = 1;
// Appended persisted field: old preferences images default to consent required.
uint8_t one_key_dm_enabled = 0;
// Appended after all existing fields so older images keep Bluetooth enabled.
uint8_t bluetooth_enabled = 1;
// Published tail: zero retains the board's legacy GPS sync cadence.
uint16_t gps_sync_interval_hours = 0;
// Debug verbosity is independent of the packet/output master switch. Keep
// this after the published GPS cadence so legacy images default to quiet.
uint8_t usb_debug_enabled = 0;
// Appended opt-in private-message response: 0=off, 1=no, 2=yes.
uint8_t lost_reply = 0;
#if defined(RAK_4631)
// A checked footer follows every existing on-flash field, never an insertion.
uint8_t peripherals_shared = 0; // applied at boot, not on a live CLI save
#endif
// Checked footer after all previously published fields/board footers.
uint8_t tx_media = mesh::RADIO_TX_AUTO;
uint8_t tx_ssh = mesh::RADIO_TX_AUTO;
private:
class RadioPrefs : public CommonRadioPrefs {
CompanionNodePrefs* _parent;
protected:
void structure() override {
def("freq", _parent->freq);
def("bw", _parent->bw);
def("sf", _parent->sf);
def("cr", _parent->cr);
def("cad", _parent->cad_enabled);
def("int_thr", _parent->interference_threshold);
def("rxgain", _parent->rx_boosted_gain);
def("fem_rxgain", _parent->radio_fem_rxgain);
def("fem_txgain", _parent->radio_fem_txgain);
def("tx", _parent->tx_power_dbm);
def("af", _parent->airtime_factor);
def("rxdelay", _parent->rx_delay_base);
def("f_txdelay", _parent->tx_delay_factor);
def("d_txdelay", _parent->direct_tx_delay_factor);
def("agc_int", _parent->agc_reset_interval);
def("hash_mode", _parent->path_hash_mode);
def("multi_ack", _parent->multi_acks);
def("usb_debug", _parent->usb_debug_enabled);
#ifdef TBEAM_1W
def("fan", _parent->fan_mode, sizeof(_parent->fan_mode));
def("fan_lo", _parent->fan_lo);
def("fan_hi", _parent->fan_hi);
#endif
}
public:
explicit RadioPrefs(CompanionNodePrefs* parent) : _parent(parent) { }
bool getByKey(const char* key, char* value, size_t max_len) override {
if (key != nullptr && strcmp(key, "usb_debug") == 0) {
if (value == nullptr || max_len == 0) return false;
if (max_len < 2) {
value[0] = 0;
return false;
}
value[0] = _parent->usb_debug_enabled == 1 ? '1' : '0';
value[1] = 0;
return true;
}
return CommonRadioPrefs::getByKey(key, value, max_len);
}
bool setByKey(const char* key, const char* value) override {
if (key != nullptr && strcmp(key, "usb_debug") == 0) {
if (value == nullptr
|| (strcmp(value, "0") != 0 && strcmp(value, "1") != 0)) {
return false;
}
_parent->usb_debug_enabled = value[0] == '1' ? 1 : 0;
markDirty();
return true;
}
return CommonRadioPrefs::setByKey(key, value);
}
float getFreq() const override { return _parent->freq; }
void setFreq(float value) override {
_parent->freq = value;
markDirty();
}
float getBandwidth() const override { return _parent->bw; }
void setBandwidth(float value) override {
_parent->bw = value;
markDirty();
}
uint8_t getSpreadFactor() const override { return _parent->sf; }
void setSpreadFactor(uint8_t value) override {
_parent->sf = value;
markDirty();
}
uint8_t getCodingRate() const override { return _parent->cr; }
void setCodingRate(uint8_t value) override {
_parent->cr = value;
markDirty();
}
float getAirtimeFactor() const override { return _parent->airtime_factor; }
void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); }
bool isCadEnabled() const override { return _parent->cad_enabled; }
void setCadEnabled(bool en) override { _parent->cad_enabled = en; markDirty(); }
uint8_t getIntThresh() const override { return _parent->interference_threshold; }
void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); }
uint8_t getRxGain() const override { return _parent->rx_boosted_gain; }
void setRxGain(uint8_t value) override {
_parent->rx_boosted_gain = value;
markDirty();
}
int8_t getTxPower() const override { return _parent->tx_power_dbm; }
void setTxPower(int8_t value) override {
_parent->tx_power_dbm = value;
markDirty();
}
float getRxDelay() const override { return _parent->rx_delay_base; }
void setRxDelay(float d) override { _parent->rx_delay_base = d; markDirty(); }
uint16_t getAgcResetInt() const override { return _parent->agc_reset_interval * 4; }
void setAgcResetInt(uint16_t secs) override { _parent->agc_reset_interval = secs / 4; markDirty(); }
uint8_t getHashMode() const override { return _parent->path_hash_mode; }
void setHashMode(uint8_t value) override {
_parent->path_hash_mode = value;
markDirty();
}
uint8_t getMultiAcks() const override { return _parent->multi_acks; }
void setMultiAcks(uint8_t m) override { _parent->multi_acks = m; markDirty(); }
float getFloodTxDelay() const override { return _parent->tx_delay_factor; }
void setFloodTxDelay(float d) override { _parent->tx_delay_factor = d; markDirty(); }
float getDirectTxDelay() const override { return _parent->direct_tx_delay_factor; }
void setDirectTxDelay(float d) override { _parent->direct_tx_delay_factor = d; markDirty(); }
uint8_t getFEMRxGain() const override { return _parent->radio_fem_rxgain; }
void setFEMRxGain(uint8_t value) override {
_parent->radio_fem_rxgain = value;
_parent->radio_fem_rxgain_override = 1;
markDirty();
}
uint8_t getFEMTxGain() const override { return _parent->radio_fem_txgain; }
void setFEMTxGain(uint8_t value) override {
_parent->radio_fem_txgain = value;
markDirty();
}
};
// Runtime-only adapters must remain after the persisted field block above.
RadioPrefs radio;
DynamicConfigSerializer custom;
public:
CompanionNodePrefs() : radio(this), custom(&radio) { }
// Only the scalar/array value prefix is a snapshot. Copying the complete
// object also copies adapter owner/fallback pointers and costs two large
// memberwise copies in the transactional loader. Keep both live adapters
// untouched, and leave a normally constructed snapshot's adapters local.
bool copyPersistedValuesFrom(const CompanionNodePrefs& source) {
if (&source == this) return true;
const auto& last_value = tx_ssh;
static_assert(sizeof(tx_ssh) == sizeof(uint8_t),
"The last persisted traffic policy is one byte");
const uintptr_t first = reinterpret_cast<uintptr_t>(&airtime_factor);
const uintptr_t last = reinterpret_cast<uintptr_t>(&last_value);
if (last < first
|| last - first > sizeof(*this) - sizeof(tx_ssh)) {
return false;
}
const size_t size = static_cast<size_t>(last - first)
+ sizeof(tx_ssh);
const auto bounded = [size](const CompanionNodePrefs& values) -> bool {
const uintptr_t object = reinterpret_cast<uintptr_t>(&values);
const uintptr_t begin =
reinterpret_cast<uintptr_t>(&values.airtime_factor);
const uintptr_t end = reinterpret_cast<uintptr_t>(
&values.tx_ssh
);
const uintptr_t radio_begin = reinterpret_cast<uintptr_t>(&values.radio);
const uintptr_t custom_begin = reinterpret_cast<uintptr_t>(&values.custom);
// Subtractions happen only after the corresponding ordering checks.
// Never include either runtime adapter, even if this layout is changed.
return begin >= object && begin - object <= sizeof(values)
&& size <= sizeof(values) - static_cast<size_t>(begin - object)
&& end >= begin
&& end - begin == size - sizeof(values.tx_ssh)
&& radio_begin >= begin && size <= radio_begin - begin
&& custom_begin >= begin && size <= custom_begin - begin;
};
if (!bounded(*this) || !bounded(source)) return false;
memcpy(&airtime_factor, &source.airtime_factor, size);
return true;
}
bool isRepeatEn() const { return client_repeat != 0; }
void setRepeatEn(bool enabled) { client_repeat = enabled ? 1 : 0; }
CommonRadioPrefs* getRadioPrefs() { return &radio; }
KeyValueStore* getCustom() { return &custom; }
bool isDirty() const {
return radio.isDirty() || custom.isDirty();
}
void clearDirty() {
radio.clearDirty();
custom.clearDirty();
}
};
inline bool migrateCompanionPowerSavingDefault(CompanionNodePrefs& prefs) {
if (prefs.powersaving_policy_version == COMPANION_POWERSAVING_POLICY_VERSION) {
return false;
}
prefs.powersaving_enabled = 1;
prefs.powersaving_policy_version = COMPANION_POWERSAVING_POLICY_VERSION;
return true;
}
inline void clearCompanionBluetoothStealthPeer(CompanionNodePrefs& prefs) {
if (mesh::companion::bluetoothStealthEnabled(prefs.bluetooth_stealth_mode)) {
prefs.bluetooth_stealth_mode = mesh::companion::BLUETOOTH_STEALTH_PAIRING;
}
prefs.bluetooth_stealth_peer_type =
mesh::companion::BLUETOOTH_PEER_ADDRESS_NONE;
memset(prefs.bluetooth_stealth_peer, 0, sizeof(prefs.bluetooth_stealth_peer));
}
// An on/off flag must not change the address policy or erase a valid bond
// when the same value is submitted repeatedly.
inline bool setCompanionBluetoothStealth(CompanionNodePrefs& prefs,
bool enabled) {
if (mesh::companion::bluetoothStealthEnabled(prefs.bluetooth_stealth_mode)
== enabled) {
return false;
}
prefs.bluetooth_stealth_mode = enabled
? mesh::companion::BLUETOOTH_STEALTH_PAIRING
: mesh::companion::BLUETOOTH_STEALTH_OFF;
clearCompanionBluetoothStealthPeer(prefs);
return true;
}