Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save AeroJohnBE/94660321ead05171c1cb3bfb541aff92 to your computer and use it in GitHub Desktop.
Save AeroJohnBE/94660321ead05171c1cb3bfb541aff92 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment