mirror of
https://github.com/yellowcooln/meshcore-mqtt-live-map.git
synced 2026-04-20 23:23:36 +00:00
356 lines
20 KiB
Markdown
356 lines
20 KiB
Markdown
# Mesh Live Map
|
||
|
||
Version: `1.8.4` (see [VERSIONS.md](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 or TCP, decodes MeshCore packets with the official [`@michaelhart/meshcore-decoder`](https://www.npmjs.com/package/@michaelhart/meshcore-decoder), and streams updates to the browser via WebSockets.
|
||
|
||
Live example sites:
|
||
https://live.bostonme.sh/ - Greater Boston Mesh Map (reference)
|
||
|
||
Other community maps (versions may differ):
|
||
- https://meshcore-map.ctmesh.org/ - CTMesh MeshCore Map
|
||
- https://livemap.wcmesh.com/ - West Coast Mesh
|
||
- https://map.eastmesh.au/ - Aus Eastern Mesh Live Map
|
||
- https://mapa.meshcore.cz/ - Czech Republic
|
||
- https://map.meshcorebayreuth.de/ - Bayreuth Live Map
|
||
|
||
|
||

|
||
---
|
||

|
||
|
||
## Features
|
||
- Live node markers with roles (Repeater, Companion, Room Server, Unknown)
|
||
- MQTT online indicator (green outline + popup status) based on MQTT `status`/`internal` topics, with conservative role detection from explicit MQTT role fields only
|
||
- Animated route/trace lines
|
||
- Hidden arcade Pacman route visualization mode for live route direction
|
||
- Dev route inspection: click a route line in dev (`PROD_MODE=false`) to log hop-by-hop details in the browser console, including resolved `point_id` / `point_label` data (PR #14, credit: https://github.com/sefator)
|
||
- 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
|
||
- History panel can be dismissed with an X without hiding history lines (re-open via History tool)
|
||
- Peers tool showing incoming/outgoing neighbors with on-map lines (blue = incoming, purple = outgoing)
|
||
- Coverage layer from the legacy [coverage map API](https://github.com/nullrouten0/meshcore-coverage-map) or the new [MeshMapper Coverage API](https://github.com/MeshMapper/MeshMapper_Wiki/blob/main/docs/coverage-api.md) (button hidden when not configured)
|
||
- MeshMapper coverage viewport sync reuses cached rectangles instead of recreating every visible square on each pan/zoom, which keeps the coverage layer responsive on larger meshes
|
||
- Weather tool panel with independent Radar and Wind toggles
|
||
- 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
|
||
- Node popups can copy the full public key from the short key shown under the node name, with an optional MeshCore contact QR modal that shows the node name and a clickable truncated key
|
||
- Adjustable node size slider (defaults from env, saves locally)
|
||
- LOS tool with elevation profile + peak markers, hover sync, realtime draggable endpoints (Shift+click or long‑press nodes), and Earth-curvature-aware blockage checks
|
||
- Embeddable metadata (Open Graph/Twitter tags) driven by env vars
|
||
- Preview image renders in-bounds device dots for shared links
|
||
- Route pruning via neighbor-aware closest-hop selection + max hop distance (configurable)
|
||
- Route lines are derived from decoded packet paths only (no MQTT observer/receiver fallback)
|
||
- First-hop collision fix prefers the closest repeater/room to the sender (Issue #11)
|
||
- Mixed hop-prefix support for path decoding (`AB`, `ABCD`, and `ABCDEF`, including mixed networks during rollout)
|
||
- Optional legacy route fallback for colliding 1-byte hop prefixes via `ROUTE_ALLOW_AMBIGUOUS_ONE_BYTE_FALLBACK=true`
|
||
- Propagation panel lives on the right and keeps the last render until you generate a new one (click an origin marker to remove it)
|
||
- Propagation tool supports adjustable **TX antenna gain (dBi)**, and now defaults **Rx AGL** to **1m**
|
||
- 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 broadcast
|
||
- `backend/config.py`: environment configuration
|
||
- `backend/state.py`: shared in-memory state + dataclasses
|
||
- `backend/decoder.py`: payload parsing + multibyte MeshCore decoder integration
|
||
- `backend/los.py`: LOS math + elevation helpers
|
||
- `backend/history.py`: route history persistence + pruning
|
||
- `backend/weather.py`: weather radar country-bounds lookup API
|
||
- `backend/static/index.html`: HTML shell + template placeholders
|
||
- `backend/static/styles.css`: UI styles
|
||
- `backend/static/app.js`: map logic + UI controls
|
||
- `backend/static/sw.js`: PWA service worker
|
||
- `docker-compose.yaml`: runtime configuration (reads from `.env`)
|
||
- `data/`: runtime state (created at first run)
|
||
|
||
## Quick Start
|
||
1) Clone the repo and enter it:
|
||
```bash
|
||
git clone https://github.com/yellowcooln/meshcore-mqtt-live-map
|
||
cd meshcore-mqtt-live-map
|
||
```
|
||
2) Copy env template:
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
3) Edit `.env` with your MQTT broker and site metadata.
|
||
- See `howto.md` for a step-by-step guide to setting up the MQTT server and this live map.
|
||
4) Build and run:
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
5) Open: `http://localhost:8080/` (or your `WEB_PORT`)
|
||
|
||
## Configuration (.env)
|
||
Debugging:
|
||
- `DEBUG_PAYLOAD` (verbose decode logs)
|
||
- `DEBUG_PAYLOAD_MAX` / `PAYLOAD_PREVIEW_MAX` (log truncation limits)
|
||
- `DEBUG_LAST_MAX` / `DEBUG_STATUS_MAX` (debug endpoint entry caps)
|
||
|
||
Storage + server:
|
||
- `STATE_DIR` (persisted state path)
|
||
- `BACKUP_ENABLED` (enable automatic `.tar.gz` backups)
|
||
- `BACKUP_INTERVAL_SECONDS` (seconds between backups; default `43200` / 12 hours)
|
||
- `BACKUP_DIR` (backup archive directory; default `/backup`)
|
||
- `BACKUP_RETENTION_DAYS` (delete backup archives older than N days; `0` disables pruning)
|
||
- `STATE_FILE` (full state file path override)
|
||
- `DEVICE_ROLES_FILE` (optional role override JSON file)
|
||
- `DEVICE_COORDS_FILE` (optional coordinate override JSON file; default `/data/device_coords.json`)
|
||
- `NEIGHBOR_OVERRIDES_FILE` (optional JSON mapping for neighbor overrides)
|
||
- `CHANNEL_SECRETS_FILE` (optional JSON file of MeshCore channel secrets for decrypting sender names from group text packets)
|
||
- `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=` or `Authorization: Bearer`)
|
||
|
||
Backups:
|
||
- when enabled, the server writes timestamped `meshmap-backup-YYYY-MM-DDTHH-MM-SS.tar.gz` archives to `BACKUP_DIR`
|
||
- backup archives are separate from `/data` by default; the compose file mounts `./backup:/backup`
|
||
- with the default backup settings (`12` hours, `7` days retention), expect under `30 MB` of backup storage on a network around `250` nodes
|
||
|
||
Turnstile protection (prod-only):
|
||
- `TURNSTILE_ENABLED` (requires `PROD_MODE=true`)
|
||
- `TURNSTILE_SITE_KEY`
|
||
- `TURNSTILE_SECRET_KEY`
|
||
- `TURNSTILE_API_URL`
|
||
- `TURNSTILE_TOKEN_TTL_SECONDS`
|
||
- `TURNSTILE_BOT_BYPASS` (allowlist embed bots like Discord)
|
||
- `TURNSTILE_BOT_ALLOWLIST` (comma-separated user-agent tokens; default: `discordbot,twitterbot,slackbot,facebookexternalhit,linkedinbot,telegrambot,whatsapp,skypeuripreview,redditbot`)
|
||
|
||
Site metadata (page title + embeds):
|
||
- `SITE_TITLE`
|
||
- `SITE_DESCRIPTION`
|
||
- `SITE_OG_IMAGE` (optional; leave blank to omit embed image)
|
||
- `SITE_URL` (public URL)
|
||
- `SITE_ICON`
|
||
- `SITE_FEED_NOTE`
|
||
- `CUSTOM_LINK_URL` (optional extra HUD link; hidden when blank)
|
||
- `PACKET_ANALYZER_URL` (optional analyzer base URL for Route Details hashes; e.g. `https://analyzer.letsmesh.net/packets?packet_hash=`)
|
||
- `QR_CODE_BUTTON_ENABLED` (show a `Generate QR Code` button in node popups that opens a theme-aware MeshCore-compatible contact QR modal; default `false`)
|
||
- `PEERS_DEFAULT_LIMIT` (optional default number of incoming/outgoing peers returned by `/peers/{device_id}`; default `8`)
|
||
- `MAP_BOUNDARY_MODE` (`radius` or `polygon`; default `radius`)
|
||
- `MAP_BOUNDARY_FILE` (JSON file used when `MAP_BOUNDARY_MODE=polygon`; default `/data/map_boundary.json`)
|
||
- `MAP_BOUNDARY_SHOW` (draw the active radius/polygon boundary overlay on the map)
|
||
- `DISTANCE_UNITS` (`km` or `mi`, default display units)
|
||
- `NODE_MARKER_RADIUS` (default node marker size in pixels)
|
||
|
||
MQTT:
|
||
- `MQTT_HOST`
|
||
- `MQTT_PORT`
|
||
- `MQTT_USERNAME` (for `meshcore-mqtt-broker`, usually a broker-side `SUBSCRIBER_N` username, not `v1_<PUBLIC_KEY>`)
|
||
- `MQTT_PASSWORD` (matching subscriber password from the broker config)
|
||
- `MQTT_TRANSPORT` (`tcp` or `websockets`)
|
||
- `MQTT_WS_PATH` (usually `/` or `/mqtt`)
|
||
- `MQTT_TLS` (`true`)
|
||
- `MQTT_TLS_INSECURE` (allow invalid TLS certs)
|
||
- `MQTT_CA_CERT` (custom CA bundle path)
|
||
- `MQTT_CLIENT_ID` (optional client id override)
|
||
- `MQTT_TOPIC` (e.g. `meshcore/#` or `meshcore/#,other/topic/+` for multiple topics)
|
||
|
||
Coverage layer:
|
||
- `COVERAGE_API_URL` (legacy coverage-map base URL, or `https://meshmapper.net`; button hidden when blank)
|
||
- `COVERAGE_API_KEY` (MeshMapper only; optional key for `https://meshmapper.net/coverage.php`; not used by legacy coverage maps)
|
||
- `COVERAGE_MAX_AGE_DAYS` (MeshMapper only; default `30`; only coverage from the last N days is sent to the map, while MeshMapper can still cache the full upstream dataset locally; not used by legacy coverage maps)
|
||
- `COVERAGE_RATE_LIMIT_COOLDOWN_SECONDS` (MeshMapper only; fallback cooldown after HTTP 429 if the API does not report `resets_in_hours`)
|
||
- `COVERAGE_CACHE_FILE` (MeshMapper only; local JSON file served to users after server-side sync)
|
||
- `COVERAGE_SYNC_INTERVAL_SECONDS` (MeshMapper only; how often the server refreshes the local coverage cache file, default hourly)
|
||
|
||
Routing accuracy:
|
||
- Ambiguous 1-byte hop prefixes are now handled conservatively on large meshes.
|
||
- If multiple nodes share the same first byte, the map no longer guesses from broad closest/time-based fallbacks.
|
||
- Colliding 1-byte hops only resolve when there is stronger evidence such as a unique candidate or known neighbor/manual adjacency.
|
||
- `ROUTE_ALLOW_AMBIGUOUS_ONE_BYTE_FALLBACK=true` restores the pre-`v1.7.0` fallback behavior for networks that need the older route rendering.
|
||
|
||
Weather overlay:
|
||
- `WEATHER_RADAR_ENABLED`:
|
||
master switch for radar support. If both `WEATHER_RADAR_ENABLED=false` and `WEATHER_WIND_ENABLED=false`, the Weather button is hidden.
|
||
- `WEATHER_RADAR_COUNTRY_BOUNDS_ENABLED`:
|
||
set `true` to keep radar inside the country around the map center; set `false` for unrestricted radar tiles.
|
||
- `WEATHER_RADAR_COUNTRY_LOOKUP_URL`:
|
||
keep default (`/weather/radar/country-bounds`) unless you run your own country-bounds endpoint.
|
||
- `WEATHER_WIND_ENABLED`:
|
||
set `true` to show Wind in the Weather panel, or `false` to disable wind entirely.
|
||
- `WEATHER_WIND_API_URL`:
|
||
endpoint for wind data (default is Open-Meteo; custom APIs must provide current wind speed/direction).
|
||
- `WEATHER_WIND_GRID_SIZE`:
|
||
number of wind samples per side (`1`-`5`); larger grid gives more arrows but increases API load.
|
||
- `WEATHER_WIND_REFRESH_SECONDS`:
|
||
wind refresh interval (seconds, minimum `30`); increase this to reduce API calls.
|
||
|
||
Device + route tuning:
|
||
- `DEVICE_TTL_HOURS` (advert/device stale window; default `96`)
|
||
- `PATH_TTL_SECONDS` (path stale window; default `172800`)
|
||
- `TRAIL_LEN` (points per device trail; `0` disables trails)
|
||
- `ROUTE_TTL_SECONDS`
|
||
- `ROUTE_PATH_MAX_LEN` (skip oversized path-hash lists)
|
||
- `ROUTE_PAYLOAD_TYPES` (packet types used for live routes)
|
||
- `ROUTE_MAX_HOP_DISTANCE` (km; prunes unrealistic hops)
|
||
- `ROUTE_INFRA_ONLY` (true = only repeaters/rooms in route lines)
|
||
- `MESSAGE_ORIGIN_TTL_SECONDS`
|
||
|
||
History overlay:
|
||
- `ROUTE_HISTORY_ENABLED`
|
||
- `ROUTE_HISTORY_HOURS`
|
||
- `ROUTE_HISTORY_MAX_SEGMENTS`
|
||
- `ROUTE_HISTORY_COMPACT_INTERVAL`
|
||
- `ROUTE_HISTORY_FILE`
|
||
- `ROUTE_HISTORY_PAYLOAD_TYPES`
|
||
- `ROUTE_HISTORY_ALLOWED_MODES` (comma-separated route modes; default `path`)
|
||
- `HISTORY_LINK_SCALE` (default history line weight multiplier)
|
||
|
||
Heat + online status:
|
||
- `HEAT_TTL_SECONDS`
|
||
- `MQTT_ONLINE_SECONDS` (legacy/global fallback TTL for MQTT presence)
|
||
- `MQTT_ONLINE_STATUS_TTL_SECONDS` (how long `/status` keeps a node connected)
|
||
- `MQTT_ONLINE_INTERNAL_TTL_SECONDS` (how long `/internal` keeps a node connected)
|
||
- `MQTT_ACTIVITY_PACKETS_TTL_SECONDS` (how long `/packets` counts as feeding activity)
|
||
- `MQTT_STATUS_OFFLINE_VALUES` (comma-separated status values that force offline, even inside TTL)
|
||
- `MQTT_ONLINE_TOPIC_SUFFIXES` (legacy compatibility setting; primary online source is status/internal TTLs)
|
||
- `MQTT_SEEN_BROADCAST_MIN_SECONDS`
|
||
- `MQTT_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`, or `topo`; localStorage overrides)
|
||
- `MAP_RADIUS_KM` (`0` disables radius filtering; `.env.example` uses `241.4` km ≈ 150mi)
|
||
- `MAP_RADIUS_SHOW` (`true` draws the radius debug circle)
|
||
- `LOS_ELEVATION_URL` (elevation API for LOS tool)
|
||
- `LOS_ELEVATION_PROXY_URL` (server proxy for client-side LOS elevation fetches)
|
||
- `LOS_SAMPLE_MIN` / `LOS_SAMPLE_MAX` / `LOS_SAMPLE_STEP_METERS`
|
||
- `ELEVATION_CACHE_TTL` (seconds)
|
||
- `LOS_CURVATURE_ENABLED` (default `true`; include Earth curvature in LOS calculations)
|
||
- `LOS_CURVATURE_FACTOR` (default `1.333333`; effective Earth radius multiplier used by the LOS tool)
|
||
- `LOS_PEAKS_MAX` (max peaks shown on LOS profile)
|
||
|
||
Decoder helpers:
|
||
- `DECODE_WITH_NODE` (toggle Node-based MeshCore decoder usage)
|
||
- `NODE_DECODE_TIMEOUT_SECONDS`
|
||
- `DIRECT_COORDS_MODE` (`topic` or `payload`)
|
||
- `DIRECT_COORDS_TOPIC_REGEX` (topic matcher for direct coords)
|
||
- `DIRECT_COORDS_ALLOW_ZERO` (allow `0,0` coords if `true`)
|
||
|
||
## Common Commands
|
||
- Rebuild/restart: `docker compose up -d --build`
|
||
- Logs: `docker compose logs -f meshmap-live`
|
||
- Tests: `pip install -r requirements-dev.txt && pytest -q`
|
||
- 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>
|
||
```
|
||
|
||
Turnstile protection is also gated by `PROD_MODE=true`. If `PROD_MODE=false`,
|
||
Turnstile stays off even when `TURNSTILE_ENABLED=true`.
|
||
When Turnstile is enabled, browser sessions can authenticate the map + WebSocket
|
||
with a Turnstile auth token (`meshmap_auth` cookie or `?auth=` on `/ws`), but
|
||
protected API routes (`/snapshot`, `/api/nodes`, `/peers/{id}`) still require
|
||
`PROD_TOKEN`.
|
||
Ensure `PROD_MODE`/`PROD_TOKEN` are set in `.env` (docker-compose passes them through).
|
||
|
||
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).
|
||
- Runtime state is persisted to `data/state.json`.
|
||
- MQTT disconnects are handled; the client will reconnect when the broker returns.
|
||
- When using Michael Hart's `meshcore-mqtt-broker`, the map should usually log
|
||
in with a broker `SUBSCRIBER_N` account such as
|
||
`SUBSCRIBER_1=meshmap:change-this-password:2`.
|
||
- The map does not mint MeshCore JWT auth tokens by itself, so node-style
|
||
publisher auth (`v1_<PUBLIC_KEY>`) is not the normal setup for this app.
|
||
- Subscriber role `2` is the recommended broker role for most maps; role `1`
|
||
is only needed if you explicitly want `/internal` topics or other admin-only
|
||
broker visibility.
|
||
- MQTT connectivity (`MQTT online`) is based on `/status` + `/internal`; `/packets` is treated as feed activity and does not by itself mark a node online.
|
||
- If a node is still MQTT-online but has stopped sending fresh location packets, the map keeps its last known position visible until MQTT presence expires.
|
||
- Live route IDs are observer-aware (`message_hash:receiver_id`) so the same
|
||
message seen by multiple MQTT observers does not overwrite active lines.
|
||
- Line-of-sight tool: click **LOS tool** and add pins to build a chained path, or **Shift+click** nodes to place LOS pins from existing nodes. Drag endpoints or click a pin and then click the map to move that specific point. Heights are stored per pin.
|
||
- On mobile, long‑press a node to select it for LOS.
|
||
- LOS elevations are fetched via `/los/elevations` and LOS/relay math runs client-side (with `/los` fallback).
|
||
- LOS now includes Earth curvature by default using an effective Earth radius factor of `1.333333`, unless you override the LOS curvature envs.
|
||
- History tool always loads off (use the button or `history=on` in the URL).
|
||
- Peers tool uses dedicated rolling peer-history buckets so 24h counts stay accurate even on high-volume meshes; peer links are still counted from route `point_ids` even when a hop could not be drawn on the map, and forced MQTT listeners are excluded from peer lists.
|
||
- URL params override stored settings: `lat`, `lon`/`lng`/`long`, `zoom`, `layer`, `history`, `heat`, `coverage`, `weather`, `weather_radar`, `weather_wind`, `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).
|
||
- Turnstile browser auth (`meshmap_auth`/`?auth=`) is for map + WS session flow;
|
||
protected API endpoints still require `PROD_TOKEN`.
|
||
- If hop hashes collide, the backend prefers known neighbors (or overrides) before picking the closest hop and pruning beyond `ROUTE_MAX_HOP_DISTANCE`.
|
||
- Route hop prefixes can now be 1-byte, 2-byte, or 3-byte; Show Hops displays `Prefix: AB` / `Prefix: ABCD` / `Prefix: ABCDEF`.
|
||
- Device pruning can use both stale windows together (`DEVICE_TTL_HOURS` and `PATH_TTL_SECONDS`).
|
||
- Coordinates at `0,0` (including string values) are filtered from devices, trails, and routes.
|
||
- With Turnstile enabled, common embed bots (Discord, Slack, etc.) can be
|
||
allowlisted via `TURNSTILE_BOT_BYPASS` and `TURNSTILE_BOT_ALLOWLIST`.
|
||
|
||
## 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=nested` returns `{"data":{"nodes":[...]}}`
|
||
- `updated_since` applies delta filtering automatically
|
||
- Optional: `mode=full` (or `all`/`snapshot`) forces full-list response
|
||
|
||
Example:
|
||
```
|
||
https://your-host/api/nodes?token=YOUR_TOKEN
|
||
https://your-host/api/nodes?token=YOUR_TOKEN&updated_since=2025-01-01T12:00:00Z
|
||
https://your-host/api/nodes?token=YOUR_TOKEN&format=nested
|
||
https://your-host/api/nodes?token=YOUR_TOKEN&mode=full
|
||
```
|
||
|
||
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`
|
||
|
||
Boundary mode:
|
||
- Default behavior remains radius-based filtering.
|
||
- Set `MAP_BOUNDARY_MODE=polygon` to filter nodes, routes, and history against a polygon from `MAP_BOUNDARY_FILE`.
|
||
- Use `map_boundary.example.json` as the file format reference.
|
||
- The repo includes a standalone builder at `tools/map-boundary-builder.html`; a hosted copy is available at [https://yellowcooln.com/map-boundary-builder/](https://yellowcooln.com/map-boundary-builder/).
|
||
- Use either copy to click points on the map and export `map_boundary.json`.
|
||
- Returns incoming/outgoing neighbors with counts/percentages from route history.
|
||
|
||
## License
|
||
[GPL-3.0](https://github.com/yellowcooln/meshcore-mqtt-live-map?tab=License-1-ov-file#).
|
||
|
||
---
|
||
|
||
This project was vibe-coded with Codex—please expect rough edges and the occasional bug.
|
||
|
||
## Star History
|
||
|
||
<a href="https://www.star-history.com/?repos=yellowcooln%2Fmeshcore-mqtt-live-map&type=date&legend=top-left">
|
||
<picture>
|
||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=yellowcooln/meshcore-mqtt-live-map&type=date&theme=dark&legend=top-left" />
|
||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=yellowcooln/meshcore-mqtt-live-map&type=date&legend=top-left" />
|
||
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=yellowcooln/meshcore-mqtt-live-map&type=date&legend=top-left" />
|
||
</picture>
|
||
</a>
|