Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save madbobmcjim/5b8d42edce81893cd3ee47f9dfc8cbfd to your computer and use it in GitHub Desktop.
Save madbobmcjim/5b8d42edce81893cd3ee47f9dfc8cbfd to your computer and use it in GitHub Desktop.
##
# Based off Mark Gardiner's script here:
# https://gist.github.com/markgdev/ce2dbf9002385cbe5a35b81985f9c84a
# I've added some tweaks to get additional information out of the inverter.
# Note: the InverterStatus gathering (for fault reporting) is wrong, I'm pretty sure I've got my hex conversion wrong
# I know the last two batter statuses are correct as that's what the system reported when I had a battery fault :-)
##
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 = "solar-Pi"
mqtt_server = "127.0.0.1"
mqtt_username = ""
mqtt_password = ""
debug = False
InverterStatusDict = {
0: 'Waiting',
1: 'Open Loop',
2: 'SoftRun',
3: 'Normal/Generating',
4100: 'Off-Grid ',
4112: 'Grid overvoltage OV-G-V',
4113: 'Grid undervoltage UN-G-V',
4114: 'Grid overfrequency OV-G-F',
4115: 'Grid underfrequency UN-G-F',
4116: 'Grid impedance is too large G-IMP',
4117: 'No Grid',
4118: 'Grid imbalance G-PHASE',
4119: 'Grid frequency jitter G-F-FLU',
4120: 'Grid overcurrent OV-G-I',
4121: 'Grid current tracking fault IGFOL-F',
4122: 'DC overvoltage OV-DC',
4129: 'DC bus overvoltage OV-BUS',
4130: 'DC busbar uneven voltage UNB-BUS',
4131: 'DC bus undervoltage UN-BUS',
4132: 'DC busbar uneven voltage 2 UNB2-BUS',
4133: 'DC A way overcurrent OV-DCA-I',
4134: 'DC B path overcurrent OV-DCB-I',
4135: 'DC input disturbance DC-INTF',
4144: 'Grid disturbance GRID-INTF',
4145: 'DSP initialization protection INI-Fault',
4146: 'Overtemperature protection OV-TEM',
4147: 'PV insulation fault PV ISO-PRO',
4148: 'Leakage current protection ILeak-PRO',
4149: 'Relay detection protection RelayChk-FAIL',
4150: 'DSP_B protection DSP-B-FAULT',
4151: 'DC component is too large DCInj-FAULT',
4152: '12V undervoltage protection 12Power-FAULT',
4153: 'Leakage current self-test protection ILeak-Check',
4154: 'Under temperature protection UN-TEM',
4160: 'Arc self-test protection AFCI-Check',
4161: 'Arc protection ARC-FAULT',
4162: 'DSP on-chip SRAM exception RAM-FAULT',
4163: 'DSP on-chip FLASH exception FLASH-FAULT',
4164: 'DSP on-chip PC pointer is abnormal PC-FAULT',
4165: 'DSP key register exception REG-FAULT',
4166: 'Grid disturbance 02 GRID-INTF02',
4167: 'Grid current sampling abnormality IG-AD',
4168: 'IGBT overcurrent IGBT-OV-I',
4176: 'Network side current transient overcurrent OV-IgTr',
4177: 'Battery overvoltage hardware failure OV-Vbatt-H',
4178: 'LLC hardware overcurrent OV-ILLC',
4179: 'Battery overvoltage detection OV-Vbatt',
4180: 'Battery undervoltage detection UN-Vbatt',
4181: 'Battery no connected NO-Battery',
4182: 'Bypass overvoltage fault OV-VBackup',
4183: 'Bypass overload fault Over-Load',
8210: 'No Battery',
8213: 'Battery Alarm'
}
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)
if battery_status == 0:
battery_abs = battery_power
else:
battery_abs = -battery_power
# 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"
inverter_status = instrument.read_register(33095, functioncode=4, signed=False)
# 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)
# Grid import power yesterday (kWh)
grid_import_yesterday = instrument.read_register(33172, functioncode=4, number_of_decimals=1, signed=False)
# Grid export power yesterday (kWh)
grid_export_yesterday = instrument.read_register(33176, functioncode=4, number_of_decimals=1, signed=False)
# Grid export power yesterday (kWh)
house_load_yesterday = instrument.read_register(33180, 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)
if debug:
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"Inverter Status: {inverter_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"Grid Import Yesterday: {grid_import_yesterday}")
print(f"Grid Export Yesterday: {grid_export_yesterday}")
print(f"House Load Yesterday: {house_load_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))
msgs.append((mqtt_topic + "Battery_Abs", battery_abs, 0, False))
msgs.append((mqtt_topic + "Battery_Status", battery_status, 0, False))
if inverter_status in InverterStatusDict:
msgs.append((mqtt_topic + "Inverter_Status", InverterStatusDict[inverter_status], 0, False))
else:
msgs.append((mqtt_topic + "Inverter_Status", inverter_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 + "grid_import_yesterday", grid_import_yesterday, 0, False))
msgs.append((mqtt_topic + "grid_export_yesterday", grid_export_yesterday, 0, False))
msgs.append((mqtt_topic + "house_load_yesterday", house_load_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 Exception as e:
print(e)
print("Failed to query inverter, backing off for 30 seconds")
time.sleep(30)
if debug:
print("-------------------------------")
time.sleep(5)
@charbeltabet00
Copy link

hello how to use this code is there any tutorial or something that help?

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