mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
Merge upstream companion-v1.18.0
Fixes and features from upstream: MACThenDecrypt / command buffer overflow fixes, ACK forwarding delay, transport code 0 as unscoped, hidden nodes don't answer DISCOVER, LR2021/LR1121 fixes, GPS cold-start detection and waitFor()/drain(), NMEA fix-age counters, ESP32 RTC after a crash, remote CLI by text message (TXT_TYPE_CLI_COMMAND), PUSH_CODE_BINARY_RESPONSE, board.loop(), new boards (Wio Tracker L1 1W, ThinkNode M4/M8, Muzi Base, Waveshare LR1121, Pico W). Kept ours: NodePrefs, main.cpp, every UI, GxEPDDisplay. The companion CLI stays MyMeshCli.h's handleCliCommand(); upstream's handleCommand() calls it. The board's FEM gains go through attachDynamicPrefs() with an adapter over our NodePrefs fields. Upstream's JSON prefs and MultiSerialInterface come as their own stages. Also: the display defaults were inside RADIO_FEM_TXGAIN's #else (a missing #endif). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
#include "Features.h"
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#include <Mesh.h>
|
||||
|
||||
#include <helpers/UTF8Helpers.h>
|
||||
@@ -695,8 +698,7 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
|
||||
tryBotReplyDM(from, text, hops);
|
||||
}
|
||||
|
||||
void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
const char *text) {
|
||||
void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, const char *text) {
|
||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||
queueMessage(from, TXT_TYPE_CLI_DATA, pkt, sender_timestamp, NULL, 0, text);
|
||||
// If the on-device Admin screen sent this command (not the BLE/USB app's CLI
|
||||
@@ -711,6 +713,17 @@ void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint3
|
||||
|
||||
// Oldest room post the bot still answers (see onSignedMessageRecv()).
|
||||
#define BOT_ROOM_MAX_AGE_SECS 300
|
||||
void MyMesh::onCLICommandRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
const char *text, char* reply) {
|
||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||
if (from.isRemoteCLIAllowed()) {
|
||||
if (!handleCommand(text, sender_timestamp, reply)) {
|
||||
strcat(reply, "Unknown command"); // reply may have cmd prefix from 'text'
|
||||
}
|
||||
} else {
|
||||
queueMessage(from, TXT_TYPE_CLI_COMMAND, pkt, sender_timestamp, NULL, 0, text);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
const uint8_t *sender_prefix, const char *text) {
|
||||
@@ -1008,8 +1021,21 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
memcpy(&out_frame[i], &data[4], len - 4);
|
||||
i += (len - 4);
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (_listener && len > 4) {
|
||||
_listener->onUnhandledResponse(contact, tag, &data[4], len - 4);
|
||||
} else {
|
||||
bool handled;
|
||||
if (_listener && len > 4) {
|
||||
handled = _listener->onUnhandledResponse(contact, tag, &data[4], len - 4);
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
if (!handled) { // let the app know about unknown/unhandled responses (eg. push telemetry packets)
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_BINARY_RESPONSE;
|
||||
out_frame[i++] = 0; // reserved
|
||||
memcpy(&out_frame[i], data, len);
|
||||
i += len;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1290,6 +1316,29 @@ void MyMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
if (_listener) _listener->onACKRecv(ack_crc);
|
||||
}
|
||||
|
||||
// The board's own settings (upstream's dynamic prefs, a key-value store), over
|
||||
// the NodePrefs fields that hold them here: a board with a LoRa front end
|
||||
// reads "fem_rxgain" / "fem_txgain" at begin() and writes them from its CLI.
|
||||
bool MyMesh::BoardPrefs::getByKey(const char* key, char* value, size_t max_len) {
|
||||
uint8_t* f = field(key);
|
||||
if (!f || max_len < 2) return false;
|
||||
value[0] = *f ? '1' : '0';
|
||||
value[1] = 0;
|
||||
return true;
|
||||
}
|
||||
bool MyMesh::BoardPrefs::setByKey(const char* key, const char* value) {
|
||||
uint8_t* f = field(key);
|
||||
if (!f) return false;
|
||||
*f = strcmp(value, "1") == 0 ? 1 : 0;
|
||||
_mesh->savePrefs();
|
||||
return true;
|
||||
}
|
||||
uint8_t* MyMesh::BoardPrefs::field(const char* key) {
|
||||
if (strcmp(key, "fem_rxgain") == 0) return &_mesh->_prefs.radio_fem_rxgain;
|
||||
if (strcmp(key, "fem_txgain") == 0) return &_mesh->_prefs.radio_fem_txgain;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store)
|
||||
// Sized to match simple_repeater's pool (32), not the old client-only 16: with the
|
||||
// on-device Repeater toggle, queued retransmits (adverts/channel flood from
|
||||
@@ -1298,9 +1347,10 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
// silently drops every incoming packet — DMs and channels included — until a slot
|
||||
// frees up.
|
||||
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _listener(NULL), _iter(0) {
|
||||
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _listener(NULL), _iter(0), _board_prefs(this) {
|
||||
_iter_started = false;
|
||||
_cli_rescue = false;
|
||||
cli_command[0] = 0;
|
||||
for (int i = 0; i < RELAY_RING; i++) _relay[i].pending = false;
|
||||
_relay_head = 0;
|
||||
_relay_active = 0;
|
||||
@@ -1356,7 +1406,11 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
#ifdef RADIO_FEM_TXGAIN
|
||||
_prefs.radio_fem_txgain = RADIO_FEM_TXGAIN; // board-specific default for PA
|
||||
#else
|
||||
_prefs.radio_fem_txgain = 0;
|
||||
#endif
|
||||
#ifdef DISPLAY_ROTATION
|
||||
_prefs.display_rotation = DISPLAY_ROTATION;
|
||||
#endif
|
||||
@@ -1486,8 +1540,7 @@ void MyMesh::begin() {
|
||||
#else
|
||||
radio_driver.setPowerSaving(false); // see Features.h FEAT_RX_POWERSAVE -- ignore any stale persisted rx_powersave byte
|
||||
#endif
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
|
||||
board.attachDynamicPrefs(&_board_prefs); // FEM gains: the board reads them now, and its CLI sets them
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
}
|
||||
@@ -1631,16 +1684,16 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
uint8_t *pub_key_prefix = &cmd_frame[i];
|
||||
i += 6;
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key_prefix, 6);
|
||||
if (recipient && (txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_CLI_DATA)) {
|
||||
if (recipient && (txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_CLI_DATA || txt_type == TXT_TYPE_CLI_COMMAND)) {
|
||||
char *text = (char *)&cmd_frame[i];
|
||||
int tlen = len - i;
|
||||
uint32_t est_timeout;
|
||||
text[tlen] = 0; // ensure null
|
||||
int result;
|
||||
uint32_t expected_ack;
|
||||
if (txt_type == TXT_TYPE_CLI_DATA) {
|
||||
if (txt_type == TXT_TYPE_CLI_DATA || txt_type == TXT_TYPE_CLI_COMMAND) {
|
||||
msg_timestamp = getRTCClock()->getCurrentTimeUnique(); // Use node's RTC instead of app timestamp to avoid tripping replay protection
|
||||
result = sendCommandData(*recipient, msg_timestamp, attempt, text, est_timeout);
|
||||
result = sendCommandData(*recipient, msg_timestamp, attempt, txt_type, text, est_timeout);
|
||||
expected_ack = 0; // no Ack expected
|
||||
} else {
|
||||
result = sendMessage(*recipient, msg_timestamp, attempt, text, expected_ack, est_timeout);
|
||||
@@ -1752,8 +1805,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
|
||||
} else if (data_type == DATA_TYPE_RESERVED) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (payload_len > MAX_CHANNEL_DATA_LENGTH) {
|
||||
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH);
|
||||
} else if (payload_len > MAX_GROUP_DATA_LENGTH) {
|
||||
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_GROUP_DATA_LENGTH);
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (sendGroupData(channel.channel, path, path_len, data_type, payload, payload_len)) {
|
||||
writeOKFrame();
|
||||
@@ -2670,6 +2723,14 @@ void MyMesh::enterCLIRescue() {
|
||||
Serial.println("========= CLI Rescue =========");
|
||||
}
|
||||
|
||||
// Upstream's CLI entry (the rescue CLI, a remote contact's CLI message): our
|
||||
// companion CLI (MyMeshCli.h) answers, as it does CMD_RUN_CLI_COMMAND. `reply`
|
||||
// is upstream's buffer: 160 bytes free.
|
||||
bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char* reply) {
|
||||
(void)sender_timestamp;
|
||||
return handleCliCommand(command, reply, 160);
|
||||
}
|
||||
|
||||
void MyMesh::checkCLIRescueCmd() {
|
||||
int len = strlen(cli_command);
|
||||
// `cli_command` must stay NUL-terminated within its bounds. If it ever isn't,
|
||||
@@ -2695,15 +2756,10 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
if (len > 0 && cli_command[len - 1] == '\r') { // received complete line
|
||||
cli_command[len - 1] = 0; // replace newline with C string null terminator
|
||||
|
||||
if (memcmp(cli_command, "set ", 4) == 0) {
|
||||
const char* config = &cli_command[4];
|
||||
if (memcmp(config, "pin ", 4) == 0) {
|
||||
_prefs.ble_pin = atoi(&config[4]);
|
||||
savePrefs();
|
||||
Serial.printf(" > pin is now %06d\n", _prefs.ble_pin);
|
||||
} else {
|
||||
Serial.printf(" Error: unknown config: %s\n", config);
|
||||
}
|
||||
reply_buf[0] = 0;
|
||||
if (handleCommand(cli_command, 0, reply_buf)) {
|
||||
// command was handled, print reply output
|
||||
Serial.print(" "); Serial.print(reply_buf); Serial.println();
|
||||
} else if (strcmp(cli_command, "rebuild") == 0) {
|
||||
bool success = _store->formatFileSystem();
|
||||
if (success) {
|
||||
@@ -2907,6 +2963,11 @@ void MyMesh::loop() {
|
||||
checkCLIRescueCmd();
|
||||
} else {
|
||||
checkSerialInterface();
|
||||
#if defined(ENABLE_WIFI_INTERFACE) && defined(RP2040_PLATFORM) && !defined(ENABLE_USB_INTERFACE)
|
||||
// RP2040 WiFi builds are headless and have no way into the rescue CLI (that needs a
|
||||
// display + long-press), so serve config commands on the otherwise unused USB serial
|
||||
checkCLIRescueCmd();
|
||||
#endif
|
||||
}
|
||||
|
||||
// is there are pending dirty contacts write needed?
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <Mesh.h>
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#include "Features.h" // FEAT_RX_POWERSAVE, used by MyMesh.cpp
|
||||
#include <helpers/KeyValueStore.h>
|
||||
|
||||
// Forward declaration for UITask
|
||||
class UITask;
|
||||
@@ -83,6 +84,10 @@ class UITask;
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
|
||||
// Copied from simple_repeater
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
struct AdvertPath {
|
||||
uint8_t pubkey_prefix[7];
|
||||
uint8_t path_len;
|
||||
@@ -126,7 +131,7 @@ public:
|
||||
virtual void onACKRecv(uint32_t ack_crc) { }
|
||||
virtual uint8_t onUnhandledRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
||||
uint8_t len, uint8_t *reply) { return 0; /* unknown request type */ }
|
||||
virtual void onUnhandledResponse(const ContactInfo &from, uint32_t tag, const uint8_t* data, uint8_t len) { }
|
||||
virtual bool onUnhandledResponse(const ContactInfo &from, uint32_t tag, const uint8_t* data, uint8_t len) { return false; /* not handled */ }
|
||||
virtual void onTraceRecv(mesh::Packet *pkt, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
||||
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) { }
|
||||
virtual void onRawDataRecv(mesh::Packet *pkt) { }
|
||||
@@ -316,6 +321,8 @@ protected:
|
||||
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,
|
||||
@@ -448,7 +455,7 @@ public:
|
||||
// reply is a text frame (TXT_TYPE_CLI_DATA) delivered via onCommandDataRecv();
|
||||
// this just tracks which contact's reply AdminScreen is currently waiting on.
|
||||
bool sendAdminCommand(const ContactInfo& contact, const char* cmd_text, uint32_t& est_timeout) {
|
||||
if (sendCommandData(contact, rtc_clock.getCurrentTime(), 0, cmd_text, est_timeout) == MSG_SEND_FAILED)
|
||||
if (sendCommandData(contact, rtc_clock.getCurrentTime(), 0, TXT_TYPE_CLI_DATA, cmd_text, est_timeout) == MSG_SEND_FAILED)
|
||||
return false;
|
||||
memcpy(&ui_pending_admin_reply, contact.id.pub_key, 4);
|
||||
return true;
|
||||
@@ -634,6 +641,7 @@ private:
|
||||
|
||||
void checkCLIRescueCmd();
|
||||
bool cliRadioCommand(const char* command, char* reply, int n);
|
||||
bool handleCommand(const char* text, uint32_t sender_timestamp, char* reply);
|
||||
void checkSerialInterface();
|
||||
bool isValidClientRepeatFreq(uint32_t f) const;
|
||||
#ifdef ENABLE_SCREENSHOT
|
||||
@@ -690,6 +698,16 @@ private:
|
||||
uint32_t _last_relay_seq; // seq of the most recent tracked send (for the UI to record)
|
||||
bool send_unscoped; // force un-scoped flood (instead of using send_scope)
|
||||
char cli_command[80];
|
||||
char reply_buf[166];
|
||||
class BoardPrefs : public KeyValueStore { // MyMesh.cpp
|
||||
MyMesh* _mesh;
|
||||
uint8_t* field(const char* key);
|
||||
public:
|
||||
BoardPrefs(MyMesh* mesh) : _mesh(mesh) { }
|
||||
bool setByKey(const char* key, const char* value) override;
|
||||
bool getByKey(const char* key, char* value, size_t max_len) override;
|
||||
};
|
||||
BoardPrefs _board_prefs;
|
||||
uint8_t app_target_ver;
|
||||
uint8_t *sign_data;
|
||||
uint32_t sign_data_len;
|
||||
|
||||
@@ -21,6 +21,7 @@ bool MyMesh::handleCliCommand(const char* command, char* reply, int n) {
|
||||
command += 3;
|
||||
}
|
||||
if (cliRadioCommand(command, reply, n)) return true;
|
||||
if (n >= 64 && board.handleCommand(command, 0, reply)) return true; // the board's own (radio.fem.*); short replies
|
||||
|
||||
if (strcmp(command, "reboot") == 0 || strcmp(command, "poweroff") == 0 || strcmp(command, "shutdown") == 0) {
|
||||
bool restart = command[0] == 'r';
|
||||
|
||||
@@ -372,6 +372,7 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
board.loop();
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
@@ -113,7 +113,7 @@ void setup() {
|
||||
uint32_t start = millis();
|
||||
while (!Serial && millis() - start < 3000) delay(10);
|
||||
delay(100);
|
||||
#if defined(ESP32) && ARDUINO_USB_MODE
|
||||
#if defined(ESP32) && defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
|
||||
Serial.setTxTimeoutMs(USB_TX_TIMEOUT_MS);
|
||||
Serial.setTxBufferSize(USB_TX_BUFFER_SIZE);
|
||||
#endif
|
||||
@@ -149,6 +149,8 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
board.loop();
|
||||
|
||||
if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) {
|
||||
radio_driver.triggerNoiseFloorCalibrate(0);
|
||||
next_noise_floor_calib_ms = millis();
|
||||
|
||||
@@ -341,7 +341,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
int results_offset = 0;
|
||||
uint8_t results_buffer[130];
|
||||
for(int index = 0; index < count && index + offset < neighbours_count; index++){
|
||||
|
||||
|
||||
// stop if we can't fit another entry in results
|
||||
int entry_size = pubkey_prefix_length + 4 + 1;
|
||||
if(results_offset + entry_size > sizeof(results_buffer)){
|
||||
@@ -704,7 +704,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint8_t flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA || flags == TXT_TYPE_CLI_COMMAND)) {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
|
||||
} else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks
|
||||
bool is_retry = (sender_timestamp == client->last_timestamp);
|
||||
@@ -790,6 +790,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6
|
||||
&& !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime())
|
||||
&& !isHiddenNode() // this node wishes to NOT be discoverable
|
||||
) {
|
||||
int i = 1;
|
||||
uint8_t filter = packet->payload[i++];
|
||||
@@ -927,6 +928,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_max_advert = 8;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
_prefs.loop_detect = LOOP_DETECT_MINIMAL;
|
||||
|
||||
// bridge defaults
|
||||
_prefs.bridge_enabled = 1; // enabled
|
||||
@@ -1001,8 +1003,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
|
||||
|
||||
board.attachDynamicPrefs(_prefs.getCustom());
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -1193,7 +1195,7 @@ void MyMesh::formatRadioStatsReply(char *reply) {
|
||||
}
|
||||
|
||||
void MyMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,6 @@ struct RepeaterStats {
|
||||
uint32_t n_recv_errors;
|
||||
};
|
||||
|
||||
#ifndef MAX_CLIENTS
|
||||
#define MAX_CLIENTS 32
|
||||
#endif
|
||||
|
||||
struct NeighbourInfo {
|
||||
mesh::Identity id;
|
||||
uint32_t advert_timestamp;
|
||||
@@ -71,11 +67,11 @@ struct NeighbourInfo {
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.17.1"
|
||||
#define FIRMWARE_VERSION "v1.18.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "repeater"
|
||||
@@ -191,6 +187,9 @@ public:
|
||||
NodePrefs* getNodePrefs() {
|
||||
return &_prefs;
|
||||
}
|
||||
bool isHiddenNode() const {
|
||||
return _prefs.advert_interval == 0 && _prefs.flood_advert_interval == 0;
|
||||
}
|
||||
|
||||
void savePrefs() override {
|
||||
_cli.savePrefs(_fs);
|
||||
|
||||
@@ -244,6 +244,8 @@ void loop() {
|
||||
command[0] = 0; // reset command buffer
|
||||
}
|
||||
|
||||
board.loop(); // let the board feed its watchdog, run periodic housekeeping
|
||||
|
||||
#ifdef ETHERNET_ENABLED
|
||||
ethernet_loop_maintain();
|
||||
if (ethernet_read_line(ethernet_command, sizeof(ethernet_command))) {
|
||||
|
||||
@@ -443,7 +443,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint8_t flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA || flags == TXT_TYPE_CLI_COMMAND)) {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags);
|
||||
} else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks, but send Acks for retries
|
||||
bool is_retry = (sender_timestamp == client->last_timestamp);
|
||||
@@ -463,7 +463,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
|
||||
uint8_t temp[166];
|
||||
bool send_ack;
|
||||
if (flags == TXT_TYPE_CLI_DATA) {
|
||||
if (flags == TXT_TYPE_CLI_DATA || flags == TXT_TYPE_CLI_COMMAND) {
|
||||
if (client->isAdmin()) {
|
||||
if (is_retry) {
|
||||
temp[5] = 0; // no reply
|
||||
@@ -725,8 +725,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
|
||||
|
||||
board.attachDynamicPrefs(_prefs.getCustom());
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -988,6 +988,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
addSystemPost(msg);
|
||||
snprintf(reply, MAX_POST_TEXT_LEN, "OK");
|
||||
}
|
||||
} else if (memcmp(command, "set repeat on", 13) == 0) {
|
||||
strcpy(reply, "ERR - repeat now deprecated");
|
||||
} else{
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.17.1"
|
||||
#define FIRMWARE_VERSION "v1.18.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
|
||||
@@ -199,6 +199,7 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
board.loop();
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
@@ -245,6 +245,10 @@ protected:
|
||||
|
||||
void onCommandDataRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override {
|
||||
}
|
||||
|
||||
void onCLICommandRecv(const ContactInfo& contact, 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 {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "SensorMesh.h"
|
||||
#include <helpers/RoutingPolicy.h>
|
||||
#include <helpers/sensors/LPPDataHelpers.h>
|
||||
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
@@ -54,6 +56,10 @@
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
#define REQ_TYPE_GET_AVG_MIN_MAX 0x04
|
||||
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
||||
#define REQ_TYPE_GET_NEIGHBOURS 0x06 // repeater only (at present)
|
||||
|
||||
#define REQ_TYPE_SUBSCRIBE 0x08
|
||||
#define REQ_TYPE_UNSUBSCRIBE 0x09
|
||||
|
||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||
|
||||
@@ -73,104 +79,70 @@ static File openAppend(FILESYSTEM* _fs, const char* fname) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint8_t getDataSize(uint8_t type) {
|
||||
switch (type) {
|
||||
case LPP_GPS:
|
||||
return 9;
|
||||
case LPP_POLYLINE:
|
||||
return 8; // TODO: this is MINIMIUM
|
||||
case LPP_GYROMETER:
|
||||
case LPP_ACCELEROMETER:
|
||||
return 6;
|
||||
case LPP_GENERIC_SENSOR:
|
||||
case LPP_FREQUENCY:
|
||||
case LPP_DISTANCE:
|
||||
case LPP_ENERGY:
|
||||
case LPP_UNIXTIME:
|
||||
return 4;
|
||||
case LPP_COLOUR:
|
||||
return 3;
|
||||
case LPP_ANALOG_INPUT:
|
||||
case LPP_ANALOG_OUTPUT:
|
||||
case LPP_LUMINOSITY:
|
||||
case LPP_TEMPERATURE:
|
||||
case LPP_CONCENTRATION:
|
||||
case LPP_BAROMETRIC_PRESSURE:
|
||||
case LPP_RELATIVE_HUMIDITY:
|
||||
case LPP_ALTITUDE:
|
||||
case LPP_VOLTAGE:
|
||||
case LPP_CURRENT:
|
||||
case LPP_DIRECTION:
|
||||
case LPP_POWER:
|
||||
return 2;
|
||||
/* --------------------- Cayenne LPP helpers ----------------------------*/
|
||||
|
||||
static float findTelemValue(const uint8_t* buf, uint8_t size, uint8_t channel, uint8_t type, float def_value) {
|
||||
uint8_t i = 0;
|
||||
|
||||
while (i + 2 < size) {
|
||||
// Get channel #
|
||||
uint8_t ch = buf[i++];
|
||||
// Get data type
|
||||
uint8_t t = buf[i++];
|
||||
uint8_t sz = LPPData::getDataSize(t);
|
||||
|
||||
if (ch == channel && t == type) {
|
||||
return LPPData::getFloat(&buf[i], sz, LPPData::getMultiplier(t), LPPData::isSigned(t));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint32_t getMultiplier(uint8_t type) {
|
||||
switch (type) {
|
||||
case LPP_CURRENT:
|
||||
case LPP_DISTANCE:
|
||||
case LPP_ENERGY:
|
||||
return 1000;
|
||||
case LPP_VOLTAGE:
|
||||
case LPP_ANALOG_INPUT:
|
||||
case LPP_ANALOG_OUTPUT:
|
||||
return 100;
|
||||
case LPP_TEMPERATURE:
|
||||
case LPP_BAROMETRIC_PRESSURE:
|
||||
case LPP_RELATIVE_HUMIDITY:
|
||||
return 10;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool isSigned(uint8_t type) {
|
||||
return type == LPP_ALTITUDE || type == LPP_TEMPERATURE || type == LPP_GYROMETER ||
|
||||
type == LPP_ANALOG_INPUT || type == LPP_ANALOG_OUTPUT || type == LPP_GPS || type == LPP_ACCELEROMETER;
|
||||
}
|
||||
|
||||
static float getFloat(const uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) {
|
||||
uint32_t value = 0;
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
value = (value << 8) + buffer[i];
|
||||
i += sz; // skip
|
||||
}
|
||||
return def_value; // not found
|
||||
}
|
||||
|
||||
int sign = 1;
|
||||
if (is_signed) {
|
||||
uint32_t bit = 1ul << ((size * 8) - 1);
|
||||
if ((value & bit) == bit) {
|
||||
value = (bit << 1) - value;
|
||||
sign = -1;
|
||||
/* ------------------ end Cayenne LPP helpers ----------------------*/
|
||||
|
||||
bool SensorMesh::telemHasChanged(ClientInfo* c) {
|
||||
auto buf = telemetry.getBuffer();
|
||||
uint8_t size = telemetry.getSize();
|
||||
uint8_t i = 0;
|
||||
bool changed = false;
|
||||
|
||||
while (i + 2 < c->extra.sensor.min_deltas_len) {
|
||||
uint8_t ch = c->extra.sensor.min_deltas[i]; // Get channel #
|
||||
uint8_t t = c->extra.sensor.min_deltas[i + 1]; // Get data type
|
||||
uint8_t sz = LPPData::getDataSize(t);
|
||||
|
||||
float min_delta = LPPData::getFloat(&c->extra.sensor.min_deltas[i + 2], sz, LPPData::getMultiplier(t), LPPData::isSigned(t));
|
||||
float pv = LPPData::getFloat(&c->extra.sensor.prev_telem[i + 2], sz, LPPData::getMultiplier(t), LPPData::isSigned(t));
|
||||
|
||||
float v = findTelemValue(buf, size, ch, t, 0.0f);
|
||||
if (abs(v - pv) > min_delta) changed = true; // Yes, has changed
|
||||
|
||||
i += 2 + sz; // skip
|
||||
}
|
||||
if (changed) {
|
||||
// take snapshot of all _monitored_ telem values, for next cycle
|
||||
i = 0;
|
||||
while (i + 2 < c->extra.sensor.min_deltas_len) {
|
||||
uint8_t ch = c->extra.sensor.min_deltas[i]; // Get channel #
|
||||
uint8_t t = c->extra.sensor.min_deltas[i + 1]; // Get data type
|
||||
uint8_t sz = LPPData::getDataSize(t);
|
||||
|
||||
c->extra.sensor.prev_telem[i] = ch;
|
||||
c->extra.sensor.prev_telem[i + 1] = t;
|
||||
|
||||
float v = findTelemValue(buf, size, ch, t, 0.0f);
|
||||
LPPData::putFloat(&c->extra.sensor.prev_telem[i + 2], v, sz, LPPData::getMultiplier(t), LPPData::isSigned(t));
|
||||
|
||||
i += 2 + sz; // skip
|
||||
}
|
||||
}
|
||||
return sign * ((float) value / multiplier);
|
||||
return changed;
|
||||
}
|
||||
|
||||
static uint8_t putFloat(uint8_t * dest, float value, uint8_t size, uint32_t multiplier, bool is_signed) {
|
||||
// check sign
|
||||
bool sign = value < 0;
|
||||
if (sign) value = -value;
|
||||
uint8_t SensorMesh::handleRequest(ClientInfo* from, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) {
|
||||
uint8_t perms = from->isAdmin() ? 0xFF : from->permissions;
|
||||
|
||||
// get value to store
|
||||
uint32_t v = value * multiplier;
|
||||
|
||||
// format an uint32_t as if it was an int32_t
|
||||
if (is_signed & sign) {
|
||||
uint32_t mask = (1 << (size * 8)) - 1;
|
||||
v = v & mask;
|
||||
if (sign) v = mask - v + 1;
|
||||
}
|
||||
|
||||
// add bytes (MSB first)
|
||||
for (uint8_t i=1; i<=size; i++) {
|
||||
dest[size - i] = (v & 0xFF);
|
||||
v >>= 8;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) {
|
||||
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||
|
||||
if (req_type == REQ_TYPE_GET_TELEMETRY_DATA) { // allow all
|
||||
@@ -181,6 +153,10 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(0xFF & perm_mask, telemetry); // allow all telemetry permissions for admin or guest
|
||||
// TODO: let requester know permissions they have: telemetry.addPresence(TELEM_CHANNEL_SELF, perms);
|
||||
float temperature = board.getMCUTemperature();
|
||||
if (!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
|
||||
}
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
@@ -211,12 +187,12 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
||||
auto d = &data[i];
|
||||
reply_data[ofs++] = d->_channel;
|
||||
reply_data[ofs++] = d->_lpp_type;
|
||||
uint8_t sz = getDataSize(d->_lpp_type);
|
||||
uint32_t mult = getMultiplier(d->_lpp_type);
|
||||
bool is_signed = isSigned(d->_lpp_type);
|
||||
ofs += putFloat(&reply_data[ofs], d->_min, sz, mult, is_signed);
|
||||
ofs += putFloat(&reply_data[ofs], d->_max, sz, mult, is_signed);
|
||||
ofs += putFloat(&reply_data[ofs], d->_avg, sz, mult, is_signed);
|
||||
uint8_t sz = LPPData::getDataSize(d->_lpp_type);
|
||||
uint32_t mult = LPPData::getMultiplier(d->_lpp_type);
|
||||
bool is_signed = LPPData::isSigned(d->_lpp_type);
|
||||
ofs += LPPData::putFloat(&reply_data[ofs], d->_min, sz, mult, is_signed);
|
||||
ofs += LPPData::putFloat(&reply_data[ofs], d->_max, sz, mult, is_signed);
|
||||
ofs += LPPData::putFloat(&reply_data[ofs], d->_avg, sz, mult, is_signed);
|
||||
}
|
||||
return ofs;
|
||||
}
|
||||
@@ -234,6 +210,45 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
||||
return ofs;
|
||||
}
|
||||
}
|
||||
if (req_type == REQ_TYPE_SUBSCRIBE && payload_len >= 8 && (perms & PERM_ACL_ROLE_MASK) >= PERM_ACL_READ_ONLY) {
|
||||
memcpy(&from->extra.sensor.push_tag, &payload[0], 4);
|
||||
uint16_t timeout_secs;
|
||||
memcpy(&timeout_secs, &payload[4], 2);
|
||||
uint8_t reserved = payload[6];
|
||||
uint8_t min_deltas_len = payload[7];
|
||||
RegionEntry* r;
|
||||
if (recv_pkt_region && !recv_pkt_region->isWildcard()) { // use request scope
|
||||
r = recv_pkt_region;
|
||||
} else { // use default scope
|
||||
r = region_map.getDefaultRegion();
|
||||
}
|
||||
uint8_t reply_len;
|
||||
if (r && min_deltas_len >= 3 && min_deltas_len <= sizeof(from->extra.sensor.min_deltas)) {
|
||||
from->extra.sensor.scope_region_id = r->id;
|
||||
from->extra.sensor.expiry_timestamp = getRTCClock()->getCurrentTime() + timeout_secs;
|
||||
from->extra.sensor.min_deltas_len = min_deltas_len;
|
||||
memcpy(from->extra.sensor.min_deltas, &payload[8], min_deltas_len);
|
||||
// reply with actual expiry seconds (we could modify/impose restriction)
|
||||
memcpy(&reply_data[4], &timeout_secs, 2);
|
||||
memset(&reply_data[6], 0, 2); // reserved
|
||||
strcpy((char *)&reply_data[8], r ? r->name : ""); // reply with name of scope that will be used
|
||||
reply_len = 8 + strlen((char *)&reply_data[8]);
|
||||
} else {
|
||||
memset(&reply_data[4], 0, 4); // expiry secs (0 for error)
|
||||
reply_len = 8;
|
||||
}
|
||||
return reply_len;
|
||||
}
|
||||
if (req_type == REQ_TYPE_UNSUBSCRIBE && (perms & PERM_ACL_ROLE_MASK) >= PERM_ACL_READ_ONLY) {
|
||||
from->extra.sensor.scope_region_id = 0;
|
||||
from->extra.sensor.push_tag = 0;
|
||||
from->extra.sensor.expiry_timestamp = 0;
|
||||
from->extra.sensor.min_deltas_len = 0;
|
||||
// REVISIT: maybe return some stats, eg total number of telemetry pushes since SUBSCRIBE?
|
||||
memset(&reply_data[4], 0, 8); // success
|
||||
getRNG()->random(&reply_data[12], 2); // just some entropy for better packet-hash uniqueness
|
||||
return 12 + 2;
|
||||
}
|
||||
return 0; // unknown command
|
||||
}
|
||||
|
||||
@@ -262,7 +277,7 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) {
|
||||
sendDirect(pkt, c->out_path, c->out_path_len);
|
||||
} else {
|
||||
unsigned long delay_millis = 0;
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
}
|
||||
}
|
||||
t->send_expiry = futureMillis(ALERT_ACK_EXPIRY_MILLIS);
|
||||
@@ -330,6 +345,25 @@ int SensorMesh::getAGCResetInterval() const {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
|
||||
void SensorMesh::startRegionsLoad() {
|
||||
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
||||
memset(load_stack, 0, sizeof(load_stack));
|
||||
load_stack[0] = &temp_map.getWildcard();
|
||||
region_load_active = true;
|
||||
}
|
||||
|
||||
bool SensorMesh::saveRegions() {
|
||||
return region_map.save(_fs);
|
||||
}
|
||||
|
||||
void SensorMesh::onDefaultRegionChanged(const RegionEntry* r) {
|
||||
if (r) {
|
||||
region_map.getTransportKeysFor(*r, &default_scope, 1);
|
||||
} else {
|
||||
memset(default_scope.key, 0, sizeof(default_scope.key));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) {
|
||||
ClientInfo* client;
|
||||
if (data[0] == 0) { // blank password, just check if sender is in ACL
|
||||
@@ -343,7 +377,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
|
||||
} else {
|
||||
if (strcmp((char *) data, _prefs.password) != 0) { // check for valid admin password
|
||||
#if MESH_DEBUG
|
||||
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
|
||||
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[0]);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -379,7 +413,41 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
|
||||
return 13; // reply length
|
||||
}
|
||||
|
||||
void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
void SensorMesh::handleCommand(ClientInfo* from, uint32_t sender_timestamp, char* command, char* reply) {
|
||||
if (region_load_active) {
|
||||
if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation
|
||||
region_map = temp_map; // copy over the temp instance as new current map
|
||||
region_load_active = false;
|
||||
|
||||
sprintf(reply, "OK - loaded %d regions", region_map.getCount());
|
||||
} else {
|
||||
char *np = command;
|
||||
while (*np == ' ') np++; // skip indent
|
||||
int indent = np - command;
|
||||
|
||||
char *ep = np;
|
||||
while (RegionMap::is_name_char(*ep)) ep++;
|
||||
if (*ep) { *ep++ = 0; } // set null terminator for end of name
|
||||
|
||||
while (*ep && *ep != 'F') ep++; // look for (optional) flags
|
||||
|
||||
if (indent > 0 && indent < 8 && strlen(np) > 0) {
|
||||
auto parent = load_stack[indent - 1];
|
||||
if (parent) {
|
||||
auto old = region_map.findByName(np);
|
||||
auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists)
|
||||
if (nw) {
|
||||
nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr
|
||||
|
||||
load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's
|
||||
}
|
||||
}
|
||||
}
|
||||
reply[0] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
@@ -400,10 +468,11 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
||||
if (sp == NULL) {
|
||||
strcpy(reply, "Err - bad params");
|
||||
} else {
|
||||
int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
||||
uint8_t pubkey[PUB_KEY_SIZE];
|
||||
|
||||
*sp++ = 0; // replace space with null terminator
|
||||
|
||||
uint8_t pubkey[PUB_KEY_SIZE];
|
||||
int hex_len = min(sp - hex, PUB_KEY_SIZE*2);
|
||||
if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) {
|
||||
uint8_t perms = atoi(sp);
|
||||
if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) {
|
||||
@@ -451,6 +520,21 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r
|
||||
}
|
||||
}
|
||||
|
||||
mesh::DispatcherAction SensorMesh::onRecvPacket(mesh::Packet* pkt) {
|
||||
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
|
||||
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
|
||||
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
|
||||
recv_pkt_region = NULL;
|
||||
} else {
|
||||
recv_pkt_region = ®ion_map.getWildcard();
|
||||
}
|
||||
} else {
|
||||
recv_pkt_region = NULL;
|
||||
}
|
||||
return Mesh::onRecvPacket(pkt);
|
||||
}
|
||||
|
||||
void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
uint32_t timestamp;
|
||||
@@ -472,10 +556,10 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
if (reply) sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,7 +587,7 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
||||
void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size) {
|
||||
if (dest.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
mesh::Packet* ack = createAck(ack_hash);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY, path_hash_size);
|
||||
if (ack) sendFloodScoped(default_scope, ack, TXT_ACK_DELAY, path_hash_size);
|
||||
} else {
|
||||
uint32_t d = TXT_ACK_DELAY;
|
||||
if (getExtraAckTransmitCount() > 0) {
|
||||
@@ -531,7 +615,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
if (timestamp > from->last_timestamp) { // prevent replay attacks
|
||||
uint8_t reply_len = handleRequest(from->isAdmin() ? 0xFF : from->permissions, timestamp, data[4], &data[5], len - 5);
|
||||
uint8_t reply_len = handleRequest(from, timestamp, data[4], &data[5], len - 5);
|
||||
if (reply_len == 0) return; // invalid command
|
||||
|
||||
from->last_timestamp = timestamp;
|
||||
@@ -541,14 +625,14 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (from->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, from->out_path, from->out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,12 +655,12 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||
mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
if (path) sendFlood(path, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
if (path) sendFloodReply(path, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
} else {
|
||||
sendAckTo(*from, ack_hash, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
} else if (flags == TXT_TYPE_CLI_DATA) {
|
||||
} else if (flags == TXT_TYPE_CLI_DATA || flags == TXT_TYPE_CLI_COMMAND) {
|
||||
from->last_timestamp = sender_timestamp;
|
||||
from->last_activity = getRTCClock()->getCurrentTime();
|
||||
|
||||
@@ -586,7 +670,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
uint8_t temp[166];
|
||||
char *command = (char *) &data[5];
|
||||
char *reply = (char *) &temp[5];
|
||||
handleCommand(sender_timestamp, command, reply);
|
||||
handleCommand(from, sender_timestamp, command, reply);
|
||||
|
||||
int text_len = strlen(reply);
|
||||
if (text_len > 0) {
|
||||
@@ -601,7 +685,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len);
|
||||
if (reply) {
|
||||
if (from->out_path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
|
||||
sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
|
||||
} else {
|
||||
sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||
}
|
||||
@@ -699,7 +783,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
|
||||
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
region_map(key_store),
|
||||
region_map(key_store), temp_map(key_store),
|
||||
_cli(board, rtc, sensors, region_map, acl, &_prefs, this),
|
||||
telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
@@ -708,6 +792,8 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
last_read_time = 0;
|
||||
num_alert_tasks = 0;
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
recv_pkt_region = NULL;
|
||||
region_load_active = false;
|
||||
|
||||
// defaults
|
||||
_prefs.airtime_factor = 1.0;
|
||||
@@ -771,8 +857,8 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
|
||||
|
||||
board.attachDynamicPrefs(_prefs.getCustom());
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -820,11 +906,43 @@ void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t
|
||||
revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params
|
||||
}
|
||||
|
||||
void SensorMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size) {
|
||||
if (scope.isNull()) {
|
||||
sendFlood(pkt, delay_millis, path_hash_size);
|
||||
} else {
|
||||
uint16_t codes[2];
|
||||
codes[0] = scope.calcTransportCode(pkt);
|
||||
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||
sendFlood(pkt, codes, delay_millis, path_hash_size);
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) {
|
||||
TransportKey req_scope;
|
||||
bool is_wildcard = recv_pkt_region != NULL && recv_pkt_region->isWildcard();
|
||||
bool req_scope_known = recv_pkt_region != NULL && !is_wildcard
|
||||
&& region_map.getTransportKeysFor(*recv_pkt_region, &req_scope, 1) > 0;
|
||||
|
||||
switch (mesh::chooseReplyScope(req_scope_known, is_wildcard, !default_scope.isNull())) {
|
||||
case mesh::REPLY_SCOPE_REQUEST:
|
||||
sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request
|
||||
break;
|
||||
case mesh::REPLY_SCOPE_DEFAULT:
|
||||
// requester's scope is unknown: DIRECT request (no transport codes), or code matched no Region.
|
||||
// un-scoped would be dropped at hop 0 by repeaters running flood.max.unscoped=0
|
||||
sendFloodScoped(default_scope, packet, delay_millis, path_hash_size);
|
||||
break;
|
||||
case mesh::REPLY_SCOPE_NONE:
|
||||
sendFlood(packet, delay_millis, path_hash_size); // send un-scoped
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
if (pkt) {
|
||||
if (flood) {
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
} else {
|
||||
sendZeroHop(pkt, delay_millis);
|
||||
}
|
||||
@@ -867,23 +985,7 @@ void SensorMesh::formatPacketStatsReply(char *reply) {
|
||||
}
|
||||
|
||||
float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) {
|
||||
auto buf = telemetry.getBuffer();
|
||||
uint8_t size = telemetry.getSize();
|
||||
uint8_t i = 0;
|
||||
|
||||
while (i + 2 < size) {
|
||||
// Get channel #
|
||||
uint8_t ch = buf[i++];
|
||||
// Get data type
|
||||
uint8_t t = buf[i++];
|
||||
uint8_t sz = getDataSize(t);
|
||||
|
||||
if (ch == channel && t == type) {
|
||||
return getFloat(&buf[i], sz, getMultiplier(t), isSigned(t));
|
||||
}
|
||||
i += sz; // skip
|
||||
}
|
||||
return 0.0f; // not found
|
||||
return findTelemValue(telemetry.getBuffer(), telemetry.getSize(), channel, type, 0.0f);
|
||||
}
|
||||
|
||||
bool SensorMesh::getGPS(uint8_t channel, float& lat, float& lon, float& alt) {
|
||||
@@ -903,7 +1005,7 @@ void SensorMesh::loop() {
|
||||
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
unsigned long delay_millis = 0;
|
||||
if (pkt) sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
if (pkt) sendFloodScoped(default_scope, pkt, delay_millis, _prefs.path_hash_mode + 1);
|
||||
|
||||
updateFloodAdvertTimer(); // schedule next flood advert
|
||||
updateAdvertTimer(); // also schedule local advert (so they don't overlap)
|
||||
@@ -932,6 +1034,39 @@ void SensorMesh::loop() {
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions
|
||||
// This MCU temperature will be overridden by external sensors (if any)
|
||||
float temperature = board.getMCUTemperature();
|
||||
if (!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
|
||||
}
|
||||
|
||||
// compare with previous telemetry, check if any deltas are greater than subscriber minimums
|
||||
for (int i = 0; i < acl.getNumClients(); i++) {
|
||||
auto c = acl.getClientByIdx(i);
|
||||
if (c->permissions == 0 || c->extra.sensor.scope_region_id == 0 || c->extra.sensor.min_deltas_len == 0) continue; // skip deleted entries, or Not subscribed to deltas
|
||||
RegionEntry* r = region_map.findById(c->extra.sensor.scope_region_id);
|
||||
if (r == NULL) continue; // unknown region scope
|
||||
if (curr > c->extra.sensor.expiry_timestamp) continue; // subscription now expired
|
||||
if (telemHasChanged(c)) {
|
||||
TransportKey scope;
|
||||
if (region_map.getTransportKeysFor(*r, &scope, 1) > 0) {
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(reply_data, &c->extra.sensor.push_tag, 4);
|
||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(&reply_data[4], ×tamp, 4);
|
||||
memcpy(&reply_data[8], telemetry.getBuffer(), tlen);
|
||||
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, c->id, c->shared_secret, reply_data, 8 + tlen);
|
||||
if (reply) {
|
||||
if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, c->out_path, c->out_path_len, 0);
|
||||
} else {
|
||||
sendFloodScoped(scope, reply, 0, _prefs.path_hash_mode + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSensorDataRead();
|
||||
|
||||
@@ -974,7 +1109,7 @@ void SensorMesh::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// is there are pending dirty contacts write needed?
|
||||
// pending dirty contacts write needed?
|
||||
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||
acl.save(_fs);
|
||||
dirty_contacts_expiry = 0;
|
||||
|
||||
@@ -34,16 +34,16 @@
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
|
||||
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.17.1"
|
||||
#define FIRMWARE_VERSION "v1.18.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
|
||||
#define MAX_SEARCH_RESULTS 8
|
||||
#define MAX_SEARCH_RESULTS 24
|
||||
#define MAX_CONCURRENT_ALERTS 4
|
||||
|
||||
class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
@@ -51,7 +51,7 @@ 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);
|
||||
void handleCommand(ClientInfo* from, uint32_t sender_timestamp, char* command, char* reply);
|
||||
|
||||
// CommonCLI callbacks
|
||||
const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
|
||||
@@ -78,9 +78,15 @@ public:
|
||||
void saveIdentity(const mesh::LocalIdentity& new_id) override;
|
||||
void clearStats() override { }
|
||||
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
|
||||
void startRegionsLoad() override;
|
||||
bool saveRegions() override;
|
||||
void onDefaultRegionChanged(const RegionEntry* r) override;
|
||||
|
||||
float getTelemValue(uint8_t channel, uint8_t type);
|
||||
|
||||
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis, uint8_t path_hash_size);
|
||||
void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
|
||||
|
||||
protected:
|
||||
// current telemetry data queries
|
||||
float getVoltage(uint8_t channel) { return getTelemValue(channel, LPP_VOLTAGE); }
|
||||
@@ -93,12 +99,12 @@ protected:
|
||||
bool getGPS(uint8_t channel, float& lat, float& lon, float& alt);
|
||||
|
||||
// alerts
|
||||
enum AlertPriority { LOW_PRI_ALERT, HIGH_PRI_ALERT };
|
||||
enum AlertPriority : uint8_t { LOW_PRI_ALERT, HIGH_PRI_ALERT };
|
||||
|
||||
struct Trigger {
|
||||
uint32_t timestamp;
|
||||
AlertPriority pri;
|
||||
uint32_t expected_acks[4];
|
||||
AlertPriority pri;
|
||||
int8_t curr_contact_idx;
|
||||
uint8_t attempt;
|
||||
unsigned long send_expiry;
|
||||
@@ -122,6 +128,7 @@ protected:
|
||||
int getInterferenceThreshold() const override;
|
||||
bool getCADEnabled() const override;
|
||||
int getAGCResetInterval() const override;
|
||||
mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;
|
||||
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;
|
||||
@@ -129,6 +136,7 @@ protected:
|
||||
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;
|
||||
|
||||
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:
|
||||
@@ -141,7 +149,9 @@ private:
|
||||
unsigned long dirty_contacts_expiry;
|
||||
CayenneLPP telemetry;
|
||||
TransportKeyStore key_store;
|
||||
RegionMap region_map;
|
||||
RegionMap region_map, temp_map;
|
||||
RegionEntry* recv_pkt_region;
|
||||
RegionEntry* load_stack[8];
|
||||
TransportKey default_scope;
|
||||
uint32_t last_read_time;
|
||||
int matching_peer_indexes[MAX_SEARCH_RESULTS];
|
||||
@@ -152,9 +162,11 @@ private:
|
||||
float pending_bw;
|
||||
uint8_t pending_sf;
|
||||
uint8_t pending_cr;
|
||||
bool region_load_active;
|
||||
|
||||
bool telemHasChanged(ClientInfo* c);
|
||||
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);
|
||||
uint8_t handleRequest(ClientInfo* from, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
|
||||
mesh::Packet* createSelfAdvert();
|
||||
|
||||
void sendAlert(const ClientInfo* c, Trigger* t);
|
||||
|
||||
@@ -145,7 +145,7 @@ void loop() {
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
command[len - 1] = 0; // replace newline with C string null terminator
|
||||
char reply[160];
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
the_mesh.handleCommand(NULL, 0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
if (reply[0]) {
|
||||
Serial.print(" -> "); Serial.println(reply);
|
||||
}
|
||||
@@ -153,6 +153,8 @@ void loop() {
|
||||
command[0] = 0; // reset command buffer
|
||||
}
|
||||
|
||||
board.loop(); // let the board feed its watchdog, run periodic housekeeping
|
||||
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
Reference in New Issue
Block a user