…and a general look at solar inverter challenges/cool things.
Update 21 November 2020:
I’ve had a few people asking after the application and source code used to bridge this inverter into an MQTT broker. Unfortunately this inverter failed not long after this post was written (never turn on “Auto Bypass/Option 9″…) and as such I’ve moved on haha 😬.
The utility I created, which is a C#/Mono executable targeting the arm32/Raspberry Pi CPU can be found here. The source code is available on Github here. You may need to install mono-complete on your machine to run the binary executable: sudo apt install mono-complete.
The operation of the binary executable is detailed below.
Main Story:
Back in 2012, Eskom (the only public electricity supply utility in South Africa) announced that they would be increasing their electricity tariffs by roughly 16% and wanted to increase tariffs year on year by 25.9% (subject to various approvals). This was a ridiculous number, of course, but the point was made: electricity would become a lot more expensive and would continue to increase in cost at a rate greater than inflation. I decided that having a few solar panels to offset some of my demand would be a good idea and set about getting quotes.
The first quote I received was off-the-wall expensive and even in a worst-case-eskom-rates-scenario the installation would never have paid for itself. So I sourced the panels and grid-tie inverters myself and did a self-install. That was 8 years ago.
Fast-forward to 2020 and load shedding is back… but inverter technology has improved a lot and so it seemed like a good idea to upgrade. Some research, stock enquires and a trip to Brights Hardware Store later:
The inverter market is flooded with a variety of different brands. You’ve got Mecer, KODAK(!?), Synerji, THESUNPAYS, MaxPower, etc. There are loads of them, but they all roughly look the same: metal box with small LCD panel.
It seemed like most inverters were actually made by a company called Voltronic/Axpert underneath. The Axpert protocol is somewhat known, with various repositories demonstrating communication attempts available on Github and the like. “Great” I thought, “this Synapse unit must be an Axpert unit underneath, I’ll buy it and use some open source code to read it”.
What’s the point of having solar panels if you aren’t going to be energy efficient? Enter the Raspberry Pi Zero: small, cheap and low power. With the Raspberry Pi Zero connected I set about trying out the various repositories out there that target Voltronic inverters… but no go. I could see the data going through the wires; I tried RS485 and USB (the USB connector is just an RS485 converter), but nothing. Damn. After a lot of searching I found this sub-standard application for sale for $32:
Fortunately ICC’s software is “not compatible” with my inverter. I’m lazy, I would have totally gone for the $32 option. I’m going to have to try the Windows dodgy Chinese software this thing came with…
The software on the supplied disk contained a bog-standard installer called “PowerMonitor”. It installed the “PowerMonitor.exe” application, which is a .Net application and as it turns out .Net apps decompile really really well. The decompiled code is super readable (although the code itself is poorly written {the code routinely uses strings as integral parts of it’s number conversion functions}).
Although generally poorly coded, the Chinese-Panda-Inverter app code had reasonably good separation of communication, conversion and UI modules, so grabbing and stub-ing the comms code was reasonably easy. So easy, in fact, that I abandoned my Go-app attempt in favour of just compiling with Mono (even though I rarely write/work with C#). Also, a Mono app on a Raspberry Pi 1st gen CPU?! Crazy.
As it turns out, this is NOT an Axpert King inverter, which is why none of the Axpert tools worked.
This is the output from my app:
{"_accumulatedBuyPower":"169.9","_accumulatedChargerPower":"19.8","_accumulatedDischargerPower":"12.4","_accumulatedGridChargerPower":"19.8","_accumulatedLoadPower":"159.1","_accumulatedPower":"3.4","_accumulatedPvSellPower":"0","_accumulatedSelfUsePower":"5.9","_accumulatedSellPower":"0","_accumulatedTime":"00:09:02","_acRadiatorTemperature":"47","_acVoltageGrade":"230","_battCurrent":"0","_batteryRelay":"Connect","_batteryVoltage":"54.8","_battPower":"-54","_battVolGrade":"48","_busVoltage":"434.8","_chargerCurrent":"9.4","_chargerPower":"519","_chargerWorkstate":"Work Mode","_chargingState":" Float charge","_combineType":"0000","_controlCurrent":"1.6","_dcRadiatorTemperature":"38","_dcRelayState":"Connect","_chargerId":4,"_earthRelayState":"Disconnect","_errorMessage":"","_externalTemperature":"-53","_gridCurrent":"5.1","_gridFrequency":"49.82","_gridRelayState":"Connect","_gridVoltage":"242.2","_hardwareVersion":"1.02.02","_inverterBatteryVoltage":"54.5","_inverterCurrent":"1.6","_inverterErrorMessage":"","_inverterFrequency":"49.82","_inverterHardwareVersion":"1.01.01","_inverterMachineType":"PH1800","_inverterMaxNumber":"0000","_inverterNumber":"0000","_inverterRelayState":"Connect","_inverterSerialNumber":"FFFFFFFF","_inverterSoftwareVersion":"2.15.19","_inverterVoltage":"239.3","_inverterWarningMessage":"","_loadCurrent":"6.4","_loadPercent":"33","_loadRelayState":"Connect","_machineType":"0708","_mpptState":"Current limiting","_nLineRelayState":"Disconnect","_pGrid":"-984","_pInverter":"391","_pLoad":"1342","_pvRelay":"Connect","_pvVoltage":"106.8","_qgrid":"786","_qinverter":"98","_qload":"721","_radiatorTemperature":"40","_ratedCurrent":"80","_ratedPower":"5000","_serialNumber":"FFFFFFFF","_sGrid":"1259","_sInverter":"400","_sload":"1524","_softwareVersion":"1.16.23","_transformerTemperature":"50","_warningMessage":"","_workState":"Grid-Tie","_inverterId":4,"Id":0,"ChargerId":4,"InverterId":4,"RecordTime":"0001-01-01T00:00:00","MachineType":"0708","SerialNumber":"FFFFFFFF","HardwareVersion":"1.02.02","SoftwareVersion":"1.16.23","PvVoltageC":"4000","BatteryVoltageC":"4000","ChargerCurrentC":"4000","ChargerWorkEnable":"1","AbsorbVoltage":"50","FloatVoltage":"54.8","AbsorptionVoltage":"58.4","BatteryLowVoltage":"34","BatteryHighVoltage":"60","MaxChargerCurrent":"80","AbsorbChargerCurrent":"10","BatteryType":"1","BatteryAh":"200","RemoveTheAccumulatedData":"0","ChargerWorkstate":"Work Mode","MpptState":"Current limiting","ChargingState":" Float charge","PvVoltage":"106.8","BatteryVoltage":"54.8","ChargerCurrent":"9.4","ChargerPower":"519","RadiatorTemperature":"40","ExternalTemperature":"-53","BatteryRelay":"Connect","PvRelay":"Connect","ErrorMessage":"","WarningMessage":"","BattVolGrade":"48","RatedCurrent":"80","AccumulatedPower":"3.4","AccumulatedTime":"00:09:02","InverterMachineType":"PH1800","InverterSerialNumber":"FFFFFFFF","InverterHardwareVersion":"1.01.01","InverterSoftwareVersion":"2.15.19","InverterBatteryVoltageC":"3FEB","InverterVoltageC":"3F95","GridVoltageC":"FFFF","BusVoltageC":"FFFF","ControlCurrentC":"FFFF","InverterCurrentC":"FFFF","GridCurrentC":"FFFF","LoadCurrentC":"FFFF","InverterOffgridWorkEnable":"1","InverterOutputVoltageSet":"230","InverterOutputFrequencySet":"5000","InverterSearchModeEnable":"0","InverterDischargerToGridEnable":"0","EnergyUseMode":"2","GridProtectStandard":"2","SolarUseAim":"1","InverterMaxDischargerCurrent":"13","NormalVoltagePoint":"46","StartSellVoltagePoint":"54.5","GridMaxChargerCurrentSet":"15","InverterBatteryLowVoltage":"45","InverterBatteryHighVoltage":"60","MaxCombineChargerCurrent":"15","SystemSetting":"0100000000000000","ChargerSourcePriority":"0","WorkState":"Grid-Tie","AcVoltageGrade":"230","RatedPower":"5000","InverterBatteryVoltage":"54.5","InverterVoltage":"239.3","GridVoltage":"242.2","BusVoltage":"434.8","ControlCurrent":"1.6","InverterCurrent":"1.6","GridCurrent":"5.1","LoadCurrent":"6.4","PInverter":"391","PGrid":"-984","PLoad":"1342","LoadPercent":"33","SInverter":"400","SGrid":"1259","Sload":"1524","Qinverter":"98","Qgrid":"786","Qload":"721","InverterFrequency":"49.82","GridFrequency":"49.82","InverterMaxNumber":"0000","CombineType":"0000","InverterNumber":"0000","AcRadiatorTemperature":"47","TransformerTemperature":"50","DcRadiatorTemperature":"38","InverterRelayState":"Connect","GridRelayState":"Connect","LoadRelayState":"Connect","N_LineRelayState":"Disconnect","DCRelayState":"Connect","EarthRelayState":"Disconnect","AccumulatedChargerPower":"19.8","AccumulatedDischargerPower":"12.4","AccumulatedBuyPower":"169.9","AccumulatedSellPower":"0","AccumulatedLoadPower":"159.1","AccumulatedSelf_usePower":"5.9","AccumulatedPV_sellPower":"0","AccumulatedGrid_chargerPower":"19.8","InverterErrorMessage":"","InverterWarningMessage":"","BattPower":"-54","BattCurrent":"0"}
My stubby app queries the inverter, serializes the output to JSON and then dumps the result to stdout. This can be piped into mosquitto_pub to publish the data to a broker.
As above, this inverter is not an Axpert King/Voltronic inverter. It is actually made by a company called Must Power/MustPower, who, like Axpert, rebrand their equipment for the nearest vendor. This unfortunately causes a great deal of fragmentation in the market in terms of community support. This particular machine identifies itself as a Must Power PH1800 inverter rated at 5000VA. Critically, there are a bucket-load of metrics here that aren’t exposed in the desktop software or on the device’s LCD display.
I’ve got the data going into my MQTT broker, now I need to log it (read “integrate it into Home Assistant”).
The dip is night time. The steps are being caused by the MPPT function on the inverter (the open-circuit voltage would be around 135V). 106.7V is 35.5V per set of panels, which is the manufacturer-specced voltage for the “best power” voltage. This voltage was selected by the inverter’s MPPT function… so it’s pretty impressive that it’s done such a good job of figuring it out.
The next step is to integrate the write-functionality into my little app, so that Home Assistant (particularly NodeRED) can be used to manage the inverter.
{"_accumulatedBuyPower":"2651.9","_accumulatedChargerPower":"904.3","_accumulatedDischargerPower":"1884.1","_accumulatedGridChargerPower":"904.3","_accumulatedLoadPower":"3654.1","_accumulatedPower":"1856.3","_accumulatedPvSellPower":"4","_accumulatedSelfUsePower":"1002.1","_accumulatedSellPower":"4","_accumulatedTime":"206:18:58","_acRadiatorTemperature":"27","_acVoltageGrade":"230","_battCurrent":"-2","_batteryRelay":"Connect","_batteryVoltage":"52.2","_battPower":"-146","_battVolGrade":"48","_busVoltage":"415.5","_chargerCurrent":"9.8","_chargerPower":"511","_chargerWorkstate":"Work Mode","_chargingState":" Float charge","_combineType":"0000","_controlCurrent":"1.5","_dcRadiatorTemperature":"24","_dcRelayState":"Connect","_chargerId":4,"_earthRelayState":"Disconnect","_errorMessage":"","_externalTemperature":"-53","_gridCurrent":"1.1","_gridFrequency":"49.97","_gridRelayState":"Connect","_gridVoltage":"218.2","_hardwareVersion":"1.02.02","_inverterBatteryVoltage":"52.1","_inverterCurrent":"1.5","_inverterErrorMessage":"","_inverterFrequency":"49.97","_inverterHardwareVersion":"1.01.01","_inverterMachineType":"PH1800","_inverterMaxNumber":"0000","_inverterNumber":"0000","_inverterRelayState":"Connect","_inverterSerialNumber":"FFFFFFFF","_inverterSoftwareVersion":"2.55.19","_inverterVoltage":"217","_inverterWarningMessage":"","_loadCurrent":"1.5","_loadPercent":"6","_loadRelayState":"Connect","_machineType":"0708","_mpptState":"Current limiting","_nLineRelayState":"Disconnect","_pGrid":"0","_pInverter":"306","_pLoad":"288","_pvRelay":"Connect","_pvVoltage":"107.8","_qgrid":"240","_qinverter":"105","_qload":"171","_radiatorTemperature":"28","_ratedCurrent":"80","_ratedPower":"5000","_serialNumber":"FFFFFFFF","_sGrid":"242","_sInverter":"319","_sload":"335","_softwareVersion":"1.16.26","_transformerTemperature":"47","_warningMessage":"","_workState":"Grid-Tie","_inverterId":4,"Id":0,"ChargerId":4,"InverterId":4,"RecordTime":"0001-01-01T00:00:00","MachineType":"0708","SerialNumber":"FFFFFFFF","HardwareVersion":"1.02.02","SoftwareVersion":"1.16.26","PvVoltageC":"4000","BatteryVoltageC":"4000","ChargerCurrentC":"4000","ChargerWorkEnable":"1","AbsorbVoltage":"50","FloatVoltage":"52","AbsorptionVoltage":"52.5","BatteryLowVoltage":"34","BatteryHighVoltage":"60","MaxChargerCurrent":"80","AbsorbChargerCurrent":"10","BatteryType":"2","BatteryAh":"200","RemoveTheAccumulatedData":"0","ChargerWorkstate":"Work Mode","MpptState":"Current limiting","ChargingState":" Float charge","PvVoltage":"107.8","BatteryVoltage":"52.2","ChargerCurrent":"9.8","ChargerPower":"511","RadiatorTemperature":"28","ExternalTemperature":"-53","BatteryRelay":"Connect","PvRelay":"Connect","ErrorMessage":"","WarningMessage":"","BattVolGrade":"48","RatedCurrent":"80","AccumulatedPower":"1856.3","AccumulatedTime":"206:18:58","InverterMachineType":"PH1800","InverterSerialNumber":"FFFFFFFF","InverterHardwareVersion":"1.01.01","InverterSoftwareVersion":"2.55.19","InverterBatteryVoltageC":"4040","InverterVoltageC":"FFFF","GridVoltageC":"FFFF","BusVoltageC":"FFFF","ControlCurrentC":"FFFF","InverterCurrentC":"FFFF","GridCurrentC":"FFFF","LoadCurrentC":"FFFF","InverterOffgridWorkEnable":"1","InverterOutputVoltageSet":"230","InverterOutputFrequencySet":"5000","InverterSearchModeEnable":"0","InverterDischargerToGridEnable":"0","EnergyUseMode":"2","GridProtectStandard":"2","SolarUseAim":"1","InverterMaxDischargerCurrent":"21.7","NormalVoltagePoint":"46.8","StartSellVoltagePoint":"52","GridMaxChargerCurrentSet":"30","InverterBatteryLowVoltage":"46.8","InverterBatteryHighVoltage":"60","MaxCombineChargerCurrent":"80","SystemSetting":"1100010000000000","ChargerSourcePriority":"2","WorkState":"Grid-Tie","AcVoltageGrade":"230","RatedPower":"5000","InverterBatteryVoltage":"52.1","InverterVoltage":"217","GridVoltage":"218.2","BusVoltage":"415.5","ControlCurrent":"1.5","InverterCurrent":"1.5","GridCurrent":"1.1","LoadCurrent":"1.5","PInverter":"306","PGrid":"0","PLoad":"288","LoadPercent":"6","SInverter":"319","SGrid":"242","Sload":"335","Qinverter":"105","Qgrid":"240","Qload":"171","InverterFrequency":"49.97","GridFrequency":"49.97","InverterMaxNumber":"0000","CombineType":"0000","InverterNumber":"0000","AcRadiatorTemperature":"27","TransformerTemperature":"47","DcRadiatorTemperature":"24","InverterRelayState":"Connect","GridRelayState":"Connect","LoadRelayState":"Connect","N_LineRelayState":"Disconnect","DCRelayState":"Connect","EarthRelayState":"Disconnect","AccumulatedChargerPower":"904.3","AccumulatedDischargerPower":"1884.1","AccumulatedBuyPower":"2651.9","AccumulatedSellPower":"4","AccumulatedLoadPower":"3654.1","AccumulatedSelf_usePower":"1002.1","AccumulatedPV_sellPower":"4","AccumulatedGrid_chargerPower":"904.3","InverterErrorMessage":"","InverterWarningMessage":"","BattPower":"-146","BattCurrent":"-2"}
You can run the application on a regular interval using a cronjob, something similar to this (put together quickly from memory, so probably not entirely correct):
* * * * * root /usr/bin/dtach -n /dev/shm/mustinv$$ /usr/bin/bash -c "/home/pi/MustInverter.exe | mosquitto_pub -h 192.168.0.5 -t "/mustinverter" -l -u username -P password"
And this is the extra YAML I gave Home Assistant to ingest this payload:
sensor:
- platform: mqtt
state_topic: "/inverter"
name: "_acRadiatorTemperature"
expire_after: 360
value_template: "{{ value_json._acRadiatorTemperature }}"
unit_of_measurement: '°C'
- platform: mqtt
state_topic: "/inverter"
name: "_dcRadiatorTemperature"
expire_after: 360
value_template: "{{ value_json._dcRadiatorTemperature }}"
unit_of_measurement: '°C'
- platform: mqtt
state_topic: "/inverter"
name: "_transformerTemperature"
expire_after: 360
value_template: "{{ value_json._transformerTemperature }}"
unit_of_measurement: '°C'
- platform: mqtt
state_topic: "/inverter"
name: "_battCurrent"
expire_after: 360
value_template: "{{ value_json._battCurrent }}"
unit_of_measurement: 'A'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_chargerCurrent"
expire_after: 360
value_template: "{{ value_json._chargerCurrent }}"
unit_of_measurement: 'A'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_controlCurrent"
expire_after: 360
value_template: "{{ value_json._controlCurrent }}"
unit_of_measurement: 'A'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_gridCurrent"
expire_after: 360
value_template: "{{ value_json._gridCurrent }}"
unit_of_measurement: 'A'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_inverterCurrent"
expire_after: 360
value_template: "{{ value_json._inverterCurrent }}"
unit_of_measurement: 'A'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_loadCurrent"
expire_after: 360
value_template: "{{ value_json._loadCurrent }}"
unit_of_measurement: 'A'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_gridFrequency"
expire_after: 360
value_template: "{{ value_json._gridFrequency }}"
unit_of_measurement: 'Hz'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_inverterBatteryVoltage"
expire_after: 360
value_template: "{{ value_json._inverterBatteryVoltage }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_inverterVoltage"
expire_after: 360
value_template: "{{ value_json._inverterVoltage }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_loadPercent"
expire_after: 360
value_template: "{{ value_json._loadPercent }}"
unit_of_measurement: '%'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_battPower"
expire_after: 360
value_template: "{{ value_json._battPower }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_pGrid"
expire_after: 360
value_template: "{{ value_json._pGrid }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_pInverter"
expire_after: 360
value_template: "{{ value_json._pInverter }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_inverterVoltage"
expire_after: 360
value_template: "{{ value_json._pLoad }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_busVoltage"
expire_after: 360
value_template: "{{ value_json._busVoltage }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_sload"
expire_after: 360
value_template: "{{ value_json._sload }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_chargerPower"
expire_after: 360
value_template: "{{ value_json._chargerPower }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_sInverter"
expire_after: 360
value_template: "{{ value_json._sInverter }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_qgrid"
expire_after: 360
value_template: "{{ value_json._qgrid }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_qinverter"
expire_after: 360
value_template: "{{ value_json._qinverter }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_qload"
expire_after: 360
value_template: "{{ value_json._qload }}"
unit_of_measurement: 'W'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_sGrid"
expire_after: 360
value_template: "{{ value_json._sGrid }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_pvVoltage"
expire_after: 360
value_template: "{{ value_json._pvVoltage }}"
unit_of_measurement: 'V'
device_class: power
- platform: mqtt
state_topic: "/inverter"
name: "_mpptState"
expire_after: 360
value_template: "{{ value_json._mpptState }}"
- platform: mqtt
state_topic: "/inverter"
name: "PV Inverter Power"
expire_after: 360
value_template: "{{ value_json.PInverter }}"
unit_of_measurement: 'W'
device_class: power
The USB interface on these devices connects to a converter which ultimately connects to the same serial interface as the dedicated RS422/RS485 interface – so you may as well use the USB connection.
Best of luck! and feel free to drop me a line if you find this useful, always fun hearing from people who read these posts (who would’ve thought?!)
The PowerForum thread this post relates to can be found here.