| backend | ||
| data | ||
| .env.example | ||
| .eslintrc.json | ||
| .gitignore | ||
| AGENTS.md | ||
| ARCHITECTURE.md | ||
| CONTRIBUTING.md | ||
| docker-compose.yaml | ||
| docs.md | ||
| example.gif | ||
| example2.gif | ||
| howto.md | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| VERSION.txt | ||
| VERSIONS.md | ||
Mesh Live Map
Version: 1.0.3 (see VERSIONS.md)
Live MeshCore traffic map that renders nodes, routes, and activity in real time on a Leaflet map. The backend subscribes to MQTT over WebSockets + TLS, decodes MeshCore packets with @michaelhart/meshcore-decoder, and streams updates to the browser via WebSockets.
Live example sites:
- https://live.bostonme.sh/ - Greater Boston Mesh Map
- https://map.eastmesh.au/ - Aus Eastern Mesh Live Map
- https://mesh-map.e-l33t.org/ - NSW Mesh - Live Mesh Traffic Map
Features
- Live node markers with roles (Repeater, Companion, Room Server, Unknown)
- MQTT online indicator (green outline + popup status)
- Animated route/trace lines and message fanout
- Heat map for the last 10 minutes of message activity (includes adverts)
- Persistent device state and optional trails (disable with
TRAIL_LEN=0) - 24-hour route history tool with volume-based coloring, click-to-view packet details, a heat-band slider, and a link-size slider
- Peers tool showing incoming/outgoing neighbors with on-map lines
- Coverage layer from a coverage map API (button hidden when not configured)
- Update available banner (git local vs upstream) with dismiss
- UI controls: legend toggle, dark map, topo map, units toggle (km/mi), labels toggle, hide nodes, heat toggle
- Share button that copies a URL with current view + settings
- URL parameters to open the map at a specific view (center, zoom, toggles)
- Node search by name or public key
- Adjustable node size slider (defaults from env, saves locally)
- LOS tool with elevation profile + peak markers and hover sync (Shift+click or long‑press nodes)
- Embeddable metadata (Open Graph/Twitter tags) driven by env vars
- Propagation panel lives on the right and keeps the last render until you generate a new one
- Installable PWA (manifest + service worker) for Add to Home Screen
- Click the logo to hide/show the left HUD panel while tools stay open
Project Structure
backend/app.py: FastAPI server wiring, MQTT lifecycle, WS broadcastbackend/config.py: environment configurationbackend/state.py: shared in-memory state + dataclassesbackend/decoder.py: payload parsing + meshcore-decoder integrationbackend/los.py: LOS math + elevation helpersbackend/history.py: route history persistence + pruningbackend/routes/: API, websocket, debug, and static route modulesbackend/services/: MQTT, broadcaster, reaper, persistence servicesbackend/scripts/meshcore_decode.mjs: Node MeshCore decoder helperbackend/static/index.html: HTML shell + template placeholdersbackend/static/styles.css: UI stylesbackend/static/app.js: map logic + UI controlsbackend/static/sw.js: PWA service workerdocker-compose.yaml: runtime configuration (reads from.env)data/: runtime state (created at first run)
Quick Start
- Clone the repo and enter it:
git clone https://github.com/yellowcooln/meshcore-mqtt-live-map
cd meshcore-mqtt-live-map
- Copy env template:
cp .env.example .env
- Edit
.envwith your MQTT broker and site metadata.- See
howto.mdfor a step-by-step guide to setting up the MQTT server and this live map.
- See
- Build and run:
docker compose up -d --build
- Open:
http://localhost:8080/(or yourWEB_PORT)
Configuration (.env)
Debugging:
DEBUG_PAYLOAD(verbose decode logs)DEBUG_PAYLOAD_MAX/PAYLOAD_PREVIEW_MAX(log truncation limits)
Storage + server:
STATE_DIR(persisted state path)STATE_SAVE_INTERVAL(seconds between state saves)WEB_PORT(host port for the web UI)PROD_MODE(true to require a token for API + WS)PROD_TOKEN(required token; send via?token=orAuthorization: Bearer)
Site metadata (page title + embeds):
SITE_TITLESITE_DESCRIPTIONSITE_OG_IMAGE(optional; leave blank to omit embed image)SITE_URL(public URL)SITE_ICONSITE_FEED_NOTECUSTOM_LINK_URL(optional extra HUD link; hidden when blank)DISTANCE_UNITS(kmormi, default display units)NODE_MARKER_RADIUS(default node marker size in pixels)
MQTT:
MQTT_HOSTMQTT_PORTMQTT_USERNAMEMQTT_PASSWORDMQTT_TRANSPORT(websockets)MQTT_WS_PATH(usually/or/mqtt)MQTT_TLS(true)MQTT_TOPIC(e.g.meshcore/#ormeshcore/#,other/topic/+for multiple topics)
Coverage layer:
COVERAGE_API_URL(URL to coverage map API; button hidden when blank)
Device + route tuning:
DEVICE_TTL_SECONDS(node expiry)TRAIL_LEN(points per device trail;0disables trails)ROUTE_TTL_SECONDSROUTE_PATH_MAX_LEN(skip oversized path-hash lists)ROUTE_PAYLOAD_TYPES(packet types used for live routes)MESSAGE_ORIGIN_TTL_SECONDS
History overlay:
ROUTE_HISTORY_ENABLEDROUTE_HISTORY_HOURSROUTE_HISTORY_MAX_SEGMENTSROUTE_HISTORY_COMPACT_INTERVALROUTE_HISTORY_FILEROUTE_HISTORY_PAYLOAD_TYPESHISTORY_LINK_SCALE(default history line weight multiplier)
Heat + online status:
HEAT_TTL_SECONDSMQTT_ONLINE_SECONDS(online window for status ring)MQTT_ONLINE_TOPIC_SUFFIXES(comma-separated topics that count as “online”)MQTT_SEEN_BROADCAST_MIN_SECONDSMQTT_ONLINE_FORCE_NAMES(comma-separated names to force as MQTT online; also excluded from peers)
Update checks:
GIT_CHECK_ENABLED(show update banner if repo is behind)GIT_CHECK_FETCH(fetch before comparing)GIT_CHECK_PATH(path to git repo in the container)GIT_CHECK_INTERVAL_SECONDS(defaults to 43200 = 12h)
Map + LOS:
MAP_START_LAT/MAP_START_LON/MAP_START_ZOOM(default map view)MAP_DEFAULT_LAYER(light,dark, ortopo; localStorage overrides)MAP_RADIUS_KM(default241.4km ≈ 150mi;0disables radius filtering)MAP_RADIUS_SHOW(truedraws the radius debug circle)LOS_ELEVATION_URL(elevation API for LOS tool)LOS_SAMPLE_MIN/LOS_SAMPLE_MAX/LOS_SAMPLE_STEP_METERSELEVATION_CACHE_TTL(seconds)LOS_PEAKS_MAX(max peaks shown on LOS profile)
Common Commands
- Rebuild/restart:
docker compose up -d --build - Logs:
docker compose logs -f meshmap-live - Snapshot:
curl -s http://localhost:8080/snapshot - Stats:
curl -s http://localhost:8080/stats
Production Token
Enable protection by setting:
PROD_MODE=true
PROD_TOKEN=<random-string>
Generate a token:
openssl rand -hex 32
Use it:
- HTTP:
http://host:8080/snapshot?token=YOUR_TOKEN - WS:
ws://host:8080/ws?token=YOUR_TOKEN - Or send
Authorization: Bearer YOUR_TOKEN
Notes
- The map can only draw routes for hops that appear in your MQTT feed.
- To see full paths, the feed must include Path/Trace packets (payload types 8/9) or multiple observers for fanout.
- Runtime state is persisted to
data/state.json. - MQTT disconnects are handled; the client will reconnect when the broker returns.
- Line-of-sight tool: click LOS tool and pick two points, or Shift+click two nodes to measure LOS between them.
- On mobile, long‑press a node to select it for LOS.
- LOS runs server-side via
/los(no client-side elevation fetch). - History tool always loads off (use the button or
history=onin the URL). - Peers tool uses route history segments; forced MQTT listeners are excluded from peer lists.
- URL params override stored settings:
lat,lon/lng/long,zoom,layer,history,heat,labels,nodes,legend,menu,units,history_filter. - Dark map also darkens node popups for readability.
- Route styling uses payload type: 2/5 = Message (blue), 8/9 = Trace (orange), 4 = Advert (green).
- If hop hashes collide, the backend skips those hashes (unique-only mapping).
- Coordinates at
0,0(including string values) are filtered from devices, trails, and routes.
API
The backend exposes a nodes API for external tools (e.g. MeshBuddy):
GET /api/nodes?token=YOUR_TOKEN- Default response:
{"data":{"nodes":[...]}} - Optional:
format=flatreturns{"data":[...]} - Optional:
mode=deltaappliesupdated_sincefiltering
- Default response:
Example:
https://your-host/api/nodes?token=YOUR_TOKEN
https://your-host/api/nodes?token=YOUR_TOKEN&mode=delta&updated_since=2025-01-01T12:00:00Z
https://your-host/api/nodes?token=YOUR_TOKEN&format=flat
Each node includes:
public_key, name, device_role (1/2/3), last_seen (ISO), timestamp (epoch), and location with latitude/longitude.
Peer summary:
GET /peers/{device_id}?token=YOUR_TOKEN- Returns incoming/outgoing neighbors with counts/percentages from route history.
License
This project was vibe-coded with Codex—please expect rough edges and the occasional bug.

