mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 03:06:39 +00:00
refactor: port upstream MyMesh::Listener; MyMesh no longer calls the UI directly
Ports upstream's "Abstract UI overhaul" (PR #3431 + follow-ups) with identical names/signatures. MyMesh talks to the UI only through MyMesh::Listener (setListener()); a second block holds Solo extensions (own-send mirroring, relay echoes, room login/admin replies, [LOC] shares, contact/channel removal, bot device actions, requestShutdown), all no-op by default. AbstractUITask becomes the Listener and carries the glue MyMesh used to run for the UI (display filter, room-post labels, notifications, connection polling). BLE PIN randomisation moves to main.cpp as upstream. Also fixes ui-orig's stale advertReceived event on buzzer boards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,10 +62,19 @@ interleaved. Classified:
|
|||||||
|
|
||||||
### 1. `UiCore` owns the mesh-facing interface
|
### 1. `UiCore` owns the mesh-facing interface
|
||||||
|
|
||||||
`UiCore` implements `AbstractUITask`. `MyMesh` keeps calling exactly the same
|
`MyMesh` talks to the UI only through `MyMesh::Listener` (set with
|
||||||
methods; they land in the Core, which updates models/engines and emits events.
|
`setListener()`), ported from upstream's "Abstract UI overhaul" (PR #3431 and
|
||||||
The frontend no longer implements `AbstractUITask` at all. This is the single
|
follow-ups `3caf033d`, `5c3d9281`, `30dd723c`, `b3b17025`, `64434c53`) with
|
||||||
point that makes both frontends receive identical behaviour.
|
identical names/signatures. A second block in the same interface holds this
|
||||||
|
fork's extensions (own-send mirroring, relay echoes, room login/admin replies,
|
||||||
|
`[LOC]` shares, contact/channel removal, bot device actions,
|
||||||
|
`requestShutdown`), each defaulting to a no-op.
|
||||||
|
|
||||||
|
Today `AbstractUITask` is the Listener and carries the glue `MyMesh` used to
|
||||||
|
run for the UI (display filter, room-post labelling, notifications). In the
|
||||||
|
target design `UiCore` becomes the Listener instead: it updates models/engines
|
||||||
|
and emits events, and the frontends no longer implement it at all. This is the
|
||||||
|
single point that makes both frontends receive identical behaviour.
|
||||||
|
|
||||||
### 2. Engines
|
### 2. Engines
|
||||||
|
|
||||||
@@ -140,7 +149,8 @@ consumes keys only.
|
|||||||
Each step keeps `WioTrackerL1_companion_solo_dual` behaviour identical and is
|
Each step keeps `WioTrackerL1_companion_solo_dual` behaviour identical and is
|
||||||
checked in the sim plus on L1 hardware before the next one.
|
checked in the sim plus on L1 hardware before the next one.
|
||||||
|
|
||||||
1. **Skeleton.** `examples/companion_radio/ui-core/`; add it to every solo env's `build_src_filter` / include path. Move `MessageHistory` + DM unread tracking into it; `ui-new` uses them through the Core.
|
0. **Listener boundary** ✅ (upstream `MyMesh::Listener` ported; `MyMesh` has no UI calls left).
|
||||||
|
1. **Skeleton.** `examples/companion_radio/ui-core/`, compiled through a single unity `.cpp` inside each frontend directory so the 66 variant `platformio.ini` files that already build `ui-new` need no changes; add it to every solo env's `build_src_filter` / include path. Move `MessageHistory` + DM unread tracking into it; `ui-new` uses them through the Core.
|
||||||
2. **Engines, one per commit.** Clock tools → ping → course-over-ground → live share → locator → trail → notifications. `UITask` shrinks to screen management + drawing.
|
2. **Engines, one per commit.** Clock tools → ping → course-over-ground → live share → locator → trail → notifications. `UITask` shrinks to screen management + drawing.
|
||||||
3. **Flip the interface.** `UiCore` implements `AbstractUITask`; `ui-new`'s `UITask` becomes a frontend fed by events.
|
3. **Flip the interface.** `UiCore` implements `AbstractUITask`; `ui-new`'s `UITask` becomes a frontend fed by events.
|
||||||
4. **Settings schema.** Convert `SettingsScreen` section by section.
|
4. **Settings schema.** Convert `SettingsScreen` section by section.
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
#include <helpers/ui/UIScreen.h>
|
#include <helpers/ui/UIScreen.h>
|
||||||
#include <helpers/SensorManager.h>
|
#include <helpers/SensorManager.h>
|
||||||
#include <helpers/BaseSerialInterface.h>
|
#include <helpers/BaseSerialInterface.h>
|
||||||
#include <helpers/BaseChatMesh.h> // MAX_TEXT_LEN, for addOwnChannelMsg() below
|
#include <helpers/BaseChatMesh.h> // MAX_TEXT_LEN
|
||||||
|
#include <helpers/TxtDataHelpers.h> // TXT_TYPE_*
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#ifdef PIN_BUZZER
|
#ifdef PIN_BUZZER
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "NodePrefs.h"
|
#include "NodePrefs.h"
|
||||||
|
#include "MyMesh.h"
|
||||||
|
|
||||||
enum class UIEventType {
|
enum class UIEventType {
|
||||||
none,
|
none,
|
||||||
@@ -24,16 +26,30 @@ enum class UIEventType {
|
|||||||
ack
|
ack
|
||||||
};
|
};
|
||||||
|
|
||||||
class AbstractUITask {
|
// Every on-device UI (ui-new / ui-orig / ui-tiny) is MyMesh's Listener. The
|
||||||
|
// glue MyMesh used to run for the UI itself -- which message types show on
|
||||||
|
// screen, how a room post is labelled, which notification plays -- lives here
|
||||||
|
// now, so each UI variant keeps receiving the same newMsg()/addDMMsg()/
|
||||||
|
// notify() calls it always did.
|
||||||
|
class AbstractUITask : public MyMesh::Listener {
|
||||||
protected:
|
protected:
|
||||||
mesh::MainBoard* _board;
|
mesh::MainBoard* _board;
|
||||||
BaseSerialInterface* _serial;
|
BaseSerialInterface* _serial;
|
||||||
bool _connected;
|
bool _connected;
|
||||||
|
int _queue_len = 0; // last onQueueSizeChanged() -- newMsg()'s msgcount
|
||||||
|
|
||||||
AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) {
|
AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) {
|
||||||
_connected = false;
|
_connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasConnection() means "a BLE companion app is connected". Not isConnected()
|
||||||
|
// (a dual interface hardcodes that true as a USB send-fallback). Drives the BT
|
||||||
|
// status indicator, pairing PIN, and the GPX-export collision warning — all
|
||||||
|
// BLE-specific. The Auto buzzer mute / message-wake use isClientConnected()
|
||||||
|
// (BLE *or* an open USB port) directly instead. Each UI calls this at the top
|
||||||
|
// of its loop() (MyMesh::loop() used to push it at the end of its own).
|
||||||
|
void pollConnection() { setHasConnection(_serial->isBLEConnected()); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void setHasConnection(bool connected) {
|
void setHasConnection(bool connected) {
|
||||||
bool prev = _connected;
|
bool prev = _connected;
|
||||||
@@ -45,37 +61,6 @@ public:
|
|||||||
// An end-to-end ACK (CRC) arrived for one of our sent messages — drives the
|
// An end-to-end ACK (CRC) arrived for one of our sent messages — drives the
|
||||||
// DM delivery-status marker. Default no-op for UIs that don't track it.
|
// DM delivery-status marker. Default no-op for UIs that don't track it.
|
||||||
virtual void onMsgAck(uint32_t ack_crc) { (void)ack_crc; }
|
virtual void onMsgAck(uint32_t ack_crc) { (void)ack_crc; }
|
||||||
// A repeater rebroadcast of one of our channel sends was heard (seq from
|
|
||||||
// lastChannelRelaySeq()) — drives the channel "relayed into mesh" marker.
|
|
||||||
// May fire once per distinct repeater within earshot for the same seq;
|
|
||||||
// repeater_hash/hash_size (when given) is that repeater's path hash, so the
|
|
||||||
// UI can list every repeater that confirmed, not just "was it heard at all".
|
|
||||||
virtual void onChannelRelayed(uint32_t seq, const uint8_t* repeater_hash = nullptr, uint8_t hash_size = 0) {
|
|
||||||
(void)seq; (void)repeater_hash; (void)hash_size;
|
|
||||||
}
|
|
||||||
// Result of an on-device-UI-triggered MyMesh::sendRoomLogin() arrived.
|
|
||||||
// pub_key is the contact's key prefix (>=4 bytes valid); permissions is the
|
|
||||||
// room/repeater ACL byte (only meaningful when success is true).
|
|
||||||
virtual void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) { (void)pub_key; (void)success; (void)permissions; }
|
|
||||||
// Text reply to an on-device-UI-triggered MyMesh::sendAdminCommand() arrived
|
|
||||||
// (see AdminScreen). pub_key is the contact's key prefix (>=4 bytes valid).
|
|
||||||
virtual void onAdminReply(const uint8_t* pub_key, const char* text) { (void)pub_key; (void)text; }
|
|
||||||
// Bot action commands (!gps/!buzz, see MyMesh::botCommandReply) -- device
|
|
||||||
// state changes triggered remotely, gated by the bot_actions_* prefs.
|
|
||||||
// Default no-op so UI variants that don't wire these up just ignore them.
|
|
||||||
virtual void botSetGPS(bool on) { (void)on; }
|
|
||||||
virtual void botBuzz(int seconds) { (void)seconds; }
|
|
||||||
// !gpio1..!gpio4 (idx 1-4). botSetGPIO returns false if the pin isn't
|
|
||||||
// currently configured as an Output (or the board has none) -- lets the
|
|
||||||
// bot reply distinguish "set" from "ignored". botGetGPIO returns false if
|
|
||||||
// the pin is Off/unsupported; on true, fills is_output (current direction)
|
|
||||||
// and value (live level).
|
|
||||||
virtual bool botSetGPIO(int idx, bool on) { (void)idx; (void)on; return false; }
|
|
||||||
virtual bool botGetGPIO(int idx, bool& is_output, bool& value) { (void)idx; (void)is_output; (void)value; return false; }
|
|
||||||
// Analog read for pins that support it (GPIO1/GPIO2 on Wio Tracker L1 --
|
|
||||||
// the nRF52840's AIN0/AIN5). Returns false if the pin isn't in Analog mode
|
|
||||||
// or doesn't support it; on true, fills millivolts with the reading.
|
|
||||||
virtual bool botGetGPIOAnalog(int idx, int& millivolts) { (void)idx; (void)millivolts; return false; }
|
|
||||||
// True only when a BLE central is actually bonded/connected. On a dual
|
// True only when a BLE central is actually bonded/connected. On a dual
|
||||||
// (BLE+USB) interface hasConnection() is always true (USB counts), so use
|
// (BLE+USB) interface hasConnection() is always true (USB counts), so use
|
||||||
// this for BLE-specific UI like the pairing-PIN prompt.
|
// this for BLE-specific UI like the pairing-PIN prompt.
|
||||||
@@ -90,70 +75,72 @@ public:
|
|||||||
virtual void msgRead(int msgcount) = 0;
|
virtual void msgRead(int msgcount) = 0;
|
||||||
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) = 0;
|
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type = 0, const uint8_t* pub_key = nullptr) = 0;
|
||||||
virtual void notify(UIEventType t = UIEventType::none) = 0;
|
virtual void notify(UIEventType t = UIEventType::none) = 0;
|
||||||
// Returns the new entry's ring position (see MessageHistory::addChannelMsg),
|
|
||||||
// or -1 on a UI variant that doesn't track history (default no-op below) --
|
|
||||||
// callers that need it (to then arm a relay-echo tracker) should check for
|
|
||||||
// that instead of assuming a valid position. path/path_len (packed
|
|
||||||
// (hash_size-1)<<6|hop_count, same as mesh::Packet::path_len) is the hop
|
|
||||||
// route this incoming post actually took -- nullptr/0 when not known (e.g.
|
|
||||||
// this is our own outgoing post). own_message: our own post (e.g. mirrored
|
|
||||||
// from an app-originated send) -- never counted unread, unlike a genuinely
|
|
||||||
// incoming post the device's own UI doesn't happen to be showing right now.
|
|
||||||
virtual int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0,
|
|
||||||
const uint8_t* path = nullptr, uint8_t path_len = 0,
|
|
||||||
bool own_message = false) { return -1; }
|
|
||||||
// Convenience wrapper around addChannelMsg() for mirroring a channel post
|
|
||||||
// this device just sent itself (bot trigger/command reply, a !gps fix
|
|
||||||
// result, an app-originated send) into the on-device history. Always frames
|
|
||||||
// it with the literal "Me: " prefix -- the convention MessagesScreen uses
|
|
||||||
// (see its bubble-side check) to tell an outgoing post from an incoming
|
|
||||||
// one -- and always passes own_message=true, so a caller can't reintroduce
|
|
||||||
// the bug this replaced: three separate MyMeshBot.h call sites used to
|
|
||||||
// build "<node_name>: " instead, which rendered the reply as an incoming
|
|
||||||
// bubble from a stranger who happened to share the device's own name.
|
|
||||||
// text_len < 0 (default) means text is null-terminated; otherwise only the
|
|
||||||
// first text_len bytes are used (a source buffer isn't always guaranteed
|
|
||||||
// to be null-terminated, e.g. the app-originated mirror).
|
|
||||||
int addOwnChannelMsg(uint8_t channel_idx, const char* text, int text_len = -1, uint32_t timestamp = 0) {
|
|
||||||
char buf[MAX_TEXT_LEN + 8]; // "Me: "(4) + text(MAX_TEXT_LEN) + margin
|
|
||||||
if (text_len < 0) snprintf(buf, sizeof(buf), "Me: %s", text);
|
|
||||||
else snprintf(buf, sizeof(buf), "Me: %.*s", text_len, text);
|
|
||||||
return addChannelMsg(channel_idx, buf, timestamp, nullptr, 0, true);
|
|
||||||
}
|
|
||||||
// Arms the "relayed into mesh" tracker (a heard repeater rebroadcast) on the
|
|
||||||
// entry at ring position pos, e.g. right after addChannelMsg for a channel
|
|
||||||
// send this device just originated. seq: MyMesh::lastChannelRelaySeq().
|
|
||||||
virtual void armChannelRelay(int pos, uint32_t seq) {}
|
|
||||||
// ack_tag/ack_deadline_ms/resends: pending-ACK tracking for an outgoing DM
|
|
||||||
// (0 = none, e.g. incoming or "no ack expected") -- see MessageHistory::addDMMsg.
|
|
||||||
// path/path_len: the hop route an incoming DM actually took (nullptr/0 for
|
|
||||||
// outgoing -- a DM's delivery confirmation is the ack_tag above, not a path).
|
|
||||||
virtual void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0,
|
|
||||||
uint32_t ack_tag = 0, uint32_t ack_deadline_ms = 0, uint8_t resends = 0,
|
|
||||||
const uint8_t* path = nullptr, uint8_t path_len = 0) {}
|
|
||||||
// A node shared its current position via a [LOC] message. pub_key is the
|
|
||||||
// sender's key prefix for a verified DM share, or null for a channel share
|
|
||||||
// (keyed by name, best-effort). Default no-op so UI variants opt in.
|
|
||||||
virtual void onSharedLocation(const uint8_t* pub_key, const char* name,
|
|
||||||
int32_t lat_1e6, int32_t lon_1e6,
|
|
||||||
uint32_t ts, bool verified) {}
|
|
||||||
// A contact is gone — removed explicitly (companion app / CLI command) or
|
|
||||||
// silently auto-evicted to make room when the contact table is full. Lets
|
|
||||||
// UI state that references contacts by pubkey (favourite slots, the
|
|
||||||
// Locator/Live Share target) drop a reference that would otherwise dangle.
|
|
||||||
// Default no-op.
|
|
||||||
virtual void onContactRemoved(const uint8_t* pub_key) {}
|
|
||||||
// A channel slot was cleared (companion app set it to an empty secret).
|
|
||||||
// Drop any setting that referenced it by index — otherwise a new channel
|
|
||||||
// added later at the same slot would silently inherit the old one's bot/
|
|
||||||
// share target or notification melody. Default no-op.
|
|
||||||
virtual void onChannelRemoved(uint8_t channel_idx) {}
|
|
||||||
// Single choke point for every controlled power-down (low-battery auto-off,
|
// Single choke point for every controlled power-down (low-battery auto-off,
|
||||||
// long-press power-off, and now every board.reboot() caller too): flush
|
// long-press power-off, and every board.reboot() caller too): flush
|
||||||
// prefs/RTC/contacts/trail before the board actually goes down or restarts,
|
// prefs/RTC/contacts/trail before the board actually goes down or restarts,
|
||||||
// so no exit path can silently skip a pending write. restart=true reboots,
|
// so no exit path can silently skip a pending write. restart=true reboots,
|
||||||
// false powers off. Every UI variant (ui-new/ui-tiny/ui-orig) implements
|
// false powers off. Every UI variant (ui-new/ui-tiny/ui-orig) implements
|
||||||
// this the same way -- see each's UITask::shutdown().
|
// this the same way -- see each's UITask::shutdown().
|
||||||
virtual void shutdown(bool restart = false) = 0;
|
virtual void shutdown(bool restart = false) = 0;
|
||||||
virtual void loop() = 0;
|
virtual void loop() = 0;
|
||||||
|
|
||||||
|
// ---- MyMesh::Listener ----
|
||||||
|
void onQueueSizeChanged(int msgcount) override {
|
||||||
|
_queue_len = msgcount;
|
||||||
|
msgRead(msgcount); // only acts on 0 (queue drained by the app) in every UI
|
||||||
|
}
|
||||||
|
void onACKRecv(uint32_t ack_crc) override { onMsgAck(ack_crc); }
|
||||||
|
void onAdvertHeard(bool was_flood) override {
|
||||||
|
notify(was_flood ? UIEventType::advertReceivedFlood : UIEventType::advertReceivedZeroHop);
|
||||||
|
}
|
||||||
|
bool requestShutdown(bool restart) override { shutdown(restart); return true; }
|
||||||
|
|
||||||
|
void onMessageRecv(mesh::Packet *pkt, const ContactInfo &from, uint8_t txt_type, uint32_t sender_timestamp, const char* text) override {
|
||||||
|
onMessageRecvEx(pkt, from, txt_type, sender_timestamp, nullptr, 0, text);
|
||||||
|
}
|
||||||
|
void onMessageRecvEx(mesh::Packet *pkt, const ContactInfo &from, uint8_t txt_type, uint32_t sender_timestamp,
|
||||||
|
const uint8_t* extra, int extra_len, const char* text) override {
|
||||||
|
// we only want to show text messages on display, not cli data
|
||||||
|
if (!(txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN)) return;
|
||||||
|
uint8_t path_len = pkt->isRouteFlood() ? pkt->path_len : 0xFF;
|
||||||
|
newMsg(path_len, from.name, text, _queue_len, from.type, from.id.pub_key);
|
||||||
|
notify(from.type == ADV_TYPE_ROOM ? UIEventType::roomMessage : UIEventType::contactMessage);
|
||||||
|
// Add to the on-device conversation history. Room servers (ADV_TYPE_ROOM) are
|
||||||
|
// viewed through the same history list as chat contacts (keyed by the server's
|
||||||
|
// pubkey), so their posts must be stored too — otherwise an incoming room
|
||||||
|
// message fires the notification and reaches the app via the offline queue but
|
||||||
|
// never shows when the room is opened directly on the device.
|
||||||
|
if (from.type == ADV_TYPE_CHAT) {
|
||||||
|
addDMMsg(from.id.pub_key, false, text, sender_timestamp, 0, 0, 0, pkt->path, (uint8_t)pkt->path_len);
|
||||||
|
} else if (from.type == ADV_TYPE_ROOM) {
|
||||||
|
// A room carries many guests, so prefix the post with its author so the UI
|
||||||
|
// can attribute each line. The signed message's `extra` holds the sender's
|
||||||
|
// pubkey prefix; resolve it to a contact name, falling back to a short hex.
|
||||||
|
char labeled[MAX_TEXT_LEN + 40]; // room text + "Sender: " (history store truncates)
|
||||||
|
if (extra && extra_len >= 4) {
|
||||||
|
ContactInfo* sc = the_mesh.lookupContactByPubKey(extra, extra_len);
|
||||||
|
if (sc && sc->name[0])
|
||||||
|
snprintf(labeled, sizeof(labeled), "%s: %s", sc->name, text);
|
||||||
|
else
|
||||||
|
snprintf(labeled, sizeof(labeled), "%02X%02X: %s", extra[0], extra[1], text);
|
||||||
|
} else {
|
||||||
|
snprintf(labeled, sizeof(labeled), "%s", text);
|
||||||
|
}
|
||||||
|
addDMMsg(from.id.pub_key, false, labeled, sender_timestamp, 0, 0, 0, pkt->path, (uint8_t)pkt->path_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upstream-shaped entry point without the channel slot: MyMesh itself always
|
||||||
|
// calls the Ex variant below, so this only serves a caller that doesn't know
|
||||||
|
// the slot -- show and notify, but there's no history ring to file it under.
|
||||||
|
void onChannelMessageRecv(mesh::Packet *pkt, ChannelDetails& channel_details, const char* text) override {
|
||||||
|
notify(UIEventType::channelMessage);
|
||||||
|
newMsg(pkt->isRouteFlood() ? pkt->path_len : 0xFF, channel_details.name, text, _queue_len, 0);
|
||||||
|
}
|
||||||
|
void onChannelMessageRecvEx(mesh::Packet *pkt, uint8_t channel_idx, ChannelDetails& channel_details,
|
||||||
|
uint32_t timestamp, const char* text) override {
|
||||||
|
addChannelMsg(channel_idx, text, timestamp, pkt->path, (uint8_t)pkt->path_len);
|
||||||
|
notify(UIEventType::channelMessage);
|
||||||
|
newMsg(pkt->isRouteFlood() ? pkt->path_len : 0xFF, channel_details.name, text, _queue_len, 0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
#include <Arduino.h> // needed for PlatformIO
|
#include <Arduino.h> // needed for PlatformIO
|
||||||
#include <Mesh.h>
|
#include <Mesh.h>
|
||||||
|
|
||||||
#ifdef DISPLAY_CLASS
|
|
||||||
#include "helpers/ui/DisplayDriver.h"
|
|
||||||
#include "UITask.h"
|
|
||||||
#include <helpers/UTF8Helpers.h>
|
#include <helpers/UTF8Helpers.h>
|
||||||
|
#if defined(DISPLAY_CLASS) && defined(ENABLE_SCREENSHOT)
|
||||||
|
#include "helpers/ui/DisplayDriver.h"
|
||||||
|
#include "UITask.h" // screenshot debug command reads the UI's display
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CMD_APP_START 1
|
#define CMD_APP_START 1
|
||||||
@@ -373,7 +373,7 @@ uint8_t MyMesh::getAutoAddMaxHops() const {
|
|||||||
|
|
||||||
void MyMesh::onContactOverwrite(const uint8_t* pub_key) {
|
void MyMesh::onContactOverwrite(const uint8_t* pub_key) {
|
||||||
_store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage
|
_store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage
|
||||||
if (_ui) _ui->onContactRemoved(pub_key); // same cleanup as an explicit CMD_REMOVE_CONTACT
|
if (_listener) _listener->onContactRemoved(pub_key); // same cleanup as an explicit CMD_REMOVE_CONTACT
|
||||||
if (_serial->isConnected()) {
|
if (_serial->isConnected()) {
|
||||||
out_frame[0] = PUSH_CODE_CONTACT_DELETED;
|
out_frame[0] = PUSH_CODE_CONTACT_DELETED;
|
||||||
memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE);
|
memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE);
|
||||||
@@ -389,7 +389,7 @@ void MyMesh::onContactsFull() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::onDiscoveredAdvert(bool was_flood) {
|
void MyMesh::onDiscoveredAdvert(bool was_flood) {
|
||||||
if (_ui) _ui->notify(was_flood ? UIEventType::advertReceivedFlood : UIEventType::advertReceivedZeroHop);
|
if (_listener) _listener->onAdvertHeard(was_flood);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
||||||
@@ -424,6 +424,8 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
|||||||
p->path_len = mesh::Packet::copyPath(p->path, path, path_len);
|
p->path_len = mesh::Packet::copyPath(p->path, path, path_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_listener) _listener->onDiscoveredContact(contact, is_new, path_len, path);
|
||||||
|
|
||||||
if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[]
|
if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +467,7 @@ bool MyMesh::deleteContactByKey(const uint8_t* pub_key) {
|
|||||||
if (!recipient || !removeContact(*recipient)) return false;
|
if (!recipient || !removeContact(*recipient)) return false;
|
||||||
_store->deleteBlobByKey(pub_key, PUB_KEY_SIZE);
|
_store->deleteBlobByKey(pub_key, PUB_KEY_SIZE);
|
||||||
forgetRoomPassword(pub_key);
|
forgetRoomPassword(pub_key);
|
||||||
if (_ui) _ui->onContactRemoved(pub_key);
|
if (_listener) _listener->onContactRemoved(pub_key);
|
||||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -608,37 +610,12 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
|||||||
_serial->writeFrame(frame, 1);
|
_serial->writeFrame(frame, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DISPLAY_CLASS
|
if (_listener) {
|
||||||
// we only want to show text messages on display, not cli data
|
// Queue size first: the UI caps its room-unread count against it when the
|
||||||
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
|
// message itself arrives (upstream sends it after; order is harmless there).
|
||||||
if (should_display && _ui) {
|
_listener->onQueueSizeChanged(offline_queue_len);
|
||||||
_ui->newMsg(path_len, from.name, text, offline_queue_len, from.type, from.id.pub_key);
|
_listener->onMessageRecvEx(pkt, from, txt_type, sender_timestamp, extra, extra_len, text);
|
||||||
_ui->notify(from.type == ADV_TYPE_ROOM ? UIEventType::roomMessage : UIEventType::contactMessage);
|
|
||||||
// Add to the on-device conversation history. Room servers (ADV_TYPE_ROOM) are
|
|
||||||
// viewed through the same history list as chat contacts (keyed by the server's
|
|
||||||
// pubkey), so their posts must be stored too — otherwise an incoming room
|
|
||||||
// message fires the notification and reaches the app via the offline queue but
|
|
||||||
// never shows when the room is opened directly on the device.
|
|
||||||
if (from.type == ADV_TYPE_CHAT) {
|
|
||||||
_ui->addDMMsg(from.id.pub_key, false, text, sender_timestamp, 0, 0, 0, pkt->path, (uint8_t)pkt->path_len);
|
|
||||||
} else if (from.type == ADV_TYPE_ROOM) {
|
|
||||||
// A room carries many guests, so prefix the post with its author so the UI
|
|
||||||
// can attribute each line. The signed message's `extra` holds the sender's
|
|
||||||
// pubkey prefix; resolve it to a contact name, falling back to a short hex.
|
|
||||||
char labeled[MAX_TEXT_LEN + 40]; // room text + "Sender: " (history store truncates)
|
|
||||||
if (extra && extra_len >= 4) {
|
|
||||||
ContactInfo* sc = lookupContactByPubKey(extra, extra_len);
|
|
||||||
if (sc && sc->name[0])
|
|
||||||
snprintf(labeled, sizeof(labeled), "%s: %s", sc->name, text);
|
|
||||||
else
|
|
||||||
snprintf(labeled, sizeof(labeled), "%02X%02X: %s", extra[0], extra[1], text);
|
|
||||||
} else {
|
|
||||||
snprintf(labeled, sizeof(labeled), "%s", text);
|
|
||||||
}
|
|
||||||
_ui->addDMMsg(from.id.pub_key, false, labeled, sender_timestamp, 0, 0, 0, pkt->path, (uint8_t)pkt->path_len);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||||
@@ -668,7 +645,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
|||||||
// DIFFERENT repeater's independent echo of this same send can still
|
// DIFFERENT repeater's independent echo of this same send can still
|
||||||
// match here too -- onChannelRelayed()/markChannelRelayed() append each
|
// match here too -- onChannelRelayed()/markChannelRelayed() append each
|
||||||
// additionally heard repeater instead of just flipping a single flag.
|
// additionally heard repeater instead of just flipping a single flag.
|
||||||
if (_ui) {
|
if (_listener) {
|
||||||
uint8_t hash_size = packet->getPathHashSize();
|
uint8_t hash_size = packet->getPathHashSize();
|
||||||
uint8_t hop_count = packet->getPathHashCount();
|
uint8_t hop_count = packet->getPathHashCount();
|
||||||
// The repeater we just heard directly is always the LAST hop appended
|
// The repeater we just heard directly is always the LAST hop appended
|
||||||
@@ -676,7 +653,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
|||||||
// one within our own earshot, regardless of how many further hops
|
// one within our own earshot, regardless of how many further hops
|
||||||
// this same packet may go on to take beyond it.
|
// this same packet may go on to take beyond it.
|
||||||
const uint8_t* repeater_hash = hop_count > 0 ? &packet->path[(hop_count - 1) * hash_size] : nullptr;
|
const uint8_t* repeater_hash = hop_count > 0 ? &packet->path[(hop_count - 1) * hash_size] : nullptr;
|
||||||
_ui->onChannelRelayed(s.seq, repeater_hash, repeater_hash ? hash_size : 0);
|
_listener->onChannelRelayed(s.seq, repeater_hash, repeater_hash ? hash_size : 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -933,8 +910,8 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
|
|||||||
|
|
||||||
// Live position share: a verified DM, so key the track by the sender's pubkey.
|
// Live position share: a verified DM, so key the track by the sender's pubkey.
|
||||||
int32_t loc_lat, loc_lon;
|
int32_t loc_lat, loc_lon;
|
||||||
if (_ui && geo::parseLocShare(text, loc_lat, loc_lon)) {
|
if (_listener && geo::parseLocShare(text, loc_lat, loc_lon)) {
|
||||||
_ui->onSharedLocation(from.id.pub_key, from.name, loc_lat, loc_lon, sender_timestamp, true);
|
_listener->onSharedLocation(from.id.pub_key, from.name, loc_lat, loc_lon, sender_timestamp, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// hop count of the received message. getPathHashCount() (low 6 bits of path_len)
|
// hop count of the received message. getPathHashCount() (low 6 bits of path_len)
|
||||||
@@ -954,9 +931,9 @@ void MyMesh::onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint3
|
|||||||
// terminal), also hand the reply straight to the UI -- queueMessage() above
|
// terminal), also hand the reply straight to the UI -- queueMessage() above
|
||||||
// never displays TXT_TYPE_CLI_DATA on-device (see should_display), since that
|
// never displays TXT_TYPE_CLI_DATA on-device (see should_display), since that
|
||||||
// path also serves the app's terminal, which must keep working unaffected.
|
// path also serves the app's terminal, which must keep working unaffected.
|
||||||
if (_ui && ui_pending_admin_reply && memcmp(&ui_pending_admin_reply, from.id.pub_key, 4) == 0) {
|
if (_listener && ui_pending_admin_reply && memcmp(&ui_pending_admin_reply, from.id.pub_key, 4) == 0) {
|
||||||
ui_pending_admin_reply = 0;
|
ui_pending_admin_reply = 0;
|
||||||
_ui->onAdminReply(from.id.pub_key, text);
|
_listener->onAdminReply(from.id.pub_key, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -972,10 +949,10 @@ void MyMesh::onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uin
|
|||||||
// resolve that 4-byte prefix to a contact name and track by name. Unverified:
|
// resolve that 4-byte prefix to a contact name and track by name. Unverified:
|
||||||
// we only hold a 4-byte prefix here, not the full pubkey LiveTrack keys on.
|
// we only hold a 4-byte prefix here, not the full pubkey LiveTrack keys on.
|
||||||
int32_t loc_lat, loc_lon;
|
int32_t loc_lat, loc_lon;
|
||||||
if (_ui && geo::parseLocShare(text, loc_lat, loc_lon)) {
|
if (_listener && geo::parseLocShare(text, loc_lat, loc_lon)) {
|
||||||
ContactInfo* sc = sender_prefix ? lookupContactByPubKey(sender_prefix, 4) : nullptr;
|
ContactInfo* sc = sender_prefix ? lookupContactByPubKey(sender_prefix, 4) : nullptr;
|
||||||
const char* who = (sc && sc->name[0]) ? sc->name : from.name;
|
const char* who = (sc && sc->name[0]) ? sc->name : from.name;
|
||||||
_ui->onSharedLocation(nullptr, who, loc_lat, loc_lon, sender_timestamp, false);
|
_listener->onSharedLocation(nullptr, who, loc_lat, loc_lon, sender_timestamp, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Room-server auto-reply bot — only ever fires for the room server contact
|
// Room-server auto-reply bot — only ever fires for the room server contact
|
||||||
@@ -1030,28 +1007,26 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
|||||||
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
||||||
_serial->writeFrame(frame, 1);
|
_serial->writeFrame(frame, 1);
|
||||||
}
|
}
|
||||||
#ifdef DISPLAY_CLASS
|
if (_listener) {
|
||||||
if (_ui) _ui->addChannelMsg(channel_idx, text, timestamp, pkt->path, (uint8_t)pkt->path_len);
|
ChannelDetails channel_details;
|
||||||
if (_ui) _ui->notify(UIEventType::channelMessage);
|
if (!getChannel(channel_idx, channel_details)) {
|
||||||
const char *channel_name = "Unknown";
|
strcpy(channel_details.name, "Unknown");
|
||||||
ChannelDetails channel_details;
|
}
|
||||||
if (getChannel(channel_idx, channel_details)) {
|
_listener->onQueueSizeChanged(offline_queue_len); // first -- see queueMessage()
|
||||||
channel_name = channel_details.name;
|
_listener->onChannelMessageRecvEx(pkt, channel_idx, channel_details, timestamp, text);
|
||||||
}
|
}
|
||||||
if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len, 0);
|
|
||||||
|
|
||||||
// Live position share on a channel. The sender's identity here is only the
|
// Live position share on a channel. The sender's identity here is only the
|
||||||
// unsigned "name: msg" prefix (no pubkey), so track it by name — best-effort
|
// unsigned "name: msg" prefix (no pubkey), so track it by name — best-effort
|
||||||
// and unverified. parseLocShare requires an explicit [LOC] tag, so ordinary
|
// and unverified. parseLocShare requires an explicit [LOC] tag, so ordinary
|
||||||
// chatter is ignored.
|
// chatter is ignored.
|
||||||
int32_t loc_lat, loc_lon;
|
int32_t loc_lat, loc_lon;
|
||||||
if (_ui && geo::parseLocShare(text, loc_lat, loc_lon)) {
|
if (_listener && geo::parseLocShare(text, loc_lat, loc_lon)) {
|
||||||
char sender[32];
|
char sender[32];
|
||||||
const char* msg;
|
const char* msg;
|
||||||
botChannelSenderSplit(text, sender, sizeof(sender), &msg);
|
botChannelSenderSplit(text, sender, sizeof(sender), &msg);
|
||||||
_ui->onSharedLocation(nullptr, sender, loc_lat, loc_lon, timestamp, false);
|
_listener->onSharedLocation(nullptr, sender, loc_lat, loc_lon, timestamp, false);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// hop count for !hops (see onMessageRecv); not the wire path_len above.
|
// hop count for !hops (see onMessageRecv); not the wire path_len above.
|
||||||
uint8_t ch_hops = pkt->getPathHashCount();
|
uint8_t ch_hops = pkt->getPathHashCount();
|
||||||
@@ -1097,6 +1072,9 @@ void MyMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *
|
|||||||
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
||||||
_serial->writeFrame(frame, 1);
|
_serial->writeFrame(frame, 1);
|
||||||
}
|
}
|
||||||
|
if (_listener) {
|
||||||
|
_listener->onChannelDataRecv(pkt, channel, data_type, data, data_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
||||||
@@ -1144,6 +1122,8 @@ uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_tim
|
|||||||
memcpy(&reply[4], telemetry.getBuffer(), tlen);
|
memcpy(&reply[4], telemetry.getBuffer(), tlen);
|
||||||
return 4 + tlen;
|
return 4 + tlen;
|
||||||
}
|
}
|
||||||
|
} else if (_listener) {
|
||||||
|
return _listener->onUnhandledRequest(contact, sender_timestamp, data, len, reply);
|
||||||
}
|
}
|
||||||
return 0; // unknown
|
return 0; // unknown
|
||||||
}
|
}
|
||||||
@@ -1210,7 +1190,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
|||||||
} else {
|
} else {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
_ui->onRoomLoginResult(contact.id.pub_key, success, permissions);
|
if (_listener) _listener->onRoomLoginResult(contact.id.pub_key, success, permissions);
|
||||||
} else if (len > 4 && // check for status response
|
} else if (len > 4 && // check for status response
|
||||||
pending_status &&
|
pending_status &&
|
||||||
memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme
|
memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme
|
||||||
@@ -1248,6 +1228,8 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
|||||||
memcpy(&out_frame[i], &data[4], len - 4);
|
memcpy(&out_frame[i], &data[4], len - 4);
|
||||||
i += (len - 4);
|
i += (len - 4);
|
||||||
_serial->writeFrame(out_frame, i);
|
_serial->writeFrame(out_frame, i);
|
||||||
|
} else if (_listener && len > 4) {
|
||||||
|
_listener->onUnhandledResponse(contact, tag, &data[4], len - 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1532,7 +1514,7 @@ void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
|||||||
memcpy(r.pub_key, pub_key, PUB_KEY_SIZE);
|
memcpy(r.pub_key, pub_key, PUB_KEY_SIZE);
|
||||||
r.timestamp = getRTCClock()->getCurrentTime();
|
r.timestamp = getRTCClock()->getCurrentTime();
|
||||||
}
|
}
|
||||||
if (_ui) _ui->notify(packet->isRouteFlood() ? UIEventType::advertReceivedFlood : UIEventType::advertReceivedZeroHop);
|
if (_listener) _listener->onAdvertHeard(packet->isRouteFlood());
|
||||||
return; // our discover — don't forward to BLE app
|
return; // our discover — don't forward to BLE app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1564,6 +1546,8 @@ void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
|||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline");
|
MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_listener) _listener->onControlDataRecv(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
||||||
@@ -1584,6 +1568,9 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
|||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onRawDataRecv(), data received while app offline");
|
MESH_DEBUG_PRINTLN("onRawDataRecv(), data received while app offline");
|
||||||
}
|
}
|
||||||
|
if (_listener) {
|
||||||
|
_listener->onRawDataRecv(packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
||||||
@@ -1657,6 +1644,9 @@ void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code,
|
|||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onTraceRecv(), data received while app offline");
|
MESH_DEBUG_PRINTLN("onTraceRecv(), data received while app offline");
|
||||||
}
|
}
|
||||||
|
if (_listener) {
|
||||||
|
_listener->onTraceRecv(packet, tag, auth_code, flags, path_snrs, path_hashes, path_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t MyMesh::calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const {
|
uint32_t MyMesh::calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const {
|
||||||
@@ -1784,10 +1774,10 @@ void MyMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
|||||||
// device UI registers in BaseChatMesh's own ack table instead, so gating on
|
// device UI registers in BaseChatMesh's own ack table instead, so gating on
|
||||||
// `mine` here would leave every on-device DM stuck at ✗. The UI matches the
|
// `mine` here would leave every on-device DM stuck at ✗. The UI matches the
|
||||||
// crc against its own pending tag, so an unrelated/overheard ACK is ignored.
|
// crc against its own pending tag, so an unrelated/overheard ACK is ignored.
|
||||||
if (_ui) _ui->onMsgAck(ack_crc);
|
if (_listener) _listener->onACKRecv(ack_crc);
|
||||||
}
|
}
|
||||||
|
|
||||||
MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui)
|
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
|
// 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
|
// on-device Repeater toggle, queued retransmits (adverts/channel flood from
|
||||||
// neighbours) can now hold packet-pool slots for their retransmit delay window. A
|
// neighbours) can now hold packet-pool slots for their retransmit delay window. A
|
||||||
@@ -1795,7 +1785,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
|||||||
// silently drops every incoming packet — DMs and channels included — until a slot
|
// silently drops every incoming packet — DMs and channels included — until a slot
|
||||||
// frees up.
|
// frees up.
|
||||||
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
|
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _listener(NULL), _iter(0) {
|
||||||
_iter_started = false;
|
_iter_started = false;
|
||||||
_cli_rescue = false;
|
_cli_rescue = false;
|
||||||
for (int i = 0; i < RELAY_RING; i++) _relay[i].pending = false;
|
for (int i = 0; i < RELAY_RING; i++) _relay[i].pending = false;
|
||||||
@@ -1891,7 +1881,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::begin(bool has_display) {
|
void MyMesh::begin() {
|
||||||
BaseChatMesh::begin();
|
BaseChatMesh::begin();
|
||||||
|
|
||||||
if (!_store->loadMainIdentity(self_id)) {
|
if (!_store->loadMainIdentity(self_id)) {
|
||||||
@@ -1950,24 +1940,9 @@ void MyMesh::begin(bool has_display) {
|
|||||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
||||||
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||||
|
|
||||||
#ifdef BLE_PIN_CODE // 123456 by default
|
// The session PIN (random when a display can show it) is resolved by
|
||||||
if (_prefs.ble_pin == 0) {
|
// main.cpp, which knows whether there is one -- see resolveBLEPin().
|
||||||
#ifdef DISPLAY_CLASS
|
_active_ble_pin = _prefs.ble_pin;
|
||||||
if (has_display && BLE_PIN_CODE == 123456) {
|
|
||||||
StdRNG rng;
|
|
||||||
_active_ble_pin = rng.nextInt(100000, 999999); // random pin each session
|
|
||||||
} else {
|
|
||||||
_active_ble_pin = BLE_PIN_CODE; // otherwise static pin
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
_active_ble_pin = BLE_PIN_CODE; // otherwise static pin
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
_active_ble_pin = _prefs.ble_pin;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
_active_ble_pin = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
resetContacts();
|
resetContacts();
|
||||||
_store->loadContacts(this);
|
_store->loadContacts(this);
|
||||||
@@ -2025,6 +2000,9 @@ NodePrefs *MyMesh::getNodePrefs() {
|
|||||||
uint32_t MyMesh::getBLEPin() {
|
uint32_t MyMesh::getBLEPin() {
|
||||||
return _active_ble_pin;
|
return _active_ble_pin;
|
||||||
}
|
}
|
||||||
|
void MyMesh::setBLEPin(uint32_t active_pin) {
|
||||||
|
_active_ble_pin = active_pin;
|
||||||
|
}
|
||||||
|
|
||||||
struct FreqRange {
|
struct FreqRange {
|
||||||
uint32_t lower_freq, upper_freq;
|
uint32_t lower_freq, upper_freq;
|
||||||
@@ -2067,8 +2045,8 @@ bool MyMesh::setChannelLocal(uint8_t idx, const ChannelDetails& ch) {
|
|||||||
// An all-zero secret is this codebase's "empty slot" sentinel (same check
|
// An all-zero secret is this codebase's "empty slot" sentinel (same check
|
||||||
// loadChannels()/saveChannels() use) -- drop anything that referenced it by
|
// loadChannels()/saveChannels() use) -- drop anything that referenced it by
|
||||||
// index, the same way onContactRemoved() does for contacts.
|
// index, the same way onContactRemoved() does for contacts.
|
||||||
if (_ui && isAllZero(ch.channel.secret, sizeof(ch.channel.secret)))
|
if (_listener && isAllZero(ch.channel.secret, sizeof(ch.channel.secret)))
|
||||||
_ui->onChannelRemoved(idx);
|
_listener->onChannelRemoved(idx);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2185,9 +2163,9 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
// uses); resends stays 0 -- the app already owns its own resend/retry
|
// uses); resends stays 0 -- the app already owns its own resend/retry
|
||||||
// decision, so this only drives the on-screen status, never a second,
|
// decision, so this only drives the on-screen status, never a second,
|
||||||
// independent auto-resend from the device itself.
|
// independent auto-resend from the device itself.
|
||||||
if (_ui && txt_type == TXT_TYPE_PLAIN) {
|
if (_listener && txt_type == TXT_TYPE_PLAIN) {
|
||||||
uint32_t ack_deadline_ms = expected_ack ? (millis() + est_timeout + 4000) : 0;
|
uint32_t ack_deadline_ms = expected_ack ? (millis() + est_timeout + 4000) : 0;
|
||||||
_ui->addDMMsg(recipient->id.pub_key, true, text, msg_timestamp, expected_ack, ack_deadline_ms, 0);
|
_listener->addDMMsg(recipient->id.pub_key, true, text, msg_timestamp, expected_ack, ack_deadline_ms, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -2450,9 +2428,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
int out_len;
|
int out_len;
|
||||||
if ((out_len = getFromOfflineQueue(out_frame)) > 0) {
|
if ((out_len = getFromOfflineQueue(out_frame)) > 0) {
|
||||||
_serial->writeFrame(out_frame, out_len);
|
_serial->writeFrame(out_frame, out_len);
|
||||||
#ifdef DISPLAY_CLASS
|
if (_listener) _listener->onQueueSizeChanged(offline_queue_len);
|
||||||
if (_ui) _ui->msgRead(offline_queue_len);
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
out_frame[0] = RESP_CODE_NO_MORE_MESSAGES;
|
out_frame[0] = RESP_CODE_NO_MORE_MESSAGES;
|
||||||
_serial->writeFrame(out_frame, 1);
|
_serial->writeFrame(out_frame, 1);
|
||||||
@@ -2563,9 +2539,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
writeOKFrame();
|
writeOKFrame();
|
||||||
}
|
}
|
||||||
} else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
|
} else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
|
||||||
if (_ui) {
|
if (!(_listener && _listener->requestShutdown(true))) {
|
||||||
_ui->shutdown(true);
|
|
||||||
} else {
|
|
||||||
flushDirtyContacts();
|
flushDirtyContacts();
|
||||||
savePrefs();
|
savePrefs();
|
||||||
board.reboot();
|
board.reboot();
|
||||||
@@ -3150,7 +3124,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
#ifdef ENABLE_SCREENSHOT
|
#ifdef ENABLE_SCREENSHOT
|
||||||
void MyMesh::handleScreenshotRequest() {
|
void MyMesh::handleScreenshotRequest() {
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
UITask* ui_task = static_cast<UITask*>(getUITask());
|
UITask* ui_task = static_cast<UITask*>(getListener());
|
||||||
if (!ui_task || !ui_task->hasDisplay()) {
|
if (!ui_task || !ui_task->hasDisplay()) {
|
||||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||||
return;
|
return;
|
||||||
@@ -3387,9 +3361,7 @@ void MyMesh::checkCLIRescueCmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (strcmp(cli_command, "reboot") == 0) {
|
} else if (strcmp(cli_command, "reboot") == 0) {
|
||||||
if (_ui) {
|
if (!(_listener && _listener->requestShutdown(true))) {
|
||||||
_ui->shutdown(true);
|
|
||||||
} else {
|
|
||||||
flushDirtyContacts();
|
flushDirtyContacts();
|
||||||
savePrefs(); // flush any on-device setting change not yet persisted -- see UITask::shutdown()'s comment
|
savePrefs(); // flush any on-device setting change not yet persisted -- see UITask::shutdown()'s comment
|
||||||
board.reboot(); // doesn't return
|
board.reboot(); // doesn't return
|
||||||
@@ -3470,15 +3442,6 @@ void MyMesh::loop() {
|
|||||||
if (pkt) sendZeroHop(pkt);
|
if (pkt) sendZeroHop(pkt);
|
||||||
_next_auto_advert_ms = futureMillis(_prefs.advert_auto_interval_sec * 1000UL);
|
_next_auto_advert_ms = futureMillis(_prefs.advert_auto_interval_sec * 1000UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DISPLAY_CLASS
|
|
||||||
// hasConnection() means "a BLE companion app is connected". Not isConnected()
|
|
||||||
// (a dual interface hardcodes that true as a USB send-fallback). Drives the BT
|
|
||||||
// status indicator, pairing PIN, and the GPX-export collision warning — all
|
|
||||||
// BLE-specific. The Auto buzzer mute / message-wake use isClientConnected()
|
|
||||||
// (BLE *or* an open USB port) directly instead.
|
|
||||||
if (_ui) _ui->setHasConnection(_serial->isBLEConnected());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MyMesh::advert() {
|
bool MyMesh::advert() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Mesh.h>
|
#include <Mesh.h>
|
||||||
#include "AbstractUITask.h"
|
|
||||||
#include <helpers/ui/DisplayDriver.h>
|
#include <helpers/ui/DisplayDriver.h>
|
||||||
#include "Features.h" // FEAT_RX_POWERSAVE, used by MyMesh.cpp
|
#include "Features.h" // FEAT_RX_POWERSAVE, used by MyMesh.cpp
|
||||||
|
|
||||||
@@ -107,14 +106,101 @@ struct DiscoverResult {
|
|||||||
|
|
||||||
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
||||||
public:
|
public:
|
||||||
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL);
|
// Everything MyMesh tells the on-device UI goes through this one interface
|
||||||
|
// (set with setListener()); MyMesh itself has no UI concepts. The first
|
||||||
|
// block mirrors upstream meshcore-dev/MeshCore's MyMesh::Listener (PR #3431
|
||||||
|
// and follow-ups) with identical names/signatures, so upstream merges stay
|
||||||
|
// mechanical. The second block is this fork's extensions -- events and
|
||||||
|
// device controls upstream doesn't have (yet). Every method there defaults
|
||||||
|
// to a no-op, so an upstream-style listener still works unchanged.
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
// ---- upstream MyMesh::Listener ----
|
||||||
|
virtual void onMessageRecv(mesh::Packet *pkt, const ContactInfo &from, uint8_t txt_type, uint32_t sender_timestamp, const char* text) = 0;
|
||||||
|
virtual void onChannelMessageRecv(mesh::Packet *pkt, ChannelDetails& channel_details, const char* text) = 0;
|
||||||
|
virtual void onQueueSizeChanged(int msgcount) = 0;
|
||||||
|
virtual void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) { }
|
||||||
|
virtual void onControlDataRecv(const mesh::Packet* pkt) { }
|
||||||
|
virtual void onChannelDataRecv(mesh::Packet *pkt, const mesh::GroupChannel &channel, uint16_t data_type,
|
||||||
|
const uint8_t *data, size_t data_len) { }
|
||||||
|
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 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) { }
|
||||||
|
virtual ~Listener() { }
|
||||||
|
|
||||||
void begin(bool has_display);
|
// ---- Solo fork extensions ----
|
||||||
|
// onMessageRecv() plus the signed-message author prefix (a room post's
|
||||||
|
// real author; extra_len 0 otherwise). MyMesh calls this one; the
|
||||||
|
// default forwards to the upstream method.
|
||||||
|
virtual void onMessageRecvEx(mesh::Packet *pkt, const ContactInfo &from, uint8_t txt_type, uint32_t sender_timestamp,
|
||||||
|
const uint8_t* extra, int extra_len, const char* text) {
|
||||||
|
onMessageRecv(pkt, from, txt_type, sender_timestamp, text);
|
||||||
|
}
|
||||||
|
// onChannelMessageRecv() plus the channel slot and sender timestamp.
|
||||||
|
virtual void onChannelMessageRecvEx(mesh::Packet *pkt, uint8_t channel_idx, ChannelDetails& channel_details,
|
||||||
|
uint32_t timestamp, const char* text) {
|
||||||
|
onChannelMessageRecv(pkt, channel_details, text);
|
||||||
|
}
|
||||||
|
// Any advert heard (contact or discover response) -- sound/notify hook.
|
||||||
|
virtual void onAdvertHeard(bool was_flood) { }
|
||||||
|
// Our own channel send (app, bot) to mirror into on-device history.
|
||||||
|
// Returns the history ring position, or -1 if none is kept.
|
||||||
|
virtual int addChannelMsg(uint8_t channel_idx, const char* text, uint32_t timestamp = 0,
|
||||||
|
const uint8_t* path = nullptr, uint8_t path_len = 0,
|
||||||
|
bool own_message = false) { return -1; }
|
||||||
|
// Mirror of our own channel post, framed "Me: " (see MessagesScreen).
|
||||||
|
// text_len < 0: text is null-terminated; otherwise only text_len bytes.
|
||||||
|
int addOwnChannelMsg(uint8_t channel_idx, const char* text, int text_len = -1, uint32_t timestamp = 0) {
|
||||||
|
char buf[MAX_TEXT_LEN + 8]; // "Me: "(4) + text(MAX_TEXT_LEN) + margin
|
||||||
|
if (text_len < 0) snprintf(buf, sizeof(buf), "Me: %s", text);
|
||||||
|
else snprintf(buf, sizeof(buf), "Me: %.*s", text_len, text);
|
||||||
|
return addChannelMsg(channel_idx, buf, timestamp, nullptr, 0, true);
|
||||||
|
}
|
||||||
|
// Arm the "relayed into mesh" tracker on ring position pos (seq:
|
||||||
|
// lastChannelRelaySeq()), and report each repeater echo heard for it.
|
||||||
|
virtual void armChannelRelay(int pos, uint32_t seq) { }
|
||||||
|
virtual void onChannelRelayed(uint32_t seq, const uint8_t* repeater_hash = nullptr, uint8_t hash_size = 0) { }
|
||||||
|
// DM history entry: incoming (path = route taken) or our own outgoing
|
||||||
|
// send from the app/bot (ack_tag/ack_deadline_ms drive the marker).
|
||||||
|
virtual void addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text, uint32_t sender_timestamp = 0,
|
||||||
|
uint32_t ack_tag = 0, uint32_t ack_deadline_ms = 0, uint8_t resends = 0,
|
||||||
|
const uint8_t* path = nullptr, uint8_t path_len = 0) { }
|
||||||
|
// Results of on-device-UI-initiated requests (sendRoomLogin/sendAdminCommand).
|
||||||
|
virtual void onRoomLoginResult(const uint8_t* pub_key, bool success, uint8_t permissions) { }
|
||||||
|
virtual void onAdminReply(const uint8_t* pub_key, const char* text) { }
|
||||||
|
// A [LOC] share (pub_key set = verified DM; null = channel/room, by name).
|
||||||
|
virtual void onSharedLocation(const uint8_t* pub_key, const char* name,
|
||||||
|
int32_t lat_1e6, int32_t lon_1e6, uint32_t ts, bool verified) { }
|
||||||
|
// A contact / channel slot is gone -- drop references to it.
|
||||||
|
virtual void onContactRemoved(const uint8_t* pub_key) { }
|
||||||
|
virtual void onChannelRemoved(uint8_t channel_idx) { }
|
||||||
|
// Remote bot device actions (!gps/!buzz/!gpioN), gated by bot_actions_* prefs.
|
||||||
|
virtual void botSetGPS(bool on) { }
|
||||||
|
virtual void botBuzz(int seconds) { }
|
||||||
|
virtual bool botSetGPIO(int idx, bool on) { return false; }
|
||||||
|
virtual bool botGetGPIO(int idx, bool& is_output, bool& value) { return false; }
|
||||||
|
virtual bool botGetGPIOAnalog(int idx, int& millivolts) { return false; }
|
||||||
|
// Controlled reboot/power-off (CLI/app "reboot"): the listener flushes
|
||||||
|
// its state and restarts. Returns false if it doesn't handle it, and
|
||||||
|
// MyMesh then flushes and reboots by itself.
|
||||||
|
virtual bool requestShutdown(bool restart) { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void setListener(Listener* listener) { _listener = listener; }
|
||||||
|
Listener* getListener() const { return _listener; }
|
||||||
void startInterface(BaseSerialInterface &serial);
|
void startInterface(BaseSerialInterface &serial);
|
||||||
|
|
||||||
const char *getNodeName();
|
const char *getNodeName();
|
||||||
NodePrefs *getNodePrefs();
|
NodePrefs *getNodePrefs();
|
||||||
uint32_t getBLEPin();
|
uint32_t getBLEPin();
|
||||||
|
void setBLEPin(uint32_t active_pin);
|
||||||
|
|
||||||
void loop();
|
void loop();
|
||||||
void handleCmdFrame(size_t len);
|
void handleCmdFrame(size_t len);
|
||||||
@@ -176,7 +262,6 @@ public:
|
|||||||
void clearPingResult(uint32_t tag);
|
void clearPingResult(uint32_t tag);
|
||||||
PingResult* getPingResult(uint32_t tag);
|
PingResult* getPingResult(uint32_t tag);
|
||||||
PingCallback getPingCallback() const { return _ping_callback; }
|
PingCallback getPingCallback() const { return _ping_callback; }
|
||||||
AbstractUITask* getUITask() const { return _ui; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float getAirtimeBudgetFactor() const override;
|
float getAirtimeBudgetFactor() const override;
|
||||||
@@ -257,9 +342,9 @@ public:
|
|||||||
// GroupChannel&, ...) already calls trackRelaySend() unconditionally, so
|
// GroupChannel&, ...) already calls trackRelaySend() unconditionally, so
|
||||||
// lastChannelRelaySeq() is already the seq for the send just made.
|
// lastChannelRelaySeq() is already the seq for the send just made.
|
||||||
int mirrorOwnChannelMsg(uint8_t channel_idx, const char* text, int text_len = -1, uint32_t timestamp = 0) {
|
int mirrorOwnChannelMsg(uint8_t channel_idx, const char* text, int text_len = -1, uint32_t timestamp = 0) {
|
||||||
if (!_ui) return -1;
|
if (!_listener) return -1;
|
||||||
int pos = _ui->addOwnChannelMsg(channel_idx, text, text_len, timestamp);
|
int pos = _listener->addOwnChannelMsg(channel_idx, text, text_len, timestamp);
|
||||||
if (pos >= 0) _ui->armChannelRelay(pos, lastChannelRelaySeq());
|
if (pos >= 0) _listener->armChannelRelay(pos, lastChannelRelaySeq());
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
@@ -279,7 +364,7 @@ public:
|
|||||||
// The room server's ACL grants permission per-identity (self_id), not per
|
// The room server's ACL grants permission per-identity (self_id), not per
|
||||||
// command source, so this reuses the same sendLogin() the BLE CMD_SEND_LOGIN
|
// command source, so this reuses the same sendLogin() the BLE CMD_SEND_LOGIN
|
||||||
// path uses; the async result lands in onContactResponse() and is pushed to
|
// path uses; the async result lands in onContactResponse() and is pushed to
|
||||||
// the UI via AbstractUITask::onRoomLoginResult().
|
// the UI via Listener::onRoomLoginResult().
|
||||||
bool sendRoomLogin(const ContactInfo& contact, const char* password, uint32_t& est_timeout) {
|
bool sendRoomLogin(const ContactInfo& contact, const char* password, uint32_t& est_timeout) {
|
||||||
if (sendLogin(contact, password, est_timeout) == MSG_SEND_FAILED) return false;
|
if (sendLogin(contact, password, est_timeout) == MSG_SEND_FAILED) return false;
|
||||||
clearPendingReqs();
|
clearPendingReqs();
|
||||||
@@ -521,7 +606,7 @@ private:
|
|||||||
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
|
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
|
||||||
uint32_t pending_req; // pending _BINARY_REQ
|
uint32_t pending_req; // pending _BINARY_REQ
|
||||||
BaseSerialInterface *_serial;
|
BaseSerialInterface *_serial;
|
||||||
AbstractUITask* _ui;
|
Listener* _listener;
|
||||||
|
|
||||||
ContactsIterator _iter;
|
ContactsIterator _iter;
|
||||||
uint32_t _iter_filter_since;
|
uint32_t _iter_filter_since;
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ void MyMesh::tryBotReplyDM(const ContactInfo& from, const char* text, uint8_t ho
|
|||||||
botDmRecord(from.id.pub_key);
|
botDmRecord(from.id.pub_key);
|
||||||
_bot_reply_count++;
|
_bot_reply_count++;
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->addDMMsg(from.id.pub_key, true, expanded);
|
if (_listener) _listener->addDMMsg(from.id.pub_key, true, expanded);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ void MyMesh::tryBotReplyRoom(const ContactInfo& from, const uint8_t* sender_pref
|
|||||||
_bot_last_room_reply_ms = millis();
|
_bot_last_room_reply_ms = millis();
|
||||||
_bot_reply_count++;
|
_bot_reply_count++;
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->addDMMsg(from.id.pub_key, true, expanded);
|
if (_listener) _listener->addDMMsg(from.id.pub_key, true, expanded);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ bool MyMesh::botCommandReply(const char* cmd, const char* arg, const char* arg2,
|
|||||||
bool off = !strcmp(arg, "off");
|
bool off = !strcmp(arg, "off");
|
||||||
if (!on && !off) { snprintf(out, out_len, "gpio%d: on|off?", idx); return true; }
|
if (!on && !off) { snprintf(out, out_len, "gpio%d: on|off?", idx); return true; }
|
||||||
bool is_out = false, val = false;
|
bool is_out = false, val = false;
|
||||||
if (_ui && _ui->botGetGPIO(idx, is_out, val) && is_out) {
|
if (_listener && _listener->botGetGPIO(idx, is_out, val) && is_out) {
|
||||||
_bot_gpio_action[idx - 1] = on ? 1 : 0; // deferred -- see applyPendingBotActions()
|
_bot_gpio_action[idx - 1] = on ? 1 : 0; // deferred -- see applyPendingBotActions()
|
||||||
snprintf(out, out_len, "gpio%d: %s", idx, on ? "on" : "off");
|
snprintf(out, out_len, "gpio%d: %s", idx, on ? "on" : "off");
|
||||||
} else {
|
} else {
|
||||||
@@ -344,9 +344,9 @@ bool MyMesh::botCommandReply(const char* cmd, const char* arg, const char* arg2,
|
|||||||
}
|
}
|
||||||
int mv = 0;
|
int mv = 0;
|
||||||
bool is_out = false, val = false;
|
bool is_out = false, val = false;
|
||||||
if (_ui && _ui->botGetGPIOAnalog(idx, mv)) {
|
if (_listener && _listener->botGetGPIOAnalog(idx, mv)) {
|
||||||
snprintf(out, out_len, "gpio%d: %dmV", idx, mv);
|
snprintf(out, out_len, "gpio%d: %dmV", idx, mv);
|
||||||
} else if (_ui && _ui->botGetGPIO(idx, is_out, val)) {
|
} else if (_listener && _listener->botGetGPIO(idx, is_out, val)) {
|
||||||
snprintf(out, out_len, "gpio%d: %s %s", idx, is_out ? "out" : "in", val ? "on" : "off");
|
snprintf(out, out_len, "gpio%d: %s %s", idx, is_out ? "out" : "in", val ? "on" : "off");
|
||||||
} else {
|
} else {
|
||||||
snprintf(out, out_len, "gpio%d: off", idx);
|
snprintf(out, out_len, "gpio%d: off", idx);
|
||||||
@@ -457,7 +457,7 @@ bool MyMesh::tryBotCommand(const ContactInfo& from, const char* text, uint8_t ho
|
|||||||
botDmRecord(from.id.pub_key);
|
botDmRecord(from.id.pub_key);
|
||||||
_bot_reply_count++;
|
_bot_reply_count++;
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->addDMMsg(from.id.pub_key, true, out);
|
if (_listener) _listener->addDMMsg(from.id.pub_key, true, out);
|
||||||
#endif
|
#endif
|
||||||
if (_locfix_requested) startLocFix(LOCFIX_DEST_CONTACT, from.id.pub_key, 0);
|
if (_locfix_requested) startLocFix(LOCFIX_DEST_CONTACT, from.id.pub_key, 0);
|
||||||
applyPendingBotActions();
|
applyPendingBotActions();
|
||||||
@@ -524,7 +524,7 @@ bool MyMesh::tryBotRoomCommand(const ContactInfo& from, const uint8_t* sender_pr
|
|||||||
_bot_last_room_reply_ms = millis();
|
_bot_last_room_reply_ms = millis();
|
||||||
_bot_reply_count++;
|
_bot_reply_count++;
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->addDMMsg(from.id.pub_key, true, out);
|
if (_listener) _listener->addDMMsg(from.id.pub_key, true, out);
|
||||||
#endif
|
#endif
|
||||||
if (_locfix_requested) startLocFix(LOCFIX_DEST_CONTACT, from.id.pub_key, 0);
|
if (_locfix_requested) startLocFix(LOCFIX_DEST_CONTACT, from.id.pub_key, 0);
|
||||||
applyPendingBotActions();
|
applyPendingBotActions();
|
||||||
@@ -541,11 +541,11 @@ bool MyMesh::tryBotRoomCommand(const ContactInfo& from, const uint8_t* sender_pr
|
|||||||
// MyMesh.h). !gps fix's startLocFix() is armed separately by the caller (it
|
// MyMesh.h). !gps fix's startLocFix() is armed separately by the caller (it
|
||||||
// needs the destination, which this function doesn't have).
|
// needs the destination, which this function doesn't have).
|
||||||
void MyMesh::applyPendingBotActions() {
|
void MyMesh::applyPendingBotActions() {
|
||||||
if (_bot_gps_action_pending && _ui) _ui->botSetGPS(_bot_gps_action_on);
|
if (_bot_gps_action_pending && _listener) _listener->botSetGPS(_bot_gps_action_on);
|
||||||
if (_bot_buzz_action_secs > 0 && _ui) _ui->botBuzz(_bot_buzz_action_secs);
|
if (_bot_buzz_action_secs > 0 && _listener) _listener->botBuzz(_bot_buzz_action_secs);
|
||||||
if (_bot_advert_action_pending) advert();
|
if (_bot_advert_action_pending) advert();
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if (_bot_gpio_action[i] >= 0 && _ui) _ui->botSetGPIO(i + 1, _bot_gpio_action[i] != 0);
|
if (_bot_gpio_action[i] >= 0 && _listener) _listener->botSetGPIO(i + 1, _bot_gpio_action[i] != 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +578,7 @@ void MyMesh::startLocFix(uint8_t dest_type, const uint8_t* pub_key, uint8_t chan
|
|||||||
_loc_fix.averaging_until_ms = 0;
|
_loc_fix.averaging_until_ms = 0;
|
||||||
_loc_fix.deadline_ms = futureMillis(_locfix_requested_timeout_ms);
|
_loc_fix.deadline_ms = futureMillis(_locfix_requested_timeout_ms);
|
||||||
_loc_fix.gps_was_on = (_prefs.gps_enabled != 0);
|
_loc_fix.gps_was_on = (_prefs.gps_enabled != 0);
|
||||||
if (!_loc_fix.gps_was_on && _ui) _ui->botSetGPS(true);
|
if (!_loc_fix.gps_was_on && _listener) _listener->botSetGPS(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// !gps fix state machine, ticked every MyMesh::loop() while _loc_fix.active.
|
// !gps fix state machine, ticked every MyMesh::loop() while _loc_fix.active.
|
||||||
@@ -627,7 +627,7 @@ void MyMesh::tickLocFix() {
|
|||||||
}
|
}
|
||||||
sendLocFixResult(msg);
|
sendLocFixResult(msg);
|
||||||
|
|
||||||
if (!_loc_fix.gps_was_on && _ui) _ui->botSetGPS(false);
|
if (!_loc_fix.gps_was_on && _listener) _listener->botSetGPS(false);
|
||||||
_loc_fix.active = false;
|
_loc_fix.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,7 +659,7 @@ void MyMesh::sendLocFixResult(const char* msg) {
|
|||||||
else botDmRecord(c->id.pub_key);
|
else botDmRecord(c->id.pub_key);
|
||||||
_bot_reply_count++;
|
_bot_reply_count++;
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
if (_ui) _ui->addDMMsg(c->id.pub_key, true, msg);
|
if (_listener) _listener->addDMMsg(c->id.pub_key, true, msg);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,11 +135,7 @@ static uint32_t _atoi(const char* sp) {
|
|||||||
|
|
||||||
StdRNG fast_rng;
|
StdRNG fast_rng;
|
||||||
SimpleMeshTables tables;
|
SimpleMeshTables tables;
|
||||||
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
|
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store);
|
||||||
#ifdef DISPLAY_CLASS
|
|
||||||
, &ui_task
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
|
|
||||||
/* END GLOBAL OBJECTS */
|
/* END GLOBAL OBJECTS */
|
||||||
|
|
||||||
@@ -147,6 +143,29 @@ void halt() {
|
|||||||
while (1) ;
|
while (1) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Session BLE PIN, resolved after the_mesh.begin() has loaded prefs and before
|
||||||
|
// the serial interface starts advertising with it. A saved PIN wins; otherwise
|
||||||
|
// the default 123456 becomes a random per-session PIN when there's a display to
|
||||||
|
// show it on (moved here from MyMesh::begin(), as upstream did -- MyMesh has no
|
||||||
|
// display concept).
|
||||||
|
static void resolveBLEPin(bool has_display) {
|
||||||
|
#ifdef BLE_PIN_CODE // 123456 by default
|
||||||
|
if (the_mesh.getNodePrefs()->ble_pin == 0) {
|
||||||
|
if (has_display && BLE_PIN_CODE == 123456) {
|
||||||
|
StdRNG rng;
|
||||||
|
the_mesh.setBLEPin(rng.nextInt(100000, 999999)); // random pin each session
|
||||||
|
} else {
|
||||||
|
the_mesh.setBLEPin(BLE_PIN_CODE); // otherwise static pin
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
the_mesh.setBLEPin(the_mesh.getNodePrefs()->ble_pin);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)has_display;
|
||||||
|
the_mesh.setBLEPin(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* WIFI RECONNECT TRACKERS */
|
/* WIFI RECONNECT TRACKERS */
|
||||||
#if defined(ESP32) && defined(WIFI_SSID)
|
#if defined(ESP32) && defined(WIFI_SSID)
|
||||||
bool wifi_needs_reconnect = false;
|
bool wifi_needs_reconnect = false;
|
||||||
@@ -193,7 +212,8 @@ void setup() {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
store.begin();
|
store.begin();
|
||||||
the_mesh.begin(
|
the_mesh.begin();
|
||||||
|
resolveBLEPin(
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
disp != NULL
|
disp != NULL
|
||||||
#else
|
#else
|
||||||
@@ -212,7 +232,8 @@ void setup() {
|
|||||||
#elif defined(RP2040_PLATFORM)
|
#elif defined(RP2040_PLATFORM)
|
||||||
LittleFS.begin();
|
LittleFS.begin();
|
||||||
store.begin();
|
store.begin();
|
||||||
the_mesh.begin(
|
the_mesh.begin();
|
||||||
|
resolveBLEPin(
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
disp != NULL
|
disp != NULL
|
||||||
#else
|
#else
|
||||||
@@ -238,7 +259,8 @@ void setup() {
|
|||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
SPIFFS.begin(true);
|
SPIFFS.begin(true);
|
||||||
store.begin();
|
store.begin();
|
||||||
the_mesh.begin(
|
the_mesh.begin();
|
||||||
|
resolveBLEPin(
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
disp != NULL
|
disp != NULL
|
||||||
#else
|
#else
|
||||||
@@ -277,7 +299,8 @@ void setup() {
|
|||||||
#elif defined(SIM_PLATFORM)
|
#elif defined(SIM_PLATFORM)
|
||||||
// sim_fs already exists/mkdir'd itself in its constructor above.
|
// sim_fs already exists/mkdir'd itself in its constructor above.
|
||||||
store.begin();
|
store.begin();
|
||||||
the_mesh.begin(
|
the_mesh.begin();
|
||||||
|
resolveBLEPin(
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
disp != NULL
|
disp != NULL
|
||||||
#else
|
#else
|
||||||
@@ -303,6 +326,7 @@ void setup() {
|
|||||||
if (disp && the_mesh.getNodePrefs())
|
if (disp && the_mesh.getNodePrefs())
|
||||||
disp->setBrightness(the_mesh.getNodePrefs()->display_brightness);
|
disp->setBrightness(the_mesh.getNodePrefs()->display_brightness);
|
||||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
||||||
|
the_mesh.setListener(&ui_task);
|
||||||
#ifdef DISPLAY_HAS_BUSY_PUMP
|
#ifdef DISPLAY_HAS_BUSY_PUMP
|
||||||
if (disp) disp->setBusyPumpFn(pumpRadioDuringDisplayBusyWait, nullptr);
|
if (disp) disp->setBusyPumpFn(pumpRadioDuringDisplayBusyWait, nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1843,7 +1843,7 @@ void UITask::handlePingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back
|
|||||||
|
|
||||||
// Static ping callback (for MyMesh)
|
// Static ping callback (for MyMesh)
|
||||||
static void onPingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) {
|
static void onPingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) {
|
||||||
AbstractUITask* ui = the_mesh.getUITask();
|
MyMesh::Listener* ui = the_mesh.getListener();
|
||||||
if (ui) {
|
if (ui) {
|
||||||
UITask* task = static_cast<UITask*>(ui);
|
UITask* task = static_cast<UITask*>(ui);
|
||||||
task->handlePingResult(tag, snr_out_x4, snr_back_x4, rtt_ms);
|
task->handlePingResult(tag, snr_out_x4, snr_back_x4, rtt_ms);
|
||||||
@@ -2642,6 +2642,7 @@ void UITask::pollHallSensor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UITask::loop() {
|
void UITask::loop() {
|
||||||
|
pollConnection(); // BLE link state -> hasConnection() (see AbstractUITask)
|
||||||
// Background delivery: resend pending on-device DMs whose ACK timed out, and
|
// Background delivery: resend pending on-device DMs whose ACK timed out, and
|
||||||
// finalise the ✗ marker — runs regardless of which screen is active.
|
// finalise the ✗ marker — runs regardless of which screen is active.
|
||||||
((MessagesScreen*)messages_screen)->tickDmResends();
|
((MessagesScreen*)messages_screen)->tickDmResends();
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ switch(t){
|
|||||||
buzzer.play("ack:d=32,o=8,b=120:c");
|
buzzer.play("ack:d=32,o=8,b=120:c");
|
||||||
break;
|
break;
|
||||||
case UIEventType::roomMessage:
|
case UIEventType::roomMessage:
|
||||||
case UIEventType::advertReceived:
|
case UIEventType::advertReceivedFlood:
|
||||||
|
case UIEventType::advertReceivedZeroHop:
|
||||||
case UIEventType::none:
|
case UIEventType::none:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -314,6 +315,7 @@ void UITask::shutdown(bool restart){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UITask::loop() {
|
void UITask::loop() {
|
||||||
|
pollConnection(); // BLE link state -> hasConnection() (see AbstractUITask)
|
||||||
#ifdef PIN_USER_BTN
|
#ifdef PIN_USER_BTN
|
||||||
if (_userButton) {
|
if (_userButton) {
|
||||||
_userButton->update();
|
_userButton->update();
|
||||||
|
|||||||
@@ -581,6 +581,7 @@ bool UITask::isButtonPressed() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UITask::loop() {
|
void UITask::loop() {
|
||||||
|
pollConnection(); // BLE link state -> hasConnection() (see AbstractUITask)
|
||||||
char c = 0;
|
char c = 0;
|
||||||
#if UI_HAS_JOYSTICK
|
#if UI_HAS_JOYSTICK
|
||||||
int ev = user_btn.check();
|
int ev = user_btn.check();
|
||||||
|
|||||||
Reference in New Issue
Block a user