Сохранено: страница POWER (батарея/ADC/reboot), настройка радио с разрядами частоты, темы и кириллица на дисплеях, меню уведомлений (тихие часы, режимы каналов), стартовый экран MESHINFO, свои варианты плат. Адаптировано под новый API 1.18.0: Listener (onMessageRecv/onChannelMessageRecv/ onQueueSizeChanged) вместо msgRead/newMsg, фильтрация уведомлений переехала в UITask, в ChannelDetails добавлен индекс канала. Отклонения от upstream: убран пин toolchain-gccarmnoneeabi@^1.140201.0 (такой версии нет в реестре PlatformIO, сборка nRF52 висела) и не добавлен NRF52_POWER_MANAGEMENT (upstream отключил его как ломающий не-Li-Ion батареи).
296 lines
13 KiB
C++
296 lines
13 KiB
C++
#pragma once
|
||
#include <cstdint> // For uint8_t, uint32_t
|
||
#include <helpers/ConfigSerializer.h>
|
||
#include <helpers/CommonRadioPrefs.h>
|
||
#include <helpers/DynamicConfigSerializer.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
|
||
|
||
// Режимы уведомлений по каналам (по 2 бита на канал)
|
||
#define CH_MODE_POPUP 0 // звук + экран переключается на сообщение
|
||
#define CH_MODE_SILENT 1 // в непрочитанные, но без звука и без всплытия
|
||
#define CH_MODE_OFF 2 // на устройстве не уведомляем вообще
|
||
|
||
// Что показывать рядом с иконкой батареи в статус-баре
|
||
#define BATT_DISPLAY_VOLT 0 // напряжение, например 4.05V
|
||
#define BATT_DISPLAY_PERC 1 // процент заряда
|
||
#define BATT_DISPLAY_OFF 2 // индикатор батареи скрыт
|
||
|
||
// Сколько каналов покрываем настройками. Совпадает с MAX_GROUP_CHANNELS у companion-сборок,
|
||
// но этот заголовок его не включает, поэтому задаём свой предел.
|
||
#ifndef NOTIFY_MAX_CHANNELS
|
||
#define NOTIFY_MAX_CHANNELS 40
|
||
#endif
|
||
#define CH_MODE_BYTES ((NOTIFY_MAX_CHANNELS * 2 + 7) / 8)
|
||
|
||
class NodePrefs : public ConfigSerializer { // persisted to file
|
||
public:
|
||
float airtime_factor = 0;
|
||
char node_name[32];
|
||
double node_lat = 0, node_lon = 0;
|
||
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;
|
||
float tx_delay_factor = 0;
|
||
float direct_tx_delay_factor = 0;
|
||
uint32_t ble_pin = 0;
|
||
uint8_t advert_loc_policy = 0;
|
||
uint8_t buzzer_quiet = 0;
|
||
uint8_t vibe_quiet = 0;
|
||
uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled)
|
||
uint32_t gps_interval = 0; // GPS read interval in seconds
|
||
uint8_t autoadd_config = 0; // bitmask for auto-add contacts config
|
||
uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted)
|
||
uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA)
|
||
uint8_t radio_fem_txgain = 0; // external LoRa FEM TX gain (low by default)
|
||
uint8_t _client_repeat = 0; // DEPRECATED -> use repeat.disable_fwd
|
||
uint8_t path_hash_mode = 0; // which path mode to use when sending
|
||
uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
||
uint8_t cad_enabled = 0;
|
||
uint8_t interference_threshold = 0;
|
||
uint8_t agc_reset_interval = 0; // secs / 4
|
||
char default_scope_name[31];
|
||
uint8_t default_scope_key[16];
|
||
int8_t tz_offset = 0;
|
||
char ui_theme[12]; // имя темы дисплея; пусто = тема по умолчанию для платы
|
||
|
||
// Уведомления о входящих (меню на первом экране)
|
||
uint8_t msg_popup = 1; // переключать экран на входящее сообщение
|
||
uint8_t msg_wake_display = 1; // зажигать дисплей при входящем сообщении
|
||
uint8_t quiet_en = 0; // часы тишины включены
|
||
uint8_t quiet_from = 23; // час начала (локальный)
|
||
uint8_t quiet_to = 7; // час окончания (локальный)
|
||
uint8_t ch_mode[CH_MODE_BYTES]; // режимы по каналам (CH_MODE_*), по 2 бита
|
||
uint8_t batt_display = BATT_DISPLAY_PERC; // что показывать рядом с батареей (BATT_DISPLAY_*)
|
||
float adc_multiplier = 0; // коррекция АЦП батареи (0 = как считает плата)
|
||
#ifdef ENABLE_WIFI_INTERFACE
|
||
#ifndef WIFI_SSID
|
||
#define WIFI_SSID ""
|
||
#endif
|
||
char wifi_ssid[33] = {0}; // if empty, the compile-time WIFI_SSID is used
|
||
char wifi_pwd[64] = {0};
|
||
uint8_t wifi_enabled = 1; // enabled by default to allow wifi only builds to work. wifi won't be started if ssid is empty
|
||
// use ssid from prefs, or fallback to ssid from build flags
|
||
const char* getWifiSSID() const { return wifi_ssid[0] ? wifi_ssid : WIFI_SSID; }
|
||
#endif
|
||
|
||
private:
|
||
class RadioPrefs : public CommonRadioPrefs {
|
||
NodePrefs* _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); // fem_rxgain WAS mapped to wrong JSON property previously
|
||
def("fem_txgain", _parent->radio_fem_txgain);
|
||
def("tx", _parent->tx_power_dbm);
|
||
def("af", _parent->airtime_factor);
|
||
def("adc_mult", _parent->adc_multiplier); // коррекция АЦП батареи (как в CommonCLI)
|
||
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);
|
||
}
|
||
public:
|
||
RadioPrefs(NodePrefs* parent) : _parent(parent) { }
|
||
|
||
// CommonRadioPrefs interface
|
||
float getFreq() const override { return _parent->freq; }
|
||
void setFreq(float f) override { _parent->freq = f; markDirty(); }
|
||
float getBandwidth() const override { return _parent->bw; }
|
||
void setBandwidth(float bw) override { _parent->bw = bw; markDirty(); }
|
||
uint8_t getSpreadFactor() const override { return _parent->sf; }
|
||
void setSpreadFactor(uint8_t sf) override { _parent->sf = sf; markDirty(); }
|
||
uint8_t getCodingRate() const override { return _parent->cr; }
|
||
void setCodingRate(uint8_t cr) override { _parent->cr = cr; 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 g) override { _parent->rx_boosted_gain = g; markDirty(); }
|
||
uint8_t getTxPower() const override { return _parent->tx_power_dbm; }
|
||
void setTxPower(uint8_t dbm) override { _parent->tx_power_dbm = dbm; 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 m) override { _parent->path_hash_mode = m; 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 g) override { _parent->radio_fem_rxgain = g; markDirty(); }
|
||
uint8_t getFEMTxGain() const override { return _parent->radio_fem_txgain; }
|
||
void setFEMTxGain(uint8_t g) override { _parent->radio_fem_txgain = g; markDirty(); }
|
||
};
|
||
RadioPrefs radio;
|
||
|
||
class GPSPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now)
|
||
NodePrefs* _parent;
|
||
protected:
|
||
void structure() override {
|
||
def("en", _parent->gps_enabled); // boolean
|
||
def("int", _parent->gps_interval); // interval in seconds
|
||
def("adv_loc", _parent->advert_loc_policy);
|
||
}
|
||
public:
|
||
GPSPrefs(NodePrefs* parent) : _parent(parent) { }
|
||
};
|
||
GPSPrefs gps;
|
||
|
||
class RepeatPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now)
|
||
public:
|
||
uint8_t disable_fwd = 1;
|
||
protected:
|
||
void structure() override {
|
||
def("disable", disable_fwd);
|
||
//def("f_max", flood_max);
|
||
//def("f_max_uns", flood_max_unscoped);
|
||
//def("f_max_adv", flood_max_advert);
|
||
//def("loop", loop_detect);
|
||
}
|
||
};
|
||
RepeatPrefs repeat;
|
||
|
||
class CompanionPrefs : public ConfigSerializer {
|
||
NodePrefs* _parent;
|
||
protected:
|
||
void structure() override {
|
||
def("auto_max", _parent->autoadd_max_hops); // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
||
def("defs_nm", _parent->default_scope_name, sizeof(_parent->default_scope_name));
|
||
def("defs_key", (void *) _parent->default_scope_key, sizeof(_parent->default_scope_key));
|
||
def("pin", _parent->ble_pin);
|
||
def("buzz_q", _parent->buzzer_quiet);
|
||
def("vibe_q", _parent->vibe_quiet);
|
||
def("auto_add", _parent->autoadd_config); // bitmask for auto-add contacts config
|
||
def("man_add", _parent->manual_add_contacts);
|
||
def("theme", _parent->ui_theme, sizeof(_parent->ui_theme)); // имя темы дисплея
|
||
// уведомления
|
||
def("msg_popup", _parent->msg_popup);
|
||
def("msg_wake", _parent->msg_wake_display);
|
||
def("quiet_en", _parent->quiet_en);
|
||
def("quiet_from", _parent->quiet_from);
|
||
def("quiet_to", _parent->quiet_to);
|
||
def("ch_mode", (void *)_parent->ch_mode, sizeof(_parent->ch_mode)); // hex-блоб
|
||
def("batt_disp", _parent->batt_display); // что показывать у батареи
|
||
def("tel_base", _parent->telemetry_mode_base);
|
||
def("tel_loc", _parent->telemetry_mode_loc);
|
||
def("tel_env", _parent->telemetry_mode_env);
|
||
def("tz_offset", _parent->tz_offset);
|
||
}
|
||
public:
|
||
CompanionPrefs(NodePrefs* parent) : _parent(parent) { }
|
||
};
|
||
CompanionPrefs companion;
|
||
|
||
DynamicConfigSerializer custom;
|
||
|
||
#ifdef ENABLE_WIFI_INTERFACE
|
||
class WiFiPrefs : public ConfigSerializer {
|
||
NodePrefs* _parent;
|
||
protected:
|
||
void structure() override {
|
||
def("ssid", _parent->wifi_ssid, sizeof(_parent->wifi_ssid));
|
||
def("pwd", _parent->wifi_pwd, sizeof(_parent->wifi_pwd));
|
||
def("enabled", _parent->wifi_enabled);
|
||
}
|
||
public:
|
||
WiFiPrefs(NodePrefs* parent) : _parent(parent) { }
|
||
};
|
||
WiFiPrefs wifi;
|
||
#endif
|
||
|
||
protected:
|
||
void structure() override {
|
||
def("name", node_name, sizeof(node_name));
|
||
//def("adv_int", advert_interval);
|
||
//def("f_adv_int", flood_advert_interval);
|
||
def("lat", node_lat);
|
||
def("lon", node_lon);
|
||
def("radio", radio);
|
||
def("gps", gps);
|
||
def("repeat", repeat);
|
||
def("comp", companion);
|
||
def("custom", custom);
|
||
#ifdef ENABLE_WIFI_INTERFACE
|
||
def("wifi", wifi);
|
||
#endif
|
||
}
|
||
public:
|
||
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio)
|
||
#ifdef ENABLE_WIFI_INTERFACE
|
||
, wifi(this)
|
||
#endif
|
||
{
|
||
node_name[0] = 0;
|
||
default_scope_name[0] = 0;
|
||
ui_theme[0] = 0;
|
||
memset(ch_mode, 0, sizeof(ch_mode)); // все каналы — CH_MODE_POPUP
|
||
memset(default_scope_key, 0, sizeof(default_scope_key));
|
||
}
|
||
// new accessor methods
|
||
bool isRepeatEn() const { return repeat.disable_fwd == 0; }
|
||
void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; }
|
||
|
||
// Настройки уведомлений по каналам
|
||
uint8_t getChannelMode(uint8_t idx) const {
|
||
if (idx >= NOTIFY_MAX_CHANNELS) return CH_MODE_POPUP;
|
||
uint8_t sh = (uint8_t)((idx & 3) * 2);
|
||
return (uint8_t)((ch_mode[idx >> 2] >> sh) & 0x03);
|
||
}
|
||
void setChannelMode(uint8_t idx, uint8_t mode) {
|
||
if (idx >= NOTIFY_MAX_CHANNELS) return;
|
||
uint8_t sh = (uint8_t)((idx & 3) * 2);
|
||
ch_mode[idx >> 2] = (uint8_t)((ch_mode[idx >> 2] & ~(0x03 << sh)) | ((mode & 0x03) << sh));
|
||
}
|
||
|
||
// Локальный час суток по unix-времени RTC и смещению часового пояса
|
||
uint8_t localHour(uint32_t utc) const {
|
||
int32_t h = (int32_t)((utc / 3600) % 24) + tz_offset;
|
||
h %= 24;
|
||
if (h < 0) h += 24;
|
||
return (uint8_t)h;
|
||
}
|
||
// Тихие часы: окно может пересекать полночь (например, 23..7)
|
||
bool isQuietNow(uint32_t utc) const {
|
||
if (!quiet_en) return false;
|
||
if (quiet_from == quiet_to) return false;
|
||
uint8_t h = localHour(utc);
|
||
if (quiet_from < quiet_to) return h >= quiet_from && h < quiet_to;
|
||
return h >= quiet_from || h < quiet_to;
|
||
}
|
||
|
||
CommonRadioPrefs* getRadioPrefs() { return &radio; }
|
||
KeyValueStore* getCustom() { return &custom; }
|
||
|
||
bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || custom.isDirty(); }
|
||
void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); custom.clearDirty(); }
|
||
};
|