MeshCore Plugin for Domoticz
Connect your MeshCore LoRa mesh nodes to Domoticz as native automation devices. Telemetry, messaging, a real-time dashboard and a scripting bridge — all over a native WebSocket channel.
Screenshots
Click any image to open the full-size view. Use arrow keys or the buttons to navigate.
Requirements
| Requirement | Notes |
|---|---|
| Domoticz build ≥ 17956 2025.2.17956 / 2026-05-16 |
The real-time dashboard uses Domoticz.WebSocketSend /
onWebSocketMessage added in that build. Plugin devices
work on any version; only the dashboard requires this minimum. |
| Python 3.6+ | Must be enabled in your Domoticz build (default on most platforms). |
meshcore Python package |
Install with pip install meshcore on the Domoticz machine. |
| MeshCore node reachable via TCP or USB serial | TCP: companion app, USB-to-TCP bridge, or MeshcoMod firmware (native WiFi on Heltec v3/v4). Serial: USB cable directly to the node. |
Installation
Install the Python package
pip install meshcore
Docker: add pip3 install meshcore (or
pip3 install --break-system-packages meshcore on Debian 12)
to your customstart.sh hook so it survives image rebuilds.
Clone the plugin
cd ~/domoticz/plugins
git clone https://github.com/galadril/Domoticz-MeshCore-Plugin.git MeshCore
Naming the folder MeshCore is conventional but not required — Domoticz finds the plugin by scanning for plugin.py in any subdirectory.
Restart Domoticz
sudo service domoticz.sh restart
Add the hardware
Go to Setup → Hardware, click Add, and select
type MeshCore. Set the host and port of your TCP endpoint
(default port 5000), or select a Serial port. Click Save.
Verify
Open Setup → Log. Within seconds you should see MeshCore connected and your self-node name. Devices appear under Setup → Devices as contacts are discovered.
Configuration
Edit via Setup → Hardware → MeshCore → Edit.
TCP transport
| Field | Default | Description |
|---|---|---|
| MeshCore Host | IP address or hostname of the MeshCore TCP endpoint. | |
| MeshCore Port | 5000 |
TCP port. Leave at default unless changed on the device side. |
Serial transport
| Field | Default | Description |
|---|---|---|
| Serial Port | COM / tty port — auto-populated dropdown in Domoticz. | |
| Baud Rate | 115200 |
Must match the setting on the connected node. |
Plugin options
| Field | Default | Description |
|---|---|---|
| Command Bridge Channel | empty | Channel name to monitor for !-prefixed commands.
Creates three bridge devices for dzVents scripts.
Leave blank to disable the bridge. See Automation. |
| Install Custom Dashboard | Yes | Copies meshcore.html into Domoticz templates on start.
Access via Setup → More Options → Custom Pages → meshcore. |
| Debug Level | None | None — errors only · Basic — connection events & device updates · All — full packet trace (very verbose). |
Poll intervals
| What | Interval |
|---|---|
| Message drain + contacts refresh | 30 s |
| Full contacts refresh (liveness probe) | 5 min |
| Self-node stats (battery, RSSI, uptime, counters) | 5 min |
| Stale connection detection → reconnect | 10 min without push events |
Available channels are logged on startup, e.g.
MeshCore channels: #0 = General, #1 = MyRoom.
Use the exact channel name (without #) in the Command Bridge Channel field.
Custom Dashboard
Enable Install Custom Dashboard in the hardware settings, then open:
Setup → More Options → Custom Pages → meshcore
Requires Domoticz build ≥ 17956. The dashboard uses
Domoticz.WebSocketSend / onWebSocketMessage introduced
in that release. Older Domoticz versions can use the plugin devices but not this dashboard.
| Feature | Details |
|---|---|
| Node cards | Online/offline badge, battery bar, color-coded SNR bars, hops, last seen. Every metric links to its Domoticz device log. |
| Node map | Leaflet dark-mode map showing contacts and heard nodes. Draws real multi-hop repeater path lines. Click the pin icon on any card to center on that node. Hidden until at least one node has coordinates. |
| Activity heatmap | 24-hour rolling Leaflet heatmap of RX events overlaid on the topology map. |
| Message inbox | SQLite-backed, server-side paginated with infinite scroll. Full-text search and per-channel / per-contact scope filters. |
| DM threads | Pin any contact to open its full direct-message conversation with delivery ACK markers. |
| Compose & reply | Channel or direct target picker, emoji picker (700+), @mention highlighting, hover-to-reply on any message. |
| Historical analytics | Side panel: RSSI/SNR/noise time-series, packet volume (per-minute and hourly), top relay keys, per-channel stats. 30-day window. |
| RX firehose | Live packet stream with type filters, pause/resume, per-row SNR/RSSI/path breakdown. |
| Heard nodes | Non-contact adverts with hit count, GPS pin, bulk purge by age or low hit-count. |
| Statistics | Packet-type donut, lifetime totals, leaderboards (top senders, deepest hops, longest relay keys). |
| Coverage & LoS tools | Terrain-aware propagation map and per-link path-profile chart with Bullington knife-edge attenuation. |
Sending Messages
Write to the Mesh Inbox device via the Domoticz JSON API, a dzVents script, or the dashboard compose bar.
Message format
| Write to device | Result |
|---|---|
hello world | Direct message to the first discovered contact. |
garden: hello | Direct message to the contact named garden. |
#General: hello | Broadcast on the channel named General. |
#0: hello | Broadcast on channel index 0. |
Internal control commands
!-prefixed writes configure the plugin or device — they are never transmitted as messages.
| Command | Effect |
|---|---|
!remove <name> | Remove the named contact from the connected device. |
!favorite add <name> | Mark contact as favorite (sorted first, persisted to meshcore_favorites.json). |
!favorite remove <name> | Drop favorite. |
!flood_scope #<tag> | Set default flood scope (empty = global). |
!set adv_loc_policy <0|1> | Never share / share location in adverts. |
!set telemetry_base <0|1|2> | Off / Public / Always. |
Device Reference
All devices are created automatically as data arrives — no manual configuration needed.
Global devices
| Device | Type | Description |
|---|---|---|
| Mesh Inbox | Text | Last received message. Format: [C0|sender] text or [P|sender] text. Value change triggers a WebSocket push to the dashboard. |
| Mesh Msgs Received | Custom (msgs) | Running counter of messages received since plugin start. |
| Mesh Msgs Sent | Custom (msgs) | Running counter of messages sent since plugin start. |
Self (connected) node — DeviceID: self
| Device | Type | Source |
|---|---|---|
| Status | Switch | On when connected; Off on disconnect. |
| Battery % | Percentage | battery_mv from get_stats_core |
| Battery V | Custom (V) | battery_mv from get_stats_core |
| RSSI | Custom (dBm) | last_rssi from get_stats_radio |
| SNR | Custom (dB) | last_snr from get_stats_radio |
| Noise Floor | Custom (dBm) | noise_floor from get_stats_radio |
| Last Seen | Text | Timestamp of last received event. |
| Uptime | Custom (min) | uptime_secs from get_stats_core |
| Airtime TX | Custom (s) | tx_air_secs from get_stats_radio |
| Pkts Sent | Custom (pkts) | sent from get_stats_packets |
| Pkts Recv | Custom (pkts) | recv from get_stats_packets |
Remote contacts — DeviceID: pubkey[:12]
| Device | Type | Notes |
|---|---|---|
| Status | Switch | Online when last advert < 8 h ago; offline otherwise. |
| SNR | Custom (dB) | From incoming messages (not a radio poll). |
| Last Seen | Text | Timestamp of last advert or message. |
| Hops | Custom (hops) | Contact out_path_len from the contacts list. |
Command Bridge devices
Created when a Command Bridge Channel is configured. See Automation.
| Device | Type | Direction |
|---|---|---|
| MeshCore Command In | Text | Plugin → dzVents. JSON payload of each inbound !-command. |
| MeshCore Reply | Text | dzVents → Plugin. Write JSON reply here before triggering Send. |
| MeshCore Send | Switch (Push On) | dzVents → Plugin. Turn On to dispatch. |
Manual Node Locations
Pin nodes without GPS on the dashboard map by creating
meshcore_locations.json in domoticz/plugins/MeshCore/:
{
"Garden": { "lat": 52.3690, "lon": 4.9075 },
"Rooftop": { "lat": 52.3712, "lon": 4.9102 }
}
- Node names are case-sensitive and must match contact names exactly.
- Live GPS data from a node automatically overrides its manual entry.
- The map section is hidden until at least one node has coordinates.
Automation & dzVents Bridge
The Command Bridge turns any channel message starting with
! into a dzVents event — giving your scripts live access to incoming
LoRa commands and the ability to broadcast replies back to the mesh.
Setup
- Set a Command Bridge Channel in the hardware settings.
- Restart the plugin — three bridge devices are created.
- Copy an example script from below to
~/domoticz/scripts/dzVents/generated_scripts/. - Edit the
CONFIGURATIONblock and enable the script.
Inbound payload (written to MeshCore Command In)
{
"id": 42, // origin handle — valid for 5 min after arrival
"seq": 17, // monotonic; use to deduplicate across restarts
"cmd": "!status", // full command string, including ! prefix
"sender": "AlphaNode",
"pubkey": "a1b2c3d4e5f6",
"channel": "General",
"snr": 8.5,
"ts": 1716023456
}
Reply formats (write to MeshCore Reply, then trigger MeshCore Send)
// Id-based (first reply only — plugin resolves channel from id)
{ "id": 42, "text": "reply text" }
// Channel-based (queued follow-ups; id is consumed after first send)
{ "to": "#General", "text": "follow-up text" }
// Direct message override
{ "id": 42, "to": "AlphaNode", "text": "private reply" }
The plugin consumes the id record on the first reply. For multi-part
responses (e.g. !status), use id-based routing for the first message,
then channel-based routing for follow-ups — spaced at least one minute apart
to avoid LoRa TX overlap.
Example: Simple Bridge
File: examples/dzvents_meshcore_bridge.lua
The minimal starting point. Extend the if/elseif chain with your own commands.
return {
on = { devices = { "MeshCore Command In" } },
logging = { level = domoticz.LOG_DEBUG, marker = "MeshCore-Bridge" },
execute = function(dz, item)
local ok, m = pcall(dz.utils.fromJSON, item.text)
if not ok or type(m) ~= "table" then return end
local cmd = m.cmd or ""
local sender = m.sender or "?"
local lower_cmd = string.lower(cmd)
local reply
if lower_cmd == "!ping" then
reply = "pong"
elseif lower_cmd == "!status" then
reply = string.format("OK | time=%s uptime=%dm",
os.date("%Y-%m-%d %H:%M:%S"),
math.floor(dz.utils.osSeconds() / 60))
elseif string.sub(lower_cmd, 1, 6) == "!hello" then
local name = string.sub(cmd, 8)
if name == "" then name = sender end
reply = "Hello, " .. name .. "!"
else
reply = "unknown command: " .. cmd
end
-- 1. Write reply payload 2. Trigger dispatch
dz.devices("MeshCore Reply").updateText(
dz.utils.toJSON({ id = m.id, text = reply })
)
dz.devices("MeshCore Send").switchOn().checkFirst()
end,
}
Example: Chat Responder
File: examples/meshcore_chat_responder.lua
A full interactive bot. Queries live Domoticz sensor data and returns themed replies. Multi-part responses are queued one per minute.
Supported commands
| Command | Response |
|---|---|
!help | List all available commands. |
!status | Full summary: climate, weather, energy, home (multi-part). |
!climate | Indoor temperature, humidity, thermostat, ventilation. |
!weather | Outdoor temperature, wind, rain. |
!energy | Grid usage/delivery, solar, home battery, gas. |
!home | Water meter, presence status. |
!device <name> | Query any Domoticz device by its exact name. |
!switches | All switches and their state (capped at 15). |
!temp | All temperature sensors (capped at 10). |
Configuration block
Set any entry to nil to silently skip it.
local MESHCORE_INBOX = 'MeshCore Command In'
local MESHCORE_REPLY = 'MeshCore Reply'
local MESHCORE_SEND = 'MeshCore Send'
local CMD_PREFIX = '!'
local DEVICES = {
tempIndoor = 'Temperature - Living Room',
tempBathroom = 'Temperature - Bathroom', -- optional
tempOutdoor = 'Temperature - Outside',
thermostat = 'Thermostat',
heatpump = 'Heat Pump Status', -- Text device, optional
ventilation = 'Ventilation', -- Selector switch, optional
power = 'Power', -- P1 Smart Meter
solar = 'Solar Power', -- optional
homeBattery = 'Home Battery', -- Percentage device, optional
gas = 'Gas', -- optional
water = 'Water Meter', -- optional
presence = 'Presence', -- Selector switch, optional
wind = 'Wind', -- optional
rain = 'Rain', -- optional
}
Example mesh conversation
[You] !climate
[Bot] Climate: Indoor 20.3C, 52% | Thermostat 19.5C | Heat pump: Heating
[You] !device Power
[Bot] Power: 380W (updated: 2025-01-15 14:32:00)
[You] !status
[Bot] Climate: Indoor 20.3C, 52% | Thermostat 19.5C
[Bot+1m] Weather: 14.8C, 65% | Wind W 3.2 m/s
[Bot+2m] Energy: Solar 1240W | Delivering 380W | Gas today 0.42
Example: Status Reporter
File: examples/meshcore_status_report.lua
Sends periodic home-status updates to a channel without any user command. Ideal for a monitoring-only node that needs regular snapshots.
Behaviour
- Every hour (minute 00): builds themed messages and queues them.
- Every minute: drains one queued message (so a 3-topic status takes 3 minutes).
- On presence change: fires an immediate alert to the channel.
Configuration block
local CHANNEL_NAME = 'General' -- target channel name
local MESHCORE_REPLY = 'MeshCore Reply'
local MESHCORE_SEND = 'MeshCore Send'
local DEVICES = {
tempIndoor = 'Temperature - Living Room',
tempOutdoor = 'Temperature - Outside',
thermostat = 'Thermostat',
power = 'Power',
solar = 'Solar Power', -- optional
gas = 'Gas', -- optional
presence = 'Presence', -- optional, triggers immediate alert
}
Example output on the mesh
[14:00] Climate: Indoor 20.3C, 52% | Thermostat 19.5C
[14:01] Weather: 14.8C, 65%
[14:02] Energy: Solar 1240W | Delivering 380W | Gas today 0.42
Works With
Software and firmware that pairs well with this plugin.
Open-source home automation platform. This plugin runs inside the Domoticz Python plugin runtime.
domoticz.com →LoRa mesh node firmware. The plugin connects via the MeshCore TCP companion port or USB serial.
Modern dark UI for Domoticz with 16 theme presets, command palette (Ctrl+K), keyboard navigation, sparkline charts, and real-time WebSocket updates. Latest: v0.2.9.
GitHub →MeshCore-compatible firmware for Heltec v3/v4 boards with native WiFi — exposes the TCP port without a USB bridge.
meshcomod.com →Lua scripting engine built into Domoticz. Powers the command bridge examples — no extra install needed.
Domoticz wiki →Changelog
Full release notes on GitHub Releases.
Automated release workflow
Added GitHub Actions workflow for automated releases on tag push.
Map tools, historical analytics & fixes
- Coverage & line-of-sight terrain tools on the node map
- Historical analytics side panel (RSSI, SNR, packet volume, relay keys)
- Queued-message origin resolution fix
dzVents command bridge
- Command Bridge channel: three bridge devices for dzVents scripting
- Inbox scope filter and layout fixes
- Clock-skew correction for timestamps
UI & theming fixes
- Dashboard UI and theming improvements
- Collapsible node settings panel
- Contact view state preserved in local storage
WebSocket dashboard & SQLite message store
- Real-time dashboard over native Domoticz WebSocket channel (no more JSON-file polling)
- SQLite message store as single source of truth for inbox and DM threads
- Serial transport support
- Reliable messaging and device-side settings
dzVents example scripts
Status reporter and chat responder example scripts added.
Initial release
Self-node telemetry, message inbox, send controls and custom dashboard.
Troubleshooting
Enable Basic or All debug logging in the hardware settings, then check Setup → Log.
| Symptom | Fix |
|---|---|
meshcore package not installed in log |
Run pip install meshcore and restart Domoticz. On Docker, add it to customstart.sh. |
| No devices appear after connecting | Wait 30 s for the initial contacts poll. Check that your node is advertising on the mesh. Enable Basic debug to see discovered contact names. |
| Dashboard shows "Connecting…" indefinitely | Your Domoticz build is older than 17956 — update Domoticz. The WebSocket plugin channel was added in that release. |
| Command Bridge devices not created | Set a non-empty channel name in the Command Bridge Channel field, save, then restart the plugin. |
| dzVents script fires but no reply arrives on the mesh | Verify the channel name matches the plugin setting (case-insensitive, leading # stripped). Enable debug and look for Replying (id=…) in the log. |
| Connection drops and never recovers | The plugin detects a stale link after 10 min and reconnects automatically. Enable All debug to trace reconnect attempts. |
| Remote node battery / RSSI not showing | These require a STATUS_RESPONSE push from the remote node, which needs firmware support. Only the self (connected) node reliably provides these metrics. |
| Old devices orphaned after upgrade | After the DomoticzEx migration, delete old per-node devices via Setup → Devices, filtered by MeshCore hardware. The plugin recreates them automatically. |
Updating
cd ~/domoticz/plugins/MeshCore
git pull
sudo service domoticz.sh restart
For bugs and feature requests, open a GitHub Issue.
Support & Donate
This plugin is free and open-source. If it saves you time or brings your LoRa mesh into Domoticz in a useful way, a small contribution is always appreciated.
If the plugin saves you time, a small PayPal donation goes a long way.
Open an issue on GitHub. Include your Domoticz version, plugin version, and the relevant log lines.
Ideas and pull requests are welcome. Check the existing issues first to avoid duplicates.
Join the official MeshCore Discord to discuss firmware, hardware, range tests and share your mesh setups.