Reverse Engineering a Solar Inverter Telemetry Protocol

…and a general look at solar inverter challenges/cool things.

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.

A (very dodgy) Chinese Solar Grid-Tie Inverter prior to installation. The fans on these units failed shortly after purchase and they generated a lot of RF interference. They were extremely inefficient, but affordable.
8 years and new fans later.
Solar Panels – CHINALAND 270w panels, sourced from Mantech Electronics (electrical components distributor). I surprised myself with this self-install. These panels were quoted as 13.7% efficient at capturing solar energy. Modern panels apparently exceed 20%.

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:

Enter the CB Solar Synapse 4.0+ inverter.

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”.

Wiring in the inverter into the home distribution board (after Dents Electrical told me they could only help me in 2 weeks time, pfffft). I’m really happy I had this 3-tier board installed. Some Shelly 4CH Pro controller/energy monitor units are present.
Of course, there’s gotta be a Raspberry Pi involved. Hack harder Bobby! On the left is the USB input (which comes off a Mikrotik router’s USB port, which is battery-backed). On the right is an OTG adapter with a USB cable going out to the inverter.
Moaaar batteries! These are locally manufactured 102Ah Sealed Lead Acid batteries. They’re wired in series for 48V @ 102Ah. Their max discharge is 80%, so in practice they can supply ~3.8 kwH. This will generally last us 2 load shedding slots.
The data connectivity PCB of the inverter – from left to right: RJ45 port (but carrying RS485 on two pins), USB Port and “dry contact” signalling relay outputs.

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:

ICC Software’s ICC MQTT app. This is supposed to run on a Raspberry Pi?! They really have missed the point. It’s like they’ve taken all the crappy things about Windows and ported them to a minimalist platform.
This is, unfortunately, very South African. Filthy customers. This kind of crap makes me want to build my own stuff.

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…

oh man, nothing says quality like a mini-cd with a Panda on it.
I mean, what was I expecting? “PowerMonitor.exe” running on a Windows 10 machine.

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.

Decompiled code, specifically for the PH1800 and related units (PH1100, PH1000, etc.) There are a number of other models supported, like the EP3000 and EP2000Pro. One can’t help but notice the main app is called “CCControl” which, when combined with “Chinese” makes me think of Command and Control worms and some dodgy PLA building.
Yes, it’s PuTTY on Windows, don’t judge me, it was handy.

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.

The inverter LCD display cycles through “pages”. This page indicates on the top left the PV array input voltage (106 VDC) and the output from the array in kW (0.51 kw). Below it indicates that the PV array is going through the DC/DC module in the inverter and the output is being used to both charge the batteries and power the house (via the DC/AC inverter component).

I’ve got the data going into my MQTT broker, now I need to log it (read “integrate it into Home Assistant”).

Home Assistant YAML configuration. I’m picking out the fields I’m interested in viewing and logging in Home Assistant. The JSON structure is flat, so the JsonPath directives are pretty simple.
Damn I love technology. Solar PV array voltage output as seen in Home Assistant web interface (Lovelace).

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.

Leave a Reply