mirror of
https://github.com/yellowcooln/meshcore-mqtt-live-map.git
synced 2026-04-20 23:23:36 +00:00
12 KiB
12 KiB
Mesh Map Live: Implementation Notes
This document captures the state of the project and the key changes made so far, so a new Codex session can pick up without losing context.
Overview
This project renders live MeshCore traffic on a Leaflet + OpenStreetMap map. A FastAPI backend subscribes to MQTT (WSS/TLS), decodes MeshCore packets using @michaelhart/meshcore-decoder, and broadcasts device updates and routes over WebSockets to the frontend. Core logic is split into config/state/decoder/LOS/history modules plus routes/ and services/ layers so changes are localized. The UI includes heatmap, LOS tools, map mode toggles, and a 24‑hour route history layer.
Versioning
VERSIONS.mdis the append-only changelog by version.VERSION.txtmirrors the latest version entry.
Key Paths
backend/app.py: FastAPI server + MQTT lifecycle and websocket broadcasting.backend/config.py: environment/config constants (shared across backend modules).backend/state.py: shared runtime state (devices/routes/history) + dataclasses.backend/decoder.py: payload parsing, meshcore-decoder integration, route helpers.backend/los.py: LOS math + elevation sampling.backend/history.py: route history persistence + pruning.backend/routes/: API, websocket, debug, and static route modules.backend/services/: MQTT, broadcaster, reaper, persistence services.backend/scripts/meshcore_decode.mjs: Node MeshCore decoder helper.backend/static/index.html: HTML shell + template placeholders.backend/static/styles.css: UI styles.backend/static/app.js: Leaflet UI, markers, legends, routes, tools.backend/static/sw.js: PWA service worker.- Static files are served from
APP_DIR/staticso the app is resilient to different working directories. docker-compose.yaml: runtime configuration.data/state.json: persisted device/trail/roles/names (loaded at startup).data/route_history.jsonl: rolling 24h route history segments (lines)..env: dev configuration (mirrors template variables).
Runtime Commands (Typical Workflow)
docker compose up -d --build(run after any file changes).docker compose logs -f meshmap-live(watch MQTT + decode logs).curl -s http://localhost:8080/snapshot(current device map).curl -s http://localhost:8080/stats(counters, route types).curl -s http://localhost:8080/debug/last(recent MQTT decode/debug entries).curl -s http://localhost:8080/peers/<device_id>(peer counts for a node; uses route history).
Env Notes (Recent Additions)
CUSTOM_LINK_URLadds a HUD link button; blank hides it.MQTT_ONLINE_FORCE_NAMESforces named nodes to show MQTT online and skips them in peers.GIT_CHECK_ENABLED,GIT_CHECK_FETCH,GIT_CHECK_PATHenable update checks.GIT_CHECK_INTERVAL_SECONDScontrols how often the server re-checks for updates.
MQTT + Decoder
- MQTT is WebSockets + TLS (
MQTT_TRANSPORT=websockets,MQTT_TLS=true,MQTT_WS_PATH=/or/mqtt). - Decoder uses Node +
@michaelhart/meshcore-decoderinstalled in the container. backend/decoder.pywrites a small Node helper and calls it to decode MeshCore packets.
Frontend UI
- Header includes a GitHub link icon and HUD summary (stats, feed note).
- Base map toggle: Light/Dark/Topo; persisted to localStorage.
- Dark map also darkens node popups for readability.
- Node popups do not auto-pan; dragging the map won’t snap back to keep a popup in view.
- Legend is collapsible and persisted to localStorage.
- HUD is capped to
90vhand scrolls to avoid running off-screen. - Map start position is configurable with
MAP_START_LAT,MAP_START_LON,MAP_START_ZOOM. - Radius filter:
MAP_RADIUS_KM=241.4(150mi) drops nodes/routes/history outside the circle; set0to disable.MAP_RADIUS_SHOW=truedraws a debug circle. - Default base layer can be set with
MAP_DEFAULT_LAYER(localStorage overrides). - Units toggle (km/mi) is site-wide; default from
DISTANCE_UNITSand stored in localStorage. - Node size slider defaults from
NODE_MARKER_RADIUSand persists in localStorage. - History link size slider defaults from
HISTORY_LINK_SCALEand persists in localStorage. - Node search (name or key) and a labels toggle (persisted to localStorage).
- History tool defaults off and opens a right-side panel with a heat filter slider (visibility is not persisted).
- History slider modes: 0 = All, 1 = Blue only, 2 = Yellow only, 3 = Yellow + Red, 4 = Red only.
- History legend swatch is hidden unless the History tool is active.
- Peers tool shows incoming/outgoing neighbors for a selected node, with counts and percentages pulled from route history.
- Peers tool skips nodes listed in
MQTT_ONLINE_FORCE_NAMES(observer listeners). - Coverage tool only appears when
COVERAGE_API_URLis set; it fetches tiles on demand. - Trail text in the HUD is only shown when
TRAIL_LEN > 0;TRAIL_LEN=0disables trails entirely. - Hide Nodes toggle hides markers, trails, heat, routes, and history layers.
- Heat toggle can hide the heatmap; it defaults on and the button turns green when heat is off.
- HUD logo uses
SITE_ICON; if unset or broken it falls back to a small “Map” badge so the toggle still works. - History line weight was reduced for improved readability.
- Propagation overlay keeps heat/routes/trails/markers above it after render; the panel lives on the right and retains the last render until you generate a new one.
- Heatmap includes all route payload types (adverts are no longer skipped).
- MQTT online status shows as a green marker outline and popup status; it uses
mqtt_seen_tsfrom/statusor/packetstopics (configurable). MQTT_ONLINE_FORCE_NAMEScan force named nodes to show as MQTT online regardless of last seen.- PWA install support is enabled via
/manifest.webmanifestand a service worker at/sw.js. - Clicking the HUD logo hides/shows the left panel while tool panels stay open.
- Share button copies a URL with the current view + toggles (including HUD visibility).
- Optional custom HUD link appears when
CUSTOM_LINK_URLis set. - Update banner shows when
GIT_CHECK_ENABLED=trueand the repo is behind; users can dismiss it per remote SHA. - Update banner dismissal relies on
.hud-update[hidden]to ensure the banner actually disappears. - URL params override stored settings:
lat,lon/lng/long,zoom,layer,history,heat,labels,nodes,legend,menu,units,history_filter. - Service worker uses
no-storefor navigation requests so env-driven UI toggles (like the radius ring) update without clearing site data.
LOS (Line of Sight) Tool
- LOS runs server-side only via
/los(no client-side elevation fetch). - UI draws an LOS line (green clear / red blocked), renders an elevation profile, and marks peaks.
- When blocked, the server can return a relay suggestion marker (amber/green).
- Peak markers show coords + elevation and copy coords on click.
- Hovering the profile or the LOS line syncs a cursor tooltip on the profile.
- Hovering the LOS profile also tracks a cursor on the map and highlights nearby peaks.
- LOS legend items (clear/blocked/peaks/relay) are hidden unless the LOS tool is active.
- Shift+click nodes (or long‑press on mobile) or click two points on the map to run LOS.
Device Names + Roles
- Names come from advert payloads or status messages when available.
- Roles are only accepted from explicit decoder fields:
deviceRole/deviceRoleName(MeshCore advert flags), orrolefields in payload.- Name-based role heuristics were removed to avoid mislabels.
- Roles are not assigned to the receiver of a packet. For decoded packets, the role applies to the advertised pubkey (decoded
location.pubkeyordecoded_pubkey). - Roles persist to
data/state.jsonwithdevice_role_sources. Only explicit/override roles are restored on load. - Optional overrides:
data/device_roles.jsoncan force roles per device_id.
Routes / Message Paths
Routes are drawn when:
- A packet contains a path list (decoder
pathHashesorpath), or - Multiple observers see the same message hash (fanout), or
- As a fallback, when one hash maps to a known device, a direct line is drawn to the receiver.
When a hop hash collides, the backend skips it (unique-only); oversized path lists are ignored via
ROUTE_PATH_MAX_LEN.
24h History Layer
- Every route segment is persisted to
data/route_history.jsonland kept for the lastROUTE_HISTORY_HOURS. - History lines are color‑coded by volume (blue = low, orange = mid, red = high) and weight scales with counts.
- History is hidden by default; the History tool opens a right panel with a slider to filter by heat band.
- The History tool also includes a link size slider; it scales line weight without changing counts.
- History records
path,direct, andfanoutroute modes by default (configureROUTE_HISTORY_ALLOWED_MODES).
If routes aren’t visible:
- The packet may only include a single hop (
path: ["24"]). - Other repeaters might not be publishing to MQTT, so the message is only seen by one observer.
- Routes and trails drop any
0,0coordinates (including string values) and will purge bad entries on load. - Route styling uses payload type: 2/5 = Message (blue), 8/9 = Trace (orange), 4 = Advert (green).
- If history is empty but routes show, confirm
ROUTE_HISTORY_ALLOWED_MODESincludes the active route mode.
Frontend Map UI
- Legend includes Trace/Message/Advert line styles and Repeater/Companion/Room/Unknown dot colors.
- Unknown markers were made more visible (larger, higher contrast gray).
- Zoom control moved to bottom-right.
- Route lines are thicker/bolder for large screens.
- LOS + Propagation panels appear on the right; on mobile they stack to avoid overlap.
Persistence
- Devices, trails, names, and roles are saved to
data/state.json. - On restart, devices should stay visible if
state.jsonexists. - Route history is persisted separately to
data/route_history.jsonl(rolling window). - If stale/mis-labeled roles appear, delete
data/state.jsonor remove role entries. - State load now removes any
0,0coordinates from devices/trails (including string values). - When
TRAIL_LEN=0, stored trails are cleared on load and no new trails are written.
Troubleshooting Notes
- If map is empty but MQTT is connected, check
/debug/lastfor decoded payloads andpayloadType. - If markers appear in the wrong place, inspect
decoder_metaand location fields. - If roles flip incorrectly, verify
role_target_idin/debug/last. - If routes don’t show, verify message hashes appear under multiple receivers in MQTT.
- If MQTT online looks wrong, confirm
MQTT_ONLINE_TOPIC_SUFFIXESin.env(default/status,/packets).
Recent Fixes / Changes Summary
- Added full WSS support and TLS options.
- Integrated meshcore-decoder for advert/location + role parsing.
- Added
/stats,/snapshot,/debug/last,/debug/statusendpoints. - Added persistence and state reload logic; safer role restore rules.
- Added route drawing for traces/paths/messages with TTL cleanup.
- Added fallback route when only one hop is known.
- UI: route legend, role legend, and improved marker styles.
- Roles now apply to advertised pubkey, not receiver.
- Docker restarts are required after file changes (always run
docker compose up -d --build). - LOS is server-side only; elevation profile/peaks are returned by
/los. - MQTT online indicator (green outline + legend) and configurable online window.
- Filters out
0,0GPS points from devices, trails, and routes (including string values). - Added 24h route history storage + history toggle with volume-based colors.
- Hide nodes now hides heat/routes/history along with markers/trails.
- Fixed MQTT disconnect callback signature so broker drops don’t crash the MQTT loop.
- Route hash collisions are now ignored (unique-only) and long path lists are skipped (
ROUTE_PATH_MAX_LEN). - Trails can be disabled by setting
TRAIL_LEN=0(HUD trail text is removed). - Node marker size can be tuned via
NODE_MARKER_RADIUS(users can override locally). - Units toggle defaults from
DISTANCE_UNITSand persists in localStorage. - Mobile LOS selection supports long-press on nodes.
- History tool visibility no longer persists (always off unless
history=onin the URL).