Skip to content

Production-ready Modbus to MQTT bridge with automatic bus collision prevention, adaptive polling, and runtime reconfiguration

License

Notifications You must be signed in to change notification settings

groupsky/ya-modbus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

ya-modbus

Production-ready Modbus to MQTT bridge with automatic bus collision prevention, adaptive polling, and runtime reconfiguration

License: GPL v3 TypeScript Node codecov

Features

  • 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

Quick Start

Installation

npm install -g @ya-modbus/cli

Basic Usage

# 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

Configuration Example

{
  "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
    }
  ]
}

Why ya-modbus?

The Multi-Device Problem

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 needed

Competitive Analysis

We 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.

Architecture

┌─────────────────────────────────────────────────────────┐
│                    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.

Supported Devices

Built-in Drivers

Energy Meters

  • SDM630 - Eastron 3-phase energy meter
  • DDS519MR - Single-phase energy meter
  • EX9EM - ABB energy meter

Solar Inverters

  • SUN2000 - Huawei solar inverter
  • MICROSYST-SR04 - Microsyst solar inverter

HVAC

  • BAC002 - Climate controller

Generic Modbus

  • Configurable generic driver for any Modbus device

Third-Party Driver Ecosystem

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

Documentation

Development

Prerequisites

  • Node.js 20+ (see .nvmrc)
  • npm 10+

Setup

# 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 dev

Project Structure

packages/
├── 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)

Runtime Configuration via MQTT

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
}'

Monitoring

Device Status

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
  }
}

Error Monitoring

Subscribe to errors:

mosquitto_sub -t "modbus/+/errors/#"

Integration with Telegraf

# 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"

Docker Deployment

# 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-modbus

Docker Compose

version: '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

Performance

RTU Performance (9600 baud)

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%

TCP Performance

  • Concurrent device polling (no mutex)
  • 100+ polls/second per device
  • Limited by network bandwidth and CPU

License

GPL-3.0-or-later - see LICENSE for details

Contributing

Contributions welcome! See CONTRIBUTING.md for:

  • Development workflow
  • Testing guidelines
  • Code style guidelines
  • Pull request process

Support

Acknowledgments

Roadmap

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)

About

Production-ready Modbus to MQTT bridge with automatic bus collision prevention, adaptive polling, and runtime reconfiguration

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published