Files
meshcore-mqtt/README.md
claude[bot] 899eeebd09 docs: update TCP port from 12345 to 5000
Updated all documentation references to reflect the correct TCP port 5000 for MeshCore device connections, as verified with Heltec V3.

- Updated README.md (8 instances including Docker section)
- Updated config.example.yaml
- Updated config.example.json

Fixes #13

Co-authored-by: JingleManSweep <jinglemansweep@users.noreply.github.com>
2025-11-16 22:25:18 +00:00

709 lines
22 KiB
Markdown

# MeshCore MQTT Bridge
[![License](https://img.shields.io/badge/License-GPL_v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Python](https://img.shields.io/badge/Python-3.11+-blue.svg)](https://www.python.org/)
[![CI](https://github.com/ipnet-mesh/meshcore-mqtt/actions/workflows/ci.yml/badge.svg)](https://github.com/ipnet-mesh/meshcore-mqtt/actions/workflows/ci.yml)
[![Docker Build and Push](https://github.com/ipnet-mesh/meshcore-mqtt/actions/workflows/docker-build.yml/badge.svg)](https://github.com/ipnet-mesh/meshcore-mqtt/actions/workflows/docker-build.yml)
[![Code style: black](https://img.shields.io/badge/Code%20style-black-000000.svg)](https://github.com/psf/black)
[![Typing: mypy](https://img.shields.io/badge/Typing-mypy-blue.svg)](https://mypy.readthedocs.io/)
A robust bridge service that connects MeshCore devices to MQTT brokers, enabling seamless integration with IoT platforms and message processing systems.
## Features
- **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
- **Message Rate Limiting**: Configurable rate limiting to prevent network flooding and ensure reliable message delivery
- **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**: 70+ unit tests with pytest and pytest-asyncio including rate limiting tests
- **Code Quality**: Streamlined CI/CD with pre-commit hooks for black formatting, flake8 linting, mypy type checking, and automated testing
## Installation
### From Source
```bash
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
```
### Development Installation
```bash
pip install -r requirements-dev.txt
pre-commit install
```
## Configuration
The bridge supports multiple configuration methods with the following precedence:
1. Command-line arguments (highest priority)
2. Configuration file (JSON or YAML)
3. Environment variables (lowest priority)
### Configuration Options
#### MQTT Settings
- `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 Settings
- `meshcore_connection`: Connection type (serial, ble, tcp)
- `meshcore_address`: Device address (required)
- `meshcore_port`: Device port for TCP connections (default: 5000)
- `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_message_initial_delay`: Initial delay in seconds before sending the first message (default: 15.0, range: 0.0-60.0)
- `meshcore_message_send_delay`: Delay in seconds between consecutive message sends (default: 15.0, range: 0.0-60.0)
- `meshcore_events`: List of MeshCore event types to subscribe to (see [Event Types](#event-types))
#### General Settings
- `log_level`: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
### Configuration Examples
#### JSON Configuration (config.json)
```json
{
"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": 5000,
"baudrate": 115200,
"timeout": 10,
"auto_fetch_restart_delay": 10,
"message_initial_delay": 15.0,
"message_send_delay": 15.0,
"events": [
"CONTACT_MSG_RECV",
"CHANNEL_MSG_RECV",
"CONNECTED",
"DISCONNECTED",
"BATTERY",
"DEVICE_INFO"
]
},
"log_level": "INFO"
}
```
#### YAML Configuration (config.yaml)
```yaml
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: 5000
baudrate: 115200
timeout: 10
auto_fetch_restart_delay: 10
message_initial_delay: 15.0
message_send_delay: 15.0
events:
- CONTACT_MSG_RECV
- CHANNEL_MSG_RECV
- CONNECTED
- DISCONNECTED
- BATTERY
- DEVICE_INFO
log_level: INFO
```
#### Environment Variables
```bash
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=5000
export MESHCORE_BAUDRATE=115200
export MESHCORE_AUTO_FETCH_RESTART_DELAY=10
export MESHCORE_MESSAGE_INITIAL_DELAY=15.0
export MESHCORE_MESSAGE_SEND_DELAY=15.0
export MESHCORE_EVENTS="CONNECTED,DISCONNECTED,BATTERY,DEVICE_INFO"
export LOG_LEVEL=INFO
```
## Usage
### Command Line Interface
#### Using Configuration File
```bash
python -m meshcore_mqtt.main --config-file config.json
```
#### Using Command Line Arguments
```bash
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 5000 \
--meshcore-auto-fetch-restart-delay 10 \
--meshcore-events "CONNECTED,DISCONNECTED,BATTERY"
```
#### Using Environment Variables
```bash
python -m meshcore_mqtt.main --env
```
### Connection Types
#### TCP Connection
```bash
python -m meshcore_mqtt.main \
--mqtt-broker localhost \
--meshcore-connection tcp \
--meshcore-address 192.168.1.100 \
--meshcore-port 5000
```
#### Serial Connection
```bash
python -m meshcore_mqtt.main \
--mqtt-broker localhost \
--meshcore-connection serial \
--meshcore-address /dev/ttyUSB0 \
--meshcore-baudrate 9600
```
#### BLE Connection
```bash
python -m meshcore_mqtt.main \
--mqtt-broker localhost \
--meshcore-connection ble \
--meshcore-address AA:BB:CC:DD:EE:FF
```
## Event Types
The bridge can subscribe to various MeshCore event types. You can configure which events to monitor using the `events` configuration option.
### Default Events
If no events are specified, the bridge subscribes to these default events:
- `CONTACT_MSG_RECV` - Contact messages received
- `CHANNEL_MSG_RECV` - Channel messages received
- `DEVICE_INFO` - Device information updates
- `BATTERY` - Battery status updates
- `NEW_CONTACT` - New contact discovered
- `ADVERTISEMENT` - Device advertisement broadcasts
- `TRACE_DATA` - Network trace information
- `TELEMETRY_RESPONSE` - Telemetry data responses
- `CHANNEL_INFO` - Channel configuration details
### Additional Supported Events
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 authentication
- `LOGIN_FAILED` - Failed authentication
- `MESSAGES_WAITING` - Pending messages notification
- `CONTACTS` - Contact list updates
- `SELF_INFO` - Own device information
### Configuration Examples
#### Minimal Events (Performance Optimized)
```yaml
meshcore:
events:
- CONNECTED
- DISCONNECTED
- BATTERY
```
#### Message-Focused Events
```yaml
meshcore:
events:
- CONTACT_MSG_RECV
- CHANNEL_MSG_RECV
- TEXT_MESSAGE_RX
```
#### Full Monitoring
```yaml
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.
## MQTT Topics
The bridge provides full bidirectional communication between MQTT and MeshCore devices. Using the configured topic prefix (default: "meshcore"):
### Published Topics (MeshCore → MQTT)
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
### Command Topics (MQTT → MeshCore)
Send commands to MeshCore devices via MQTT using `{prefix}/command/{command_type}` with JSON payloads:
#### Message Commands
- `{prefix}/command/send_msg` - Send direct message to contact/node
```json
{"destination": "contact_name_or_node_id", "message": "Hello!"}
```
- `{prefix}/command/send_chan_msg` - Send channel message
```json
{"channel": 0, "message": "Hello channel!"}
```
#### Device Management Commands
- `{prefix}/command/device_query` - Query device information
```json
{}
```
- `{prefix}/command/get_battery` - Get battery status
```json
{}
```
- `{prefix}/command/set_name` - Set device name
```json
{"name": "MyDevice"}
```
#### Network Commands
- `{prefix}/command/ping` - Ping a specific node
```json
{"destination": "node_id"}
```
- `{prefix}/command/send_trace` - Send trace packet for routing diagnostics
```json
// Basic trace
{}
// Trace with specific routing path through repeaters
{"auth_code": 12345, "path": "23,5f,3a", "flags": 1}
```
### MQTT Command Examples
Using `mosquitto_pub` client:
```bash
# 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"
```
### Available MeshCore Commands
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"}` |
### Topic Examples
- `meshcore/message/channel/0` - Channel 0 messages
- `meshcore/message/direct/a1b2c3` - Direct messages from node a1b2c3
- `meshcore/status` - Bridge connection status ("connected"/"disconnected")
- `meshcore/events/connection` - Raw MeshCore connection events (JSON)
- `meshcore/battery` - Battery level updates
- `meshcore/device_info` - Device specifications and capabilities
- `meshcore/advertisement` - Device advertisement broadcasts
- `meshcore/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)
## Docker Deployment
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.
### Docker Features
- **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
### Quick Start with Docker
#### Using Pre-built Images from GHCR
Pre-built Docker images are available from GitHub Container Registry:
**Available Tags:**
- `latest` - Latest stable release from main branch
- `develop` - Latest development build
- `v1.0.0` - Specific version tags
- `v1.0` - Major.minor version tags
- `v1` - Major version tags
```bash
# 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
```
#### Building Locally
```bash
# 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
```
#### Using Environment File
```bash
# 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
```
#### Option 3: Using Docker Compose
```bash
# 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
```
### Docker Environment Variables
All configuration options can be set via environment variables:
```bash
# 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=5000 # Only for TCP connections
MESHCORE_TIMEOUT=30
MESHCORE_AUTO_FETCH_RESTART_DELAY=10 # Restart delay after NO_MORE_MSGS (1-60 seconds)
MESHCORE_MESSAGE_INITIAL_DELAY=15.0 # Initial delay before first message (0.0-60.0 seconds)
MESHCORE_MESSAGE_SEND_DELAY=15.0 # Delay between consecutive messages (0.0-60.0 seconds)
# Event Configuration (comma-separated)
MESHCORE_EVENTS=CONNECTED,DISCONNECTED,BATTERY,DEVICE_INFO
```
#### Connection Type Examples
**Serial Connection (Default):**
```bash
MESHCORE_CONNECTION=serial
MESHCORE_ADDRESS=/dev/ttyUSB0
MESHCORE_BAUDRATE=115200
# Note: Use --device=/dev/ttyUSB0 in docker run for device access
```
**TCP Connection:**
```bash
MESHCORE_CONNECTION=tcp
MESHCORE_ADDRESS=192.168.1.100
MESHCORE_PORT=5000
```
**BLE Connection:**
```bash
MESHCORE_CONNECTION=ble
MESHCORE_ADDRESS=AA:BB:CC:DD:EE:FF
```
### Health Monitoring
The container includes health checks:
```bash
# 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
```
### Container Management
```bash
# 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
```
## Development
### Running Tests
```bash
# Run all tests
pytest
# Run with coverage
pytest --cov=meshcore_mqtt
# Run specific test file
pytest tests/test_config.py -v
```
### Code Quality
```bash
# 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
```
## Architecture
The bridge uses an **Inbox/Outbox Architecture** with independent workers:
### Core Components
1. **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
2. **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
3. **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
4. **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
5. **Configuration System** (`config.py`)
- Pydantic-based configuration with validation
- Support for JSON, YAML, environment variables, and CLI args
- Type-safe configuration with proper defaults
6. **CLI Interface** (`main.py`)
- Click-based command line interface
- Configuration loading and validation
- Logging setup and error handling
### Benefits of Inbox/Outbox Architecture
- **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
## License
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## Support
For issues and questions, please open an issue on the GitHub repository.