Racks & WattsLOCAL HARDWARE / CLEAR PICKS

Meshtastic

Bridge Meshtastic to Home Assistant with MQTT

Connect an ESP32 gateway to a local Mosquitto broker for battery graphs and telemetry. Set privacy-safe uplinks and add selected-message alerts.

An unbranded radio node beside a compact server on a warm workbench, with abstract charts on a wall tablet.

OUR PICKS / YOUR SETUP

Find your fit.

Use a Heltec V3 Wi-Fi gateway and your own Mosquitto broker, with only the intended channel uplinked and every downlink off. JSON is plaintext; keep it local. Use the custom HACS integration for direct-message triggers at the recipient gateway.

  • Gateway for JSONESP32 / Wi-Fi
  • Start directionUplink only
  • BrokerLocal Mosquitto

How we evaluate hardware.

A radio message doesn’t need to stop at your phone. Give one Meshtastic node a path to a local MQTT broker and you can graph battery readings, watch telemetry and get the useful messages in Home Assistant—without building a public radio-to-internet bridge.

This research guide uses Meshtastic, Home Assistant and maker documentation, checked October 29, 2026.

Start with a Heltec V3 Wi-Fi gateway, your own Mosquitto broker and uplink only. Keep radio downlinks off while you build the dashboard. For direct-message alerts, the Meshtastic project’s custom HA integration offers a separate connection to the recipient node.

What the MQTT module actually does

Meshtastic’s MQTT module bridges radio packets and a broker. Its integration reference explains the two directions:

Setting What it changes Starting choice
Channel uplink Publishes that channel’s packets to MQTT On only for the controlled channel you intend to bridge
Channel downlink Accepts broker traffic for transmission onto LoRa Off on every channel
Encryption Enabled Keeps the protobuf packet payload encrypted for MQTT On
JSON Enabled Adds readable JSON for supported packet types On only to your access-controlled local broker
TLS Enabled Protects the broker connection in transit Configure for your broker’s TLS setup
Map Reporting Publishes a separate unencrypted map report Off

JSON packets are plaintext, even with Encryption Enabled on. That encryption switch protects the protobuf route, not the JSON copy. TLS protects transport; broker access and retained data still matter.

JSON generation isn’t supported on nRF52 devices such as the RAK4631. An ESP32 gateway can receive another node’s telemetry over LoRa and produce the JSON for HA. Giving an nRF52 board Ethernet or a phone proxy doesn’t add JSON generation to its firmware.

Keep the broker yours

Use the official Mosquitto Broker app, formerly called the add-on. Home Assistant’s MQTT documentation recommends a private broker and can configure Mosquitto alongside the MQTT integration.

A local broker gives you control of logins, topic access and storage. Never uplink a private channel to a public broker. Use a dedicated, controlled bench channel with a generated private key for the lesson below; don’t reuse the default public key on a private broker. Meshtastic warns that private brokers don’t enforce the public server’s traffic restrictions and can flood the radio mesh.

Leave downlinks off and skip automatic echo replies. A battery graph needs incoming readings, not an acknowledgement transmitted over LoRa for every database update. The channels and encryption guide covers the radio side of that choice.

Buy the gateway; reuse the HA host if you have one

The Heltec WiFi LoRa 32 V3 is the ESP32-based Wi-Fi board pick from our starter-node guide. It needs USB power, a matching antenna and a suitable case. Attach the antenna before powering the radio, and set the region your country permits; the linked board is the US 915 MHz version.

Already have Home Assistant, including HA Green? Keep it. If you need a host, use the complete Pi 5 NVMe stack below—not just a bare board.

Job Part Fit
Wi-Fi gateway Heltec WiFi LoRa 32 V3, 915 MHz ESP32-S3 board for the JSON route
HA host board Raspberry Pi 5 8GB Board for the complete NVMe host
Power Official 27W USB-C PSU Official 5.1 V, 5 A supply
Cooling Raspberry Pi Active Cooler Fits beneath the standard HAT+
Storage adapter Raspberry Pi M.2 HAT+ Standard version: 2230/2242 drives only
SSD Kingston NV3 500GB 2230 SNV3SM3/500G, the short drive
Open case base Official Pi 5 case Remove its lid and supplied fan for the standard HAT+ stack
Integrated case alternative Argon ONE V3 M.2 Replaces the HAT+, cooler and official case with its own adapter/cooling; still needs Pi, PSU and SSD

Follow the complete Pi 5 NVMe build for installation and Argon’s HA OS fan/control trade-offs. The solar relay guide covers the remote node’s power system; the mains-powered gateway does the Wi-Fi work here.

Connect Mosquitto and the radio

Periodic battery and voltage telemetry travels on the source node’s primary channel. The gateway must share that channel’s name and key and uplink it; adding a secondary chat channel on the source won’t move its telemetry there. Renaming a primary channel changes its automatically selected frequency slot, so explicitly match the slot when the nodes have different primary names. The Meshtastic channel guide explains both settings.

  1. In HA, install and start Mosquitto under Settings → Apps → Install app. Review its logs, then configure the discovered MQTT integration under Settings → Devices & services.
  2. Give the gateway its own broker login. The Mosquitto app documentation covers additional logins and topic ACLs. Limit that login to the topics it needs; keep credentials and channel keys out of screenshots.
  3. Enable the node’s Wi-Fi connection and enter your local broker’s address, username and password in MQTT settings. Don’t use localhost on the radio—that means the radio itself. Don’t leave the address blank: Meshtastic then uses its default public server.
  4. Enable the MQTT module, JSON and protobuf encryption. Set a distinct root topic; the fictional example below uses labdemo. Leave Map Reporting off.
  5. For the example below, make BenchChat the primary channel on both SolarDemo and the gateway, with the same generated private key and modem preset. Enable uplink on the gateway’s BenchChat channel and leave all downlinks off. Save, then use MQTT’s Listen to a topic tool in HA to inspect its JSON.

A supported Ethernet gateway is another network path, with its own hardware requirements. MQTT Client Proxy instead uses the connected client’s network connection, such as your phone’s. It depends on that client remaining connected, so use the independent Wi-Fi gateway for a dashboard you want available without your phone.

Turn one JSON reading into a battery sensor

Everything in this example—names, IDs, topics and values—is invented. SolarDemo has ID !12345678, or decimal 305419896. Both nodes use the BenchChat primary channel configured above. The publishing gateway is !87654321:

labdemo/2/json/BenchChat/!87654321

The final topic segment identifies the gateway. The JSON from field identifies the originating node; don’t mistake one for the other.

{"type":"telemetry","from":305419896,"sender":"!87654321","payload":{"battery_level":72,"voltage":4.01}}

The raw topic mixes message types. Filter real battery samples into a separate local topic so a text packet doesn’t refresh the battery sensor’s expiry. Paste this into an automation’s YAML editor, then substitute your own root, channel and originating ID:

alias: Mesh demo battery to sensor
triggers:
  - trigger: mqtt
    topic: "labdemo/2/json/BenchChat/+"
conditions:
  - condition: template
    value_template: >-
      {% set p = trigger.payload_json | default(none) %}
      {{ p is mapping and p.get('type') == 'telemetry'
         and p.get('from') == 305419896
         and p.get('payload') is mapping
         and is_number(p.payload.get('battery_level'))
         and p.payload.battery_level is not boolean
         and 0 <= (p.payload.battery_level | float) <= 100 }}
actions:
  - action: mqtt.publish
    data:
      topic: "labdemo/ha/solar_demo/battery"
      payload: "{{ trigger.payload_json.payload.battery_level }}"
      retain: false
mode: queued

Then merge this into your existing mqtt: configuration in configuration.yaml—don’t create a second top-level mqtt: key:

mqtt:
  sensor:
    - name: "Solar Demo Battery"
      unique_id: meshtastic_demo_battery
      state_topic: "labdemo/ha/solar_demo/battery"
      device_class: battery
      state_class: measurement
      unit_of_measurement: "%"
      expire_after: 10800

Save, check your HA configuration, then run mqtt.reload from Developer tools → Actions, or restart Home Assistant.

Example assumptions: hourly battery reports, expiry after three missed reports, an alert below 20% and rearm above 25%. Three hours is 10,800 seconds; choose expiry for your actual reporting schedule rather than increasing radio traffic to feed a graph. Use non-retained measurement messages and clear retained test samples before testing expiry.

Home Assistant documents MQTT triggers, templates and MQTT sensor expiry. Keep out-of-range or missing battery values out of this percentage sensor, rather than converting them to zero.

For channel text, duplicate the normalizer with type: text, your allowed from ID and a string-valued payload.text; publish that text to a separate local topic. Add another MQTT sensor for it without a measurement class or percent unit. Meshtastic’s HA examples show the text and telemetry fields. Environmental temperature/humidity needs the corresponding sensor hardware and payload; it isn’t supplied by every node.

The custom integration: useful for direct messages

The Meshtastic project hosts meshtastic/home-assistant, a custom community integration installed through HACS, not a Home Assistant core integration. It connects directly to a radio over TCP, serial or Bluetooth, imports selected nodes and exposes metrics, message triggers and actions. It can also act as an MQTT client proxy; it isn’t simply a replacement JSON sensor parser.

For private-message alerts, connect it to the recipient gateway and select the gateway’s Receives Message on [DM entity] trigger (direct_message.received, filtered by entity_id). Remote nodes expose sent-message triggers. For a sender-specific alert, use the text-message event in the recipe below; the gateway device trigger has no sender field. Meshtastic’s encryption documentation explains that a node decrypts a message for its connected client, while current PKI DMs belong to their endpoints. Don’t expect MQTT JSON to reveal every DM overheard by the gateway.

The integration documents a single-client connection limit. Choose HA or the phone as the owner of that client connection; don’t make both compete for it.

Three useful automations

Low battery on a solar node. Trigger on each new message on the normalized battery topic. If the fresh value is below the example alert threshold and a “Battery alert sent” Toggle helper (created off) is off, notify your phone and turn the helper on. Reset it after a fresh reading above the rearm threshold. This catches a node that is already low on its next report, without depending on a threshold crossing or repeatedly buzzing for every sample.

A DM from one node. In Developer tools → Events, listen for meshtastic_api_text_message while the fictional FieldDemo sends a DM to the gateway. Copy the numeric data.from, data.to.node and data.gateway values into an Event trigger’s event-data filter; these select FieldDemo as sender and your gateway as both receiver and connected radio. Then send a Companion phone notification. Test with a FieldDemo DM, a different sender and a channel message: only the first should notify. Keep replies manual. The integration’s device triggers and text-message event implementation define these fields.

Log telemetry, not everything. Graph the battery sensor in History; apply the same filtered-topic pattern to voltage or a real environmental probe. HA’s Recorder stores history and supports exclusions. Exclude the last-text sensor if you don’t want message content in that archive, and review the custom integration’s separate message-log and position-tracker features.

Leave location out of the bridge

Map Reporting off stops the special map report, not ordinary position packets that might travel on an uplinked channel. Review position broadcasts and channel precision too. Bridge only nodes and data you own or have permission to publish; keep the bench example free of position data. Meshtastic also advises keeping private channel keys off unattended relay nodes—they can relay traffic without decrypting it.

Finish with a local test: publish the synthetic JSON to the fictional ingress topic, confirm the battery sensor updates, then try a text packet and confirm it doesn’t update the battery path. Test notifications to your phone without transmitting test floods over LoRa. Keep downlinks disabled and verify that no public broker or map feed is part of the configuration.

Explore the Meshtastic hub for the radio projects and the Home Assistant hub for the dashboard side. The win is a quiet little bridge: radio readings arrive, graphs move, and only the messages you chose interrupt your day.

Sources & methodology

Where these facts come from.

Source dates are listed above.

As an Amazon Associate I earn from qualifying purchases.

Parts from this article

The gear, on Amazon

The models recommended above, with the specifications that matter for your choice.

DIY starter board

Heltec WiFi LoRa 32 V3, 915 MHz

Build around an OLED-equipped ESP32 board with Wi-Fi and a USB-C setup connection.

ESP32-S3 + SX12620.96-inch OLEDAdd battery and case
Check price on Amazon →

The board

Raspberry Pi 5 8GB

Quad-core Pi 5 with Gigabit Ethernet, PCIe for NVMe and USB ports for connected hardware.

BCM2712 2.4 GHz8 GB LPDDR4XPCIe 2.0 x1
Check price on Amazon →

Power

Raspberry Pi 27W USB-C Power Supply

The official 5.1 V 5 A supply for a Pi 5 driving an SSD and USB devices.

5.1 V 5 AUSB-C PD
Check price on Amazon →

Cooling

Raspberry Pi Active Cooler

Clip-on heatsink and temperature-controlled fan; the M.2 HAT+ stacks above it.

Aluminium heatsinkTemperature-controlled fan
Check price on Amazon →

NVMe adapter

Raspberry Pi M.2 HAT+

Connects a 2230 or 2242 NVMe SSD to the Pi 5 PCIe connector.

PCIe 2.02230/2242Fits over Active Cooler
Check price on Amazon →

The SSD

Kingston NV3 500GB M.2 2230 NVMe SSD (SNV3SM3/500G)

A 2230 NVMe drive that fits the M.2 HAT+ and the Argon case.

500 GBM.2 2230NVMe
Check price on Amazon →

Official case base

Official Raspberry Pi 5 case

Use the base for a standard M.2 HAT+ stack with the lid and supplied case fan removed.

Pi 5 caseIncluded fanCompact HAT+ fits
Check price on Amazon →

Closed-case option

Argon ONE V3 M.2 NVMe case for Raspberry Pi 5

Aluminium case with its own NVMe board, fan and power button, for drives up to 2280.

M.2 up to 2280Blower fanPower button
Check price on Amazon →

Prepared by Racks & Watts.