Production-ready Modbus to MQTT bridge with automatic bus collision prevention, adaptive polling, and runtime reconfiguration
- Automatic bus collision prevention - Zero-config async-mutex for RTU serial buses
- Adaptive polling - Different rates for dynamic vs static registers (60-80% traffic reduction)
- Runtime reconfiguration - Add/modify/remove devices via MQTT without restart
- Device discovery - Auto-detect devices, baud rate, parity, and slave ID
- Multi-register optimization - Batch adjacent registers (70-90% fewer operations)
- Production monitoring - Comprehensive diagnostics, error publishing, health checks
- RTU & TCP support - Serial (RS-485/RS-232) and network Modbus devices
- Software emulator - Test device drivers without physical hardware
- Agent-friendly - Comprehensive documentation, per-directory guides for AI assistants
npm install -g @ya-modbus/cli# Start bridge with configuration file
ya-modbus bridge --config config.json
# Discover devices on serial bus
ya-modbus discover /dev/ttyUSB0
# Test device connectivity
ya-modbus test --device SDM630 --port /dev/ttyUSB0 --slave 1{
"mqtt": {
"broker": "mqtt://localhost:1883",
"username": "user",
"password": "pass"
},
"devices": [
{
"id": "meter_1",
"driver": "SDM630",
"transport": "rtu",
"port": "/dev/ttyUSB0",
"baudRate": 9600,
"slaveId": 1,
"pollInterval": 5000
},
{
"id": "inverter_1",
"driver": "SUN2000",
"transport": "tcp",
"host": "192.168.1.100",
"port": 502,
"slaveId": 1,
"pollInterval": 10000
}
]
}Modbus RTU uses a shared serial bus. Reading from multiple devices simultaneously causes protocol violations:
Device 1: Read request → [Bus collision!] ← Device 2: Read request
Result: Timeouts, CRC errors, data corruption
Common workaround (manual delays):
await device1.read()
await sleep(100) // Manual delay - error-prone!
await device2.read()Our solution (automatic mutex):
await device1.read() // Mutex acquired automatically
await device2.read() // Waits for mutex, then executes
// No manual delays, no collisions, no configuration neededWe analyzed 48+ competing solutions. None provide all of:
- ✅ Automatic mutex for RTU bus protection
- ✅ Adaptive polling (dynamic vs static registers)
- ✅ Runtime reconfiguration via MQTT
- ✅ Multi-register read optimization
- ✅ Device discovery & auto-detection
- ✅ TypeScript with comprehensive tooling
See ARCHITECTURE.md for detailed comparison.
┌─────────────────────────────────────────────────────────┐
│ MQTT Interface │
│ Configuration, Status, Data Publishing, Discovery │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Bridge Orchestrator │
│ Device lifecycle, polling coordination, persistence │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Adaptive Polling Engine │
│ Dynamic/static/on-demand, multi-register optimization │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Device Abstraction Layer │
│ Driver interface, register definitions, constraints │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Mutex Layer (RTU only) │
│ Prevents simultaneous serial bus access │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ Transport Layer │
│ RTU (serial), TCP (network), RTU-over-TCP bridges │
└─────────────────────────────────────────────────────────┘
See ARCHITECTURE.md for details.
- SDM630 - Eastron 3-phase energy meter
- DDS519MR - Single-phase energy meter
- EX9EM - ABB energy meter
- SUN2000 - Huawei solar inverter
- MICROSYST-SR04 - Microsyst solar inverter
- BAC002 - Climate controller
- Configurable generic driver for any Modbus device
Device drivers can be distributed as independent npm packages:
# Install bridge + third-party driver
npm install -g @ya-modbus/cli ya-modbus-driver-solar
# Use in configuration
{
"devices": [{
"driver": "ya-modbus-driver-solar",
"deviceType": "X1000", // Optional - auto-detect if omitted
"transport": "tcp",
"host": "192.168.1.100"
}]
}Naming conventions:
- Monorepo packages:
@ya-modbus/driver-<name>(e.g.,@ya-modbus/driver-ex9em) - Third-party packages:
ya-modbus-driver-<name>for easy discovery
Auto-detection: Drivers can auto-detect device model when deviceType is omitted
Want to create a driver? See docs/DRIVER-DEVELOPMENT.md
Driver developers get:
- Standardized SDK with TypeScript types
- Test harness with emulator integration
- Device characterization tools (auto-discover capabilities)
- CLI for testing with real devices
- Independent release cycle
- Getting Started - Installation, configuration, first steps
- Architecture - System design, key decisions, data flow
- Driver Development - Creating third-party device drivers
- Contributing - Development workflow, testing, code style
- AGENTS.md - Quick guide for AI assistants and developers
- API Reference - Package APIs and interfaces
- Device Drivers - Adding and converting device drivers
- Troubleshooting - Common issues and solutions
- FAQ - Frequently asked questions
- Node.js 20+ (see
.nvmrc) - npm 10+
# Clone repository
git clone https://github.com/groupsky/ya-modbus.git
cd ya-modbus
# Install dependencies
npm install
# Build all packages
npm run build
# Run tests
npm test
# Start development mode
npm run devpackages/
├── mqtt-bridge/ # Bridge orchestration, MQTT publishing, polling
├── cli/ # Command-line tool (test, provision, monitor)
├── transport/ # RTU/TCP transport implementations
├── driver-types/ # TypeScript type definitions
├── driver-sdk/ # Runtime SDK for driver development
├── driver-loader/ # Dynamic driver loading
├── device-profiler/ # Device discovery and register scanning
├── emulator/ # Software Modbus device emulator for testing
└── driver-*/ # Device drivers (e.g., driver-ex9em, driver-xymd1)
Add devices without restarting:
# Add device
mosquitto_pub -t "modbus/config/devices/add" -m '{
"id": "meter_2",
"driver": "SDM630",
"transport": "rtu",
"port": "/dev/ttyUSB0",
"slaveId": 2
}'
# Remove device
mosquitto_pub -t "modbus/config/devices/remove" -m "meter_2"
# Enable/disable device
mosquitto_pub -t "modbus/config/devices/meter_1/enabled" -m "false"
# Update polling interval
mosquitto_pub -t "modbus/config/devices/meter_1/polling" -m '{
"interval": 10000
}'Subscribe to device status:
mosquitto_sub -t "modbus/+/status/#"Example output:
{
"timestamp": "2025-12-22T10:30:45.123Z",
"connected": true,
"pollRate": 9.8,
"avgLatency": 42,
"errorRate": 0.001,
"errors": {
"total": 15,
"timeout": 10,
"crc": 5
}
}Subscribe to errors:
mosquitto_sub -t "modbus/+/errors/#"# telegraf.conf
[[inputs.mqtt_consumer]]
servers = ["tcp://localhost:1883"]
topics = ["modbus/+/data"]
data_format = "json"
tag_keys = ["device_id", "device_type"]
[[outputs.influxdb_v2]]
urls = ["http://localhost:8086"]
token = "$INFLUX_TOKEN"
organization = "my-org"
bucket = "modbus"# Build image
docker build -t ya-modbus .
# Run with configuration
docker run -d \
--name modbus-bridge \
-v $(pwd)/config.json:/app/config.json \
-v $(pwd)/data:/data \
--device /dev/ttyUSB0:/dev/ttyUSB0 \
-e MQTT_BROKER=mqtt://mosquitto:1883 \
ya-modbusversion: '3.8'
services:
modbus-bridge:
image: ya-modbus
volumes:
- ./config.json:/app/config.json
- ./data:/data
- /dev/ttyUSB0:/dev/ttyUSB0
devices:
- /dev/ttyUSB0
environment:
MQTT_BROKER: mqtt://mosquitto:1883
STATE_FILE: /data/bridge-state.json
restart: unless-stopped| Scenario | Operations/cycle | Time/cycle | Improvement |
|---|---|---|---|
| Individual reads (10 registers) | 10 reads | ~100ms | Baseline |
| Batched reads (10 registers) | 1 read | ~10ms | 90% |
| Uniform polling (all registers) | Continuous | - | Baseline |
| Adaptive polling (dynamic only) | Continuous | - | 60-80% |
- Concurrent device polling (no mutex)
- 100+ polls/second per device
- Limited by network bandwidth and CPU
GPL-3.0-or-later - see LICENSE for details
Contributions welcome! See CONTRIBUTING.md for:
- Development workflow
- Testing guidelines
- Code style guidelines
- Pull request process
- GitHub Issues: Bug reports and features
- GitHub Discussions: Questions and ideas
- Documentation: Full docs
- Inspired by zigbee2mqtt architecture
- Uses modbus-serial for Modbus protocol implementation
- Uses async-mutex for RTU bus locking
See GitHub Projects for planned features.
Key upcoming features:
- Web UI for configuration and monitoring
- Advanced device fingerprinting
- High-availability mode with failover
- Additional device drivers (community-driven)