December 2018, the kitchen table. My wife and I decided to have solar panels installed on our roof. For my wife, it was a way of saving money and CO2. For me, it was a opportuntiy to start a new data side project: collecting metrics from the inverter.
Original implementation
Not all inverter brands have the option of reading data locally. The worst case scenario: no local access, the only way to retrieve data is using the online portal maintained by the manufacturer. SMA inverters seemd to be open enough so I decided to order a SMA Sunny Boy. It had a local web interface and, after enabling using Installer account, can also expose data using Modbus.
I first spent some hours trying to use a Modbus implementation and get some data out of the inverter myself. Luckily, I found a working python script on a German forum. I adapted it so it would write to InfluxDB. I also started posting metrics to a MQTT server and from there, I stored 15-minute interval data in a PostgreSQL database.
However, after some time, I ended up with a pile of scripts that read data from some device and then saved it to InfluxDB. This became difficult to maintain and there was duplicated functionality (handling network errors, system reboots and such).
So I found out about Telegraf and started to move away from custom scripts and settled on Telegraf doing the metrics collection and sending it to MQTT and InfluxDB. At some point, all of my hacky scripts had been replaced by Telegraf. Except the metrics from SMA Modbus: they were still retrieved using the custom script.
Using Telegraf to read SMA Sunny Boy Modbus
Telegraf supports Modbus. But Modbus is not trivial. You need to know about registers, data types and byte ordering. So after a lot of trial and error and Googling, I came up with this solution. It works for my inverter, which is a SB3.0-1AV-41 902 running on firmware 4.0.55.R.
[[inputs.modbus]]
name = "sma"
# Host address of the inverter
controller = "tcp://192.168.178.171:502"
configuration_type = "request"
timeout = "1s"
[[inputs.modbus.request]]
slave_id = 3
byte_order = "ABCD"
register = "holding"
measurement = "sma"
fields = [
{ address=30513, name="wh_out_total", type="UINT64", scale = 1.0 },
{ address=30517, name="wh_out_today", type="UINT64", scale = 1.0 },
{ address=30783, name="phase_1_out_volt", type="UINT32", scale= 0.01 },
{ address=30775, name="out_watt", type="INT32", scale = 1.0 },
{ address=30769, name="string_a_amp", type="INT32", scale = 0.001 },
{ address=30771, name="string_a_volt", type="INT32", scale = 0.01 },
{ address=30773, name="string_a_watt", type="INT32", scale = 1.0 },
{ address=30957, name="string_b_amp", type="INT32", scale = 0.001 },
{ address=30959, name="string_b_volt", type="INT32", scale = 0.01 },
{ address=30961, name="string_b_watt", type="INT32", scale = 1.0 },
{ address=30953, name="inverter_temp", type="INT32", scale = 0.1 },
]
[[processors.starlark]]
namepass = ["sma"]
source = '''
def apply(metric):
if metric.fields['out_watt'] < 0 or metric.fields['phase_1_out_volt'] > 12000:
return None
return metric
'''
The address numbers can be found in the Modbus Documentation provided by SMA. I am only interested in a few but there are many more.
This results in a InfluxDB measurement called sma
and metrics from the specified registers. And now the metrics are coming in:
time host inverter_temp name out_watt phase_1_out_volt slave_id string_a_amp string_a_volt string_a_watt string_b_amp string_b_volt string_b_watt type wh_out_today wh_out_total
---- ---- ------------- ---- -------- ---------------- -------- ------------ ------------- ------------- ------------ ------------- ------------- ---- ------------ ------------
1698579340000000000 server1 30.6 sma 835 225.66 3 3.355 201.3 675 1.345 170.55 229 holding_register 1635 10556610
1698579350000000000 server1 30.6 sma 805 225.33 3 3.283 197.51 648 1.34 166.72 223 holding_register 1638 10556612
1698579360000000000 server1 30.6 sma 712 225.27 3 2.833 205.09 581 1.167 174.29 203 holding_register 1640 10556614
1698579370000000000 server1 30.6 sma 480 223.78 3 1.823 208.75 380 0.975 174.23 169 holding_register 1642 10556616
1698579380000000000 server1 30.6 sma 328 221.49 3 1.217 205.34 249 0.877 166.97 146 holding_register 1643 10556617
1698579390000000000 server1 30.6 sma 318 220.92000000000002 3 1.188 201.87 239 0.862 170.48 146 holding_register 1644 10556618
The 'starlark' section is an hack: the inverter enters a sleep mode, after sunset. And in that case, it return the maximum values (or minimum for signed ints) that are possible:
time host inverter_temp name out_watt phase_1_out_volt slave_id string_a_amp string_a_volt string_a_watt string_b_amp string_b_volt string_b_watt type wh_out_today wh_out_total
---- ---- ------------- ---- -------- ---------------- -------- ------------ ------------- ------------- ------------ ------------- ------------- ---- ------------ ------------
1697052610000000000 server1 -214748364.8 sma -2147483648 42949672.95 3 -2147483.648 -21474836.48 -2147483648 -2147483.648 -21474836.48 -214748364.8 holding_register 2393 10507239
Before the numbers are sent to InfluxDB, this little function will check if the values are valid and if not, return None, which will not be saved in InfluxDB. So we don't store metrics in case the inverter is in standby.
End result
This is how my metrics setup looks:
- The smart meter is read using a Youless and Telegraf will talk to the Youless
- The SMA is read directly by Telegraf using Modbus
- A few Zigbee devices are exposed using Zigbee2MQTT which is read by Telegraf
So everthing is going through Telegraf, which turns out to be a stable and reliable solution
Top comments (0)