Skip to content

Instantly share code, notes, and snippets.

@markgdev
Last active October 29, 2023 19:30
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save markgdev/ce2dbf9002385cbe5a35b81985f9c84a to your computer and use it in GitHub Desktop.
Save markgdev/ce2dbf9002385cbe5a35b81985f9c84a to your computer and use it in GitHub Desktop.
Query a Solis Hybrid 5G inverter and push to MQTT
import minimalmodbus
import time
import paho.mqtt.publish as publish
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1, debug = False)
instrument.serial.baudrate = 9600
# This probably doesn't need setting but I was getting timeout failures
# rather than wrong data address when I had the registers wrong
instrument.serial.timeout = 2
# An example read for long
# instrument.read_long(33004, functioncode=4, signed=False)
# MQTT
mqtt = os.environ['USE_MQTT']
mqtt_client = os.environ['MQTT_CLIENT_ID']
mqtt_server = os.environ['MQTT_SERVER']
mqtt_username = os.environ['MQTT_USERNAME']
mqtt_password = os.environ['MQTT_PASSWORD']
def get_status():
# Inverter Temp
inverter_temp = instrument.read_register(33093, functioncode=4, number_of_decimals=1, signed=False)
# Inverter Time
inverter_time_hour = instrument.read_register(33025, functioncode=4, signed=False)
inverter_time_min = instrument.read_register(33026, functioncode=4, signed=False)
inverter_time_sec = instrument.read_register(33027, functioncode=4, signed=False)
# Battery Status, 0=Charge, 1=Discharge
battery_status = instrument.read_register(33135, functioncode=4, signed=False)
# Battery SOC
battery_soc = instrument.read_register(33139, functioncode=4, signed=False)
# Grid Power (w), Positive=Export, Negative=Import
grid_power = instrument.read_long(33130, functioncode=4, signed=True)
# House load power (w)
house_power = instrument.read_register(33147, functioncode=4, signed=False)
# Battery Power (w)
battery_power = instrument.read_long(33149, functioncode=4, signed=True)
# Current generation (Active power) (w), need to confirm when generating
current_generation = instrument.read_long(33057, functioncode=4, signed=False)
total_active_power = instrument.read_long(33263, functioncode=4, signed=True)
# instrument.read_long(33079, functioncode=4, signed=True)
# possibly this too 33263? "Meter total active power"
# Total generation today (kWh)
generation_today = instrument.read_register(33035, functioncode=4, number_of_decimals=1, signed=False)
# Total generation yesterday (kWh)
generation_yesterday = instrument.read_register(33036, functioncode=4, number_of_decimals=1, signed=False)
# Battery storage mode, 33=self use, 35=timed charge
storage_mode = instrument.read_register(43110, functioncode=3, signed=False)
print(f"Inverter time: {str(inverter_time_hour).zfill(2)}:{str(inverter_time_min).zfill(2)}:{str(inverter_time_sec).zfill(2)}")
print(f"Inverter Temperature: {inverter_temp}")
print(f"Battery Status: {battery_status}")
print(f"Battery SOC: {battery_soc}")
print(f"Grid Power: {grid_power}")
print(f"House Power: {house_power}")
print(f"Battery Power: {battery_power}")
print(f"Current Generation: {current_generation}")
print(f"Total Active Power: {total_active_power}")
print(f"Generation Today: {generation_today}")
print(f"Generation Yesterday: {generation_yesterday}")
print(f"Battery Storage Mode: {storage_mode}")
# Push to MQTT
msgs = []
mqtt_topic = ''.join([mqtt_client, "/" ]) # Create the topic base using the client_id and serial number
if (mqtt_username != "" and mqtt_password != ""):
auth_settings = {'username':mqtt_username, 'password':mqtt_password}
else:
auth_settings = None
msgs.append((mqtt_topic + "Battery_Charge_Percent", battery_soc, 0, False))
msgs.append((mqtt_topic + "Battery_Power", battery_power, 0, False))
# Fix to match what we get from m.ginlong.com while we're switching between the two
Battery_Status = 1.0 # Default to charging
if battery_status == 1:
Battery_Status = 2.0
msgs.append((mqtt_topic + "Battery_Status", Battery_Status, 0, False))
msgs.append((mqtt_topic + "Power_Grid_Total_Power", grid_power, 0, False))
# Fix to match what we get from m.ginlong.com while we're switching between the two
Power_Grid_Status = 2.0 # Default to importing
if grid_power > 0:
Power_Grid_Status = 1.0
msgs.append((mqtt_topic + "Power_Grid_Status", Power_Grid_Status, 0, False))
msgs.append((mqtt_topic + "Consumption_Power", house_power, 0, False))
msgs.append((mqtt_topic + "AC_Power", current_generation, 0, False))
msgs.append((mqtt_topic + "generation_today", generation_today, 0, False))
msgs.append((mqtt_topic + "generation_yesterday", generation_yesterday, 0, False))
msgs.append((mqtt_topic + "inverter_temp", inverter_temp, 0, False))
msgs.append((mqtt_topic + "storage_mode", storage_mode, 0, False))
publish.multiple(msgs, hostname=mqtt_server, auth=auth_settings)
def timed_charge():
# We can use read_registers to grab all the values in one call:
# instrument.read_registers(43143, number_of_registers=8, functioncode=3)
# Not going to check charge/discharge for now, we haven't implemented it yet.
# Timed charge start
# Hour
charge_start_hour = instrument.read_register(43143, functioncode=3, signed=False)
# Minute
charge_start_min = instrument.read_register(43144, functioncode=3, signed=False)
# Timed charge end
# Hour
charge_end_hour = instrument.read_register(43145, functioncode=3, signed=False)
# Minute
charge_end_min = instrument.read_register(43146, functioncode=3, signed=False)
# Timed discharge start
# Hour
discharge_start_hour = instrument.read_register(43147, functioncode=3, signed=False)
# Minute
discharge_start_min = instrument.read_register(43148, functioncode=3, signed=False)
# Timed discharge end
# Hour
discharge_end_hour = instrument.read_register(43149, functioncode=3, signed=False)
# Minute
discharge_end_min = instrument.read_register(43150, functioncode=3, signed=False)
print(f"Charge Start: {charge_start_hour}:{charge_start_min}")
print(f"Charge End: {charge_end_hour}:{charge_end_min}")
print(f"Discharge Start: {discharge_start_hour}:{discharge_start_min}")
print(f"Discharge End: {discharge_end_hour}:{discharge_end_min}")
# Change charge start time minute, will work the same for the othe values.
# instrument.write_register(43144, functioncode=6, signed=False, value=54)
# We can write all the times in one call with write_registers:
# instrument.write_registers(43143, [18, 00, 8, 0, 8, 0, 18, 0])
# Storage control switch
# 33 = Self use mode
# 35 = Timed charge mode
# Read/Write register
# instrument.read_register(43110, functioncode=3, signed=False)
# instrument.write_register(43110, functioncode=6, signed=False, value=33)
# instrument.write_register(43110, functioncode=6, signed=False, value=35)
# Read only register
# instrument.read_register(33132, functioncode=4, signed=False)
while True:
try:
get_status()
except:
print("Failed to query inverter, backing off for 30 seconds")
time.sleep(30)
print("-------------------------------")
time.sleep(5)
@madbobmcjim
Copy link

I just wanted to say thinks for this :-) I grabbed a RS485 adapter and connector off eBay, wired it up to an old Pi and it all worked first time.

@AeroJohnBE
Copy link

Where can you find all the register numbers for Solis inverter (and their function(s))?

@madbobmcjim
Copy link

@AeroJohnBE The one I've been using is here: https://www.scss.tcd.ie/coghlan/Elios4you/RS485_MODBUS-Hybrid-BACoghlan-201811228-1854.pdf

I've made some tweaks to the one I'm using for reporting some more stuff, so I've posted it here: https://gist.github.com/madbobmcjim/5b8d42edce81893cd3ee47f9dfc8cbfd

@AeroJohnBE
Copy link

Can you write multiple charge and discharge periods to the registers? E.g. charging from 8 am to 9 am and from 17 pm to 19 pm (and the rest is discharge)

@madbobmcjim
Copy link

It doesn't look like it from the doc. I guess you'd have to set it each time, so set it to charge between 8am and 9am, then after 9am change it to charge from 5pm to 7pm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment