I tested on S6-EH1P6K-L-PRO connected via modbus RS485 serial connection.
Code: Select all
; Supports Solis Hybrid Inverter S6
; Connecting to hybrid inverter via port RS485 modbus
; Not tested on other models but probably needs changing the registers only
; Compiler: Purebasic v6.20
; By: JCV
EnableExplicit
#SerialPort = 0
#ModBus_FunctionCode_ReadDiscreteInputs = 2
#ModBus_FunctionCode_ReadInputRegisters = 4
Structure RegisterInfo
addr.i
type.s
scale.f
unit.s
EndStructure
Structure DataValue
value.s
unit.s
EndStructure
Structure FaultCategory
startAddr.i
endAddr.i
Map faults.s()
EndStructure
Global NewMap Registers.RegisterInfo()
Global NewMap InverterData.DataValue()
Global NewMap StatusCodes.s()
Global NewMap FaultCategories.FaultCategory()
Global Port.i, Address.i = 1
Global ThreadID.i, MutexID.i, Quit.i, ErrorMessage.s
Global ModBus_RTU_Echo.b
Procedure DefineRegisters()
Registers("model_number")\addr = 33000 : Registers("model_number")\type = "uint16" : Registers("model_number")\scale = 1 : Registers("model_number")\unit = "Hex"
Registers("dsp_version")\addr = 33001 : Registers("dsp_version")\type = "uint16" : Registers("dsp_version")\scale = 1 : Registers("dsp_version")\unit = "Hex"
Registers("hmi_version")\addr = 33002 : Registers("hmi_version")\type = "uint16" : Registers("hmi_version")\scale = 1 : Registers("hmi_version")\unit = "Hex"
Registers("protocol_version")\addr = 33003 : Registers("protocol_version")\type = "uint16" : Registers("protocol_version")\scale = 1 : Registers("protocol_version")\unit = "Hex"
Registers("year")\addr = 33022 : Registers("year")\type = "uint16" : Registers("year")\scale = 1 : Registers("year")\unit = "Years (0-99)"
Registers("month")\addr = 33023 : Registers("month")\type = "uint16" : Registers("month")\scale = 1 : Registers("month")\unit = "Months"
Registers("day")\addr = 33024 : Registers("day")\type = "uint16" : Registers("day")\scale = 1 : Registers("day")\unit = "Days"
Registers("hour")\addr = 33025 : Registers("hour")\type = "uint16" : Registers("hour")\scale = 1 : Registers("hour")\unit = "Hours"
Registers("minute")\addr = 33026 : Registers("minute")\type = "uint16" : Registers("minute")\scale = 1 : Registers("minute")\unit = "Minutes"
Registers("second")\addr = 33027 : Registers("second")\type = "uint16" : Registers("second")\scale = 1 : Registers("second")\unit = "Seconds"
Registers("energy_total")\addr = 33029 : Registers("energy_total")\type = "uint32" : Registers("energy_total")\scale = 1 : Registers("energy_total")\unit = "kWh"
Registers("energy_this_month")\addr = 33031 : Registers("energy_this_month")\type = "uint32" : Registers("energy_this_month")\scale = 1 : Registers("energy_this_month")\unit = "kWh"
Registers("energy_last_month")\addr = 33033 : Registers("energy_last_month")\type = "uint32" : Registers("energy_last_month")\scale = 1 : Registers("energy_last_month")\unit = "kWh"
Registers("energy_today")\addr = 33035 : Registers("energy_today")\type = "uint16" : Registers("energy_today")\scale = 0.1 : Registers("energy_today")\unit = "kWh"
Registers("energy_yesterday")\addr = 33036 : Registers("energy_yesterday")\type = "uint16" : Registers("energy_yesterday")\scale = 0.1 : Registers("energy_yesterday")\unit = "kWh"
Registers("energy_this_year")\addr = 33037 : Registers("energy_this_year")\type = "uint32" : Registers("energy_this_year")\scale = 1 : Registers("energy_this_year")\unit = "kWh"
Registers("energy_last_year")\addr = 33039 : Registers("energy_last_year")\type = "uint32" : Registers("energy_last_year")\scale = 1 : Registers("energy_last_year")\unit = "kWh"
Registers("dc_voltage_1")\addr = 33049 : Registers("dc_voltage_1")\type = "uint16" : Registers("dc_voltage_1")\scale = 0.1 : Registers("dc_voltage_1")\unit = "V"
Registers("dc_current_1")\addr = 33050 : Registers("dc_current_1")\type = "uint16" : Registers("dc_current_1")\scale = 0.1 : Registers("dc_current_1")\unit = "A"
Registers("dc_voltage_2")\addr = 33051 : Registers("dc_voltage_2")\type = "uint16" : Registers("dc_voltage_2")\scale = 0.1 : Registers("dc_voltage_2")\unit = "V"
Registers("dc_current_2")\addr = 33052 : Registers("dc_current_2")\type = "uint16" : Registers("dc_current_2")\scale = 0.1 : Registers("dc_current_2")\unit = "A"
Registers("dc_voltage_3")\addr = 33053 : Registers("dc_voltage_3")\type = "uint16" : Registers("dc_voltage_3")\scale = 0.1 : Registers("dc_voltage_3")\unit = "V"
Registers("dc_current_3")\addr = 33054 : Registers("dc_current_3")\type = "uint16" : Registers("dc_current_3")\scale = 0.1 : Registers("dc_current_3")\unit = "A"
Registers("dc_voltage_4")\addr = 33055 : Registers("dc_voltage_4")\type = "uint16" : Registers("dc_voltage_4")\scale = 0.1 : Registers("dc_voltage_4")\unit = "V"
Registers("dc_current_4")\addr = 33056 : Registers("dc_current_4")\type = "uint16" : Registers("dc_current_4")\scale = 0.1 : Registers("dc_current_4")\unit = "A"
Registers("total_dc_power")\addr = 33057 : Registers("total_dc_power")\type = "uint32" : Registers("total_dc_power")\scale = 1 : Registers("total_dc_power")\unit = "W"
Registers("dc_bus_voltage")\addr = 33071 : Registers("dc_bus_voltage")\type = "uint16" : Registers("dc_bus_voltage")\scale = 0.1 : Registers("dc_bus_voltage")\unit = "V"
Registers("dc_bus_half_voltage")\addr = 33072 : Registers("dc_bus_half_voltage")\type = "uint16" : Registers("dc_bus_half_voltage")\scale = 0.1 : Registers("dc_bus_half_voltage")\unit = "V"
Registers("grid_voltage_a")\addr = 33073 : Registers("grid_voltage_a")\type = "uint16" : Registers("grid_voltage_a")\scale = 0.1 : Registers("grid_voltage_a")\unit = "V"
Registers("grid_voltage_b")\addr = 33074 : Registers("grid_voltage_b")\type = "uint16" : Registers("grid_voltage_b")\scale = 0.1 : Registers("grid_voltage_b")\unit = "V"
Registers("grid_voltage_c")\addr = 33075 : Registers("grid_voltage_c")\type = "uint16" : Registers("grid_voltage_c")\scale = 0.1 : Registers("grid_voltage_c")\unit = "V"
Registers("grid_current_a")\addr = 33076 : Registers("grid_current_a")\type = "uint16" : Registers("grid_current_a")\scale = 0.1 : Registers("grid_current_a")\unit = "A"
Registers("grid_current_b")\addr = 33077 : Registers("grid_current_b")\type = "uint16" : Registers("grid_current_b")\scale = 0.1 : Registers("grid_current_b")\unit = "A"
Registers("grid_current_c")\addr = 33078 : Registers("grid_current_c")\type = "uint16" : Registers("grid_current_c")\scale = 0.1 : Registers("grid_current_c")\unit = "A"
Registers("active_power")\addr = 33079 : Registers("active_power")\type = "int32" : Registers("active_power")\scale = 1 : Registers("active_power")\unit = "W"
Registers("reactive_power")\addr = 33081 : Registers("reactive_power")\type = "int32" : Registers("reactive_power")\scale = 1 : Registers("reactive_power")\unit = "Var"
Registers("apparent_power")\addr = 33083 : Registers("apparent_power")\type = "int32" : Registers("apparent_power")\scale = 1 : Registers("apparent_power")\unit = "VA"
Registers("working_mode")\addr = 33091 : Registers("working_mode")\type = "uint16" : Registers("working_mode")\scale = 1 : Registers("working_mode")\unit = "Code"
Registers("grid_standard")\addr = 33092 : Registers("grid_standard")\type = "uint16" : Registers("grid_standard")\scale = 1 : Registers("grid_standard")\unit = "Code"
Registers("inverter_temp")\addr = 33093 : Registers("inverter_temp")\type = "int16" : Registers("inverter_temp")\scale = 0.1 : Registers("inverter_temp")\unit = "°C"
Registers("grid_frequency")\addr = 33094 : Registers("grid_frequency")\type = "uint16" : Registers("grid_frequency")\scale = 0.01 : Registers("grid_frequency")\unit = "Hz"
Registers("current_status")\addr = 33095 : Registers("current_status")\type = "uint16" : Registers("current_status")\scale = 1 : Registers("current_status")\unit = "Code"
Registers("lead_acid_temp")\addr = 33096 : Registers("lead_acid_temp")\type = "int16" : Registers("lead_acid_temp")\scale = 0.1 : Registers("lead_acid_temp")\unit = "°C"
Registers("battery_voltage")\addr = 33133 : Registers("battery_voltage")\type = "uint16" : Registers("battery_voltage")\scale = 0.1 : Registers("battery_voltage")\unit = "V"
Registers("battery_current")\addr = 33134 : Registers("battery_current")\type = "int16" : Registers("battery_current")\scale = 0.1 : Registers("battery_current")\unit = "A"
Registers("battery_direction")\addr = 33135 : Registers("battery_direction")\type = "uint16" : Registers("battery_direction")\scale = 1 : Registers("battery_direction")\unit = "0=Charge, 1=Discharge"
Registers("llc_bus_voltage")\addr = 33136 : Registers("llc_bus_voltage")\type = "uint16" : Registers("llc_bus_voltage")\scale = 0.1 : Registers("llc_bus_voltage")\unit = "V"
Registers("backup_voltage_a")\addr = 33137 : Registers("backup_voltage_a")\type = "uint16" : Registers("backup_voltage_a")\scale = 0.1 : Registers("backup_voltage_a")\unit = "V"
Registers("backup_current_a")\addr = 33138 : Registers("backup_current_a")\type = "uint16" : Registers("backup_current_a")\scale = 0.1 : Registers("backup_current_a")\unit = "A"
Registers("battery_soc")\addr = 33139 : Registers("battery_soc")\type = "uint16" : Registers("battery_soc")\scale = 1 : Registers("battery_soc")\unit = "%"
Registers("battery_soh")\addr = 33140 : Registers("battery_soh")\type = "uint16" : Registers("battery_soh")\scale = 1 : Registers("battery_soh")\unit = "%"
Registers("bms_voltage")\addr = 33141 : Registers("bms_voltage")\type = "uint16" : Registers("bms_voltage")\scale = 0.01 : Registers("bms_voltage")\unit = "V"
Registers("bms_current")\addr = 33142 : Registers("bms_current")\type = "int16" : Registers("bms_current")\scale = 0.1 : Registers("bms_current")\unit = "A"
Registers("bms_charge_limit")\addr = 33143 : Registers("bms_charge_limit")\type = "uint16" : Registers("bms_charge_limit")\scale = 0.1 : Registers("bms_charge_limit")\unit = "A"
Registers("bms_discharge_limit")\addr = 33144 : Registers("bms_discharge_limit")\type = "uint16" : Registers("bms_discharge_limit")\scale = 0.1 : Registers("bms_discharge_limit")\unit = "A"
Registers("battery_power")\addr = 33149 : Registers("battery_power")\type = "int32" : Registers("battery_power")\scale = 1 : Registers("battery_power")\unit = "W"
Registers("inverter_ac_grid_power")\addr = 33151 : Registers("inverter_ac_grid_power")\type = "int32" : Registers("inverter_ac_grid_power")\scale = 1 : Registers("inverter_ac_grid_power")\unit = "W"
Registers("backup_voltage_b")\addr = 33153 : Registers("backup_voltage_b")\type = "uint16" : Registers("backup_voltage_b")\scale = 0.1 : Registers("backup_voltage_b")\unit = "V"
Registers("backup_current_b")\addr = 33154 : Registers("backup_current_b")\type = "uint16" : Registers("backup_current_b")\scale = 0.1 : Registers("backup_current_b")\unit = "A"
Registers("backup_voltage_c")\addr = 33155 : Registers("backup_voltage_c")\type = "uint16" : Registers("backup_voltage_c")\scale = 0.1 : Registers("backup_voltage_c")\unit = "V"
Registers("backup_current_c")\addr = 33156 : Registers("backup_current_c")\type = "uint16" : Registers("backup_current_c")\scale = 0.1 : Registers("backup_current_c")\unit = "A"
Registers("inverting_rectifying_power")\addr = 33157 : Registers("inverting_rectifying_power")\type = "int16" : Registers("inverting_rectifying_power")\scale = 10 : Registers("inverting_rectifying_power")\unit = "W"
Registers("battery_detected")\addr = 33159 : Registers("battery_detected")\type = "uint16" : Registers("battery_detected")\scale = 1 : Registers("battery_detected")\unit = "0=Not Detected, 1=Detected"
Registers("current_battery_model")\addr = 33160 : Registers("current_battery_model")\type = "uint16" : Registers("current_battery_model")\scale = 1 : Registers("current_battery_model")\unit = "Code"
Registers("battery_charge_total")\addr = 33161 : Registers("battery_charge_total")\type = "uint32" : Registers("battery_charge_total")\scale = 1 : Registers("battery_charge_total")\unit = "kWh"
Registers("battery_charge_today")\addr = 33163 : Registers("battery_charge_today")\type = "uint16" : Registers("battery_charge_today")\scale = 0.1 : Registers("battery_charge_today")\unit = "kWh"
Registers("battery_discharge_today")\addr = 33167 : Registers("battery_discharge_today")\type = "uint16" : Registers("battery_discharge_today")\scale = 0.1 : Registers("battery_discharge_today")\unit = "kWh"
Registers("battery_discharge_yesterday")\addr = 33168 : Registers("battery_discharge_yesterday")\type = "uint16" : Registers("battery_discharge_yesterday")\scale = 0.1 : Registers("battery_discharge_yesterday")\unit = "kWh"
Registers("grid_import_total")\addr = 33169 : Registers("grid_import_total")\type = "uint32" : Registers("grid_import_total")\scale = 1 : Registers("grid_import_total")\unit = "kWh"
Registers("grid_import_today")\addr = 33171 : Registers("grid_import_today")\type = "uint16" : Registers("grid_import_today")\scale = 0.1 : Registers("grid_import_today")\unit = "kWh"
Registers("grid_export_total")\addr = 33173 : Registers("grid_export_total")\type = "uint32" : Registers("grid_export_total")\scale = 1 : Registers("grid_export_total")\unit = "kWh"
Registers("grid_export_today")\addr = 33175 : Registers("grid_export_today")\type = "uint16" : Registers("grid_export_today")\scale = 0.1 : Registers("grid_export_today")\unit = "kWh"
Registers("load_total_energy")\addr = 33177 : Registers("load_total_energy")\type = "uint32" : Registers("load_total_energy")\scale = 1 : Registers("load_total_energy")\unit = "kWh"
Registers("load_today_energy")\addr = 33179 : Registers("load_today_energy")\type = "uint16" : Registers("load_today_energy")\scale = 0.1 : Registers("load_today_energy")\unit = "kWh"
Registers("house_load_power")\addr = 33147 : Registers("house_load_power")\type = "uint16" : Registers("house_load_power")\scale = 1 : Registers("house_load_power")\unit = "W"
Registers("backup_load_power")\addr = 33148 : Registers("backup_load_power")\type = "uint16" : Registers("backup_load_power")\scale = 1 : Registers("backup_load_power")\unit = "W"
Registers("meter_voltage")\addr = 33128 : Registers("meter_voltage")\type = "uint16" : Registers("meter_voltage")\scale = 0.1 : Registers("meter_voltage")\unit = "V"
Registers("meter_current")\addr = 33129 : Registers("meter_current")\type = "uint16" : Registers("meter_current")\scale = 0.1 : Registers("meter_current")\unit = "A"
Registers("meter_active_power")\addr = 33130 : Registers("meter_active_power")\type = "int32" : Registers("meter_active_power")\scale = 1 : Registers("meter_active_power")\unit = "W"
Registers("dc_power_1")\addr = 0 : Registers("dc_power_1")\type = "calculated" : Registers("dc_power_1")\scale = 1 : Registers("dc_power_1")\unit = "W"
Registers("dc_power_2")\addr = 0 : Registers("dc_power_2")\type = "calculated" : Registers("dc_power_2")\scale = 1 : Registers("dc_power_2")\unit = "W"
Registers("dc_power_3")\addr = 0 : Registers("dc_power_3")\type = "calculated" : Registers("dc_power_3")\scale = 1 : Registers("dc_power_3")\unit = "W"
Registers("dc_power_4")\addr = 0 : Registers("dc_power_4")\type = "calculated" : Registers("dc_power_4")\scale = 1 : Registers("dc_power_4")\unit = "W"
Registers("connection_status")\addr = 0 : Registers("connection_status")\type = "string" : Registers("connection_status")\scale = 1 : Registers("connection_status")\unit = ""
EndProcedure
Procedure DefineStatusCodes()
StatusCodes("0") = "Waiting"
StatusCodes("1") = "Open Operating"
StatusCodes("2") = "Soft Run"
StatusCodes("3") = "Generating"
StatusCodes("4") = "Bypass Inverter Running"
StatusCodes("5") = "Bypass Inverter Sync"
StatusCodes("6") = "Bypass Grid Running"
StatusCodes("15") = "Normal Running"
StatusCodes("4100") = "Grid Off"
StatusCodes("61456") = "Grid Surge"
StatusCodes("61457") = "Fan Fault"
StatusCodes("4112") = "Grid Overvoltage"
StatusCodes("4113") = "Grid Undervoltage"
StatusCodes("4114") = "Grid Overfrequency"
StatusCodes("4115") = "Grid Underfrequency"
StatusCodes("4116") = "Grid Reverse Current"
StatusCodes("4117") = "No-Grid"
StatusCodes("4118") = "Grid Unbalanced"
StatusCodes("4119") = "Grid Frequency Fluctuation"
StatusCodes("4120") = "Grid Overcurrent"
StatusCodes("4121") = "Grid Current Sampling Error"
StatusCodes("4128") = "DC Overvoltage"
StatusCodes("4129") = "DC Bus Overvoltage"
StatusCodes("4130") = "DC Bus Unbalanced"
StatusCodes("4131") = "DC Bus Undervoltage"
StatusCodes("4132") = "DC Bus Unbalanced 2"
StatusCodes("4133") = "DC(Channel A) Overcurrent"
StatusCodes("4134") = "DC(Channel B) Overcurrent"
StatusCodes("4135") = "DC Interference"
StatusCodes("4136") = "DC Reverse"
StatusCodes("4137") = "PV Midpoint Grounding"
StatusCodes("4144") = "Grid Interference Protection"
StatusCodes("4145") = "DSP Inital Protection"
StatusCodes("4146") = "Over Temperature Protection"
StatusCodes("4147") = "PV Insulation Fault"
StatusCodes("4148") = "Leakage Current Protection"
StatusCodes("4149") = "Relay Check Protection"
StatusCodes("4150") = "DSP_B Protection"
StatusCodes("4151") = "DC Injection Protection"
StatusCodes("4152") = "12V Undervoltage Faulty"
StatusCodes("4153") = "Leakage Current Check Protection"
StatusCodes("4154") = "Under Temperature Protection"
StatusCodes("4160") = "AFCI Check Fault"
StatusCodes("4161") = "AFCI Fault"
StatusCodes("4162") = "DSP Chip SRAM Fault"
StatusCodes("4163") = "DSP Chip FLASH Fault"
StatusCodes("4164") = "DSP Chip PC Pointer Fault"
StatusCodes("4165") = "DSP Chip Register Fault"
StatusCodes("4166") = "Grid Interference Protection 02"
StatusCodes("4167") = "Grid Current Sampling Error"
StatusCodes("4168") = "IGBT Overcurrent"
StatusCodes("4176") = "Grid Transient Overcurrent"
StatusCodes("4177") = "Battery Hardware Overvoltage fault"
StatusCodes("4178") = "LLC Hardware Overcurrent"
StatusCodes("4179") = "Battery Overvoltage"
StatusCodes("4180") = "Battery Undervoltage"
StatusCodes("4181") = "Battery Not Connected"
StatusCodes("4182") = "Backup Overvoltage"
StatusCodes("4183") = "Backup Overload"
StatusCodes("4184") = "DSP Selfcheck Error"
StatusCodes("8208") = "Fail Safe"
StatusCodes("8209") = "Meter COM Fail"
StatusCodes("8210") = "Battery COM Fail"
StatusCodes("8212") = "DSP COM Fail"
StatusCodes("8213") = "BMS Alarm"
StatusCodes("8214") = "BatName-FAIL"
StatusCodes("8215") = "BMS Alarm 2"
StatusCodes("8216") = "DRM Connect Fail"
StatusCodes("8217") = "Meter Select Fail"
StatusCodes("8224") = "Lead-acid Battery High Temp"
StatusCodes("8225") = "Lead-acid Battery Low Temp"
EndProcedure
Procedure DefineFaultCategories()
FaultCategories("grid_status")\startAddr = 12501
FaultCategories("grid_status")\endAddr = 12516
FaultCategories("grid_status")\faults("12501") = "no_grid"
FaultCategories("grid_status")\faults("12502") = "grid_overvoltage"
FaultCategories("grid_status")\faults("12503") = "grid_undervoltage"
FaultCategories("grid_status")\faults("12504") = "grid_overfrequency"
FaultCategories("grid_status")\faults("12505") = "grid_underfrequency"
FaultCategories("grid_status")\faults("12506") = "grid_phase_failure"
FaultCategories("grid_status")\faults("12507") = "grid_island"
FaultCategories("grid_status")\faults("12508") = "grid_voltage_unbalance"
FaultCategories("grid_status")\faults("12509") = "grid_overcurrent"
FaultCategories("grid_status")\faults("12510") = "grid_reverse_power"
FaultCategories("grid_status")\faults("12511") = "grid_phase_sequence_error"
FaultCategories("grid_status")\faults("12512") = "grid_voltage_spike"
FaultCategories("grid_status")\faults("12513") = "grid_current_spike"
FaultCategories("grid_status")\faults("12514") = "grid_voltage_harmonic_exceeded"
FaultCategories("grid_status")\faults("12515") = "grid_current_harmonic_exceeded"
FaultCategories("grid_status")\faults("12516") = "grid_power_factor_exceeded"
FaultCategories("load_status")\startAddr = 12517
FaultCategories("load_status")\endAddr = 12532
FaultCategories("load_status")\faults("12517") = "backup_overvoltage_fault"
FaultCategories("load_status")\faults("12518") = "backup_undervoltage_fault"
FaultCategories("load_status")\faults("12519") = "backup_overcurrent_fault"
FaultCategories("load_status")\faults("12520") = "backup_short_circuit"
FaultCategories("load_status")\faults("12521") = "backup_overload"
FaultCategories("load_status")\faults("12522") = "backup_phase_failure"
FaultCategories("load_status")\faults("12523") = "backup_voltage_unbalance"
FaultCategories("load_status")\faults("12524") = "backup_current_unbalance"
FaultCategories("load_status")\faults("12525") = "backup_power_factor_exceeded"
FaultCategories("load_status")\faults("12526") = "backup_frequency_out_of_range"
FaultCategories("load_status")\faults("12527") = "backup_voltage_spike"
FaultCategories("load_status")\faults("12528") = "backup_current_spike"
FaultCategories("load_status")\faults("12529") = "backup_voltage_harmonic_exceeded"
FaultCategories("load_status")\faults("12530") = "backup_current_harmonic_exceeded"
FaultCategories("load_status")\faults("12531") = "backup_temperature_exceeded"
FaultCategories("load_status")\faults("12532") = "backup_fan_failure"
FaultCategories("battery_status")\startAddr = 12533
FaultCategories("battery_status")\endAddr = 12548
FaultCategories("battery_status")\faults("12533") = "battery_not_connected"
FaultCategories("battery_status")\faults("12534") = "battery_overvoltage"
FaultCategories("battery_status")\faults("12535") = "battery_undervoltage"
FaultCategories("battery_status")\faults("12536") = "battery_overcurrent"
FaultCategories("battery_status")\faults("12537") = "battery_short_circuit"
FaultCategories("battery_status")\faults("12538") = "battery_overtemperature"
FaultCategories("battery_status")\faults("12539") = "battery_undertemperature"
FaultCategories("battery_status")\faults("12540") = "battery_cell_imbalance"
FaultCategories("battery_status")\faults("12541") = "battery_charge_limit_exceeded"
FaultCategories("battery_status")\faults("12542") = "battery_discharge_limit_exceeded"
FaultCategories("battery_status")\faults("12543") = "battery_communication_failure"
FaultCategories("battery_status")\faults("12544") = "battery_bms_failure"
FaultCategories("battery_status")\faults("12545") = "battery_voltage_spike"
FaultCategories("battery_status")\faults("12546") = "battery_current_spike"
FaultCategories("battery_status")\faults("12547") = "battery_soc_out_of_range"
FaultCategories("battery_status")\faults("12548") = "battery_soh_degraded"
FaultCategories("inverter_status")\startAddr = 12581
FaultCategories("inverter_status")\endAddr = 12587
FaultCategories("inverter_status")\faults("12581") = "normal"
FaultCategories("inverter_status")\faults("12582") = "inverter_overvoltage"
FaultCategories("inverter_status")\faults("12583") = "inverter_undervoltage"
FaultCategories("inverter_status")\faults("12584") = "inverter_overcurrent"
FaultCategories("inverter_status")\faults("12585") = "inverter_overtemperature"
FaultCategories("inverter_status")\faults("12586") = "inverter_short_circuit"
FaultCategories("inverter_status")\faults("12587") = "inverter_fan_failure"
EndProcedure
Procedure.u ModBus_CalcCRC(*Buffer, Length.i)
Protected CRC.u = $FFFF, i.i, j.i
If *Buffer And Length > 0
For i = 0 To Length - 1
CRC ! PeekA(*Buffer + i)
For j = 0 To 7
CRC = (CRC >> 1) ! ((CRC & 1) * $A001)
Next
Next
EndIf
ProcedureReturn CRC
EndProcedure
Procedure.i ModBus_CheckCRC(*Buffer, Length.i)
If Length < 2 Or Not *Buffer
ProcedureReturn #False
EndIf
Protected CalculatedCRC.u = ModBus_CalcCRC(*Buffer, Length - 2)
Protected ReceivedCRC.u = PeekU(*Buffer + Length - 2)
ProcedureReturn Bool(CalculatedCRC = ReceivedCRC)
EndProcedure
Procedure.u ModBus_BigEndian16(Value.u)
ProcedureReturn ((Value & $FF) << 8) | (Value >> 8)
EndProcedure
Procedure.i ReconnectSerialPort()
LockMutex(MutexID) ; Ensure thread safety
If Port
CloseSerialPort(Port)
Port = 0
Debug "Closed serial port due to CRC error"
EndIf
Delay(500)
Port = OpenSerialPort(#PB_Any, "COM3", 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_NoHandshake, 1024, 1024)
If Port
Debug "Reconnected serial port successfully on COM3"
UnlockMutex(MutexID)
ProcedureReturn 1
Else
ErrorMessage = "Failed to reconnect serial port COM3"
Debug ErrorMessage
UnlockMutex(MutexID)
ProcedureReturn 0
EndIf
EndProcedure
Procedure.i ModBus_RTU_ReadDiscreteInputs(SerialPort.i, SlaveAddress.i, StartInput.i, InputCount.i, Array DiscreteBits.i(1))
Protected ReturnCode.i = -1 ; 0: success, negative: error, positive: device error
Protected *RequestBuffer, ResponseTimeout.i, ResponseSize.i, DataByteCount.i, CurrentByte.i
Protected DebugOutput.s, ByteIndex.i, BitIndex.i
If SerialPort = 0
Debug "Serial port not open in ReadDiscreteInputs"
ProcedureReturn -3
EndIf
*RequestBuffer = AllocateMemory(128)
If Not *RequestBuffer
Debug "Memory allocation failed in ReadDiscreteInputs"
ProcedureReturn -1
EndIf
; Build Modbus RTU request
PokeA(*RequestBuffer, SlaveAddress)
PokeA(*RequestBuffer + 1, #ModBus_FunctionCode_ReadDiscreteInputs)
PokeU(*RequestBuffer + 2, ModBus_BigEndian16(StartInput))
PokeU(*RequestBuffer + 4, ModBus_BigEndian16(InputCount))
PokeU(*RequestBuffer + 6, ModBus_CalcCRC(*RequestBuffer, 6))
DebugOutput = "Sending (Discrete Inputs): " + RSet(Hex(PeekL(*RequestBuffer) & $FFFFFF), 6, "0") + " " +
RSet(Hex(PeekL(*RequestBuffer + 4) & $FFFF), 4, "0") + " " + Hex(PeekW(*RequestBuffer + 6), #PB_Word)
Debug DebugOutput
If Not WriteSerialPortData(SerialPort, *RequestBuffer, 8)
Debug "Failed to write to serial port in ReadDiscreteInputs"
FreeMemory(*RequestBuffer)
ProcedureReturn -3
EndIf
; Handle echo if enabled
If ModBus_RTU_Echo
Protected EchoTimeout.i = 100
While AvailableSerialPortInput(SerialPort) < 8 And EchoTimeout > 0
Delay(1) : EchoTimeout - 1
Wend
If EchoTimeout > 0
ReadSerialPortData(SerialPort, *RequestBuffer, 8)
EndIf
EndIf
; Read response
ResponseTimeout = 3000
ResponseSize = 0
While ResponseTimeout > 0
If AvailableSerialPortInput(SerialPort)
ReadSerialPortData(SerialPort, *RequestBuffer + ResponseSize, 1)
ResponseSize + 1
If ResponseSize >= 3 ; Check for function code and byte count
If PeekA(*RequestBuffer + 1) & $7F = #ModBus_FunctionCode_ReadDiscreteInputs
If PeekA(*RequestBuffer + 1) & $80 ; Error response
Debug "Error response from device: " + Str(PeekA(*RequestBuffer + 2))
FreeMemory(*RequestBuffer)
ProcedureReturn PeekA(*RequestBuffer + 2) ; Return error code
EndIf
DataByteCount = PeekA(*RequestBuffer + 2)
If ResponseSize = DataByteCount + 5 ; Complete response (address + function + byte count + data + CRC)
DebugOutput = "Received (Discrete Inputs): "
For ByteIndex = 0 To ResponseSize - 1
DebugOutput + Hex(PeekB(*RequestBuffer + ByteIndex) & $FF, #PB_Byte) + " "
Next
Debug DebugOutput
If ModBus_CheckCRC(*RequestBuffer, ResponseSize)
ReDim DiscreteBits(InputCount - 1)
For ByteIndex = 0 To DataByteCount - 1
CurrentByte = PeekA(*RequestBuffer + 3 + ByteIndex)
For BitIndex = 0 To 7
If ByteIndex * 8 + BitIndex < InputCount
DiscreteBits(ByteIndex * 8 + BitIndex) = (CurrentByte >> BitIndex) & 1
EndIf
Next
Next
Debug "Parsed discrete bits: " + Str(DiscreteBits(0)) + " " + Str(DiscreteBits(1)) + "..."
ReturnCode = 0
Else
Debug "CRC check failed in ReadDiscreteInputs"
ReturnCode = -2
EndIf
Break
EndIf
EndIf
EndIf
Else
Delay(1)
ResponseTimeout - 1
EndIf
Wend
If ResponseTimeout = 0
Debug "Response timeout in ReadDiscreteInputs"
ReturnCode = -10
EndIf
FreeMemory(*RequestBuffer)
ProcedureReturn ReturnCode
EndProcedure
Procedure.i ModBus_RTU_ReadInputRegisters(SerialPort.i, SlaveAddress.i, StartRegister.i, RegisterCount.i, Array RegisterValues.i(1))
Protected ReturnCode.i = -1 ; 0: success, negative: error, positive: device error
Protected *RequestBuffer, ResponseTimeout.i, ResponseSize.i, DataByteCount.i
Protected DebugOutput.s, RegisterIndex.i
If SerialPort = 0
Debug "Serial port not open in ReadInputRegisters"
ProcedureReturn -3
EndIf
*RequestBuffer = AllocateMemory(128)
If Not *RequestBuffer
Debug "Memory allocation failed in ReadInputRegisters"
ProcedureReturn -1
EndIf
; Build Modbus RTU request
PokeA(*RequestBuffer, SlaveAddress)
PokeA(*RequestBuffer + 1, #ModBus_FunctionCode_ReadInputRegisters)
PokeU(*RequestBuffer + 2, ModBus_BigEndian16(StartRegister))
PokeU(*RequestBuffer + 4, ModBus_BigEndian16(RegisterCount))
PokeU(*RequestBuffer + 6, ModBus_CalcCRC(*RequestBuffer, 6))
DebugOutput = "Sending (Input Registers): " + RSet(Hex(PeekL(*RequestBuffer) & $FFFFFF), 6, "0") + " " +
RSet(Hex(PeekL(*RequestBuffer + 4) & $FFFF), 4, "0") + " " + Hex(PeekW(*RequestBuffer + 6), #PB_Word)
Debug DebugOutput
If Not WriteSerialPortData(SerialPort, *RequestBuffer, 8)
Debug "Failed to write to serial port in ReadInputRegisters"
FreeMemory(*RequestBuffer)
ProcedureReturn -3
EndIf
; Handle echo if enabled
If ModBus_RTU_Echo
Protected EchoTimeout.i = 100
While AvailableSerialPortInput(SerialPort) < 8 And EchoTimeout > 0
Delay(1) : EchoTimeout - 1
Wend
If EchoTimeout > 0
ReadSerialPortData(SerialPort, *RequestBuffer, 8)
EndIf
EndIf
; Read response
ResponseTimeout = 3000
ResponseSize = 0
While ResponseTimeout > 0
If AvailableSerialPortInput(SerialPort)
ReadSerialPortData(SerialPort, *RequestBuffer + ResponseSize, 1)
ResponseSize + 1
If ResponseSize >= 3 ; Check for function code and byte count
If PeekA(*RequestBuffer + 1) & $7F = #ModBus_FunctionCode_ReadInputRegisters
If PeekA(*RequestBuffer + 1) & $80 ; Error response
Debug "Error response from device: " + Str(PeekA(*RequestBuffer + 2))
FreeMemory(*RequestBuffer)
ProcedureReturn PeekA(*RequestBuffer + 2) ; Return error code
EndIf
DataByteCount = PeekA(*RequestBuffer + 2)
If ResponseSize = DataByteCount + 5 ; Complete response (address + function + byte count + data + CRC)
DebugOutput = "Received (Input Registers): "
For RegisterIndex = 0 To ResponseSize - 1
DebugOutput + Hex(PeekB(*RequestBuffer + RegisterIndex) & $FF, #PB_Byte) + " "
Next
Debug DebugOutput
If ModBus_CheckCRC(*RequestBuffer, ResponseSize)
ReDim RegisterValues(RegisterCount - 1)
For RegisterIndex = 0 To RegisterCount - 1
RegisterValues(RegisterIndex) = ModBus_BigEndian16(PeekU(*RequestBuffer + 3 + RegisterIndex * 2))
Next
Debug "Parsed register values: " + Str(RegisterValues(0))
ReturnCode = 0
Else
Debug "CRC check failed in ReadInputRegisters"
ReturnCode = -2
EndIf
Break
EndIf
EndIf
EndIf
Else
Delay(1)
ResponseTimeout - 1
EndIf
Wend
If ResponseTimeout = 0
Debug "Response timeout in ReadInputRegisters"
ReturnCode = -10
EndIf
FreeMemory(*RequestBuffer)
ProcedureReturn ReturnCode
EndProcedure
Procedure.s ReadRegister(SerialPort.i, SlaveAddress.i, *Register.RegisterInfo)
Protected ReadResult.i, ScaledValue.d, RawValue.q, Dim RegisterData.i(1)
Protected MaxRetries.i = 1, RetryAttempt.i, RegisterCount.i
Debug "Reading register " + Str(*Register\addr) + " (" + *Register\type + ")"
; Determine register count based on type
Select *Register\type
Case "uint16", "int16"
RegisterCount = 1
Case "uint32", "int32"
RegisterCount = 2
Case "calculated", "string"
ProcedureReturn "unknown"
Default
Debug "Unsupported register type: " + *Register\type
ProcedureReturn "unknown"
EndSelect
; Retry loop for Modbus read
For RetryAttempt = 0 To MaxRetries
ReadResult = ModBus_RTU_ReadInputRegisters(SerialPort, SlaveAddress, *Register\addr, RegisterCount, RegisterData())
If ReadResult = -2 ; CRC error
If RetryAttempt < MaxRetries
Debug "CRC error on register " + Str(*Register\addr) + ", retrying (" + Str(RetryAttempt + 1) + "/" + Str(MaxRetries) + ")"
Delay(100)
Continue
Else
Debug "Persistent CRC error on register " + Str(*Register\addr) + ", attempting reconnect"
If ReconnectSerialPort()
ReadResult = ModBus_RTU_ReadInputRegisters(SerialPort, SlaveAddress, *Register\addr, RegisterCount, RegisterData())
EndIf
EndIf
EndIf
Break
Next
; Process successful read
If ReadResult = 0
Select *Register\type
Case "uint16"
ScaledValue = RegisterData(0) * *Register\scale
If MapKey(Registers()) = "current_status" And FindMapElement(StatusCodes(), Str(RegisterData(0)))
ProcedureReturn StatusCodes(Str(RegisterData(0)))
EndIf
ProcedureReturn StrF(ScaledValue, 2)
Case "int16"
RawValue = RegisterData(0)
If RawValue >= 32768
RawValue - 65536 ; Convert to signed
EndIf
ScaledValue = RawValue * *Register\scale
ProcedureReturn StrF(ScaledValue, 2)
Case "uint32"
RawValue = (RegisterData(0) << 16) + RegisterData(1)
ScaledValue = RawValue * *Register\scale
ProcedureReturn StrF(ScaledValue, 2)
Case "int32"
RawValue = (RegisterData(0) << 16) + RegisterData(1)
If RawValue & $80000000
RawValue - $100000000 ; Convert to signed
EndIf
ScaledValue = RawValue * *Register\scale
ProcedureReturn StrF(ScaledValue, 2)
EndSelect
EndIf
Debug "Failed to read register " + Str(*Register\addr) + ": Result = " + Str(ReadResult)
ProcedureReturn "unknown"
EndProcedure
Procedure.s ReadFaultStatus(SerialPort.i, SlaveAddress.i, FaultCategory.s)
Protected FaultCount.i = FaultCategories(FaultCategory)\endAddr - FaultCategories(FaultCategory)\startAddr + 1
Protected Dim FaultBits.i(15) ; Assuming max 16 bits, adjust if needed
Protected ReadResult.i, FaultIndex.i, FaultStatus.s = "normal"
Protected MaxRetries.i = 1, RetryAttempt.i
; Validate inputs
If SerialPort = 0
ProcedureReturn "Error: Serial port not open"
EndIf
If Not FindMapElement(FaultCategories(), FaultCategory)
ProcedureReturn "Error: Invalid fault category '" + FaultCategory + "'"
EndIf
; Retry loop for reading discrete inputs
For RetryAttempt = 0 To MaxRetries
ReadResult = ModBus_RTU_ReadDiscreteInputs(SerialPort, SlaveAddress, FaultCategories(FaultCategory)\startAddr, FaultCount, FaultBits())
If ReadResult = -2 ; CRC error
If RetryAttempt < MaxRetries
Debug "CRC error reading fault category '" + FaultCategory + "', retrying (" + Str(RetryAttempt + 1) + "/" + Str(MaxRetries) + ")"
Delay(100)
Continue
Else
Debug "Persistent CRC error on fault category '" + FaultCategory + "', attempting reconnect"
If ReconnectSerialPort()
ReadResult = ModBus_RTU_ReadDiscreteInputs(SerialPort, SlaveAddress, FaultCategories(FaultCategory)\startAddr, FaultCount, FaultBits())
EndIf
EndIf
EndIf
Break
Next
; Process fault status
If ReadResult = 0
For FaultIndex = 0 To FaultCount - 1
Protected RegisterAddress.s = Str(FaultCategories(FaultCategory)\startAddr + FaultIndex)
If FaultBits(FaultIndex) = 1 And FindMapElement(FaultCategories(FaultCategory)\faults(), RegisterAddress)
FaultStatus = FaultCategories(FaultCategory)\faults(RegisterAddress)
Break
EndIf
Next
Else
FaultStatus = "Error reading faults (" + Str(ReadResult) + ")"
EndIf
ProcedureReturn FaultStatus
EndProcedure
Procedure PollDataThread(*Unused)
Protected FaultCategoryKey.s, PVIndex.i, VoltageKey.s, CurrentKey.s, PowerKey.s
Protected VoltageValue.f, CurrentValue.f
While Quit = 0
LockMutex(MutexID)
If Port
; Update register data
ForEach Registers()
Protected RegisterKey.s = MapKey(Registers())
InverterData(RegisterKey)\unit = Registers()\unit
Select Registers()\type
Case "calculated"
Case "string"
InverterData(RegisterKey)\value = "connected"
Default
InverterData(RegisterKey)\value = ReadRegister(Port, Address, @Registers())
EndSelect
Next
; Calculate DC power for PV1 to PV4
For PVIndex = 1 To 4
VoltageKey = "dc_voltage_" + Str(PVIndex)
CurrentKey = "dc_current_" + Str(PVIndex)
PowerKey = "dc_power_" + Str(PVIndex)
If InverterData(VoltageKey)\value <> "unknown" And InverterData(CurrentKey)\value <> "unknown"
VoltageValue = ValF(InverterData(VoltageKey)\value)
CurrentValue = ValF(InverterData(CurrentKey)\value)
InverterData(PowerKey)\value = StrF(VoltageValue * CurrentValue, 2)
Else
InverterData(PowerKey)\value = "unknown"
EndIf
Next
; Update fault statuses
ForEach FaultCategories()
FaultCategoryKey = MapKey(FaultCategories())
InverterData("fault_" + FaultCategoryKey)\value = ReadFaultStatus(Port, Address, FaultCategoryKey)
Next
Else
ErrorMessage = "Serial port not open, attempting reconnect"
Debug ErrorMessage
If ReconnectSerialPort()
Debug "Reconnect successful"
Else
Debug "Reconnect failed"
EndIf
EndIf
UnlockMutex(MutexID)
Delay(1000) ; Poll every second
Wend
EndProcedure
Procedure.s CalculateTimeRemaining()
Protected soc.f, power.f, direction.s, capacity.f = 15360
Protected timeLabel.s, timeRemaining.s, hours.i, minutes.i
LockMutex(MutexID)
direction = InverterData("battery_direction")\value
If direction = "0.00"
direction = "Charging"
timeLabel = "Time to 100%"
ElseIf direction = "1.00"
direction = "Discharging"
timeLabel = "Time to 20%"
Else
direction = "unknown"
timeLabel = "Time Remaining"
UnlockMutex(MutexID)
ProcedureReturn timeLabel + Space(14-Len(timeLabel)) + "N/A"
EndIf
Protected energyRemaining.f, hoursRemaining.f, totalMinutes.i,energyToFull.f
soc = ValF(StringField(InverterData("battery_soc")\value, 1, " "))
power = ValF(StringField(InverterData("battery_power")\value, 1, " "))
If InverterData("battery_soc")\value <> "unknown" And InverterData("battery_power")\value <> "unknown" And power <> 0
If direction = "Discharging" And soc > 20
energyRemaining.f = capacity * (soc - 20) / 100
hoursRemaining.f = energyRemaining / power
totalMinutes.i = Int(hoursRemaining * 60)
hours = totalMinutes / 60
minutes = totalMinutes % 60
timeRemaining = Str(hours) + "h " + Str(minutes) + "m"
ElseIf direction = "Charging" And soc < 100
energyToFull.f = capacity * (100 - soc) / 100
hoursRemaining.f = energyToFull / power
totalMinutes.i = Int(hoursRemaining * 60)
hours = totalMinutes / 60
minutes = totalMinutes % 60
timeRemaining = Str(hours) + "h " + Str(minutes) + "m"
ElseIf direction = "Charging" And soc >= 100
timeRemaining = "Full"
ElseIf direction = "Discharging" And soc <= 20
timeRemaining = "Below 20%"
Else
timeRemaining = "N/A"
EndIf
Else
timeRemaining = "N/A"
EndIf
UnlockMutex(MutexID)
ProcedureReturn timeLabel + Space(14-Len(timeLabel)) + timeRemaining
EndProcedure
Procedure DisplayDashboard()
ConsoleColor(7, 0)
ClearConsole()
PrintN(StringField("", 80, "="))
PrintN("Solis Inverter Dashboard - " + FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss", Date()))
If ErrorMessage <> ""
PrintN("Error: " + ErrorMessage)
EndIf
PrintN(StringField("", 80, "="))
Protected ColumnWidth.i = 40
Protected LabelWidth.i = 14
Protected ValueWidth.i = ColumnWidth - LabelWidth - 1
Protected Dim LeftColumn.s(19)
Protected Dim RightColumn.s(19)
LockMutex(MutexID)
; Left column: Inverter and Battery
LeftColumn(0) = "INVERTER"
LeftColumn(1) = LSet("Status ", LabelWidth, " ") + LSet(InverterData("current_status")\value, ValueWidth, " ")
LeftColumn(2) = LSet("Temperature ", LabelWidth, " ") + LSet(InverterData("inverter_temp")\value + "°C", ValueWidth, " ")
LeftColumn(3) = LSet("Active Power ", LabelWidth, " ") + LSet(InverterData("active_power")\value + "W", ValueWidth, " ")
LeftColumn(4) = LSet("DC Power ", LabelWidth, " ") + LSet(InverterData("total_dc_power")\value + "W", ValueWidth, " ")
LeftColumn(5) = LSet("PV1 ", LabelWidth, " ") + LSet(InverterData("dc_voltage_1")\value + "V, " + InverterData("dc_power_1")\value + "W", ValueWidth, " ")
LeftColumn(6) = LSet("PV2 ", LabelWidth, " ") + LSet(InverterData("dc_voltage_2")\value + "V, " + InverterData("dc_power_2")\value + "W", ValueWidth, " ")
LeftColumn(7) = ""
LeftColumn(8) = "BATTERY"
LeftColumn(9) = LSet("Voltage ", LabelWidth, " ") + LSet(InverterData("battery_voltage")\value + "V", ValueWidth, " ")
LeftColumn(10) = LSet("Current ", LabelWidth, " ") + LSet(InverterData("battery_current")\value + "A", ValueWidth, " ")
LeftColumn(11) = LSet("Power ", LabelWidth, " ") + LSet(InverterData("battery_power")\value + "W", ValueWidth, " ")
LeftColumn(12) = LSet("SOC ", LabelWidth, " ") + LSet(InverterData("battery_soc")\value + "%", ValueWidth, " ")
LeftColumn(13) = LSet("SOH ", LabelWidth, " ") + LSet(InverterData("battery_soh")\value + "%", ValueWidth, " ")
Protected BatteryDirection.s = InverterData("battery_direction")\value
Select BatteryDirection
Case "0.00" : LeftColumn(14) = LSet("Direction ", LabelWidth, " ") + LSet("Charging", ValueWidth, " ")
Case "1.00" : LeftColumn(14) = LSet("Direction ", LabelWidth, " ") + LSet("Discharging", ValueWidth, " ")
Default : LeftColumn(14) = LSet("Direction ", LabelWidth, " ") + LSet("unknown", ValueWidth, " ")
EndSelect
LeftColumn(15) = LSet("Time Remaining ", LabelWidth, " ") + LSet(CalculateTimeRemaining(), ValueWidth, " ")
LeftColumn(16) = ""
LeftColumn(17) = "CONNECTIONS"
LeftColumn(18) = LSet("Tuya ", LabelWidth, " ") + LSet("OFF (Removed)", ValueWidth, " ") ; Removed
LeftColumn(19) = LSet("MQTT ", LabelWidth, " ") + LSet("Off (Removed)", ValueWidth, " ") ; Removed
; Right column: Grid and Totals
RightColumn(0) = "GRID"
RightColumn(1) = LSet("V(A/B/C) ", LabelWidth, " ") + LSet(InverterData("grid_voltage_a")\value + "/" +
InverterData("grid_voltage_b")\value + "/" +
InverterData("grid_voltage_c")\value + "V", ValueWidth, " ")
RightColumn(2) = LSet("I(A/B/C) ", LabelWidth, " ") + LSet(InverterData("grid_current_a")\value + "/" +
InverterData("grid_current_b")\value + "/" +
InverterData("grid_current_c")\value + "A", ValueWidth, " ")
RightColumn(3) = LSet("Frequency ", LabelWidth, " ") + LSet(InverterData("grid_frequency")\value + "Hz", ValueWidth, " ")
RightColumn(4) = LSet("Power ", LabelWidth, " ") + LSet(InverterData("inverter_ac_grid_power")\value + "W", ValueWidth, " ")
RightColumn(5) = ""
RightColumn(6) = "DAILY TOTALS"
RightColumn(7) = LSet("Generation ", LabelWidth, " ") + LSet(InverterData("energy_today")\value + "kWh", ValueWidth, " ")
RightColumn(8) = LSet("Grid Import ", LabelWidth, " ") + LSet(InverterData("grid_import_today")\value + "kWh", ValueWidth, " ")
RightColumn(9) = LSet("Grid Export ", LabelWidth, " ") + LSet(InverterData("grid_export_today")\value + "kWh", ValueWidth, " ")
RightColumn(10) = LSet("Batt Charged ", LabelWidth, " ") + LSet(InverterData("battery_charge_today")\value + "kWh", ValueWidth, " ")
RightColumn(11) = LSet("Batt Discharged ", LabelWidth, " ") + LSet(InverterData("battery_discharge_today")\value + "kWh", ValueWidth, " ")
RightColumn(12) = LSet("Load ", LabelWidth, " ") + LSet(InverterData("load_today_energy")\value + "kWh", ValueWidth, " ")
RightColumn(13) = ""
RightColumn(14) = "FAULTS"
RightColumn(15) = LSet("Grid ", LabelWidth, " ") + LSet(InverterData("fault_grid_status")\value, ValueWidth, " ")
RightColumn(16) = LSet("Load ", LabelWidth, " ") + LSet(InverterData("fault_load_status")\value, ValueWidth, " ")
RightColumn(17) = LSet("Battery ", LabelWidth, " ") + LSet(InverterData("fault_battery_status")\value, ValueWidth, " ")
RightColumn(18) = LSet("Inverter ", LabelWidth, " ") + LSet(InverterData("fault_inverter_status")\value, ValueWidth, " ")
UnlockMutex(MutexID)
; Display columns
Protected TotalLines.i = 20, LineIndex.i
For LineIndex = 0 To TotalLines - 1
Protected LeftText.s = LeftColumn(LineIndex)
Protected RightText.s = RightColumn(LineIndex)
If Len(LeftText) < ColumnWidth
LeftText + Space(ColumnWidth - Len(LeftText))
Else
LeftText = Left(LeftText, ColumnWidth) ; Truncate if too long
EndIf
If Len(RightText) < ColumnWidth
RightText + Space(ColumnWidth - Len(RightText))
Else
RightText = Left(RightText, ColumnWidth) ; Truncate if too long
EndIf
PrintN(LeftText + RightText)
Next
PrintN(StringField("", 80, "="))
PrintN("Press Ctrl+C to exit")
PrintN(StringField("", 80, "="))
ConsoleCursor(0)
EndProcedure
Procedure Main()
DefineRegisters()
DefineStatusCodes()
DefineFaultCategories()
Port = OpenSerialPort(#PB_Any, "COM3", 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_NoHandshake, 1024, 1024)
If Port
Debug "Serial port opened successfully on COM3"
Else
ErrorMessage = "Failed to open serial port COM3"
Debug ErrorMessage
EndIf
MutexID = CreateMutex()
If MutexID
ThreadID = CreateThread(@PollDataThread(), 0)
If ThreadID
Debug "Polling thread started"
Else
ErrorMessage = "Failed to start polling thread"
Debug ErrorMessage
EndIf
Else
ErrorMessage = "Failed to create mutex"
Debug ErrorMessage
EndIf
If OpenConsole() = 0
ErrorMessage = "Failed to open console"
Debug ErrorMessage
End
EndIf
Repeat
DisplayDashboard()
Delay(2000)
Until Inkey() = Chr(3) Or Quit = 1 ; Allow thread to signal quit
Quit = 1
If ThreadID
WaitThread(ThreadID)
EndIf
If MutexID
FreeMutex(MutexID)
EndIf
If Port
CloseSerialPort(Port)
EndIf
CloseConsole()
EndProcedure
Main()
Here's the output in Console.
You can add MQTT support to use on Home Assistant for custom dashboard.
