mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
Core: PathEditor (UiCore::route) loads, edits, traces and saves the route to a contact; MyMesh::sendTrace over an explicit route with per-hop SNRs, setContactPath / setContactPinned. A route can be kept (CONTACT_FLAG_ROUTE_PINNED, ContactInfo::flags bit 4): a path heard from the node doesn't replace it and the last DM resend doesn't drop it. ui-lvgl: Route screen (node detail), repeater picker by distance, picking hops by tapping the nodes map (numbered badges, line, Undo / Done). The map's markers are one drawn layer with hit-testing and name decluttering instead of three objects per node. ui-new: Path / trace view in the Nodes action menu; Add reuses the list filtered to repeaters. First cut: built for L2 LVGL, L1 OLED and L1 e-ink, tried on L2 only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
123 lines
4.3 KiB
C
123 lines
4.3 KiB
C
#pragma once
|
|
// Node discovery and ping (trace) from the UI.
|
|
// Not part of upstream MyMesh.cpp: included at its bottom, after all class
|
|
// definitions (as MyMeshBot.h and MyMeshCli.h).
|
|
|
|
void MyMesh::sendNodeDiscoverReq() {
|
|
uint8_t data[10];
|
|
data[0] = CTL_TYPE_NODE_DISCOVER_REQ;
|
|
data[1] = (1 << ADV_TYPE_REPEATER) | (1 << ADV_TYPE_SENSOR) | (1 << ADV_TYPE_ROOM);
|
|
getRNG()->random(&data[2], 4);
|
|
memcpy(&_pending_node_discover_tag, &data[2], 4);
|
|
_pending_node_discover_until = futureMillis(8000);
|
|
_discover_count = 0;
|
|
uint32_t since = 0;
|
|
memcpy(&data[6], &since, 4);
|
|
auto pkt = createControlData(data, sizeof(data));
|
|
if (pkt) sendZeroHop(pkt);
|
|
}
|
|
|
|
int MyMesh::getDiscoverResults(DiscoverResult dest[], int max_count) {
|
|
int n = min(_discover_count, max_count);
|
|
memcpy(dest, _discover_results, n * sizeof(DiscoverResult));
|
|
return n;
|
|
}
|
|
|
|
// ── Ping/Trace functionality ─────────────────────────────────────────────────
|
|
|
|
uint32_t MyMesh::sendTrace(const uint8_t* path, uint8_t hops, uint8_t hash_width, uint32_t* est_timeout) {
|
|
// A trace's hashes are 1 << flags bytes wide: 1, 2, 4 (no 3).
|
|
if (hash_width != 1 && hash_width != 2 && hash_width != 4) return 0;
|
|
if (hops == 0 || hops > TRACE_SNR_MAX || hops * hash_width > MAX_PATH_SIZE) return 0;
|
|
|
|
// Generate random tag and auth code
|
|
uint32_t tag, auth;
|
|
getRNG()->random((uint8_t*)&tag, 4);
|
|
getRNG()->random((uint8_t*)&auth, 4);
|
|
|
|
// Find a free slot in ping results
|
|
int slot = -1;
|
|
for (int i = 0; i < PING_RESULT_MAX; i++) {
|
|
if (!_ping_results[i].received && _ping_results[i].tag == 0) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
if (slot < 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Initialize ping result tracking
|
|
PingResult& result = _ping_results[slot];
|
|
memset(&result, 0, sizeof(result));
|
|
result.tag = tag;
|
|
result.auth_code = auth;
|
|
result.sent_ms = millis();
|
|
|
|
auto pkt = createTrace(tag, auth, hash_width == 4 ? 2 : hash_width - 1); // flags = log2(hash_width)
|
|
if (pkt) {
|
|
sendDirect(pkt, path, hops * hash_width);
|
|
if (est_timeout) { // as CMD_SEND_TRACE_PATH answers the app: from the airtime, per hop
|
|
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
|
*est_timeout = calcDirectTimeoutMillisFor(t, hops);
|
|
}
|
|
return tag;
|
|
}
|
|
|
|
// Failed to create packet
|
|
memset(&result, 0, sizeof(result));
|
|
return 0;
|
|
}
|
|
|
|
// Ping: a trace over just the destination's hash (one hop, it must be heard directly).
|
|
uint32_t MyMesh::sendPing(const uint8_t* dest_pubkey, uint8_t hash_width, uint32_t* est_timeout) {
|
|
if (hash_width != 1 && hash_width != 2 && hash_width != 4) return 0;
|
|
return sendTrace(dest_pubkey, 1, hash_width, est_timeout);
|
|
}
|
|
|
|
void MyMesh::setPingCallback(PingCallback cb, void* arg) {
|
|
_ping_callback = cb;
|
|
_ping_callback_arg = arg;
|
|
}
|
|
|
|
void MyMesh::clearPingResult(uint32_t tag) {
|
|
if (tag == 0) return;
|
|
for (int i = 0; i < PING_RESULT_MAX; i++) {
|
|
if (_ping_results[i].tag == tag) {
|
|
memset(&_ping_results[i], 0, sizeof(_ping_results[i]));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
MyMesh::PingResult* MyMesh::getPingResult(uint32_t tag) {
|
|
for (int i = 0; i < PING_RESULT_MAX; i++) {
|
|
if (_ping_results[i].tag == tag) {
|
|
return &_ping_results[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// True when this (tag, responder) pair was already seen recently; records it
|
|
// otherwise. A discover response is often heard more than once — the zero-hop
|
|
// direct copy and a re-flooded copy relayed by another repeater carry
|
|
// different packet hashes, so the mesh duplicate filter passes both. Without
|
|
// this, the standalone scan appended the same node twice and an app-driven
|
|
// discover had both copies forwarded, so the app listed one repeater as two.
|
|
bool MyMesh::isDupDiscoverResp(uint32_t tag, const uint8_t* pub_key) {
|
|
for (int i = 0; i < DISCOVER_SEEN_MAX; i++) {
|
|
DiscoverSeen& e = _disc_seen[i];
|
|
if (e.until != 0 && !millisHasNowPassed(e.until)
|
|
&& e.tag == tag && memcmp(e.pk, pub_key, sizeof(e.pk)) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
DiscoverSeen& s = _disc_seen[_disc_seen_head];
|
|
_disc_seen_head = (_disc_seen_head + 1) % DISCOVER_SEEN_MAX;
|
|
s.tag = tag;
|
|
memcpy(s.pk, pub_key, sizeof(s.pk));
|
|
s.until = futureMillis(10000); // copies of one response arrive within seconds
|
|
return false;
|
|
}
|