mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +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
|
||||
|
||||
Reference in New Issue
Block a user