mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-14 14:45:39 +00:00
Implement radio2, tempradio2, radioat2 and tempradioat2 across Mesh roles, with RX-only/RX+TX operation, optional preambles and persistent cross-TX policy. Keep temporary OTA traffic on its profile by default and maintain independent retry ownership and backoff for each profile. Use slow-first receive scanning with 4.8-symbol visits and automatic preambles rounded up in steps of eight. Preserve pending RX, restore power saving on exit, and discard work bound to changed or expired profiles. Restore the infrastructure path.hash.mode setter and report unsupported extra.sf settings consistently. Add CLI, scheduling, scan and retry tests, setup documentation, and the V4/XIAO hardware validation results. Validation: 1,422 native tests, eight KISS tests, 63 final focused profile tests, 18 checks from the staged source, sanitizer-enabled OTA transfers, and builds for V4 Mesh roles, Full XIAO Companion and nRF52 T1000-E. Hardware checks cover reception, cross-TX policy, expiry, reboot and OTA discovery while receiving main-channel adverts.
246 lines
10 KiB
C++
246 lines
10 KiB
C++
#pragma once
|
|
|
|
#include <Arduino.h> // needed for PlatformIO
|
|
#include <Mesh.h>
|
|
|
|
#include "TimeSeriesData.h"
|
|
|
|
#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 <helpers/ArduinoHelpers.h>
|
|
#include <helpers/StaticPoolPacketManager.h>
|
|
#include <helpers/SimpleMeshTables.h>
|
|
#include <helpers/IdentityStore.h>
|
|
#include <helpers/AdvertDataHelpers.h>
|
|
#include <helpers/TxtDataHelpers.h>
|
|
#include <helpers/CommonCLI.h>
|
|
#include <helpers/MeshClockSync.h>
|
|
#if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL)
|
|
#include <helpers/UserGpioReplyTracker.h>
|
|
#endif
|
|
#include <helpers/StatsFormatHelper.h>
|
|
#include <helpers/ClientACL.h>
|
|
#include <helpers/RegionMap.h>
|
|
#include <RTClib.h>
|
|
#include <target.h>
|
|
|
|
#define PERM_RESERVED1 (1 << 2)
|
|
#define PERM_RESERVED2 (1 << 3)
|
|
#define PERM_RESERVED3 (1 << 4)
|
|
#define PERM_RESERVED4 (1 << 5)
|
|
#define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts
|
|
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
|
|
|
#ifndef FIRMWARE_BUILD_DATE
|
|
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
|
|
#endif
|
|
|
|
#ifndef FIRMWARE_VERSION
|
|
#define FIRMWARE_VERSION "v1.17.1"
|
|
#endif
|
|
|
|
#define FIRMWARE_ROLE "sensor"
|
|
|
|
#define MAX_SEARCH_RESULTS 8
|
|
#define MAX_CONCURRENT_ALERTS 4
|
|
|
|
class SensorMesh : public mesh::Mesh, public CommonCLICallbacks,
|
|
public mesh::MeshClockSyncCallbacks {
|
|
mesh::StaticFloodAdvertLimiter<> flood_advert_limiter;
|
|
mesh::FloodAdvertLimiter* getFloodAdvertLimiter() override { return &flood_advert_limiter; }
|
|
public:
|
|
SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
|
void begin(FILESYSTEM* fs);
|
|
void loop();
|
|
void handleCommand(uint32_t sender_timestamp, char* command, char* reply,
|
|
int gpio_client_index = -1,
|
|
uint8_t gpio_path_hash_size = 1);
|
|
|
|
// CommonCLI callbacks
|
|
const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
|
|
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
|
|
const char* getRole() override { return FIRMWARE_ROLE; }
|
|
const char* getNodeName() { return _prefs.node_name; }
|
|
NodePrefs* getNodePrefs() { return &_prefs; }
|
|
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
|
|
void savePrefs(
|
|
PrefsSaveRouting::Scope scope = PrefsSaveRouting::Scope::Common) override {
|
|
_cli.savePrefs(_fs, scope);
|
|
}
|
|
void onManualClockSet() override { _clock_sync.onManualClockSet(); }
|
|
bool formatFileSystem() override;
|
|
void sendSelfAdvertisement(int delay_millis, bool flood) override;
|
|
void updateAdvertTimer() override;
|
|
void updateFloodAdvertTimer() override;
|
|
void setLoggingOn(bool enable) override { }
|
|
void eraseLogFile() override { }
|
|
void dumpLogFile() override { }
|
|
bool setTxPower(int8_t power_dbm) override;
|
|
bool setRxPowerSaving(bool enable, uint32_t rx_us, uint32_t sleep_us) override;
|
|
void recalibrateNoiseFloor() override { _radio->recalibrateNoiseFloor(); }
|
|
void getRxPsWatchdogCounts(uint32_t* soft, uint32_t* hard) override;
|
|
void formatNeighborsReply(char *reply) override {
|
|
strcpy(reply, "not supported");
|
|
}
|
|
void formatStatsReply(char *reply) override;
|
|
void formatRadioStatsReply(char *reply) override;
|
|
void formatPacketStatsReply(char *reply) override;
|
|
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
|
void saveIdentity(const mesh::LocalIdentity& new_id) override;
|
|
void clearStats() override { }
|
|
mesh::Radio* getProfileRadio() override { return _radio; }
|
|
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins, uint16_t preamble = 0) override;
|
|
bool scheduleNormalRadio() override;
|
|
#if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL)
|
|
uint32_t getUserGpioRequestSource() const override {
|
|
return _gpio_reply_tracker.requestSource();
|
|
}
|
|
void onUserGpioTimerScheduled(uint8_t pin, uint32_t request_id) override {
|
|
_gpio_reply_tracker.timerScheduled(pin, request_id);
|
|
}
|
|
void onUserGpioTimerCancelled(uint8_t pin) override {
|
|
_gpio_reply_tracker.timerCancelled(pin);
|
|
}
|
|
void onUserGpioTimerCompleted(uint8_t pin, uint8_t state,
|
|
uint32_t request_id) override;
|
|
#endif
|
|
|
|
float getTelemValue(uint8_t channel, uint8_t type);
|
|
|
|
protected:
|
|
bool isTempRadioActive() const override {
|
|
return temp_radio_applied && revert_radio_at != 0 && !millisHasNowPassed(revert_radio_at);
|
|
}
|
|
// current telemetry data queries
|
|
float getVoltage(uint8_t channel) { return getTelemValue(channel, LPP_VOLTAGE); }
|
|
float getCurrent(uint8_t channel) { return getTelemValue(channel, LPP_CURRENT); }
|
|
float getPower(uint8_t channel) { return getTelemValue(channel, LPP_POWER); }
|
|
float getTemperature(uint8_t channel) { return getTelemValue(channel, LPP_TEMPERATURE); }
|
|
float getRelativeHumidity(uint8_t channel) { return getTelemValue(channel, LPP_RELATIVE_HUMIDITY); }
|
|
float getBarometricPressure(uint8_t channel) { return getTelemValue(channel, LPP_BAROMETRIC_PRESSURE); }
|
|
float getAltitude(uint8_t channel) { return getTelemValue(channel, LPP_ALTITUDE); }
|
|
bool getGPS(uint8_t channel, float& lat, float& lon, float& alt);
|
|
|
|
// alerts
|
|
enum AlertPriority { LOW_PRI_ALERT, HIGH_PRI_ALERT };
|
|
|
|
struct Trigger {
|
|
uint32_t timestamp;
|
|
AlertPriority pri;
|
|
uint32_t expected_acks[4];
|
|
int8_t curr_contact_idx;
|
|
uint8_t attempt;
|
|
unsigned long send_expiry;
|
|
char text[MAX_PACKET_PAYLOAD];
|
|
|
|
Trigger() { text[0] = 0; }
|
|
bool isTriggered() const { return text[0] != 0; }
|
|
};
|
|
void alertIf(bool condition, Trigger& t, AlertPriority pri, const char* text);
|
|
|
|
virtual void onSensorDataRead() = 0; // for app to implement
|
|
virtual int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) = 0; // for app to implement
|
|
virtual bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) { return false; }
|
|
|
|
// Mesh overrides
|
|
float getAirtimeBudgetFactor() const override;
|
|
bool allowPacketForward(const mesh::Packet* packet) override;
|
|
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;
|
|
bool allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash,
|
|
uint8_t next_hop_hash_len) const override;
|
|
void configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* original,
|
|
uint8_t retry_attempt) override;
|
|
uint32_t getDirectRetryEchoDelay(const mesh::Packet* packet) const override;
|
|
uint8_t getDirectRetryMaxAttempts(const mesh::Packet* packet) const override;
|
|
uint32_t getDirectRetryAttemptDelay(const mesh::Packet* packet, uint8_t attempt_idx) 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;
|
|
|
|
bool supportsBasicRetryConfig() const override { return true; }
|
|
void onRetryConfigChanged() override;
|
|
int getInterferenceThreshold() const override;
|
|
bool getCADEnabled() const override;
|
|
int getAGCResetInterval() const override;
|
|
uint8_t getDefaultTxCodingRate() const override {
|
|
return active_cr;
|
|
}
|
|
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
|
int searchPeersByHash(const uint8_t* hash) override;
|
|
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
|
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
|
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
|
void onControlDataRecv(mesh::Packet* packet) override;
|
|
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
|
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;
|
|
virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len);
|
|
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size=1);
|
|
private:
|
|
FILESYSTEM* _fs;
|
|
uint16_t pending_preamble = 0;
|
|
unsigned long next_local_advert, next_flood_advert;
|
|
NodePrefs _prefs;
|
|
ClientACL acl;
|
|
CommonCLI _cli;
|
|
mesh::MeshClockSync _clock_sync;
|
|
#if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL)
|
|
UserGpioReplyTracker _gpio_reply_tracker;
|
|
#endif
|
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
|
unsigned long dirty_contacts_expiry;
|
|
uint8_t contacts_save_failures;
|
|
CayenneLPP telemetry;
|
|
TransportKeyStore key_store;
|
|
RegionMap region_map;
|
|
TransportKey default_scope;
|
|
uint32_t last_read_time;
|
|
int matching_peer_indexes[MAX_SEARCH_RESULTS];
|
|
int num_alert_tasks;
|
|
Trigger* alert_tasks[MAX_CONCURRENT_ALERTS];
|
|
unsigned long set_radio_at, revert_radio_at;
|
|
float pending_freq;
|
|
float pending_bw;
|
|
uint8_t pending_sf;
|
|
uint8_t pending_cr;
|
|
uint8_t active_cr;
|
|
bool temp_radio_applied;
|
|
bool saved_radio_apply_pending;
|
|
unsigned long radio_apply_retry_at;
|
|
uint8_t radio_apply_failures;
|
|
|
|
bool applySavedRadioParams();
|
|
bool isMillisTimerDue(unsigned long timestamp) const;
|
|
uint32_t limitSleepToMillisTimer(unsigned long timestamp,
|
|
uint32_t sleep_secs) const;
|
|
bool hasPendingWork() const;
|
|
|
|
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
|
uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
|
|
bool hasLocationTelemetryClient();
|
|
void updateGpsTelemetryPolicy();
|
|
mesh::Packet* createSelfAdvert();
|
|
|
|
void sendAlert(const ClientInfo* c, Trigger* t);
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
void applyGpsPrefs() {
|
|
sensors.setPowerSavingEnabled(_prefs.powersaving_enabled != 0);
|
|
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
|
char interval_str[12];
|
|
sprintf(interval_str, "%u", _prefs.gps_interval);
|
|
sensors.setSettingValue("gps_interval", interval_str);
|
|
}
|
|
#endif
|
|
};
|