A production-ready MQTT proxy for Meshtastic devices that enables bidirectional message forwarding between Meshtastic nodes and MQTT brokers. Supports TCP and Serial interface connections with a clean factory pattern architecture.
- ✅ Modular Architecture - Clean separation of concerns with
config.py,handlers/mqtt.py,handlers/meshtastic.py, andhandlers/queue.py - ✅ Multi-Interface Support - TCP and Serial connections to Meshtastic nodes
- ✅ Bidirectional Forwarding - Messages flow both ways between node and MQTT broker
- ✅ Message Queue - Rate-limited transmission with configurable delay to prevent radio congestion
- ✅ Robust Packet Handling - SafeInterfaceMixin prevents crashes from malformed packets
- ✅ Implicit ACK Restoration - Intelligently bypasses loop protection for echoed packets, restoring the firmware's missing delivery confirmations (fixing the "Red X" issue)
- ✅ mqttClientProxyMessage Protocol - Implements Meshtastic's official proxy protocol
- ✅ Docker Containerized - Easy deployment with Docker Compose
- ✅ Environment Configuration - Flexible configuration via environment variables
- ✅ Production Ready - Error handling, logging, and automatic reconnection
- ✅ Channel Support - Works with all Meshtastic channels and message types
- ✅ SSL/TLS Support - Auto-configuration for secure brokers (matches iOS behavior)
- ✅ Traffic Optimization - Smart subscription strategy (
msh/2/e/#) to prevent serial link saturation - ✅ Hammering Prevention - Correctly flags retained messages to avoid
NO_RESPONSEstorms - ✅ MeshMonitor Compatible - Seamless integration with MeshMonitor and other tools
Note: BLE interface is not currently supported. Use TCP or Serial interfaces.
Note
The Docker setup below is designed for Linux systems. For Windows and macOS, see the Platform Support section.
- Docker and Docker Compose (Linux)
- Meshtastic node (accessible via TCP or Serial)
- MQTT broker (configured on your Meshtastic node)
- Clone the repository:
git clone https://github.com/LN4CY/mqtt-proxy.git
cd mqtt-proxy- Create
.envfile:
cp .env.example .env- Configure your connection in
.env:
INTERFACE_TYPE=tcp
TCP_NODE_HOST=192.168.1.100
TCP_NODE_PORT=4403- Start the proxy:
docker compose up -dYou can run the proxy directly without cloning the code using the pre-built image from GitHub Container Registry:
docker run -d \
--name mqtt-proxy \
--net=host \
--restart unless-stopped \
-e INTERFACE_TYPE=tcp \
-e TCP_NODE_HOST=192.168.1.100 \
-e TCP_NODE_PORT=4403 \
ghcr.io/ln4cy/mqtt-proxy:masterTCP Interface (default):
INTERFACE_TYPE=tcp
TCP_NODE_HOST=localhost
TCP_NODE_PORT=4403Serial Interface:
INTERFACE_TYPE=serial
SERIAL_PORT=/dev/ttyACM0| Variable | Default | Description |
|---|---|---|
INTERFACE_TYPE |
tcp |
Interface type: tcp or serial |
TCP_NODE_HOST |
localhost |
TCP hostname or IP address |
TCP_NODE_PORT |
4403 |
TCP port number |
SERIAL_PORT |
/dev/ttyUSB0 |
Serial device path |
LOG_LEVEL |
INFO |
Logging level |
TCP_TIMEOUT |
300 |
TCP connection timeout (seconds) |
CONFIG_WAIT_TIMEOUT |
60 |
Node config wait timeout (seconds) |
POLL_INTERVAL |
1 |
Config polling interval (seconds) |
MESH_TRANSMIT_DELAY |
0.5 |
Delay between packets for rate limiting (seconds) |
See CONFIG.md for detailed configuration options.
The proxy implements a robust health monitoring system to ensure reliable operation:
- Connection Watchdog: Automatically restarts if the Meshtastic connection is reported lost and doesn't recover within 60 seconds.
- Radio Activity Probe: Actively probes the radio if silent for 5 minutes. If no response receives, restarts the container.
- MQTT Publish Watchdog: Monitors MQTT publish success. If 5 consecutive publish attempts fail (e.g., broker unavailable, auth error), the proxy forces a restart to attempt a clean reconnection.
This self-healing behavior ensures the proxy recovers automatically from network interruptions without manual intervention.
Connect to a Meshtastic node via TCP (e.g., MeshMonitor's virtual node):
# .env
INTERFACE_TYPE=tcp
TCP_NODE_HOST=192.168.1.100
TCP_NODE_PORT=4404Connect to a USB-connected Meshtastic device:
# .env
INTERFACE_TYPE=serial
SERIAL_PORT=/dev/ttyACM0Note: Serial interface requires privileged mode (already configured in docker-compose.yml).
docker compose logs -f mqtt-proxydocker compose downThe Docker setup is designed for Linux and works out of the box for both TCP and Serial interfaces.
TCP Interface:
Docker Desktop for Windows works perfectly for TCP connections. Use the standard docker-compose.yml.
Serial Interface (USB): Docker on Windows does not support USB passthrough directly because Docker runs in WSL2.
Option A: Run Natively with venv (Recommended)
# Create and activate virtual environment
python -m venv venv
.\venv\Scripts\Activate.ps1
# Install dependencies
pip install -r requirements.txt
# Run with environment variables
$env:INTERFACE_TYPE="serial"
$env:SERIAL_PORT="COM3" # Check Device Manager for your actual COM port
python mqtt-proxy.pyOption B: Docker via WSL2 + usbipd (Advanced)
- Install usbipd-win
- Attach device:
usbipd wsl attach --busid <BUSID> - Device appears as
/dev/ttyACM0in WSL2/Docker
TCP Interface:
Docker Desktop for Mac works perfectly for TCP connections. Use the standard docker-compose.yml.
Serial Interface (USB): Docker on macOS does not support USB passthrough directly because Docker runs in a VM.
Option A: Run Natively with venv (Recommended)
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Find your device (usually /dev/cu.usbmodem* or /dev/tty.usbmodem*)
ls /dev/cu.usbmodem*
# Run with environment variables
export INTERFACE_TYPE=serial
export SERIAL_PORT=/dev/cu.usbmodem14201 # Use your actual device path
python mqtt-proxy.pyOption B: Docker Desktop USB Forwarding (Experimental) Docker Desktop for Mac 4.27+ supports USB device forwarding:
- Enable in Docker Desktop settings: Settings → Resources → USB devices
- Select your Meshtastic device
- Device appears as
/dev/ttyACM0in containers - Update
docker-compose.ymldevices section accordingly
For a seamless integration with MeshMonitor, add the proxy as a service in your main docker-compose.yml.
Important
If you plan to use the MeshMonitor serial bridge or BLE bridge, you must use a virtual node enabled configuration for MeshMonitor to ensure proper connectivity.
- Shared Network: Use a custom bridge network (
meshtastic_net) for all services to enable service-name discovery. - Startup Order: Use a healthcheck on
meshmonitorsomqtt-proxyonly starts when the virtual node is ready. - Environment: Use
TCP_NODE_HOST=meshmonitorto avoid hardcoded IPs.
version: '3'
services:
# The main application
meshmonitor:
image: ghcr.io/yeraze/meshmonitor:latest
container_name: meshmonitor
restart: unless-stopped
ports:
- "8181:3001"
- "4404:4404"
environment:
- ENABLE_VIRTUAL_NODE=true
- VIRTUAL_NODE_PORT=4404
- MESHTASTIC_NODE_IP=serial-bridge # Connects to serial-bridge by name
- STATUS_FILE=/data/.upgrade-status
- CHECK_INTERVAL=5
- COMPOSE_PROJECT_DIR=/compose
- COMPOSE_PROJECT_NAME=meshmonitor # Critical: Forces upgrader to use shared network
command: /data/scripts/upgrade-watchdog.sh
# Add simple healthcheck to ensure port 4404 is open
healthcheck:
test: ["CMD-SHELL", "node -e 'const net = require(\"net\"); const client = new net.Socket(); client.connect(4404, \"127.0.0.1\", () => { process.exit(0); }); client.on(\"error\", () => { process.exit(1); });'"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
depends_on:
- serial-bridge
networks:
- meshtastic_net
# The generic serial bridge
serial-bridge:
image: ghcr.io/yeraze/meshtastic-serial-bridge:latest
container_name: meshtastic-serial-bridge
devices:
- /dev/ttyACM0:/dev/ttyACM0
environment:
- SERIAL_DEVICE=/dev/ttyACM0
- TCP_PORT=4403
networks:
- meshtastic_net
# This proxy service (The Glue)
mqtt-proxy:
image: ghcr.io/ln4cy/mqtt-proxy:master
container_name: mqtt-proxy
restart: unless-stopped
environment:
- INTERFACE_TYPE=tcp
- TCP_NODE_HOST=meshmonitor # Connects to meshmonitor by name
- TCP_NODE_PORT=4404
depends_on:
meshmonitor:
condition: service_healthy # Wait for port 4404 to be listening
networks:
- meshtastic_net
networks:
meshtastic_net:
driver: bridgeThe proxy uses a modular architecture with clean separation of concerns:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Meshtastic │ ◄─────► │ MQTT Proxy │ ◄─────► │ MQTT Broker │
│ Node │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────┘
TCP/Serial mqttClientProxy MQTT Protocol
│
┌────────┴────────┐
│ │
MessageQueue SafeInterfaceMixin
(Rate Limiting) (Crash Prevention)
mqtt-proxy.py- Main orchestrator and entry pointconfig.py- Centralized configuration managementhandlers/mqtt.py- MQTT connection and message handlinghandlers/meshtastic.py- Meshtastic interface with SafeInterfaceMixinhandlers/queue.py- Message queue for rate limiting and reliability- SafeInterfaceMixin - Prevents crashes from malformed packets, detects implicit ACKs
- MessageQueue - Thread-safe queue with configurable rate limiting
- Node → MQTT: Proxy receives
mqttClientProxyMessagefrom node and publishes to MQTT broker - MQTT → Node: Proxy subscribes to MQTT topics and forwards messages to node as
mqttClientProxyMessage - Implicit ACK Handling: The proxy detects when the MQTT broker echoes a message sent by the local node and forwards it back. This fulfills the firmware's requirement for a delivery confirmation, ensuring the node generates a local "Implicit ACK" rather than waiting 45 seconds and timing out (which causes Red X delivery failures in MeshMonitor).
- Transparent Operation: Node firmware handles encryption, channel mapping, and routing
- Python 3.9+
- Docker & Docker Compose
- Meshtastic node with MQTT enabled and
proxy_to_client_enabled: true
- meshtastic==2.7.7
- paho-mqtt==2.1.0
- pubsub==4.0.7
- protobuf>=3.20.0,<6.0.0
TCP Connection Fails:
- Verify node IP and port
- Check firewall rules
- Ensure node is running
Serial Connection Fails:
- Check device path (
ls /dev/tty*) - Verify device permissions
- Ensure privileged mode is enabled
No MQTT Traffic:
- Verify MQTT is enabled on node:
meshtastic --get mqtt - Check
proxy_to_client_enabled: true - Verify MQTT broker is accessible
Messages Not Appearing:
- Check MQTT broker logs
- Verify channel configuration
- Review proxy logs for errors
# Install dependencies
pip install -r requirements.txt
# Run locally
python mqtt-proxy.pydocker compose buildContributions welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
This project's source code is released under the MIT License. See the LICENSE file for details.
while the source code of this proxy is MIT licensed, it depends on third-party libraries that use different licenses:
- meshtastic-python: Licensed under GPLv3.
- paho-mqtt: Licensed under EPL-2.0 / BSD.
Important
GPL Compatibility Note:
Because the proxy imports and links with meshtastic (GPLv3), any distributed binary or Docker image containing these components is effectively subject to the terms of the GPLv3.
If you are building upon this project and plan to distribute it, ensure you comply with the requirements of the GPLv3 for the combined work.
- Built for the Meshtastic project
- Compatible with MeshMonitor
- Implements the mqttClientProxyMessage protocol from Meshtastic firmware
- Issues: GitHub Issues
- Meshtastic Discord: Join
- Documentation: See CONFIG.md for detailed configuration
- BLE Interface Support - Requires custom bleak implementation (see meshtastic-ble-bridge for reference)
- Metrics and monitoring endpoints
- Web UI for configuration
- Multi-node support
Status: Production Ready ✅
This application was developed with Antigravity and the help of Gemini