Connecting a solar hybrid inverter to ioBroker

To reduce our electricity bill, we have installed an island solar system, the central unit of which is a PowMr 5600W All In One hybrid inverter.

PowMr 5000W Solar Inverter, 48V DC to 110V/240V AC Split Phase Inverter Pure Sine Wave Power Inverter with 100A MPPT Controller

PowMr 5000W Solar Inverter

48V DC to 110V/240V AC Split Phase Inverter Pure Sine Wave Power Inverter with 100A MPPT Controller

The hybrid inverter has an LCD display that shows system data and operating status in real time.

Suitable for 48 V lead-acid or lithium battery.

In order to make the best possible use of our solar system, we need to monitor the inverter. We need to know how much electricity the solar panel produces or how much energy the battery pack stores.

The PowMr 5600W All In One hybrid inverter also has a USB port, I connected a Raspberry Pi 3 model there, but a reasonably priced Raspberry pi zero can also be perfect for this task.

Using the Python script running on Raspberry Pi, we read the data from the inverter and add it to the mqtt server running on iobroker.

Like many similar inverters (e.g. ESUN, Deye, etc.), PowMr uses SRNE Modbus-RTU communication protocol.

To get python script to communicate with the inverter, we need the MinimalModbus library. This can easily be done with the python package installer. If not already installed, install the pip package installer and then install the MinimalModbus library.

Bash
sudo apt-get update 
sudo apt-get install python3-pip 
 
pip3 install -U minimalmodbus

To use minimalmodbus, we first import the library in our python script.

Python
#!/usr/bin/python3

import minimalmodbus
...

Then we create an instance and specify the settings.

Python
...

#            minimalmodbus.Instrument(port, slave_address, mode)
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1, 'rtu')

instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity = 'N'
instrument.serial.stoppbits = 1
instrument.serial.timeout  = 0.2
instrument.mode = minimalmodbus.MODE_RTU
instrument.clear_buffers_before_each_transaction = True

The inverter registers can be read with the read_register function.

Python
# read_register(registeraddress, number_of_decimals, functioncode)
battery_remaining = instrument.read_register(0x100, 1, 3)*10
12V 100Ah LiFePO4 Lithium Akku, 1280Wh, 100A BMS, akár 15000 töltési ciklus.

Renogy Deep Cycle AGM Battery 12 Volt 100Ah

1100A Max Discharge Current, Safe Charge Most Home Appliances for RV, Camping, Cabin, Marine and Off-Grid System, Maintenance-Free

It is also a good choice for solar systems.

To communicate with the mqtt server, we will use the paho-mqtt library. If you have not done so, install it as follows.

Bash
pip3 install paho-mqtt

We also import the paho-mqtt directory.

Python
#!/usr/bin/python3

import minimalmodbus
from paho.mqtt import client as mqtt_client
...

Enter connection details to the mqtt server.

Python
...
broker = '192.168.1.180'
port = 1883
username = ''
password = ''
client_id = 'PowMr-client'
...

We need a function to connect to the mqtt server.

Python
def connect_mqtt():
  def on_connect(client, userdata, flags, rc):
    if rc == 0:
      print("Connected to ioBroker!")
    else:
      print("Failed to connect, return code %d\n", rc)

  client = mqtt_client.Client(client_id)
  client.username_pw_set(username, password)
  client.on_connect = on_connect
  client.connect(broker, port)
  return client

You also need a function to publish MQTT messages.

Python
def publish(client, topic, payload):
  result = client.publish(topic, payload)
  status = result[0]
  if status == 0:
    print(f"Send `{payload}` to topic `{topic}`")
  else:
    print(f"Failed to send message to topic {topic}")

Finally, the run function. We connect to the mqtt server, read the data from the inverter and send it to the mqtt server.

Python
def run():
  client = connect_mqtt()
  client.loop_start()

  while(1):
    try:
      battery_remaining = instrument.read_register(0x100, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/battery_remaining", battery_remaining)
    except:
      print("Failed to read the battery_remaining")

Create the inverter.py file.

Bash
nano inverter.py

Then copy and paste the code below.

Python
#!/usr/bin/python3
import time
import subprocess
import minimalmodbus
from paho.mqtt import client as mqtt_client

delay = 1

broker = '192.168.1.180'
port = 1883
username = ''
password = ''
client_id = 'PowMr-client'


instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1, 'rtu')

instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity = 'N'
instrument.serial.stoppbits = 1
instrument.clear_buffers_before_each_transaction = True
instrument.serial.timeout  = 0.2
instrument.mode = minimalmodbus.MODE_RTU


def connect_mqtt():
  def on_connect(client, userdata, flags, rc):
    if rc == 0:
      print("Connected to ioBroker!")
    else:
      print("Failed to connect, return code %d\n", rc)

  client = mqtt_client.Client(client_id)
  client.username_pw_set(username, password)
  client.on_connect = on_connect
  client.connect(broker, port)
  return client


def publish(client, topic, payload):
  result = client.publish(topic, payload)
  status = result[0]
  if status == 0:
    print(f"Send `{payload}` to topic `{topic}`")
  else:
    print(f"Failed to send message to topic {topic}")


def run():
  client = connect_mqtt()
  client.loop_start()
    
  while(1):
    try:
      battery_remaining = instrument.read_register(0x100, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/battery_remaining", battery_remaining)
    except:
      print("Failed to read the battery_remaining")
    time.sleep(delay)
        
    try:
      battery_voltage = instrument.read_register(0x101, 1, 3)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/battery_voltage", battery_voltage)
    except:
      print("Failed to read the battery_voltage")
    time.sleep(delay)
        
    try:
      battery_current = instrument.read_register(0x102, 1, 3, True)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/battery_current", battery_current)
    except:
      print("Failed to read the battery_current")
    time.sleep(delay)
        
    try:
      pv_power = instrument.read_register(0x109, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/pv_power", pv_power)
    except:
      print("Failed to read the pv_power")
    time.sleep(delay)
        
    try:
      pv_voltage = instrument.read_register(0x107, 1, 3)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/pv_voltage", pv_voltage)
    except:
      print("Failed to read the pv_voltage")
    time.sleep(delay)
        
    try:
      pv_current = instrument.read_register(0x108, 1, 3)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/pv_current", pv_current)
    except:
      print("Failed to read the pv_current")
    time.sleep(delay)
        
    try:
      battery_charge_status = instrument.read_register(0x10B, 1, 3)*10
      if battery_charge_status == 0: batt_charge_status = "0 - Töltés kikapcsolva"
      elif battery_charge_status == 1: batt_charge_status = "1 - Gyorstöltés"
      elif battery_charge_status == 2: batt_charge_status = "2 - Állandó feszültségű töltés"
      elif battery_charge_status == 3: batt_charge_status = "3 ---"
      elif battery_charge_status == 4: batt_charge_status = "4 - Lebegő töltés"
      elif battery_charge_status == 5: batt_charge_status = "5 ---"
      elif battery_charge_status == 6: batt_charge_status = "6 - Li akkumulátor aktiválása"
      elif battery_charge_status == 7: batt_charge_status = "7 ---"
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/battery_charge_status", batt_charge_status)
    except:
      print("Failed to read the battery_charge_status")
    time.sleep(delay)
        
    try:
      inverter_current_status = instrument.read_register(0x210, 1, 3)*10
      if inverter_current_status == 0: inverter_status = "0 - Bekapcsolási késleltetés"
      elif inverter_current_status == 1: inverter_status = "1 - Várakozás"
      elif inverter_current_status == 2: inverter_status = "2 - Inicializálás"
      elif inverter_current_status == 3: inverter_status = "3 - Lágy indítás"
      elif inverter_current_status == 4: inverter_status = "4 - Működés hálózatról"
      elif inverter_current_status == 5: inverter_status = "5 - Inverteres működés"
      elif inverter_current_status == 6: inverter_status = "6 - Inverter a hálózatra"
      elif inverter_current_status == 7: inverter_status = "7 - Hálózat az inverterhez"
      elif inverter_current_status == 8: inverter_status = "8 - Akkumulátor aktiválva"
      elif inverter_current_status == 9: inverter_status = "9 - Felhasználó általi leállítás"
      elif inverter_current_status == 10: inverter_status = "10 - Hiba"
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/inverter_status", inverter_status)
    except:
      print("Failed to read the inverter_status")
    time.sleep(delay)
        
    try:
      load_current = instrument.read_register(0x219, 1, 3)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/load_current", load_current)
    except:
      print("Failed to read the load_current")
    time.sleep(delay)
        
    try:
      loadpower = instrument.read_register(0x21B, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/load_power_W", loadpower)
    except:
      print("Failed to read the load_power_W")
    time.sleep(delay)
        
    try:
      mains_charging_current = instrument.read_register(0x21E, 1, 3)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/mains_charging_current", mains_charging_current)
    except:
      print("Failed to read the mains_charging_current")
    time.sleep(delay)
        
    try:
      load_rate = instrument.read_register(0x21F, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/load_rate", load_rate)
    except:
      print("Failed to read the load_rate")
    time.sleep(delay)
        
    try:
      temp_dcdc = instrument.read_register(0x220, 1, 3, True)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/temp_dcdc", temp_dcdc)
    except:
      print("Failed to read the temp_dcdc")
    time.sleep(delay)
        
    try:
      temp_dcac = instrument.read_register(0x221, 1, 3, True)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/temp_dcac", temp_dcac)
    except:
      print("Failed to read the temp_dcac")
    time.sleep(delay)
        
    try:
      temp_trans = instrument.read_register(0x222, 1, 3, True)
      publish(client,"Photovoltaik-Anlage/Inverter/parameters/temp_transformer", temp_trans)
    except:
      print("Failed to read the temp_transformer")
    time.sleep(delay)
        
        
    # fault codes
    try:
      current_fault_code_1 = instrument.read_register(0x204, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/fault_codes/current_fault_code_1", current_fault_code_1)
    except:
      print("Failed to read the current_fault_code_1")
    time.sleep(delay)
        
    try:
      current_fault_code_2 = instrument.read_register(0x205, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/fault_codes/current_fault_code_2", current_fault_code_2)
    except:
      print("Failed to read the current_fault_code_2")
    time.sleep(delay)
        
    try:
      current_fault_code_3 = instrument.read_register(0x206, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/fault_codes/current_fault_code_3", current_fault_code_3)
    except:
      print("Failed to read the current_fault_code_3")
    time.sleep(delay)
        
    try:
      current_fault_code_4 = instrument.read_register(0x207, 1, 3)*10
      publish(client,"Photovoltaik-Anlage/Inverter/fault_codes/current_fault_code_4", current_fault_code_4)
    except:
      print("Failed to read the current_fault_code_4")
    time.sleep(delay)
        
    instrument.serial.close()


if __name__ == '__main__':
    run()

When your file is ready, save it. Now we need to make it start automatically when the system boots. To do this we need a new file.

Bash
sudo nano /etc/systemd/system/inverter.service

Copy the following lines to the inverter.service file. Don’t forget to enter the file path correctly!

Bash
[Unit]
Description=inverter-service
Wants=network-online.target
After=network.target network-online.target

[Service]
Type=idle
User=username
ExecStartPre=/bin/sleep 30
ExecStart=/usr/bin/python3 /home/pi/inverter.py
Restart=always

[Install]
WantedBy=multi-user.target

Set the permissions as follows.

Bash
sudo chmod 644 /etc/systemd/system/inverter.service

Restart the systemctl background service.

Bash
sudo systemctl daemon-reload

Enable inverter.service.

Bash
sudo systemctl enable inverter.service

So every time the system boots, inverter.py will run and send the read data to the mqtt server.

More information about the SRNE modbus protocol and some registers can be found in the pdf document below. You can use it to customize the inverter.py file to your needs.