The project consists of the implementation of the modbus protocol over serial interface to get the measures from two wattmeters and translating the data into MQTT publish messages sent to a MQTT broker such as Mosquitto.
The picture shows a simple installation of a home solar system for self-consumption, that is the test environment for this project.

I wanted to read the power measures and calculate the excess power to turn on and off the home appliances accordingly.
The first thing I tried was getting the values through the inverter’s API but for that you need an “API account” and the manufacturer declines to provide me with it, so I took the path explained here.
We need to measure the power generated by the inverter and the power from the grid. Home consumption is calculated from these two values.
We have already a DDSU666-H wattmeter at the grid side from which we can get the measures of the power coming in and out to the grid. To avoid interfering the dialogue between inverter and wattmeter we don’t generate traffic at this side but sniff the responses. RS-485 is not a point-to-point connection but a bus so other devices can be added.
On the inverter side, we need to add a wattmeter. The one here is an Eastron SDM120CT-MV and in this case Tx and Rx are wired.
The add-ons to the installation are the ones in red colour in the picture above.
The HW module is made of an ESP-WROOM-32 connected to the wattmeters through two TTL to RS-485 modules: through UART1 (pins 17 and 16) to the DDSU666-H and UART2 (pins 14 and 13) to the SDM120CT.
One of the serial interfaces of the HW module is connected to the DDSU666-H wattmeter serial interface for reading only. No transmission is generated to avoid interfering into the inverter which can cause malfunctions. The information is extracted from the dialogue between inverter and the DDSU666-H which performs as follows:
The inverter sends periodically 3 types of requests to the slave address 0x0B
Every 250-300ms the inverter requests reading register 2006 (active power)
The inverter sends 0B 03 20 06 00 02 2F 60
The DDSU666H responds 0B 03 04 BE 0D 6A 16 4A B6
Around every 5 seconds the inverter sends a multiple register read from starting address 2000 for 34 (0022hex) positions = 17 registers
The inverter sends 0B 03 20 00 00 22 CE B9
The DDSU666H responds
0B 03 44
43 68 33 33 3F 16 45 A2 00 00 00 00 BE 0B C6 A8
BE 0B C6 A8 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 3E 0B C6 A8 3E 0B C6 A8 00 00 00 00
BF 80 00 00 BF 80 00 00 BF 80 00 00 00 00 00 00
42 47 F5 C3
63 F0
byte value
00 0B slave address
01 03 function code 0x03 = Read Multiple Holding Registers
02 44 byte count = 44 hex (68 dec) bytes => 68 / 4 bytes per register = 17 registers
value (4 bytes) register
03 43 68 33 33 2000
3F 16 45 A2 2002
00 00 00 00 2004
BE 0B C6 A8 2006
BE 0B C6 A8 2008
00 00 00 00 200A
00 00 00 00 200C
00 00 00 00 200E
00 00 00 00 2010
3E 0B C6 A8 2012
3E 0B C6 A8 2014
00 00 00 00 2016
BF 80 00 00 2018
BF 80 00 00 201A
BF 80 00 00 201C
00 00 00 00 201E
42 47 F5 C3 2020
63 F0 CRC
Around every 10 seconds the inverter sends a multiple register read from starting address 4000 for 32 (0020hex) positions = 16 registers
The inverter sends 0B 03 40 00 00 20 51 78
The DDSU666H responds
0B 03 40
C4 EA DB 33 C4 EA DB 33 00 00 00 00 00 00 00 00
00 00 00 00 44 E5 D5 71 44 E5 D5 71 00 00 00 00
00 00 00 00 00 00 00 00 45 68 58 52 45 68 58 52
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
CE 89
byte value
00 0B slave address
01 03 function code 0x03 = Read Multiple Holding Registers
02 40 byte count = 40 hex (64 dec) bytes => 64 / 4 bytes = 16 registers
value (4 bytes) register
03 C4 EA DB 33 4000
C4 EA DB 33 4002
00 00 00 00 4004
00 00 00 00 4006
00 00 00 00 4008
44 E5 D5 71 400A
44 E5 D5 71 400C
00 00 00 00 400E
00 00 00 00 4010
00 00 00 00 4012
45 68 58 52 4014
45 68 58 52 4016
00 00 00 00 4018
00 00 00 00 401A
00 00 00 00 401C
00 00 00 00 401E
CE 89 CRC
Observed, not from manufacturer spec.
| Register | Value | Units |
|---|---|---|
| 2000 | voltage | Volts (4-bytes floating decimal) |
| 2002 | current | Amps (4-bytes floating decimal) |
| 2004 | ||
| 2006 | active power | kW (4-bytes floating decimal) |
| 2008 | ||
| 200A | ||
| 200C | reactive power | Kvar (4-bytes floating decimal) |
| 200E | ||
| 2010 | ||
| 2012 | apparent power | kW (4-bytes floating decimal) |
| 2014 | ||
| 2016 | ||
| 2018 | power factor | (4-bytes floating decimal) |
| 201A | ||
| 201C | ||
| 201E | ||
| 2020 | frecuency | Hz (4-bytes floating decimal) |
| Register | Value | Units |
|---|---|---|
| 4000 | Active in electricity | W (4-bytes floating decimal) |
| 400A | Negative active energy | Wh (4-bytes floating decimal) |
| 4014 | Positive active energy | Wh (4-bytes floating decimal) |
The source is in C language.
The development environment is the ESP-IDF. I am running ESP-IDF v5.4-dev-2194-gd7ca8b94c8 for Linux on a Raspberry Pi.
The IP connection is through Wi-Fi. You need to set your SSID and password in the file config.h
// WiFi
#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSWORD "THE_PASSWORD_OF_YOUR_SSID"
The SW is expecting an MQTT broker to publish the measurements. So you need to overwrite the IP address and Port of your MQTT server.
// Mosquitto address
#define MQTT_HOST_IP_ADDR "192.168.1.2"
#define MQTT_HOST_IP_PORT 1883 // default Mosquitto port
And if you want, modify the MQTT device name.
#define DEVICE_MQTT_NAME "modbus2mqtt"
Version 2 adds a Rest API interface so that data can be retrieved via MQTT PUBLISH messages or as a WEB service available at <device_ip>:80. To get the information include the following json as payload:
{"type":"data_request","key":"qWpJnwA0crlmgv"}
“key” – is a shared key added for security.
“type” – can be either “data_request” to retrieve the measures or "device_info" to get some perfomance information such WiFi and TCP connection lost count and TCP and MQTT connection status.
You can test the Rest API with CURL as follows:
curl -X GET http://192.168.1.110:80 -d '{"type":"data_request","key":"qWpJnwA0crlmgv"}'
{"DDSU666H":{"v":"233.50","c":"1.81","ap":"306.90","rp":"-291.40"},"SDM120CT":{"v":"233.20","c":"8.14","ap":"1867.70","rp":"5.30"}}curl -X GET http://192.168.1.110:80 -d '{"type":"device_info","key":"qWpJnwA0crlmgv"}'
{"TCP":"ok","MQTT":"ok","WIFIlost":"0","TCPlost":"0"}The IP address of the device is reported via MQTT in the first PUBLISH message as follows:
{"ip":"192.168.1.110","MAC":"B0:A7:32:27:FF:5C"}Follow the regular process to generate and flash the code as described in the ESP IDF instructions "Configure Your Projec"
Run ESP-IDF script to make the tools usable from the command line and set the necessary environment variables.
. $HOME/esp/esp-idf/export.shGo to your project directory, e.g.
cd ~/esp/modbus2MQTTSelect your target
idf.py set-target esp32You can skip the menuconfig for the are no project specific variables to set up
idf.py menuconfigCompile and generate the code
idf.py buildExecute (choose the right port for your enviroment)
idf.py -p /dev/ttyUSB1 flash monitorUse an MQTT client to cusbscribe to the MQTT topic to receive the PUBLISH messages. You can use the Mosquitto client and the mosquitto_sub command like this:
mosquitto_sub -d -t 'modbus2mqtt/set'
Client null sending CONNECT
Client null received CONNACK (0)
Client null sending SUBSCRIBE (Mid: 1, Topic: modbus2mqtt/set, QoS: 0, Options: 0x00)
Client null received SUBACK
Subscribed (mid: 1): 0
Client null received PUBLISH (d0, q0, r0, m0, 'modbus2mqtt/set', ... (64 bytes))
{"SDM120CT":{"v":"228.80","c":"0.00","ap":"-0.00","rp":"28.80"}}
Client null received PUBLISH (d0, q0, r0, m0, 'modbus2mqtt/set', ... (67 bytes))
{"DDSU666H":{"v":"228.60","c":"1.99","ap":"363.50","rp":"-272.70"}}The Mosquitto client is an optional installation. Run the following to install it.
sudo apt install -y mosquitto mosquitto-clients