audiobook player guide updated.

Summary of what changed in each file, all confined to the change points shown in the diffs:
ChannelScreen.h — ChannelMessage gains a session-only scope_idx (initialised to 0xFF in the constructor and explicitly reset to 0xFF on SD load, since region is not persisted); addMessage gains a trailing defaulted scope_idx and stores it. The message-list line now renders (Xh)(Xb) Xm, with the byte figure taken from path_len's upper bits for floods and from the_mesh.getNodePrefs()->path_hash_mode + 1 for the 0xFF/0 sentinels. The path overlay shows Route: ... (N-byte) and a new Region: line (name, or (reg unknown), or nothing when unscoped).
MyMesh.h / .cpp — a fixed 28-entry SCOPE_NAMES table, a _scope_keys array precomputed once at the end of begin() via initScopeKeys(), resolveScopeIndex() (matches pkt->transport_codes[0] against the candidates; 0xFF unscoped, 0xFE unmatched), and the public getScopeName() accessor. onChannelMessageRecv resolves the index and passes it to newMsg.
AbstractUITask.h / UITask.h / UITask.cpp — newMsg gains a trailing uint8_t scope_idx = 0xFF; only the channel addMessage call forwards it. DMs and sent echoes keep the default, so they stay unscoped.
MsgFileRecord and the SD save/load format are untouched, so there's no version bump.
This commit is contained in:
pelgraine
2026-06-06 20:42:21 +10:00
parent 451f4b01f3
commit ea98eaead4
7 changed files with 201 additions and 24 deletions
+2 -1
View File
@@ -42,7 +42,8 @@ public:
void disableSerial() { _serial->disable(); }
virtual void msgRead(int msgcount) = 0;
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount,
const uint8_t* path = nullptr, int8_t snr = 0) = 0;
const uint8_t* path = nullptr, int8_t snr = 0,
uint8_t scope_idx = 0xFF) = 0; // 0xFF = unscoped
virtual void notify(UIEventType t = UIEventType::none) = 0;
virtual void loop() = 0;
virtual void showAlert(const char* text, int duration_millis) {}
+41 -1
View File
@@ -670,6 +670,43 @@ const char* MyMesh::getChannelScopeName(const mesh::GroupChannel& channel) {
return nullptr;
}
// --- Region scope candidate list (display-only resolution of incoming channel msgs) ---
// Fixed set of nameable regions. Keys are precomputed once at boot in initScopeKeys().
const char* const MyMesh::SCOPE_NAMES[MyMesh::SCOPE_COUNT] = {
"au",
"au-nsw", "au-vic", "au-act", "au-sa", "au-wa", "au-tas", "au-nt", "au-qld",
"au-nsw-syd", "au-nsw-bhs", "au-nsw-hun", "au-nsw-ntl", "au-nsw-wol",
"au-nsw-cw", "au-nsw-wsi", "au-nsw-syd-iwc",
"au-vic-mel", "au-vic-east", "au-vic-north", "au-vic-west",
"au-act-cbr",
"au-tas-hob",
"au-qld-bne",
"au-wa-per", "au-wa-fre", "au-wa-buy",
"au-hume"
};
void MyMesh::initScopeKeys() {
for (uint8_t i = 0; i < SCOPE_COUNT; i++) {
deriveScopeKey(SCOPE_NAMES[i], _scope_keys[i]);
}
}
uint8_t MyMesh::resolveScopeIndex(const mesh::Packet* pkt) const {
if (!pkt || !pkt->hasTransportCodes()) return 0xFF; // unscoped
uint16_t code = pkt->transport_codes[0];
for (uint8_t i = 0; i < SCOPE_COUNT; i++) {
if (_scope_keys[i].calcTransportCode(pkt) == code) return i;
}
return 0xFE; // scoped, but not one of the known regions
}
const char* MyMesh::getScopeName(uint8_t idx) const {
if (idx == 0xFF) return nullptr; // unscoped -- no region line
if (idx == 0xFE) return "(reg unknown)"; // scoped but unmatched
if (idx < SCOPE_COUNT) return SCOPE_NAMES[idx];
return nullptr;
}
void MyMesh::onMessageRecv(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
@@ -816,7 +853,8 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
}
if (_ui) {
const uint8_t* msg_path = (pkt->isRouteFlood() && pkt->path_len > 0) ? pkt->path : nullptr;
_ui->newMsg(path_len, channel_name, text, offline_queue_len, msg_path, pkt->_snr);
uint8_t scope_idx = resolveScopeIndex(pkt);
_ui->newMsg(path_len, channel_name, text, offline_queue_len, msg_path, pkt->_snr, scope_idx);
if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled
}
#endif
@@ -1530,6 +1568,8 @@ void MyMesh::begin(bool has_display) {
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm);
initScopeKeys(); // precompute region-scope transport keys for incoming-message display
}
const char *MyMesh::getNodeName() {
+16
View File
@@ -185,6 +185,10 @@ public:
bool deriveScopeKey(const char* scopeName, TransportKey& keyOut);
// Look up per-channel scope name by GroupChannel secret match. Returns nullptr if no scope set.
const char* getChannelScopeName(const mesh::GroupChannel& channel);
// Resolve a region scope index (as produced by resolveScopeIndex during onChannelMessageRecv)
// to a display name. Returns nullptr for unscoped, "(reg unknown)" for scoped-but-unmatched,
// otherwise the candidate region name. Used by the channel path-detail overlay.
const char* getScopeName(uint8_t idx) const;
protected:
@@ -308,6 +312,18 @@ private:
TransportKey send_scope;
// --- Region scope resolution for incoming channel messages (display only) ---
// A received scoped flood/direct packet carries a one-way transport code. We match
// it against this fixed candidate list, whose keys are precomputed once at boot in
// initScopeKeys(). resolveScopeIndex() returns an index into SCOPE_NAMES, 0xFF for
// unscoped packets, or 0xFE for scoped-but-unmatched. Result is held in RAM only
// (per-message), never persisted.
static const uint8_t SCOPE_COUNT = 28;
static const char* const SCOPE_NAMES[SCOPE_COUNT];
TransportKey _scope_keys[SCOPE_COUNT];
void initScopeKeys();
uint8_t resolveScopeIndex(const mesh::Packet* pkt) const;
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
uint8_t out_frame[MAX_FRAME_SIZE + 1];
CayenneLPP telemetry;
@@ -68,6 +68,7 @@ public:
uint8_t path[MSG_PATH_MAX]; // Repeater hop hashes
char text[CHANNEL_MSG_TEXT_LEN];
bool valid;
uint8_t scope_idx; // Region scope index for display (session only, 0xFF = unscoped). Not persisted.
};
// Simple hash for DM peer matching
@@ -139,6 +140,7 @@ public:
_messages[i].valid = false;
_messages[i].dm_peer_hash = 0;
memset(_messages[i].path, 0, MSG_PATH_MAX);
_messages[i].scope_idx = 0xFF;
}
// Initialize unread counts
memset(_unread, 0, sizeof(_unread));
@@ -151,7 +153,7 @@ public:
// suppressUnread: if true, do not increment the unread counter for this message
void addMessage(uint8_t channel_idx, uint8_t path_len, const char* sender, const char* text,
const uint8_t* path_bytes = nullptr, int8_t snr = 0, const char* peer_name = nullptr,
bool suppressUnread = false) {
bool suppressUnread = false, uint8_t scope_idx = 0xFF) {
// Move to next slot in circular buffer
_newestIdx = (_newestIdx + 1) % CHANNEL_MSG_HISTORY_SIZE;
@@ -161,6 +163,7 @@ public:
msg->channel_idx = channel_idx;
msg->snr = snr;
msg->valid = true;
msg->scope_idx = scope_idx;
// Set DM peer hash for conversation filtering
if (channel_idx == 0xFF) {
@@ -545,6 +548,7 @@ public:
_messages[i].dm_peer_hash = rec.dm_peer_hash;
memcpy(_messages[i].path, rec.path, MSG_PATH_MAX);
memcpy(_messages[i].text, rec.text, CHANNEL_MSG_TEXT_LEN);
_messages[i].scope_idx = 0xFF; // region scope is session-only, not stored on SD
if (_messages[i].valid) loaded++;
}
@@ -845,11 +849,21 @@ public:
display.print("Route: Local/Sent");
} else {
display.setColor(DisplayDriver::GREEN);
sprintf(tmp, "Route: %d hop%s (%dB)", hopCount, hopCount == 1 ? "" : "s", bytesPerHop);
sprintf(tmp, "Route: %d hop%s (%d-byte)", hopCount, hopCount == 1 ? "" : "s", bytesPerHop);
display.print(tmp);
}
y += lineH;
// Region (scoped channel messages only; session, not persisted)
const char* rgn = the_mesh.getScopeName(msg->scope_idx);
if (rgn) {
display.setCursor(0, y);
display.setColor(DisplayDriver::YELLOW);
sprintf(tmp, "Region: %s", rgn);
display.print(tmp);
y += lineH;
}
// SNR (if available — value is SNR×4)
if (msg->snr != 0) {
display.setCursor(0, y);
@@ -1245,14 +1259,21 @@ public:
sprintf(tmp, ">%dd ", age / 86400);
}
} else {
int hopsDisp = (msg->path_len == 0xFF) ? 0 : (msg->path_len & 63);
// Byte mode: flood packets encode it in the upper bits of path_len.
// The sentinels (0xFF direct-received, 0 locally-sent) do not encode it,
// so fall back to this device's configured path hash size.
int bphDisp = (msg->path_len == 0xFF || msg->path_len == 0)
? (the_mesh.getNodePrefs()->path_hash_mode + 1)
: ((msg->path_len >> 6) + 1);
if (age < 60) {
sprintf(tmp, "(%d) %ds ", msg->path_len == 0xFF ? 0 : (msg->path_len & 63), age);
sprintf(tmp, "(%dh)(%db) %ds ", hopsDisp, bphDisp, age);
} else if (age < 3600) {
sprintf(tmp, "(%d) %dm ", msg->path_len == 0xFF ? 0 : (msg->path_len & 63), age / 60);
sprintf(tmp, "(%dh)(%db) %dm ", hopsDisp, bphDisp, age / 60);
} else if (age < 86400) {
sprintf(tmp, "(%d) %dh ", msg->path_len == 0xFF ? 0 : (msg->path_len & 63), age / 3600);
sprintf(tmp, "(%dh)(%db) %dh ", hopsDisp, bphDisp, age / 3600);
} else {
sprintf(tmp, "(%d) %dd ", msg->path_len == 0xFF ? 0 : (msg->path_len & 63), age / 86400);
sprintf(tmp, "(%dh)(%db) %dd ", hopsDisp, bphDisp, age / 86400);
}
}
display.print(tmp);
+2 -2
View File
@@ -1577,7 +1577,7 @@ void UITask::msgRead(int msgcount) {
}
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount,
const uint8_t* path, int8_t snr) {
const uint8_t* path, int8_t snr, uint8_t scope_idx) {
_msgcount = msgcount;
// --- Dedup: suppress retry spam (same sender + text within 60s) ---
@@ -1725,7 +1725,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, dmFormatted, path, snr, nullptr, suppressNotif);
}
} else {
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, text, path, snr, nullptr, suppressNotif);
((ChannelScreen *) channel_screen)->addMessage(channel_idx, path_len, from_name, text, path, snr, nullptr, suppressNotif, scope_idx);
}
// If user is currently viewing this channel on the device, or companion
+2 -1
View File
@@ -366,7 +366,8 @@ public:
// from AbstractUITask
void msgRead(int msgcount) override;
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount,
const uint8_t* path = nullptr, int8_t snr = 0) override;
const uint8_t* path = nullptr, int8_t snr = 0,
uint8_t scope_idx = 0xFF) override;
void notify(UIEventType t = UIEventType::none) override;
void loop() override;