Storing SMA Inverter Data In InfluxDB
Tags: modbus, influxdb, telegraf, sma
Published: 2023-10-29 16:46:35

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](https://www.photovoltaikforum.com/). 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](https://www.influxdata.com/integration/modbus/) 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](https://www.sma.de/en/products/product-features-interfaces/modbus-protocol-interface) 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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/alyn3vjl3k6t8ss9uysu.png)

- The smart meter is read using a [Youless](https://www.youless.nl/) 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