Files
MeshCore/examples/companion_radio/MyMesh.h
T

831 lines
32 KiB
C++

#pragma once
#include <Arduino.h>
#include <Mesh.h>
#include "AbstractUITask.h"
#include "CompanionFeatures.h"
/*------------ Frame Protocol --------------*/
#define FIRMWARE_VER_CODE 14
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.17.1"
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#include <InternalFileSystem.h>
#elif defined(RP2040_PLATFORM)
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
#endif
#include "DataStore.h"
#include <helpers/RadioProfileCLI.h>
#include <helpers/CompanionNotificationPolicy.h>
#include "NodePrefs.h"
#if defined(ESP32_PLATFORM) && defined(WIFI_SSID) && !defined(WEBCONFIG_DISABLED)
#include <helpers/esp32/WebConfigServer.h>
#endif
#if defined(WITH_MQTT_BRIDGE) && defined(ESP32_PLATFORM) && defined(WIFI_SSID)
#include <helpers/CompanionMqttSetupPortal.h>
#include <helpers/MQTTPrefs.h>
#include <helpers/bridges/MQTTBridge.h>
#endif
#include <RTClib.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/BaseSerialInterface.h>
#include <helpers/CompanionMotaControl.h>
#include <helpers/IdentityStore.h>
#include <helpers/LogicalMessageCache.h>
#include <helpers/SimpleMeshTables.h>
#include <helpers/StaticPoolPacketManager.h>
#include <helpers/TerminalCommandTracker.h>
#include <helpers/TerminalDisplayFilter.h>
#include <target.h>
#if defined(OTA_SHARED_COMPANION_QUEUE)
#include <helpers/BorrowableFrameBuffer.h>
#include <helpers/ota/OtaContext.h>
#endif
#ifdef COMPANION_MESH_CLOCK_SYNC
#include <helpers/ClientACL.h>
#include <helpers/MeshClockSync.h>
#endif
/* ---------------------------------- CONFIGURATION ------------------------------------- */
#ifndef LORA_FREQ
#define LORA_FREQ 915.0
#endif
#ifndef LORA_BW
#define LORA_BW 250
#endif
#ifndef LORA_SF
#define LORA_SF 10
#endif
#ifndef LORA_CR
#define LORA_CR 5
#endif
#ifndef LORA_TX_POWER
#define LORA_TX_POWER 20
#endif
#ifndef MAX_LORA_TX_POWER
#define MAX_LORA_TX_POWER LORA_TX_POWER
#endif
#ifndef MAX_CONTACTS
#define MAX_CONTACTS 100
#endif
// STM32WL Companion images have only 224 KiB of application flash. Keep their
// existing filesystem boundary; larger targets retain the one-key DM feature.
#ifndef MESH_ENABLE_ONE_KEY_DM
#if defined(STM32_PLATFORM)
#define MESH_ENABLE_ONE_KEY_DM 0
#else
#define MESH_ENABLE_ONE_KEY_DM 1
#endif
#endif
#if defined(NRF52_PLATFORM) && MAX_CONTACTS > 300 \
&& !defined(OTA_SHARED_COMPANION_QUEUE) && MESH_CONTACT_CACHE
#define ONE_KEY_DM_SHARED_OFFLINE_QUEUE 1
#else
#define ONE_KEY_DM_SHARED_OFFLINE_QUEUE 0
#endif
#ifndef OFFLINE_QUEUE_SIZE
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
#define OFFLINE_QUEUE_SIZE 512
#elif defined(NRF52_PLATFORM) && MAX_CONTACTS > 300 \
&& !defined(OTA_SHARED_COMPANION_QUEUE) && !ONE_KEY_DM_SHARED_OFFLINE_QUEUE
#define OFFLINE_QUEUE_SIZE 208
#elif defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM) \
|| defined(RP2040_PLATFORM)
#define OFFLINE_QUEUE_SIZE 256
#else
#define OFFLINE_QUEUE_SIZE 16
#endif
#endif
static_assert(OFFLINE_QUEUE_SIZE > 0, "OFFLINE_QUEUE_SIZE must be positive");
#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE
static_assert(OFFLINE_QUEUE_SIZE >= 15, "Shared DM queue needs 15 frame slots");
#endif
#ifndef ROOM_MESSAGE_TIMESTAMP_CACHE_SIZE
#define ROOM_MESSAGE_TIMESTAMP_CACHE_SIZE 16
#endif
#ifndef BLE_NAME_PREFIX
#define BLE_NAME_PREFIX "MeshCore-"
#endif
#include <helpers/BaseChatMesh.h>
#include <helpers/TransportKeyStore.h>
#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE
static_assert(MAX_TEXT_LEN + 12 <= MAX_FRAME_SIZE,
"A held plain DM must fit in one offline frame");
#endif
/* -------------------------------------------------------------------------------------- */
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
struct AdvertPath {
uint8_t pubkey_prefix[7];
uint8_t path_len;
char name[32];
uint32_t recv_timestamp;
uint8_t path[MAX_PATH_SIZE];
};
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
struct DiscoveredNode {
uint8_t pubkey_prefix[9];
float snr_in;
float snr_out;
char name[32];
uint8_t type;
};
#endif
class MyMesh : public BaseChatMesh, public DataStoreHost, public UIShutdownGuard
#if COMPANION_FEATURE_NOTIFICATIONS
, public mesh::notify::Sink, public mesh::notify::Store
#endif
#if COMPANION_FEATURE_TEXT_TERMINAL
, public ContactVisitor
#endif
#ifdef WITH_WEBCONFIG
, public WebConfigServer::Callbacks
#endif
{
public:
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL);
bool handleLocalCommand(const char* command, char* reply) {
return handleCommand(command, 0, reply);
}
void begin(bool has_display, bool radio_available = true);
void activateRadio();
bool isRadioReady() const { return _radio_available; }
void startInterface(BaseSerialInterface &serial);
void cancelSerialResponseStream();
void cancelSerialOperationsForRoute(BaseSerialInterface* route);
bool hasFiniteDelayedReplyForRoute(BaseSerialInterface* route) const;
void resetUsbHostSessionInput();
const char *getNodeName();
CompanionNodePrefs *getNodePrefs();
uint32_t getBLEPin();
#if defined(BLE_PIN_CODE)
bool prepareBluetoothMacForBoot(bool& address_rotated);
bool armBluetoothMacRotationAfterConnection();
bool saveBluetoothStealthPeer(
const mesh::companion::BluetoothPeerIdentity& peer);
bool resetBluetoothStealthPairing();
#endif
int getOfflineQueueCapacity() const;
void noteInternetClockSet() {
#ifdef COMPANION_MESH_CLOCK_SYNC
_clock_sync.onInternetClockSet();
#endif
}
void setMotaSourceControl(mesh::companion::MotaSourceControl* control) {
_mota_source_control = control;
}
#if defined(WITH_MQTT_BRIDGE) && defined(ESP32_PLATFORM) && defined(WIFI_SSID)
void serviceMQTT(const char* wifi_ssid, const char* wifi_password);
void stopMQTT();
bool isMQTTConfigured() const { return _mqtt_configured; }
bool isMQTTRunning() const {
return _mqtt_started && _mqtt_bridge != nullptr
&& _mqtt_bridge->isRunning();
}
bool hasFreshMQTTNtpThisBoot() const {
return _mqtt_bridge != nullptr
&& _mqtt_bridge->hasFreshNtpThisBoot();
}
#endif
#ifdef WITH_WEBCONFIG
bool startWebConfig(bool force_ap, char* reply);
void stopWebConfig();
void serviceWebConfig();
bool isWebConfigActiveOrStopping() const;
bool isWebConfigSetupActive() const;
bool isWebConfigWiFiRecoveryActive() const;
void getNodeSnapshot(WebConfigServer::NodeSnapshot& snapshot) override;
void execCommand(char* cmd, char* reply) override;
bool supportsCliTerminal() const override { return true; }
void execAdminCommand(char* cmd, char* reply) override;
#if COMPANION_FEATURE_TEXT_TERMINAL
bool supportsStreamTerminal() const override { return true; }
bool beginStreamTerminal(Stream& output) override;
bool ownsStreamTerminal(const Stream& output) const override {
return isNetworkTerminalMode(output);
}
void runStreamTerminal(char* command) override { handleTerminalCommand(command); }
void endStreamTerminal(Stream& output) override;
#endif
void rebootNow() override;
void onConfigBatchStart() override;
void onConfigBatchEnd() override;
void buildStatsJson(char* buf, size_t buf_size) override;
#endif
void loop();
#if COMPANION_FEATURE_NOTIFICATIONS
uint8_t capabilities() const override;
bool gpioAvailable(uint8_t pin) const override;
void pulse(mesh::notify::Output output, bool on, int8_t pin) override;
void melody(const char* text) override;
void screen(int8_t mode) override;
bool save(const mesh::notify::Settings& settings) override;
bool notificationButton() { return _notifications.button(); }
void setNotificationOutputMute(mesh::notify::Output output, bool muted);
#else
bool notificationButton() { return false; }
void setNotificationOutputMute(mesh::notify::Output, bool) {}
#endif
bool handleNotificationCommand(const char* command, char* reply, size_t size);
void handleCmdFrame(size_t len);
bool advert();
void enterCLIRescue();
// Physical controls must flush deferred contact writes before entering a
// board's irreversible power-off state.
bool prepareForUserShutdown() { return prepareForUiShutdown(); }
#if COMPANION_FEATURE_TEXT_TERMINAL
void enterTerminalMode(bool show_banner = true);
void exitTerminalMode();
// Clear state owned by the current text-terminal host without changing the
// protocol owner or printing a new banner.
void resetTerminalSession();
bool isTerminalMode() const { return _terminal_mode; }
bool isTerminalWaitingForInput() const {
return _terminal_mode && _terminal_usb_silent;
}
void handleTerminalCommand(char* command);
#if COMPANION_FEATURE_NETWORK_TERMINAL || defined(WITH_WEBCONFIG)
bool enterNetworkTerminalMode(Stream& output);
void exitNetworkTerminalMode(Stream& output);
bool isNetworkTerminalMode(const Stream& output) const;
bool isAnyNetworkTerminalMode() const {
return !_terminal_mode && _terminal_output != NULL;
}
#endif
#endif
// Local control shared by every USB terminal. ESP32 WiFi companions expose
// setup/status commands here; Full builds add TempRadio and OTA commands.
bool handleLocalControlCommand(const char* command, char* reply,
size_t reply_size);
bool handleTxRoutingCommand(const char* command, char* reply, size_t reply_size);
int getRecentlyHeard(AdvertPath dest[], int max_num);
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
bool requestRepeatersDiscovery();
int getDiscoveredNodes(DiscoveredNode nodes[], int max_num);
#endif
protected:
#if COMPANION_FEATURE_TEMP_RADIO
bool isTempRadioActive() const override {
return _temp_radio_applied && _temp_radio_revert_at != 0
&& !millisHasNowPassed(_temp_radio_revert_at);
}
#endif
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
uint32_t getCADFailRetryDelay() const override;
uint32_t getCADFailMaxDuration() const override;
#ifdef WITH_MQTT_BRIDGE
uint32_t getRadioWatchdogMillis() const override { return 0; }
#endif
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
uint8_t getDefaultTxCodingRate() const override { return _prefs.cr; }
uint8_t getExtraAckTransmitCount() const override;
bool filterRecvFloodPacket(mesh::Packet* packet) override;
bool allowPacketForward(const mesh::Packet* packet) override;
bool allowFloodRetry(const mesh::Packet* packet) const override;
uint8_t getFloodRetryMaxPathLength(const mesh::Packet* packet) const override;
uint8_t getFloodRetryMaxAttempts(const mesh::Packet* packet) const override;
#ifdef COMPANION_MESH_CLOCK_SYNC
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
uint32_t timestamp, const uint8_t* app_data,
size_t app_data_len) override;
void onGroupPacketRecv(mesh::Packet* packet) override;
#endif
bool sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis);
bool sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override;
bool sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override;
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
#if defined(WITH_MQTT_BRIDGE) && defined(ESP32_PLATFORM) && defined(WIFI_SSID)
void logRx(mesh::Packet* packet, int len, float score) override;
void logTx(mesh::Packet* packet, int len) override;
#endif
bool isAutoAddEnabled() const override;
bool shouldAutoAddContactType(uint8_t type) const override;
bool shouldOverwriteWhenFull() const override;
uint8_t getAutoAddMaxHops() const override;
bool canMutateContacts() const override;
void onContactsFull() override;
bool onContactOverwrite(const ContactInfo& contact) override;
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
void onContactPathUpdated(const ContactInfo &contact) override;
#if COMPANION_FEATURE_TEXT_TERMINAL
void onContactVisit(const ContactInfo& contact) override;
#endif
ContactInfo* processAck(const uint8_t *data) override;
#if MESH_ENABLE_ONE_KEY_DM
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret,
const mesh::Identity& sender, uint8_t* data,
size_t len) override;
bool onAddressedTextPacket(mesh::Packet* packet, uint8_t src_hash,
const uint8_t* mac_and_data, size_t len) override;
#endif
void queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt, uint32_t sender_timestamp,
const uint8_t *extra, int extra_len, const char *text,
bool terminal_command_reply=false,
uint32_t terminal_command_elapsed_millis=0);
void onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
const char *text) override;
void onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
const char *text) override;
void onCLICommandRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
const char *text, char* reply) override;
void onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
const uint8_t *sender_prefix, const char *text) override;
void onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp,
const char *text) override;
void onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint16_t data_type,
const uint8_t *data, size_t data_len) override;
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
uint8_t len, uint8_t *reply) override;
void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override;
void onControlDataRecv(mesh::Packet *packet) override;
void onRawDataRecv(mesh::Packet *packet) override;
void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override;
uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override;
uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override;
void onSendTimeout() override;
// DataStoreHost methods
bool onContactLoaded(const ContactInfo& contact) override { return addContact(contact); }
bool getContactForSave(uint32_t idx, ContactInfo& contact) override { return getContactByIdx(idx, contact); }
ContactInfo* getContactForStore(uint32_t idx) override { return getContactPtrByIdx(idx); }
void onContactCacheFlushed() override;
bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); }
bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); }
void clearPendingReqs();
bool hasPendingReqs() const;
public:
bool savePrefs() {
const bool saved =
_store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon);
if (saved) _prefs.clearDirty();
return saved;
}
#if COMPANION_FEATURE_READER
bool loadReaderBookmark(mesh::bible::Position& pos) { return _store->loadReaderBookmark(pos); }
bool saveReaderBookmark(mesh::bible::Position pos) { return _store->saveReaderBookmark(pos); }
#endif
#if ENV_INCLUDE_GPS == 1
void applyGpsPrefs() {
sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0");
char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null)
sprintf(interval_str, "%u", _prefs.gps_interval);
sensors.setSettingValue("gps_interval", interval_str);
}
#endif
// To check if there is pending work
bool hasPendingWork() const;
private:
// Only snapshot the changed value: CompanionNodePrefs owns self-referencing
// runtime adapters and must not be copied as a transaction snapshot.
template <typename T> bool savePreference(T& preference, T value) {
const T previous = preference;
preference = value;
if (savePrefs()) return true;
preference = previous;
return false;
}
bool saveAdvertName(const char* name);
bool saveAdvertLocation(double latitude, double longitude);
void writeOKFrame(BaseSerialInterface* route = nullptr);
void writeErrFrame(uint8_t err_code,
BaseSerialInterface* route = nullptr);
size_t writePendingSerialFrame(const uint8_t frame[], size_t len);
void writeDisabledFrame();
bool writeContactRespFrame(uint8_t code, const ContactInfo &contact);
void stopContactsIterator();
bool updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len);
bool addToOfflineQueue(const uint8_t frame[], int len);
int getFromOfflineQueue(uint8_t frame[]);
int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) override {
return _store->getBlobByKey(key, key_len, dest_buf);
}
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) override {
return _store->putBlobByKey(key, key_len, src_buf, len);
}
void checkCLIRescueCmd();
bool handleCommand(const char* text, uint32_t sender_timestamp, char* reply);
bool handleDirectCommand(const char* command, char* reply, size_t reply_size);
void checkSerialInterface();
bool applyAndSaveFemRxGain(bool enabled);
bool applyAndSaveFemTxGain(bool enabled);
bool applyAndSaveRxBoostedGain(bool enabled);
enum class RadioSettingResult { Saved, RadioRejected, SaveFailed };
RadioSettingResult applyAndSaveTxPower(int8_t power);
bool handleCadCommand(const char* command, char* reply, size_t reply_size);
bool saveBluetoothNameOverride(const char* name);
bool applyAndSaveBluetoothName(const char* value, char* reply,
size_t reply_size);
void formatBluetoothNameStatus(char* reply, size_t reply_size) const;
#if defined(BLE_PIN_CODE)
bool saveBluetoothMac(uint8_t mode, const uint8_t* address);
bool applyAndSaveBluetoothMac(const char* value, char* reply,
size_t reply_size);
void formatBluetoothMacStatus(char* reply, size_t reply_size) const;
bool applyAndSaveBluetoothStealth(const char* value, char* reply,
size_t reply_size);
void formatBluetoothStealthStatus(char* reply, size_t reply_size) const;
#endif
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
bool applyAndSaveEspNowChannel(const char* value, char* reply,
size_t reply_size);
void formatEspNowChannel(char* reply, size_t reply_size) const;
#endif
bool applyAndSavePowerSaving(const char* value, char* reply);
bool applyAndSaveRxPowerSaving(const char* value, char* reply);
void appendRxPowerSavingAdjustmentNote(char* reply, size_t reply_size,
uint8_t sf, float bw) const;
#if defined(ESP32) && defined(WIFI_SSID)
bool applyAndSaveWiFiPowerSaving(const char* value, char* reply,
size_t reply_size);
void formatWiFiPowerSaving(char* reply, size_t reply_size) const;
void syncWiFiPowerSaving();
#endif
#if COMPANION_FEATURE_TEXT_TERMINAL
Stream& terminalOutput();
bool hasTerminalOutput() const { return _terminal_output != NULL; }
void printTerminalBanner(bool show_binary_stop);
ContactInfo* getTerminalRecipient();
void printTerminalPath(const ContactInfo& recipient);
void handleTerminalPath(ContactInfo& recipient, const char* path_spec);
void importTerminalCard(char* command);
void listTerminalChannels();
void sendTerminalChannelMessage(ChannelDetails& channel, const char* text);
void handleTerminalDisplayCommand(const char* arguments);
void printTerminalSendStatus(const char* operation,
const ContactInfo& recipient, int result,
uint32_t timeout_millis);
void clearTerminalLogin();
void serviceTerminalLogin();
void sendTerminalLogin(ContactInfo& recipient, const char* password);
void clearTerminalCommand();
void serviceTerminalCommand();
void sendTerminalCommand(ContactInfo& recipient, const char* command);
void clearTerminalTrace();
void serviceTerminalTrace();
void sendTerminalTraceRoute(const uint8_t* route, uint8_t hash_size,
uint8_t hop_count, const char* target);
void sendTerminalTrace(ContactInfo& recipient);
void sendTerminalRawTrace(const char* arguments);
#endif
bool isValidClientRepeatFreq(uint32_t f) const;
bool hasLocationTelemetryRecipient();
void updateGpsTelemetryPolicy();
mesh::RadioParamApplyResult tryApplyRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, bool temporary = false, uint16_t preamble = 0);
bool applySavedRadioParams();
void configureRadioFromPrefs();
void finishRadioParamApply(float freq, float bw, uint8_t sf, uint8_t cr,
uint8_t repeat,
BaseSerialInterface* route = nullptr);
void cancelPendingRadioParamApply();
void servicePendingRadioParamApply();
void servicePendingSerialReply();
void clearBinaryTraceReply();
void serviceBinaryTraceReply();
void cancelSigningSession();
void serviceSigningSession();
#if COMPANION_FEATURE_TEMP_RADIO
bool scheduleTempRadio(float freq, float bw, uint8_t sf, uint8_t cr,
uint32_t timeout_mins, char* reply, size_t reply_size);
void scheduleNormalRadio(char* reply, size_t reply_size);
void serviceTempRadio();
#endif
// helpers, short-cuts
bool saveChannels() { return _store->saveChannels(this); }
void saveContacts();
void scheduleContactWriteRetry();
bool isContactWriteDue() const;
bool flushContactsBeforeReboot();
bool prepareForOtaReboot() override;
bool prepareForUiShutdown() override;
bool scheduleContactWrite(const ContactInfo& contact);
bool scheduleContactWriteAfterRelease(const ContactInfo& contact,
uint16_t& released_slot);
bool restoreContactWriteAfterRelease(const ContactInfo& contact,
uint16_t released_slot);
#if defined(NRF52_PLATFORM) && defined(EXTRAFS) && !defined(QSPIFLASH)
void repairInternalExtraFS(Stream& output);
void scanInternalExtraFS(Stream& output);
#if defined(MESHCORE_EXTRAFS_HIL)
bool handleExtraFsHilCommand(const char* command, char* reply,
size_t reply_size);
#endif
#endif
DataStore* _store;
mesh::RadioProfileCLI _radio_profiles;
uint16_t _temp_radio_preamble = 0;
CompanionNodePrefs _prefs;
#if COMPANION_FEATURE_NOTIFICATIONS
mesh::notify::Controller _notifications{*this, *this};
bool _notification_button_down = false;
#endif
#ifdef COMPANION_MESH_CLOCK_SYNC
ArduinoMillis _clock_sync_millis;
ClientACL _clock_sync_acl;
mesh::MeshClockSync _clock_sync;
#endif
#if defined(WITH_MQTT_BRIDGE) && defined(ESP32_PLATFORM) && defined(WIFI_SSID)
MQTTPrefs _mqtt_prefs;
MQTTBridge* _mqtt_bridge;
bool _mqtt_configured;
bool _mqtt_started;
bool _mqtt_enabled = true;
#endif
#ifdef WITH_WEBCONFIG
WebConfigServer* _webconfig;
bool _wc_mqtt_dirty;
bool _wc_batch_active = false;
#endif
uint32_t pending_login;
uint32_t pending_status;
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
uint32_t pending_req; // pending _BINARY_REQ
BaseSerialInterface* pending_serial_reply_route;
unsigned long pending_serial_reply_deadline;
BaseSerialInterface* private_key_backup_route = nullptr;
unsigned long private_key_backup_deadline = 0;
char private_key_backup_nonce[17] = {};
uint8_t private_key_backup_sender[6] = {};
BaseSerialInterface *_serial;
mesh::companion::MotaSourceControl* _mota_source_control;
AbstractUITask* _ui;
ContactsIterator _iter;
ContactInfo _iter_pending_contact;
uint32_t _iter_filter_since;
uint32_t _iter_next_frame_at;
uint32_t _iter_total_count;
uint32_t _iter_table_revision;
uint32_t _most_recent_lastmod;
uint32_t _active_ble_pin;
bool _iter_started;
bool _iter_start_pending;
bool _iter_contact_pending;
bool _cli_rescue;
#if COMPANION_FEATURE_TEXT_TERMINAL
bool _terminal_mode;
bool _terminal_usb_silent = false;
Stream* _terminal_output;
mesh::TerminalDisplayFilter _terminal_display;
bool _terminal_recipient_set;
uint8_t _terminal_recipient_key[PUB_KEY_SIZE];
uint8_t _terminal_tmp_buf[MAX_TRANS_UNIT];
bool _terminal_login_pending;
uint8_t _terminal_login_key[4];
unsigned long _terminal_login_expires_at;
char _terminal_login_target[32];
mesh::TerminalCommandTracker<PUB_KEY_SIZE> _terminal_command;
char _terminal_command_target[32];
bool _terminal_trace_pending;
uint8_t _terminal_trace_hash_size;
uint32_t _terminal_trace_tag;
uint32_t _terminal_trace_auth;
unsigned long _terminal_trace_sent_at;
unsigned long _terminal_trace_expires_at;
char _terminal_trace_target[32];
#endif
bool saved_radio_apply_pending;
bool _radio_available;
unsigned long radio_apply_retry_at;
uint8_t radio_apply_failures;
bool command_radio_apply_pending;
float command_radio_freq;
float command_radio_bw;
uint8_t command_radio_sf;
uint8_t command_radio_cr;
uint8_t command_radio_repeat;
unsigned long command_radio_apply_deadline;
BaseSerialInterface* command_radio_reply_route;
bool binary_trace_pending;
uint32_t binary_trace_tag;
uint32_t binary_trace_auth;
unsigned long binary_trace_deadline;
BaseSerialInterface* binary_trace_reply_route;
// Deferred so USB/TCP terminals can transmit the acknowledgement before
// the transport disappears. Also used by USB interface changes.
unsigned long _scheduled_reboot_at;
#if COMPANION_FEATURE_TEMP_RADIO
unsigned long _temp_radio_set_at;
unsigned long _temp_radio_revert_at;
unsigned long _temp_radio_retry_at;
float _temp_radio_freq;
float _temp_radio_bw;
uint8_t _temp_radio_sf;
uint8_t _temp_radio_cr;
uint8_t _temp_radio_failures;
bool _temp_radio_applied;
#endif
bool send_unscoped; // force un-scoped flood (instead of using send_scope)
char cli_command[80];
char reply_buf[166];
uint8_t app_target_ver;
uint8_t *sign_data;
uint32_t sign_data_len;
BaseSerialInterface* sign_data_reply_route;
unsigned long sign_data_deadline;
unsigned long dirty_contacts_expiry;
uint8_t dirty_contacts_failures;
TransportKey send_scope;
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
uint8_t out_frame[MAX_FRAME_SIZE + 1];
CayenneLPP telemetry;
struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
bool isChannelMsg() const;
};
Frame& offlineQueueFrameAt(int logical_index);
#if ONE_KEY_DM_SHARED_OFFLINE_QUEUE
Frame& heldDMFrameAt(uint8_t index);
void removeHeldOneKeyDM(uint8_t index);
#endif
void initializeOfflineQueue();
int offline_queue_len;
int offline_queue_head;
#if defined(OTA_SHARED_COMPANION_QUEUE)
static_assert(OFFLINE_QUEUE_SIZE == 256, "Shared mOTA queue requires 256 normal slots");
mesh::BorrowableFrameBuffer<Frame, OFFLINE_QUEUE_SIZE, 128, mesh::ota::OtaContext> offline_queue;
static mesh::ota::OtaContext* acquireOfflineQueueForOta(void* owner);
static uint16_t drainOfflineQueueForOta(void* owner);
static void releaseOfflineQueueFromOta(void* owner);
#elif defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
enum {
OFFLINE_QUEUE_PSRAM_FALLBACK_SIZE =
OFFLINE_QUEUE_SIZE < 16 ? OFFLINE_QUEUE_SIZE : 16
};
Frame* offline_queue;
Frame offline_queue_fallback[OFFLINE_QUEUE_PSRAM_FALLBACK_SIZE];
int offline_queue_capacity;
#else
Frame offline_queue[OFFLINE_QUEUE_SIZE];
#endif
struct AckTableEntry {
unsigned long msg_sent;
unsigned long expires_at;
uint32_t ack;
uint32_t message_timestamp;
ContactInfo* contact;
uint8_t text_fingerprint[MAX_HASH_SIZE];
uint8_t retry_key[MAX_HASH_SIZE];
BaseSerialInterface* reply_route;
#if COMPANION_FEATURE_TEXT_TERMINAL
bool terminal_origin;
#endif
};
#define EXPECTED_ACK_TABLE_SIZE 8
AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table
#if MESH_ENABLE_ONE_KEY_DM
// A recent ACK proves that peer can decrypt our normal text packets. Retry
// attempts still send the introduction in case its contact was later erased.
static constexpr uint8_t ONE_KEY_PEERS = 8;
struct OneKeyPeerState {
uint8_t pub_key[PUB_KEY_SIZE];
uint32_t intro_tag;
uint8_t status; // 0: pending, 1: acknowledged, 2: rejected
};
OneKeyPeerState one_key_peers[ONE_KEY_PEERS] = {};
uint8_t one_key_peer_count = 0;
uint8_t one_key_peer_next = 0;
bool hasOneKeyAck(const ContactInfo& contact) const;
bool hasOneKeyReject(const ContactInfo& contact) const;
void rememberOneKeyIntro(const ContactInfo& contact, uint32_t tag);
void rememberOneKeyAck(const ContactInfo& contact);
bool rememberOneKeyReject(const ContactInfo& contact, uint32_t tag);
uint32_t sendOneKeyIntroduction(const ContactInfo& contact);
static constexpr uint8_t MAX_HELD_ONE_KEY_DMS = 15;
static constexpr uint8_t ONE_KEY_DM_ID_SIZE = 8;
uint8_t verified_pending_keys[MAX_HELD_ONE_KEY_DMS][PUB_KEY_SIZE] = {};
uint8_t verified_pending_count = 0;
struct HeldOneKeyDM {
uint8_t sender_key[PUB_KEY_SIZE];
uint8_t id[ONE_KEY_DM_ID_SIZE];
#if !ONE_KEY_DM_SHARED_OFFLINE_QUEUE
mesh::Packet packet;
#endif
};
HeldOneKeyDM held_dms[MAX_HELD_ONE_KEY_DMS];
uint8_t held_dm_count = 0;
struct DeliveredOneKeyDM {
uint8_t sender_key[PUB_KEY_SIZE];
uint8_t id[ONE_KEY_DM_ID_SIZE];
};
DeliveredOneKeyDM delivered_dms[MAX_HELD_ONE_KEY_DMS] = {};
uint8_t delivered_dm_count = 0;
uint8_t delivered_dm_next = 0;
void rememberVerifiedPendingSender(const uint8_t* pub_key);
void forgetVerifiedPendingSender(const uint8_t* pub_key);
static void makeOneKeyDMId(uint8_t id[ONE_KEY_DM_ID_SIZE], uint32_t timestamp,
const char* text);
bool wasDeliveredOneKeyDM(const uint8_t* pub_key,
const uint8_t id[ONE_KEY_DM_ID_SIZE]) const;
void rememberDeliveredOneKeyDM(const uint8_t* pub_key,
const uint8_t id[ONE_KEY_DM_ID_SIZE]);
void releaseHeldOneKeyDMs();
#endif
mesh::LogicalMessageCache<ROOM_MESSAGE_TIMESTAMP_CACHE_SIZE> room_message_timestamps;
int next_ack_idx;
unsigned long next_ack_expiry;
bool has_next_ack_expiry;
void clearExpectedAck(AckTableEntry& entry, bool cancel_retries = true);
void expireExpectedAcks();
AckTableEntry* findPendingTextMessage(
const uint8_t text_fingerprint[MAX_HASH_SIZE], uint32_t message_timestamp);
#if COMPANION_FEATURE_TEXT_TERMINAL
void rememberTerminalAck(ContactInfo& recipient, const char* text,
uint32_t message_timestamp, uint32_t expected_ack,
uint32_t est_timeout,
const uint8_t packet_retry_key[MAX_HASH_SIZE],
AckTableEntry* replacement_entry);
#endif
#define ADVERT_PATH_TABLE_SIZE 16
AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
#ifdef UI_RECENT_LIST_SIZE
#define DISCOVERED_NODES_TABLE_SIZE UI_RECENT_LIST_SIZE
#else
#define DISCOVERED_NODES_TABLE_SIZE 4
#endif
DiscoveredNode discovered_nodes[DISCOVERED_NODES_TABLE_SIZE]; // not circular, latest discovered nodes are not kept
uint32_t disc_node_req_tag = 0;
uint32_t disc_node_req_at = 0;
bool disc_node_req_active = false;
uint32_t disc_nodes_count = 0;
void checkControlDataForPendingDiscovery(uint8_t payload[], size_t p_len);
#endif
};
extern MyMesh the_mesh;