349 lines
14 KiB
C++
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;
|
|
}
|