A robust bridge service that connects MeshCore devices to MQTT brokers, enabling seamless integration with IoT platforms and message processing systems.
- Inbox/Outbox Architecture: Independent MeshCore and MQTT workers with message bus coordination
- Multiple Connection Types: Support for TCP, Serial, and BLE connections to MeshCore devices
- Full Command Support: Send messages, query device information, and network operations via MQTT
- TLS/SSL Support: Secure MQTT connections with configurable certificates
- Flexible Configuration: JSON, YAML, environment variables, and command-line configuration options
- MQTT Integration: Full MQTT client with authentication, QoS, retention, and auto-reconnection
- Configurable Event Monitoring: Subscribe to specific MeshCore event types for optimal performance
- Health Monitoring: Built-in health checks and automatic recovery for both workers
- Async Architecture: Built with Python asyncio for high performance and concurrent operations
- Type Safety: Full type annotations with mypy support
- Comprehensive Testing: 59+ unit tests with pytest and pytest-asyncio
- Code Quality: Pre-commit hooks with black formatting, flake8 linting, and automated testing
git clone <repository-url>
cd meshcore-mqtt
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
pip install -r requirements-dev.txt
pre-commit install
The bridge supports multiple configuration methods with the following precedence:
- Command-line arguments (highest priority)
- Configuration file (JSON or YAML)
- Environment variables (lowest priority)
mqtt_broker
: MQTT broker address (required)mqtt_port
: MQTT broker port (default: 1883)mqtt_username
: MQTT authentication username (optional)mqtt_password
: MQTT authentication password (optional)mqtt_topic_prefix
: MQTT topic prefix (default: "meshcore")mqtt_qos
: Quality of Service level 0-2 (default: 0)mqtt_retain
: Message retention flag (default: false)
meshcore_connection
: Connection type (serial, ble, tcp)meshcore_address
: Device address (required)meshcore_port
: Device port for TCP connections (default: 12345)meshcore_baudrate
: Baudrate for serial connections (default: 115200)meshcore_timeout
: Operation timeout in seconds (default: 5)meshcore_auto_fetch_restart_delay
: Delay in seconds before restarting auto-fetch after NO_MORE_MSGS (default: 5, range: 1-60)meshcore_events
: List of MeshCore event types to subscribe to (see Event Types)
log_level
: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
{
"mqtt": {
"broker": "mqtt.example.com",
"port": 1883,
"username": "myuser",
"password": "mypass",
"topic_prefix": "meshcore",
"qos": 1,
"retain": false
},
"meshcore": {
"connection_type": "tcp",
"address": "192.168.1.100",
"port": 12345,
"baudrate": 115200,
"timeout": 10,
"auto_fetch_restart_delay": 10,
"events": [
"CONTACT_MSG_RECV",
"CHANNEL_MSG_RECV",
"CONNECTED",
"DISCONNECTED",
"BATTERY",
"DEVICE_INFO"
]
},
"log_level": "INFO"
}
mqtt:
broker: mqtt.example.com
port: 1883
username: myuser
password: mypass
topic_prefix: meshcore
qos: 1
retain: false
meshcore:
connection_type: tcp
address: "192.168.1.100"
port: 12345
baudrate: 115200
timeout: 10
auto_fetch_restart_delay: 10
events:
- CONTACT_MSG_RECV
- CHANNEL_MSG_RECV
- CONNECTED
- DISCONNECTED
- BATTERY
- DEVICE_INFO
log_level: INFO
export MQTT_BROKER=mqtt.example.com
export MQTT_PORT=1883
export MQTT_USERNAME=myuser
export MQTT_PASSWORD=mypass
export MESHCORE_CONNECTION=tcp
export MESHCORE_ADDRESS=192.168.1.100
export MESHCORE_PORT=12345
export MESHCORE_BAUDRATE=115200
export MESHCORE_AUTO_FETCH_RESTART_DELAY=10
export MESHCORE_EVENTS="CONNECTED,DISCONNECTED,BATTERY,DEVICE_INFO"
export LOG_LEVEL=INFO
python -m meshcore_mqtt.main --config-file config.json
python -m meshcore_mqtt.main \
--mqtt-broker mqtt.example.com \
--mqtt-username myuser \
--mqtt-password mypass \
--meshcore-connection tcp \
--meshcore-address 192.168.1.100 \
--meshcore-port 12345 \
--meshcore-auto-fetch-restart-delay 10 \
--meshcore-events "CONNECTED,DISCONNECTED,BATTERY"
python -m meshcore_mqtt.main --env
python -m meshcore_mqtt.main \
--mqtt-broker localhost \
--meshcore-connection tcp \
--meshcore-address 192.168.1.100 \
--meshcore-port 12345
python -m meshcore_mqtt.main \
--mqtt-broker localhost \
--meshcore-connection serial \
--meshcore-address /dev/ttyUSB0 \
--meshcore-baudrate 9600
python -m meshcore_mqtt.main \
--mqtt-broker localhost \
--meshcore-connection ble \
--meshcore-address AA:BB:CC:DD:EE:FF
The bridge can subscribe to various MeshCore event types. You can configure which events to monitor using the events
configuration option.
If no events are specified, the bridge subscribes to these default events:
CONTACT_MSG_RECV
- Contact messages receivedCHANNEL_MSG_RECV
- Channel messages receivedDEVICE_INFO
- Device information updatesBATTERY
- Battery status updatesNEW_CONTACT
- New contact discoveredADVERTISEMENT
- Device advertisement broadcastsTRACE_DATA
- Network trace informationTELEMETRY_RESPONSE
- Telemetry data responsesCHANNEL_INFO
- Channel configuration details
You can also subscribe to these additional event types:
CONNECTED
- Device connection events (can be noisy)DISCONNECTED
- Device disconnection events (can be noisy)LOGIN_SUCCESS
- Successful authenticationLOGIN_FAILED
- Failed authenticationMESSAGES_WAITING
- Pending messages notificationCONTACTS
- Contact list updatesSELF_INFO
- Own device information
meshcore:
events:
- CONNECTED
- DISCONNECTED
- BATTERY
meshcore:
events:
- CONTACT_MSG_RECV
- CHANNEL_MSG_RECV
- TEXT_MESSAGE_RX
meshcore:
events:
- CONTACT_MSG_RECV
- CHANNEL_MSG_RECV
- CONNECTED
- DISCONNECTED
- BATTERY
- DEVICE_INFO
- ADVERTISEMENT
Note: Event names are case-insensitive. You can use connected
, CONNECTED
, or Connected
- they will all be normalized to uppercase.
The bridge provides full bidirectional communication between MQTT and MeshCore devices. Using the configured topic prefix (default: "meshcore"):
The bridge publishes to these topics based on configured MeshCore events:
{prefix}/message/channel/{channel_idx}
- Channel messages from CHANNEL_MSG_RECV events{prefix}/message/direct/{pubkey_prefix}
- Direct messages from CONTACT_MSG_RECV events{prefix}/status
- Connection status from CONNECTED/DISCONNECTED events
Message Topic Subscription Patterns:
- Subscribe to all messages:
{prefix}/message/+/+
- Subscribe to all channel messages:
{prefix}/message/channel/+
- Subscribe to specific channel:
{prefix}/message/channel/0
- Subscribe to all direct messages:
{prefix}/message/direct/+
- Subscribe to messages from specific node:
{prefix}/message/direct/{specific_pubkey_prefix}
Trace Topic Subscription Patterns:
- Subscribe to all trace responses:
{prefix}/traceroute/+
- Subscribe to specific trace tag:
{prefix}/traceroute/12345
Where:
{channel_idx}
is the numeric channel identifier (e.g., 0, 1, 2){pubkey_prefix}
is the sender's 6-byte public key prefix (hex encoded, e.g.,a1b2c3d4e5f6
){tag}
is the trace identifier (32-bit integer, e.g., 12345)
Other Published Topics:
{prefix}/login
- Authentication status from LOGIN_SUCCESS/LOGIN_FAILED events{prefix}/device_info
- Device information from DEVICE_INFO events{prefix}/battery
- Battery status from BATTERY events{prefix}/new_contact
- Contact discovery from NEW_CONTACT events{prefix}/advertisement
- Device advertisements from ADVERTISEMENT events{prefix}/telemetry
- Telemetry data from TELEMETRY_RESPONSE events{prefix}/contacts
- Contact list updates from CONTACTS events{prefix}/self_info
- Own device information from SELF_INFO events{prefix}/channel_info
- Channel configuration from CHANNEL_INFO events
Send commands to MeshCore devices via MQTT using {prefix}/command/{command_type}
with JSON payloads:
-
{prefix}/command/send_msg
- Send direct message to contact/node{"destination": "contact_name_or_node_id", "message": "Hello!"}
-
{prefix}/command/send_chan_msg
- Send channel message{"channel": 0, "message": "Hello channel!"}
{prefix}/command/device_query
- Query device information{}
{prefix}/command/get_battery
- Get battery status{}
{prefix}/command/set_name
- Set device name{"name": "MyDevice"}
{prefix}/command/ping
- Ping a specific node{"destination": "node_id"}
{prefix}/command/send_trace
- Send trace packet for routing diagnostics// Basic trace {} // Trace with specific routing path through repeaters {"auth_code": 12345, "path": "23,5f,3a", "flags": 1}
Using mosquitto_pub
client:
# Send direct message
mosquitto_pub -h localhost -t "meshcore/command/send_msg" \
-m '{"destination": "Alice", "message": "Hello Alice!"}'
# Send channel message
mosquitto_pub -h localhost -t "meshcore/command/send_chan_msg" \
-m '{"channel": 0, "message": "Hello everyone on channel 0!"}'
# Get device information
mosquitto_pub -h localhost -t "meshcore/command/device_query" -m '{}'
# Set device name
mosquitto_pub -h localhost -t "meshcore/command/set_name" \
-m '{"name": "MyMeshDevice"}'
# Send device advertisement
mosquitto_pub -h localhost -t "meshcore/command/send_advert" -m '{}'
# Send device advertisement with flood
mosquitto_pub -h localhost -t "meshcore/command/send_advert" \
-m '{"flood": true}'
# Send trace packet (basic routing diagnostics)
mosquitto_pub -h localhost -t "meshcore/command/send_trace" -m '{}'
# Request telemetry data from a node
mosquitto_pub -h localhost -t "meshcore/command/send_telemetry_req" \
-m '{"destination": "node123"}'
# Send trace packet with routing path through specific repeaters
mosquitto_pub -h localhost -t "meshcore/command/send_trace" \
-m '{"auth_code": 12345, "path": "23,5f,3a"}'
# Send trace with specific tag for tracking responses
mosquitto_pub -h localhost -t "meshcore/command/send_trace" \
-m '{"tag": 67890, "path": "a0,b1,c2"}'
# Subscribe to all trace responses
mosquitto_sub -h localhost -t "meshcore/traceroute/+"
# Subscribe to specific trace tag responses
mosquitto_sub -h localhost -t "meshcore/traceroute/67890"
The bridge supports these MeshCore commands via MQTT:
Command | Description | Required Fields | Example |
---|---|---|---|
send_msg |
Send direct message | destination , message |
{"destination": "Alice", "message": "Hello!"} |
send_chan_msg |
Send channel message | channel , message |
{"channel": 0, "message": "Hello channel!"} |
device_query |
Query device information | None | {} |
get_battery |
Get battery status | None | {} |
set_name |
Set device name | name |
{"name": "MyDevice"} |
send_advert |
Send device advertisement | None (optional: flood ) |
{} or {"flood": true} |
send_trace |
Send trace packet for routing diagnostics | None (optional: auth_code , tag , flags , path ) |
{} or {"auth_code": 12345, "path": "23,5f,3a"} |
send_telemetry_req |
Request telemetry data from a node | destination |
{"destination": "node123"} |
meshcore/message/channel/0
- Channel 0 messagesmeshcore/message/direct/a1b2c3
- Direct messages from node a1b2c3meshcore/status
- Bridge connection status ("connected"/"disconnected")meshcore/events/connection
- Raw MeshCore connection events (JSON)meshcore/battery
- Battery level updatesmeshcore/device_info
- Device specifications and capabilitiesmeshcore/advertisement
- Device advertisement broadcastsmeshcore/traceroute/{tag}
- Network trace responses (tag-specific)meshcore/command/send_msg
- Send message command (subscribed)meshcore/command/ping
- Ping command (subscribed)meshcore/command/send_trace
- Send trace packet command (subscribed)
The MeshCore MQTT Bridge provides multi-stage Docker support with Alpine Linux for minimal image size and enhanced security. Following 12-factor app principles, the Docker container is configured entirely through environment variables.
- Multi-stage build: Optimized Alpine-based images with minimal attack surface
- Non-root user: Runs as dedicated
meshcore
user for security - Environment variables: Full configuration via environment variables (12-factor app)
- Health checks: Built-in container health monitoring
- Signal handling: Proper init system with tini for clean shutdowns
- Container logging: Logs output to stdout/stderr for Docker log drivers
Pre-built Docker images are available from GitHub Container Registry:
Available Tags:
latest
- Latest stable release from main branchdevelop
- Latest development buildv1.0.0
- Specific version tagsv1.0
- Major.minor version tagsv1
- Major version tags
# Pull the latest image
docker pull ghcr.io/ipnet-mesh/meshcore-mqtt:latest
# Run with serial connection (default for MeshCore devices)
docker run -d \
--name meshcore-mqtt-bridge \
--restart unless-stopped \
--device=/dev/ttyUSB0:/dev/ttyUSB0 \
-e MQTT_BROKER=192.168.1.100 \
-e MQTT_USERNAME=meshcore \
-e MQTT_PASSWORD=meshcore123 \
-e MESHCORE_CONNECTION=serial \
-e MESHCORE_ADDRESS=/dev/ttyUSB0 \
ghcr.io/ipnet-mesh/meshcore-mqtt:latest
# Build the image locally
docker build -t meshcore-mqtt:latest .
# Run with local image
docker run -d \
--name meshcore-mqtt-bridge \
--restart unless-stopped \
--device=/dev/ttyUSB0:/dev/ttyUSB0 \
-e MQTT_BROKER=192.168.1.100 \
-e MQTT_USERNAME=meshcore \
-e MQTT_PASSWORD=meshcore123 \
-e MESHCORE_CONNECTION=serial \
-e MESHCORE_ADDRESS=/dev/ttyUSB0 \
meshcore-mqtt:latest
# Create environment file from example
cp .env.docker.example .env.docker
# Edit .env.docker with your configuration
# Run with environment file (includes device mount for serial)
docker run -d \
--name meshcore-mqtt-bridge \
--restart unless-stopped \
--device=/dev/ttyUSB0:/dev/ttyUSB0 \
--env-file .env.docker \
ghcr.io/ipnet-mesh/meshcore-mqtt:latest
# Start the entire stack with MQTT broker
docker-compose up -d
# View logs
docker-compose logs -f meshcore-mqtt
# Stop the stack
docker-compose down
All configuration options can be set via environment variables:
# Logging Configuration
LOG_LEVEL=INFO
# MQTT Broker Configuration
MQTT_BROKER=localhost
MQTT_PORT=1883
MQTT_USERNAME=user
MQTT_PASSWORD=pass
MQTT_TOPIC_PREFIX=meshcore
MQTT_QOS=1
MQTT_RETAIN=true
# MQTT TLS/SSL Configuration (optional)
MQTT_TLS_ENABLED=false
MQTT_TLS_CA_CERT=/path/to/ca.crt
MQTT_TLS_CLIENT_CERT=/path/to/client.crt
MQTT_TLS_CLIENT_KEY=/path/to/client.key
MQTT_TLS_INSECURE=false
# MeshCore Device Configuration (serial default)
MESHCORE_CONNECTION=serial
MESHCORE_ADDRESS=/dev/ttyUSB0 # Serial port, IP address, or BLE MAC address
MESHCORE_BAUDRATE=115200 # For serial connections
MESHCORE_PORT=4403 # Only for TCP connections
MESHCORE_TIMEOUT=30
MESHCORE_AUTO_FETCH_RESTART_DELAY=10 # Restart delay after NO_MORE_MSGS (1-60 seconds)
# Event Configuration (comma-separated)
MESHCORE_EVENTS=CONNECTED,DISCONNECTED,BATTERY,DEVICE_INFO
Serial Connection (Default):
MESHCORE_CONNECTION=serial
MESHCORE_ADDRESS=/dev/ttyUSB0
MESHCORE_BAUDRATE=115200
# Note: Use --device=/dev/ttyUSB0 in docker run for device access
TCP Connection:
MESHCORE_CONNECTION=tcp
MESHCORE_ADDRESS=192.168.1.100
MESHCORE_PORT=4403
BLE Connection:
MESHCORE_CONNECTION=ble
MESHCORE_ADDRESS=AA:BB:CC:DD:EE:FF
The container includes health checks:
# Check container health
docker inspect --format='{{.State.Health.Status}}' meshcore-mqtt-bridge
# View health check logs
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' meshcore-mqtt-bridge
# View container logs
docker logs -f meshcore-mqtt-bridge
# Execute commands in container
docker exec -it meshcore-mqtt-bridge sh
# Stop container
docker stop meshcore-mqtt-bridge
# Remove container
docker rm meshcore-mqtt-bridge
# Run all tests
pytest
# Run with coverage
pytest --cov=meshcore_mqtt
# Run specific test file
pytest tests/test_config.py -v
# Format code
black meshcore_mqtt/ tests/
# Lint code
flake8 meshcore_mqtt/ tests/
# Type checking
mypy meshcore_mqtt/ tests/
# Run pre-commit hooks
pre-commit run --all-files
The bridge uses an Inbox/Outbox Architecture with independent workers:
-
Bridge Coordinator (
bridge_coordinator.py
)- Coordinates independent MeshCore and MQTT workers
- Manages shared message bus for inter-worker communication
- Provides health monitoring and system statistics
- Handles graceful startup and shutdown
-
Message Bus System (
message_queue.py
)- Async message queue system for worker communication
- Thread-safe inbox/outbox pattern
- Component status tracking and health monitoring
- Message types: events, commands, status updates
-
MeshCore Worker (
meshcore_worker.py
)- Independent worker managing MeshCore device connection
- Handles device commands and event subscriptions
- Auto-reconnection and health monitoring
- Forwards events to MQTT worker via message bus
-
MQTT Worker (
mqtt_worker.py
)- Independent worker managing MQTT broker connection
- Subscribes to command topics and publishes events
- TLS/SSL support with configurable certificates
- Auto-reconnection and connection recovery
-
Configuration System (
config.py
)- Pydantic-based configuration with validation
- Support for JSON, YAML, environment variables, and CLI args
- Type-safe configuration with proper defaults
-
CLI Interface (
main.py
)- Click-based command line interface
- Configuration loading and validation
- Logging setup and error handling
- Resilience: Workers operate independently, one can fail without affecting the other
- Scalability: Easy to add new workers or modify existing ones
- Testability: Each worker can be tested in isolation
- Monitoring: Built-in health checks and statistics for each component
- Recovery: Automatic reconnection and error recovery for both workers
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
For issues and questions, please open an issue on the GitHub repository.