mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 19:02:00 +00:00
feat(map): packet flow - what the radio hears, as lines between nodes
Every packet heard runs from its origin through the repeaters with a
position to this device; a packet sent is a ring. ui-core/PacketFlow.h
resolves them (UiCore::flow, MyMesh logRx/logTx -> Listener). ui-lvgl
draws them on the map's marker layer and repaints only their area while
they move; ui-new draws the last minute on the Nodes map ("Flow" in its
options). Setting: Map options > On the map > Packet flow, CLI map.flow.
Off by default. Sim-checked only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,22 @@ static inline void drawLine(DisplayDriver& d, int x0, int y0, int x1, int y1) {
|
||||
}
|
||||
}
|
||||
|
||||
// The part of a line inside a box (x, y, w, h), Liang-Barsky; nothing when
|
||||
// it misses the box. For lines whose ends may lie far off the screen.
|
||||
static inline void drawLineClipped(DisplayDriver& d, int x0, int y0, int x1, int y1, int bx, int by, int bw, int bh) {
|
||||
float t0 = 0, t1 = 1;
|
||||
const float dx = (float)(x1 - x0), dy = (float)(y1 - y0);
|
||||
const float p[4] = { -dx, dx, -dy, dy };
|
||||
const float q[4] = { (float)(x0 - bx), (float)(bx + bw - 1 - x0), (float)(y0 - by), (float)(by + bh - 1 - y0) };
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (p[i] == 0) { if (q[i] < 0) return; continue; }
|
||||
float r = q[i] / p[i];
|
||||
if (p[i] < 0) { if (r > t1) return; if (r > t0) t0 = r; }
|
||||
else { if (r < t0) return; if (r < t1) t1 = r; }
|
||||
}
|
||||
drawLine(d, x0 + (int)(t0 * dx), y0 + (int)(t0 * dy), x0 + (int)(t1 * dx), y0 + (int)(t1 * dy));
|
||||
}
|
||||
|
||||
// Midpoint-circle outline.
|
||||
static inline void drawCircle(DisplayDriver& d, int cx, int cy, int r) {
|
||||
int x = r, y = 0, err = 1 - r;
|
||||
|
||||
@@ -263,7 +263,7 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
// ── action-menu actions (matched by id, not by row index) ────────────────────
|
||||
enum Action : uint8_t { ACT_NAV, ACT_PING, ACT_WAYPOINT, ACT_LOCATOR,
|
||||
ACT_ADD, ACT_DELETE, ACT_FAV, ACT_PIN, ACT_ADMIN, ACT_SORT, ACT_SCAN, ACT_MAP,
|
||||
ACT_TELEM, ACT_RESET_PATH, ACT_SHARE, ACT_ROUTE };
|
||||
ACT_TELEM, ACT_RESET_PATH, ACT_SHARE, ACT_ROUTE, ACT_FLOW };
|
||||
|
||||
// Set by UITask::pickAdminTarget() (Tools > Admin, which is remote-only):
|
||||
// while true, ENTER on an eligible row (a stored repeater/room contact) hands
|
||||
@@ -286,6 +286,8 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
bool _detail;
|
||||
bool _nav = false; // full-screen navigate-to-node view (over detail)
|
||||
bool _map = false; // the nodes with a position on a map (under detail)
|
||||
static const uint32_t FLOW_SHOW_MS = 60000; // packet flow lines stay this long
|
||||
uint32_t _flow_seen = 0;
|
||||
int _map_zoom = 0; // 0: all of them in view; each step halves it, on the selected
|
||||
navview::EtaTracker _nav_eta; // closing-speed/ETA for the navigate view
|
||||
unsigned long _detail_refresh_ms;
|
||||
@@ -552,6 +554,7 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
}
|
||||
|
||||
char _fav_label[12]; // "Fav: ON" / "Fav: OFF" -- rewritten in place by L/R
|
||||
char _flow_label[12]; // "Flow: ON" / "Flow: OFF" -- the map's packet flow
|
||||
char _pin_label[24]; // "Pin to dial" / "Unpin (slot N)" -- _menu stores the pointer
|
||||
char _telem_label[20]; // "Telem: ..." -- rewritten in place by L/R
|
||||
|
||||
@@ -598,6 +601,10 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
};
|
||||
|
||||
if (stored && !_map && positioned(0, 1) >= 0) add("Map", ACT_MAP);
|
||||
if (_map) { // packet flow: what the radio hears, as lines (ui-core/PacketFlow.h)
|
||||
buildFlowLabel();
|
||||
addValue(_flow_label, ACT_FLOW);
|
||||
}
|
||||
if (has_gps) add("Navigate", ACT_NAV);
|
||||
if (has_key) add("Ping", ACT_PING);
|
||||
if (has_gps) add("Save waypoint", ACT_WAYPOINT);
|
||||
@@ -645,6 +652,11 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
|
||||
// Advance the value on the menu's value rows (Sort, Fav, Telem). Sort and Fav
|
||||
// are two-state, so LEFT and RIGHT do the same there; Telem steps by `dir`.
|
||||
void buildFlowLabel() {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
snprintf(_flow_label, sizeof(_flow_label), "Flow: %s", p && p->flow_show ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
void cycleMenuValue(int i, int dir = 1) {
|
||||
if (i < 0 || i >= _menu_action_count) return;
|
||||
if (_menu_actions[i] == ACT_SORT) {
|
||||
@@ -653,6 +665,13 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
refresh();
|
||||
} else if (_menu_actions[i] == ACT_FAV) {
|
||||
toggleFavSelected();
|
||||
} else if (_menu_actions[i] == ACT_FLOW) {
|
||||
NodePrefs* p = _task->getNodePrefs();
|
||||
if (!p) return;
|
||||
p->flow_show = p->flow_show ? 0 : 1;
|
||||
the_mesh.savePrefs();
|
||||
if (!p->flow_show) _task->core().flow.clear();
|
||||
buildFlowLabel();
|
||||
} else if (_menu_actions[i] == ACT_TELEM) {
|
||||
const Entry* e = selected();
|
||||
ContactInfo* ci = e ? the_mesh.lookupContactByPubKey(e->pub_key, PUB_KEY_SIZE) : nullptr;
|
||||
@@ -733,6 +752,7 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
break;
|
||||
}
|
||||
case ACT_SORT: break; // value rows -- see cycleMenuValue()
|
||||
case ACT_FLOW: break;
|
||||
case ACT_SCAN: enterScan(); break;
|
||||
case ACT_MAP: {
|
||||
_map = true; _map_zoom = 0;
|
||||
@@ -916,6 +936,25 @@ class NearbyScreen : public UIScreen, protected NearbyModel {
|
||||
if (!inside(x, y)) continue;
|
||||
miniIconDrawCentered(d, x, y, _entries[i].is_live ? ICON_MAP_CONTACT : ICON_MAP_DOT);
|
||||
}
|
||||
// Packet flow (ui-core/PacketFlow.h): the lines of the packets heard in
|
||||
// the last minute, the newest drawn twice as thick; a ring for a packet
|
||||
// sent, or heard on its way to a repeater.
|
||||
const PacketFlow& fl = _task->core().flow;
|
||||
for (int i = 0; fl.enabled() && i < fl.count(); i++) {
|
||||
const PacketFlow::Flow& f = fl.at(i);
|
||||
if (millis() - f.t_ms > FLOW_SHOW_MS) break; // newest first
|
||||
int px[PacketFlow::MAX_PTS], py[PacketFlow::MAX_PTS];
|
||||
for (int k = 0; k < f.n; k++) project(f.lat[k], f.lon[k], px[k], py[k]);
|
||||
if (f.n == 1) {
|
||||
if (inside(px[0], py[0])) gfx::drawCircle(d, px[0], py[0], 6 * s);
|
||||
continue;
|
||||
}
|
||||
const bool bold = i < 2;
|
||||
for (int k = 1; k < f.n; k++) {
|
||||
gfx::drawLineClipped(d, px[k - 1], py[k - 1], px[k], py[k], ax, ay, aw, ah);
|
||||
if (bold) gfx::drawLineClipped(d, px[k - 1] + 1, py[k - 1], px[k] + 1, py[k], ax, ay, aw, ah);
|
||||
}
|
||||
}
|
||||
if (_own_gps) { int x, y; project(_own_lat, _own_lon, x, y); if (inside(x, y)) miniIconDrawCentered(d, x, y, ICON_MAP_CURRENT); }
|
||||
if (target) { int x, y; project(tla, tlo, x, y); if (inside(x, y)) miniIconDrawCentered(d, x, y, ICON_MAP_TARGET); }
|
||||
const int r = 4 * s;
|
||||
@@ -1093,7 +1132,9 @@ public:
|
||||
_detail_refresh_ms = millis();
|
||||
}
|
||||
renderMap(display);
|
||||
return renderActivePopup(display) ? 50 : 2000;
|
||||
if (renderActivePopup(display)) return 50;
|
||||
const PacketFlow& fl = _task->core().flow; // new lines show within a second
|
||||
return fl.enabled() && (fl.seq() != _flow_seen || fl.active(FLOW_SHOW_MS)) ? (_flow_seen = fl.seq(), 1000) : 2000;
|
||||
}
|
||||
|
||||
// ── list view ────────────────────────────────────────────────────────────
|
||||
@@ -1220,6 +1261,17 @@ public:
|
||||
return (mq_delay > 0 && mq_delay < ret) ? mq_delay : ret;
|
||||
}
|
||||
|
||||
// The nodes map, from outside the list (the sim harness).
|
||||
bool openMap() {
|
||||
int i = positioned(0, 1);
|
||||
if (i < 0) return false;
|
||||
_sel = i;
|
||||
_detail = false; // the map is drawn from the list
|
||||
_map = true; _map_zoom = 0;
|
||||
_detail_refresh_ms = millis();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Path / trace for a contact, from outside the list (the sim harness).
|
||||
bool openRoute(const uint8_t* pub_key) {
|
||||
int i = findContact(pub_key);
|
||||
|
||||
@@ -256,6 +256,10 @@ void UITask::openAdminFor(const ContactInfo& ci, bool from_picker) {
|
||||
setCurrScreen(admin_screen); // runs AdminScreen::onShow()'s reset first
|
||||
((AdminScreen*)admin_screen)->startFor(ci, from_picker);
|
||||
}
|
||||
bool UITask::openNodesMap() {
|
||||
setCurrScreen(nearby_screen);
|
||||
return ((NearbyScreen*)nearby_screen)->openMap();
|
||||
}
|
||||
void UITask::openRouteFor(const uint8_t* pub_key) {
|
||||
setCurrScreen(nearby_screen); // its onShow() reset first
|
||||
((NearbyScreen*)nearby_screen)->openRoute(pub_key);
|
||||
|
||||
@@ -331,6 +331,7 @@ public:
|
||||
void openAdminFor(const ContactInfo& ci, bool from_picker); // canonical Admin entry for a specific target (Nodes' Hold-Enter menu or the picker above)
|
||||
void openRouteFor(const uint8_t* pub_key); // Nodes > a contact > Path / trace (sim harness)
|
||||
void openMessagesList() { gotoMessagesScreen(); } // sim harness
|
||||
bool openNodesMap(); // sim harness: Nodes > Map
|
||||
void gotoNearbyScreen();
|
||||
void gotoDashboardConfig();
|
||||
void gotoAutoAdvertScreen();
|
||||
|
||||
Reference in New Issue
Block a user