diff --git a/README.md b/README.md index 9e8f372..46897b0 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,29 @@ # ModbusMQTT -A bridge between Modbus devices and MQTT. +This repository is a workspace for developing ModbusMQTT and the crates developed in the course of the project. -It is early days, but the plan is: +View the appropriate READMEs in each package directory for details about each. -* [x] Support custom Modbus transports (Sungrow WiNet-S has been implemented) - * Modbus RTU has not been tested because I don't have a serial Modbus device, but in principle it should work. Please let me know -* [x] Support reading input registers -* [x] Support reading holding registers -* [ ] Support _setting_ holding registers -* [ ] Support optional auto-configuration of Home Assistant entities, including using [MQTT Number](https://www.home-assistant.io/integrations/number.mqtt/) et al for holding registers, to allow setting the value. -* [ ] TLS MQTT connections -* [ ] WebSocket MQTT connections +## `modbus-mqtt` -NOTE: For the time being, this does not support MQTTv5. +![Crates.io](https://img.shields.io/crates/v/modbus-mqtt.svg) +![docs.rs](https://img.shields.io/docsrs/modbus-mqtt) +![Crates.io](https://img.shields.io/crates/l/modbus-mqtt) -## Installing +ModbusMQTT is a bridge between Modbus devices and MQTT. It aims to allow the operator to generically expose any compatible Modbus device as though its API were MQTT. -For now, use `cargo install` (Rust toolchain required). Soon, I will have release binaries attached to GitHub releases. In the future, there will also be Docker images made available for convenience. +## `sungrow-winets` -## Running +![Crates.io](https://img.shields.io/crates/v/sungrow-winets.svg) +![docs.rs](https://img.shields.io/docsrs/sungrow-winets.svg) +![Crates.io](https://img.shields.io/crates/l/sungrow-winets) -Start the binary, passing in the URL to your MQTT server, including any credentials: +This is a barebones API client for reading and writing settings for Sungrow solar and hybrid inverters equipped with a WiNet-S communications module. Its only known use is `tokio_modbus-winets` (see below). -```sh-session -$ modbus-mqtt mqtt://$MQTT_HOST[:$MQTT_PORT]/[$CUSTOM_MODBUS_TOPIC] -``` +## `tokio_modbus-winets` -The supported protocols are currently just `tcp://`/`mqtt://`, but with intent to support: `mqtts://`, `ssl://`/`tls://`, `ws://`, and `wss://`. +![Crates.io](https://img.shields.io/crates/v/tokio_modbus-winets.svg) +![docs.rs](https://img.shields.io/docsrs/tokio_modbus-winets) +![Crates.io](https://img.shields.io/crates/l/tokio_modbus-winets) -The default topic which ModbusMQTT monitors and to which it publishes is `modbus-mqtt`. You can vary that by changing the path portion of the MQTT URL. - -Further, you can change other MQTT options by using query params, such as setting a custom client_id: - -```sh -"mqtt://1.2.3.4/?client_id=$CUSTOM_CLIENT_ID" -``` - -For a full list of supported options, check [the MQTT client library's source code](https://github.com/bytebeamio/rumqtt/blob/c6dc1f7cfb26f6c1f676954a51b398708d49091a/rumqttc/src/lib.rs#L680-L768). - -### Connecting to Modbus devices - -To connect to a Modbus device, you need to post the connection details to MQTT under a topic of `$prefix/$connection_id/connect`. It is intended that such messages are marked as **retained** so that ModbusMQTT reconnects to your devices when it restarts. - -For instance, a simple config might be: - -```jsonc -// PUBLISH modbus-mqtt/solar-inverter/connect -{ - "host": "10.10.10.219", - "proto": "tcp", -} -``` - -If the connection is successful, you will see the following message like the following sent to the MQTT server: - -```jsonc -// modbus-mqtt/solar-inverter/state -"connected" -``` - -#### Full connection examples - -All fields accepted (optional fields show defaults) - -```jsonc -{ - // Common fields - "address_offset": 0, // optional - "unit": 1, // optional, aliased to "slave" - - // TCP: - "proto": "tcp", - "host": "1.2.3.4", - "port": 502, // optional - - // RTU / Serial: - "proto": "rtu", - "tty": "/dev/ttyACM0", - "data_bits": "Eight", // optional (TODO: accept numeric and lowercase) - // valid: Five, Six, Seven, Eight - "stop_bits": "One", // optional (TODO: accept numeric and lowercase) - // valid: One, Two - "flow_control": "None", // optional (TODO: accept lowercase) - // valid: None, Software, Hardware - "parity": "None", // optional (TODO: accept lowercase) - // valid: None, Odd, Even - - // Sungrow WiNet-S dongle - "proto": "winet-s", - "host": "1.2.3.4", -} -``` - -#### Monitoring registers - -Post to `$MODBUS_MQTT_TOPIC/$CONNECTION_ID/$TYPE/$ADDRESS` where `$TYPE` is one of `input` or `holding` with the following payload (optional fields show defaults): - -```jsonc -{ - "name": null, // OPTIONAL - gives the register a name which is used in the register MQTT topics (must be a valid topic component) - - "interval": "1m", // OPTIONAL - how often to update the registers value to MQTT - // e.g.: 3s (every 3 seconds) - // 2m (every 2 minutes) - // 1h (every 1 hour) - - "swap_bytes": false, // OPTIONAL - "swap_words": false, // OPTIONAL - - "type": "s16", // OPTIONAL - // valid: s8, s16, s32, s64 (signed) - // u8, u16, u32, u64 (unsigned) - // f32, f64 (floating point) - - "scale": 0, // OPTIONAL - number in register will be multiplied by 10^(scale) - // e.g.: to turn kW into W, you would provide scale=3 - // to turn W into kW, you would provide scale=-3 - - "offset": 0, // OPTIONAL - will be added to the final result (AFTER scaling) - - - // Additionally, "type" can be set to "array": - "type": "array", - "of": "u16" // The default array element is u16, but you can change it with the `of` field -} -``` - -Further, the `type` field can additionally be set to `"array"`, in which case, a `count` field must be provided. The array elements default to `"s16"` but can be overriden in the `"of"` field. - -NOTE: this is likely to change such that there is always a `count` field (with default of 1) and if provided to be greater than 1, it will be interpreted to be an array of elements of the `type` specified. - -There is some code to accept `"string"` type (with a required `length` field) but this is experimental and untested. - -##### Register shorthand - -When issuing the `connect` payload, you can optionally include `input` and/or `holding` fields as arrays containing the above register schema, as long as an `address` field is added. When present, these payloads will be replayed to the MQTT server as if the user had specified each register separately, as above. - -This is a recommended way to specify connections, but the registers are broken out separately so that they can be dynamically added to too. - -## Development - -TODO: set up something like https://hub.docker.com/r/oitc/modbus-server to test with - -## Similar projects - -* https://github.com/Instathings/modbus2mqtt -* https://github.com/TenySmart/ModbusTCP2MQTT - Sungrow inverter specific -* https://github.com/bohdan-s/SunGather - Sungrow inverter specific +This wraps `sungrow-winets` client in the appropriate traits from [`tokio-modbus`](https://crates.io/crates/tokio-modbus) to allow accessing the Modbus registers which the Sungrow WiNet-S dongle exposes. The reason for this is simply that Sungrow's TCP Modbus support is buggy and inconsistent (though improving). \ No newline at end of file diff --git a/modbus-mqtt/README.md b/modbus-mqtt/README.md new file mode 100644 index 0000000..e287958 --- /dev/null +++ b/modbus-mqtt/README.md @@ -0,0 +1,154 @@ +# ModbusMQTT + +![Crates.io](https://img.shields.io/crates/v/modbus-mqtt.svg) +![docs.rs](https://img.shields.io/docsrs/modbus-mqtt) +![Crates.io](https://img.shields.io/crates/l/modbus-mqtt) + +A bridge between Modbus devices and MQTT. + +It is early days, but the plan is: + +* [x] Support custom Modbus transports (Sungrow WiNet-S has been implemented) + * Modbus RTU has not been tested because I don't have a serial Modbus device, but in principle it should work. Please let me know +* [x] Support reading input registers +* [x] Support reading holding registers +* [ ] Support _setting_ holding registers +* [ ] Support optional auto-configuration of Home Assistant entities, including using [MQTT Number](https://www.home-assistant.io/integrations/number.mqtt/) et al for holding registers, to allow setting the value. +* [ ] TLS MQTT connections +* [ ] WebSocket MQTT connections + +NOTE: For the time being, this does not support MQTTv5. + +## Installing + +For now, use `cargo install` (Rust toolchain required). Soon, I will have release binaries attached to GitHub releases. In the future, there will also be Docker images made available for convenience. + +## Running + +Start the binary, passing in the URL to your MQTT server, including any credentials: + +```sh-session +$ modbus-mqtt mqtt://$MQTT_HOST[:$MQTT_PORT]/[$CUSTOM_MODBUS_TOPIC] +``` + +The supported protocols are currently just `tcp://`/`mqtt://`, but with intent to support: `mqtts://`, `ssl://`/`tls://`, `ws://`, and `wss://`. + +The default topic which ModbusMQTT monitors and to which it publishes is `modbus-mqtt`. You can vary that by changing the path portion of the MQTT URL. + +Further, you can change other MQTT options by using query params, such as setting a custom client_id: + +```sh +"mqtt://1.2.3.4/?client_id=$CUSTOM_CLIENT_ID" +``` + +For a full list of supported options, check [the MQTT client library's source code](https://github.com/bytebeamio/rumqtt/blob/c6dc1f7cfb26f6c1f676954a51b398708d49091a/rumqttc/src/lib.rs#L680-L768). + +### Connecting to Modbus devices + +To connect to a Modbus device, you need to post the connection details to MQTT under a topic of `$prefix/$connection_id/connect`. It is intended that such messages are marked as **retained** so that ModbusMQTT reconnects to your devices when it restarts. + +For instance, a simple config might be: + +```jsonc +// PUBLISH modbus-mqtt/solar-inverter/connect +{ + "host": "10.10.10.219", + "proto": "tcp", +} +``` + +If the connection is successful, you will see the following message like the following sent to the MQTT server: + +```jsonc +// modbus-mqtt/solar-inverter/state +"connected" +``` + +#### Full connection examples + +All fields accepted (optional fields show defaults) + +```jsonc +{ + // Common fields + "address_offset": 0, // optional + "unit": 1, // optional, aliased to "slave" + + // TCP: + "proto": "tcp", + "host": "1.2.3.4", + "port": 502, // optional + + // RTU / Serial: + "proto": "rtu", + "tty": "/dev/ttyACM0", + "data_bits": "Eight", // optional (TODO: accept numeric and lowercase) + // valid: Five, Six, Seven, Eight + "stop_bits": "One", // optional (TODO: accept numeric and lowercase) + // valid: One, Two + "flow_control": "None", // optional (TODO: accept lowercase) + // valid: None, Software, Hardware + "parity": "None", // optional (TODO: accept lowercase) + // valid: None, Odd, Even + + // Sungrow WiNet-S dongle + "proto": "winet-s", + "host": "1.2.3.4", +} +``` + +#### Monitoring registers + +Post to `$MODBUS_MQTT_TOPIC/$CONNECTION_ID/$TYPE/$ADDRESS` where `$TYPE` is one of `input` or `holding` with the following payload (optional fields show defaults): + +```jsonc +{ + "name": null, // OPTIONAL - gives the register a name which is used in the register MQTT topics (must be a valid topic component) + + "interval": "1m", // OPTIONAL - how often to update the registers value to MQTT + // e.g.: 3s (every 3 seconds) + // 2m (every 2 minutes) + // 1h (every 1 hour) + + "swap_bytes": false, // OPTIONAL + "swap_words": false, // OPTIONAL + + "type": "s16", // OPTIONAL + // valid: s8, s16, s32, s64 (signed) + // u8, u16, u32, u64 (unsigned) + // f32, f64 (floating point) + + "scale": 0, // OPTIONAL - number in register will be multiplied by 10^(scale) + // e.g.: to turn kW into W, you would provide scale=3 + // to turn W into kW, you would provide scale=-3 + + "offset": 0, // OPTIONAL - will be added to the final result (AFTER scaling) + + + // Additionally, "type" can be set to "array": + "type": "array", + "of": "u16" // The default array element is u16, but you can change it with the `of` field +} +``` + +Further, the `type` field can additionally be set to `"array"`, in which case, a `count` field must be provided. The array elements default to `"s16"` but can be overriden in the `"of"` field. + +NOTE: this is likely to change such that there is always a `count` field (with default of 1) and if provided to be greater than 1, it will be interpreted to be an array of elements of the `type` specified. + +There is some code to accept `"string"` type (with a required `length` field) but this is experimental and untested. + +##### Register shorthand + +When issuing the `connect` payload, you can optionally include `input` and/or `holding` fields as arrays containing the above register schema, as long as an `address` field is added. When present, these payloads will be replayed to the MQTT server as if the user had specified each register separately, as above. + +This is a recommended way to specify connections, but the registers are broken out separately so that they can be dynamically added to too. + +## Development + +TODO: set up something like https://hub.docker.com/r/oitc/modbus-server to test with + +## Similar projects + +* https://github.com/Instathings/modbus2mqtt +* https://github.com/TenySmart/ModbusTCP2MQTT - Sungrow inverter specific +* https://github.com/bohdan-s/SunGather - Sungrow inverter specific diff --git a/sungrow-winets/README.md b/sungrow-winets/README.md index 0dfd8eb..5d0490c 100644 --- a/sungrow-winets/README.md +++ b/sungrow-winets/README.md @@ -1,6 +1,10 @@ # Sungrow WiNet-S Client -This allows connecting to Sungrow inverters which use a WiNet-S networking dongle. +![Crates.io](https://img.shields.io/crates/v/sungrow-winets.svg) +![docs.rs](https://img.shields.io/docsrs/sungrow-winets.svg) +![Crates.io](https://img.shields.io/crates/l/sungrow-winets) + +This is a barebones API client for reading and writing settings for Sungrow solar and hybrid inverters equipped with a WiNet-S communications module. No attempt has been made to support other dongles, inverters, etc. diff --git a/tokio_modbus-winets/README.md b/tokio_modbus-winets/README.md new file mode 100644 index 0000000..71bb8b1 --- /dev/null +++ b/tokio_modbus-winets/README.md @@ -0,0 +1,7 @@ +## `tokio_modbus-winets` + +This wraps `sungrow-winets` client in the appropriate traits from [`tokio-modbus`](https://crates.io/crates/tokio-modbus) to allow accessing the Modbus registers which the Sungrow WiNet-S dongle exposes. The reason for this is simply that Sungrow's TCP Modbus support is buggy and inconsistent (though improving). + +![Crates.io](https://img.shields.io/crates/v/tokio_modbus-winets.svg) +![docs.rs](https://img.shields.io/docsrs/tokio_modbus-winets) +![Crates.io](https://img.shields.io/crates/l/tokio_modbus-winets)