A comprehensive Home Assistant dashboard for MeshCore mesh networking, featuring interactive maps, heatmaps, signal tracking, playback recording, and automated contact management.
- Node Map - Shows all nodes by type (Repeater π‘, Client π±, Room Server π¬)
- Hop Frequency Heatmap - Visualizes which repeaters handle the most traffic
- Direct Links Heatmap - Shows 1-hop direct connections between nodes
- 24-hour history - Snapshots recorded every 5 minutes
- Timeline scrubbing - Drag slider to view past network state
- Playback controls - Play, pause, and go live
- Threshold filter slider - Filter playback by time window (1h-48h or ALL)
- SNR/RSSI monitoring per contact
- Hop count tracking with path visualization
- Multiple reception paths displayed
- Message history with timestamps
- Auto-greeting - Welcomes new companions on Public channel (configurable)
- Auto-cleanup - Removes old contacts (30+ days) from HA and device
- Persistence - Survives HA reboots (hops data, last messages, greeted list)
- Click node names to highlight message paths
- Dashed lines show routing through mesh
- Color-coded by hop count or traffic intensity
- Home Assistant 2024.1+
- MeshCore Integration v2.3.0+
- AppDaemon 4.4+
- HACS (for optional cards)
mushroom-cards- For styled controlsauto-entities- For dynamic entity listsconfig-template-card- For template-based cards
- Go to Settings β Add-ons β Add-on Store
- Search for AppDaemon and install it
- Start the add-on
Download all .py files from this repo's appdaemon/apps/ folder and copy them to your AppDaemon apps folder.
For Home Assistant OS (Add-on):
/addon_configs/a0d7b954_appdaemon/apps/
For Home Assistant Container/Core:
/config/appdaemon/apps/
Files to copy:
meshcore_hops.py # Signal & hop tracking
meshcore_paths.py # Path visualization & hop markers
meshcore_cleanup.py # Auto-cleanup old contacts
meshcore_greeter.py # Auto-greet new contacts
meshcore_heatmap_export.py # Heatmap data export
meshcore_nodemap_export.py # Node map data export
meshcore_directlinks_export.py # Direct links data export
meshcore_snapshot_recorder.py # Playback recording
You can copy files using:
- File Editor add-on - navigate to the folder and upload
- Samba share - if enabled
- SSH/Terminal - command line access
Edit appdaemon.yaml in the AppDaemon config folder:
For Home Assistant OS: /addon_configs/a0d7b954_appdaemon/appdaemon.yaml
The default AppDaemon add-on configuration connects via the HA Supervisor proxy. This is the easiest setup but has a 4MB WebSocket message size limit that can cause crashes on larger installations.
---
appdaemon:
latitude: 51.5
longitude: -0.1
elevation: 10
time_zone: Europe/London
plugins:
HASS:
type: hass
http:
url: http://0.0.0.0:5050
admin:
api:
hadashboard:If you have many entities or see WebSocket message size errors, connect AppDaemon directly to Home Assistant using a long-lived token. This bypasses the Supervisor proxy and its 4MB limit.
---
appdaemon:
latitude: 51.5
longitude: -0.1
elevation: 10
time_zone: Europe/London
plugins:
HASS:
type: hass
ha_url: http://192.168.0.250:8123
token: YOUR_LONG_LIVED_TOKEN_HERE
http:
url: http://0.0.0.0:5050
admin:
api:
hadashboard:To create a long-lived token:
- Go to your HA profile page (click your name in the bottom-left)
- Scroll to Long-Lived Access Tokens at the bottom
- Click Create Token, give it a name (e.g. "AppDaemon")
- Copy the token and paste it into
appdaemon.yaml
β οΈ Security note: If you haveip_ban_enabled: trueinconfiguration.yaml, direct connections using a long-lived token may trigger IP bans after failed login attempts during testing. You can temporarily disable banning while setting up: setip_ban_enabled: false, configure the direct connection, then re-enable it once AppDaemon is connecting successfully.
βΉοΈ Note: The
secrets:reference inappdaemon.yamlonly works when connecting via the Supervisor proxy. When using direct connection withha_url, you must embed the token directly inappdaemon.yamlβ!secretreferences are not resolved.
Edit apps.yaml in the same folder as the Python files:
For Home Assistant OS: /addon_configs/a0d7b954_appdaemon/apps/apps.yaml
meshcore_hops:
module: meshcore_hops
class: MeshCoreHops
meshcore_paths:
module: meshcore_paths
class: MeshCorePathMap
my_pubkey: "YOUR_PUBKEY_HERE"
meshcore_cleanup:
module: meshcore_cleanup
class: MeshCoreCleanup
meshcore_greeter:
module: meshcore_greeter
class: MeshCoreGreeter
my_name: "Your Repeater Name"
hops_distant: 5
meshcore_heatmap_export:
module: meshcore_heatmap_export
class: MeshCoreHeatmapExport
meshcore_nodemap_export:
module: meshcore_nodemap_export
class: MeshCoreNodeMapExport
meshcore_directlinks_export:
module: meshcore_directlinks_export
class: MeshCoreDirectLinksExport
meshcore_snapshot_recorder:
module: meshcore_snapshot_recorder
class: MeshCoreSnapshotRecorderCopy files from this repo's www/ folder to your Home Assistant www folder:
Important: Always use /config/www/ (not the AppDaemon folder!)
/config/www/meshcore_heatmap_playback.html
/config/www/meshcore_nodemap.html
/config/www/meshcore_directlinks_playback.html
Go to Settings β Devices & Services β Helpers β Create Helper
Create 3 number helpers with these settings:
| Name | Entity ID | Min | Max | Step | Initial |
|---|---|---|---|---|---|
meshcore_advert_threshold_hours |
input_number.meshcore_advert_threshold_hours |
1 | 720 | 1 | 12 |
meshcore_messages_threshold_hours |
input_number.meshcore_messages_threshold_hours |
1 | 720 | 1 | 24 |
meshcore_heatmap_threshold_hours |
input_number.meshcore_heatmap_threshold_hours |
1 | 720 | 1 | 168 |
Create 1 dropdown helper:
| Name | Entity ID | Options |
|---|---|---|
meshcore_sort_by |
input_select.meshcore_sort_by |
Last Advert, Last Message, Direct Links |
Set your MeshCore device's pubkey in apps.yaml:
meshcore_paths:
module: meshcore_paths
class: MeshCorePathMap
my_pubkey: "Your pubkey"To find your pubkey:
- Go to Developer Tools β States
- Search for
binary_sensor.meshcore_ - Find your device's contact sensor
- Copy the
pubkey_prefixattribute (first 12 characters)
This works for both Repeaters and Companion clients.
Go to Settings β Add-ons β AppDaemon β Restart
Check the logs for any errors: Settings β Add-ons β AppDaemon β Log
Test with a simple iframe card first:
type: iframe
url: /local/meshcore_heatmap_playback.html
aspect_ratio: "4:3"If you see a map, the HTML files are working!
See dashboards/ folder for full dashboard examples.
Configure via apps.yaml:
meshcore_greeter:
module: meshcore_greeter
class: MeshCoreGreeter
my_name: "Your Repeater Name" # Name shown in greeting messages
hops_distant: 5 # Max hops to greet (nodes further away ignored)Note: my_name is just a display name for greetings, not the pubkey.
To test the greeter is working:
- Go to Developer Tools β Events
- Fire event:
meshcore_greeter_test - You should see the test message in the public channel
Default is 30 days. Edit line 25:
threshold_days = 30Default settings:
- 24 hours of rolling history
- 288 snapshots maximum (5-minute intervals)
- Snapshots only saved when data changes
The heatmap and direct links maps include playback controls:
| Button | Function |
|---|---|
| Play through history | |
| π΄ LIVE | Return to live data |
| Timeline slider | Scrub through 24h history |
| Filter slider | Filter by time window (1h-48h or ALL) |
meshcore_snapshot_recorder.pytakes snapshots every 5 minutes- Snapshots are saved to
/config/www/meshcore_*_history.json - Old snapshots (>24h) are automatically cleaned up
- Playback HTML loads history and allows timeline scrubbing
The following data survives HA reboots:
| File | Data |
|---|---|
/config/www/meshcore_hops_sensors.json |
Full hops sensor data |
/config/www/meshcore_last_messages.json |
Last message times |
/config/www/meshcore_hops_data.json |
Hop node use counts |
/config/www/meshcore_greeted.json |
Greeted contacts list |
/config/www/meshcore_directlinks_persist.json |
Direct link connections |
/config/www/meshcore_heatmap_history.json |
Heatmap playback history (24h) |
/config/www/meshcore_directlinks_history.json |
Direct links playback history (24h) |
Data older than 7 days is automatically cleaned up (except playback history which is 24h).
sensor.meshcore_hops_<pubkey>- Per-contact hop/signal datasensor.meshcore_map_entities- List of map entitiessensor.meshcore_path_entities- List of path entitiessensor.meshcore_hop_entities- List of hop node entities
device_tracker.meshcore_path_<n>- Message path trackersdevice_tracker.meshcore_hop_<n>- Hop node markers
If you see this error in AppDaemon logs:
ERROR HASS: Error from aiohttp websocket: Message size XXXXXXX exceeds limit 4194304
The Supervisor proxy has a hard 4MB WebSocket message limit. On larger installations with many entities, HA state updates can exceed this. Fix it by switching to a direct connection β see Option B in the AppDaemon configuration section above.
Alternatively, add the following to configuration.yaml (works for both proxy and direct connections):
# Configure a default setup of Home Assistant (frontend, api, etc)
default_config:
websocket_api:
max_message_size: 33554432Then restart Home Assistant (not just AppDaemon). The websocket_api block must be at the root level, not nested under homeassistant: or http:.
If you see large and growing thread queue warnings in the AppDaemon logs like:
WARNING thread-3 queue size: 2000+ (meshcore_paths processing)
This is caused by listen_state listeners registered against broad entity domains (e.g. "sensor" or "binary_sensor"). These fire on every state change in HA, including changes made by the apps themselves, creating a feedback cascade.
All apps in this repo have been updated to use listen_event("meshcore_raw_event") directly instead of watching sensor state. If you are running older versions of the scripts, update to the latest versions.
You can monitor callback counts in the AppDaemon admin UI at http://YOUR_HA_IP:5050 under the Apps tab.
- Check AppDaemon logs for errors: Settings β Add-ons β AppDaemon β Log
- Verify JSON files exist in
/config/www/ - Clear browser cache or hard refresh (Ctrl+Shift+R)
- Check browser console for JavaScript errors (F12)
- Make sure files are in
/config/www/(not/addon_configs/.../www/) - Restart Home Assistant after adding files
- Try accessing directly:
http://YOUR_HA_IP:8123/local/meshcore_heatmap_playback.html
- Check the Entity ID matches exactly (case sensitive)
- Entity ID should be
input_number.meshcore_advert_threshold_hoursnotinput_number.input_number.meshcore_...
- Check that
meshcore_raw_eventevents withevent_type: EventType.RX_LOG_DATAare firing in Developer Tools β Events - Confirm the decrypted section contains
textanddecrypted: trueβ undecrypted packets are skipped - Check that contacts have coordinates (lat/lon) in their binary sensor attributes
- Verify path has at least 2 nodes β direct (1-hop) messages don't produce a path line
- Check AppDaemon logs at 3am
- Verify MeshCore services are available
- Check
last_advertandlast_messageattributes
- Check service name:
meshcore.send_channel_message - Test manually in Developer Tools β Services
- Fire test event:
meshcore_greeter_testin Developer Tools β Events
- Wait for snapshots to accumulate (5 min intervals)
- Check
/config/www/meshcore_heatmap_history.jsonexists - Verify
meshcore_snapshot_recorderin AppDaemon logs
- MeshCore - Mesh networking protocol
- MeshCore HA Integration - Home Assistant integration
- Leaflet - Interactive maps
- Leaflet.heat - Heatmap plugin
MIT License - See LICENSE file
Contributions welcome! Please open an issue or pull request.


