Code Monkey home page Code Monkey logo

modbus-2-mqtt-bridge's Introduction

modbus2mqtt - Bidirectional Modbus 2 MQTT Bridge

modbus2mqtt allows you to have any modbus capable device (Ethernet/USB/Serial) being available and manipulatable in MQTT.

It comes with json definition files for:

  • Lambda Eureka series heatpumps (EU8L, EU13L EU15L)
  • Phoenix Contact electric vehicle chargecontroller
  • Hanmatek HM310T laboratory power supply
  • SMA sunnyboy/-store inverters (left in here, even though sma2mqtt is better for that use case)
  • GoodWe ET 15-30 inverter

and you can easily add json definition files for your own devices.

Docker Container Use

A Docker image, specifically engineered for 64-bit ARM architecture, is available. This image is compatible with a range of devices, including the Raspberry Pi, Apple Silicon Macs, and other 64-bit ARM computers. It can be employed directly using the following command:

	docker run --name modbus2mqtt \
		jollyjinx/modbus2mqtt:latest modbus2mqtt \
		--modbus-server lambda \
		--topic lambda \
		--device-description-file lambda.json

This runs modbus2mqtt for a lambda heatpump and output the values to the mqtt server topic /lambda. If there is no mqtt server specified the server named mqtt is used.

This will look like the following on MQTT Explorer or in node-red:

MQTT Explorer ScreenshotMQTT Explorer Screenshot

You can create your own docker container by using the following command:

    docker build . --file modbus2mqtt.product.dockerfile --tag modbus2mqtt
    docker run --name modbus2mqtt modbus2mqtt --modbus-server lambda --device-description-file lambda.json --topic lambda

JSON Definition Files

It's easy to setup your own modbus2mqtt definition file. A json definition file looks like this:

{
  {
    "address": 40631,                   // modbus address
    "modbustype": "holding",            // holding,coil,
    "modbusaccess": "readwrite",        // read/write/readwrite
    "valuetype": "string",              // string, ipv4address, macaddress, uint8, int8, uint16,...
    "length": 24,                       // strings need a length
    "interval": 1000,                   // interval being updated in seconds (decimal value like 0.2 possible)
                                        // interval of 0 means it will be requested only at start and then 
                                        // using mqtt retain to retain it.
    "mqtt": "visible",                  // if shown in mqtt visible/invisible/retained
    "topic": "settings/name",           // topic to post values
    "title": "Name"                     // description for gui (like node-red)
  },
  {
    "address": 30581,
    "modbustype": "holding",
    "modbusaccess": "read",
    "valuetype": "uint32",  
    "factor": 0.001,                    // factor to multiply by
    "unit": "kWh",                      // unit for gui
    "mqtt": "visible",
    "interval": 1000,
    "topic": "counter/totalusage",
    "title": "Total Yield"
  },
  {
    "address": 1,
    "modbustype": "holding",
    "modbusaccess": "read",
    "valuetype": "uint16",
    "mqtt": "visible",
    "interval": 5,
    "map": {                            // you can add mappings for values
        "0": "OFF",                     // so value will be "AUTOMATIC" if the 
        "1": "AUTOMATIK",               // raw value is 1
        "2": "MANUAL",                  // original values can then be accessed with  
        "3": "ERROR"                    // "rawValue" key in the output
    },
    "topic": "ambient/operatingstate",
    "title": "Ambient Operating State"
  },
  {
    "address": 2,
    "modbustype": "holding",
    "modbusaccess": "read",
    "valuetype": "uint16",
    "mqtt": "visible",
    "interval": 5,
    "bits": {                            // you can bit mappings for values
        "0": { "name" : "running" },     // a single bit will be mapped to a boolean
        "1-3": { "name" : "mode" },      // multiple values will be mapped to the integer number with those bits shifted to the right
        "4-15": { "name" : "reserved" }  
    },
    "topic": "test/state",
    "title": "Test State"
  }
}

Remark: Be aware that json does not support comments like in this example. After creating your own json definition, you can use it with the commandline option --device-description-file yourfilename

Bridge goes both ways

modbus2mqtt is a bridge it does not only allow modbus devices show up in mqtt, it also allows writing values to the modbus devices from mqtt. It uses a Request/Response pattern. You send a mqtt request to the mqtt request topic and are give the result of the request in the response topic path.

To set the output voltage of the HM310T to 14.04 Volt you can send the following json

{
  "value": 14.04,
  "date": "2022-10-21T08:43:22Z",
  "topic": "set/voltage",
  "id": "D2129DBF-9F94-46D7-86BC-4A07152FF1D8"
}

to the topic hm310/request/mychange of the MQTT server. The bridge will pickup the request and return the response to the response topic hm310/response/mychange . Be aware that you set the value 14.04 which will be correctly converted to the (u)int16 value of the specific modbus address.

In swift Request/Responses are defined as follows.

struct MQTTRequest:Encodable,Decodable,Hashable,Equatable
{
    let date:Date       // needs to be within request ttl time 
    let id:UUID
    let topic:String
    let value:MQTTCommandValue
}

struct MQTTResponse:Encodable,Decodable
{
    let date:Date
    let id:UUID         // same uuid of request
    let success:Bool
    let error:String?
}

Status

I'm using it 24/7 on my own modbus devices (the devices I created JSON definitions for).

Starting the application

> modbus2mqtt --topic=sma/sunnystore \
              --modbus-server=sunnyboy.local \
              --mqtt-server=mqtt.local \
              --device-description-file=sma.sunnystore.json

It supports command line help:

> ./.build/release/modbus2mqtt --help 
USAGE: modbus2mqtt <options>

OPTIONS:
  --log-level <log-level> Set the log level. (default: notice)
  --mqtt-servername <mqtt-servername>
                          MQTT Server hostname (default: mqtt)
  --mqtt-port <mqtt-port> MQTT Server port (default: 1883)
  --mqtt-username <mqtt-username>
                          MQTT Server username
  --mqtt-password <mqtt-password>
                          MQTT Server password
  --emit-interval <emit-interval>
                          Minimum interval to send updates to mqtt Server.
                          (default: 0.1)
  -t, --topic <topic>     MQTT Server topic. (default: modbus/sunnyboy)
  --mqtt-request-ttl <mqtt-request-ttl>
                          Maximum time a mqttRequest can lie in the future/past
                          to be accepted. (default: 10.0)
  --mqtt-auto-retain-time <mqtt-auto-retain-time>
                          If mqttTopic has a refreshtime larger than this value
                          it will be retained. (default: 10.0)
  --modbus-device-path <modbus-device-path>
                          Serial Modbus Device path
  --modbus-serial-speed <modbus-serial-speed>
                          Serial Modbus Speed (default: 9600)
  -m, --modbus-server <modbus-server>
                          Modbus Device Servername. (default:
                          modbus.example.com)
  --modbus-port <modbus-port>
                          Modbus Device Port number. (default: 502)
  --modbus-address <modbus-address>
                          Modbus Device Address. (default: 3)
  --device-description-file <device-description-file>
                          Modbus Device Description file (JSON). (default:
                          sma.sunnyboy.json)
  -h, --help              Show help information.

Feedback welcome

In case you add json definitions for your own devices, create pull requests.

modbus-2-mqtt-bridge's People

Contributors

acul009 avatar daftbugs avatar jollyjinx avatar pst-estos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

acul009 daftbugs

modbus-2-mqtt-bridge's Issues

Suggestion: Retain original values for mapped values, dealing with int representations

Hi,

I'm currently testing if your fine tool fits my needs to interact with my PV inverter. I'm not breaking this up into individual issues but I have 2 thoughts for now:

1.) It would be nice to (maybe optionally) have the original value for a mapped value in the JSON as well

2.) My inverter uses individual bytes of 16 bit or 32 bit registers to represent multiple values. It also uses bitfields. I was wondering if it would make sense to split values in the JSON representation for these cases to not have to handle dissection of these values after JSON parsing.

Program crashes on startup with duplicate Modbus address entries

Hi,

although not very grave in nature, I just wanted to give a heads up about the following crash on program startup:

Swift/NativeDictionary.swift:770: Fatal error: Duplicate values for key: '35121'
πŸ’£ Program crashed: Illegal instruction at 0x00007fa20fdcad12

Thread 1 crashed:

0 0x00007fa20fdcad12 assertionFailure(:_:file:line🎏) + 354 in libswiftCore.so
1 specialized static ModbusDefinition.read(from:) + 1794 in modbus2mqtt at /swift/Sources/modbus2mqtt/ModbusDefinition.swift:121:27
2 startServing(modbusDevice:mqttServer:options:) + 147 in modbus2mqtt at /swift/Sources/modbus2mqtt/modbus2mqtt.swift:157:50
3 modbus2mqtt.run() in modbus2mqtt at /swift/Sources/modbus2mqtt/modbus2mqtt.swift:127
4 static AsyncParsableCommand.main() in modbus2mqtt at /swift/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift:37
5 0x00007fa20fc450b0 completeTaskAndRelease(swift::AsyncContext*, swift::SwiftError*) in libswift_Concurrency.so

Backtrace took 2.41s
This happens if there are (at least) 2 entries with the same Modbus address in the JSON device spec file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.