Home Solar Inverter Modbus adventure - Nevin's Odyssey
Post
Cancel

Solar Inverter Modbus adventure - Nevin's Odyssey

So, picture this: A guy, used to the cozy world of REST APIs, where getting data feels like ordering KFC - quick and satisfying. But fate had other plans for me. This time, I was on a mission straight out of getting data from registers.

The Problem

I aimed to detect power grid outages to automate switching some household devices to standby mode (esp. the TV; as wheels ♫ on the bus go ♫ round and round…♫ playing on loop, consumes 1kWh a day, if left unattended during a prolonged power outage, it will quickly drain the battery!). Initially, I relied on pinging a device unconnected to the indoor inverter. However, I had to relocate the ESP32 responsible for this task to illuminate ws2812b, and unfortunately, its new socket was within the indoor inverter’s scope, rendering it unable to detect grid outages.

Although I could have retrieved this information from the new solar inverter’s cloud API, it only provided data in 5-minute intervals. Despite my preference for real-time data, I accepted the cloud API’s limitations. However, I would have appreciated more consistent support and endpoints from their service, as I found myself pulling data from various domains, which felt inconsistent.

I reached out for support to inquire about local options but received no response, prompting me to halt further emails. In a moment of disappointment, I conducted a basic nmap scan and discovered a peculiar port, along with port 80, opened on the network device. This discovery sparked a glimmer of hope. I recalled the installer using their app to scan the local network and set up the device, indicating there might be additional functionality awaiting exploration within the app.

The Exploration

I decompiled the apk using jadx, which gave me something with regards to the chip:

1
2
3
4
5
6
7
8
9
public class Type {

    /* loaded from: classes2.dex */
    public enum LoggerChipType {
        LPB_100,
        A11,
        LPB_230
    }
}

and more..:

1
2
3
4
/* loaded from: classes2.dex */
public class ConfigConstant {
    public static final String LPB_FIRST_COMMAND = "WIFIKIT-214028-READ";
}

Some quick research on Google revealed that I could utilize the Modbus TCP protocol to communicate with the device using just its serial number. I realised that the Raspberry Pi could communicate directly with the device on the port found from the nmap scan without the need for any extra wires or components, allowing me to fetch real-time data, which was precisely what I needed.

Enter Modbus TCP, a communication protocol that I have never worked on, but confident that I am armed with nothing but my trusty python environment on my raspberry pi and a flask full of coffee, I embarked on a quest to fetch the data from the solar inverter without having to depend on the cloud service.

What ensued were hours of trial and error, accompanied by frustration and a few choice words. I obtained a PDF (thanks Google!) containing Modbus register details for a completely different model of inverter. While the registers did not provide the data I was seeking, they did offer some insight into what I was searching for:

Window shadow Hours or trial and error with modbus registers

The Solution

Desktop View

For my needs, which involve putting devices on standby during grid power outages, I just need to verify the solar inverter’s status by reading the holding_register (function code 0x03) at 0x0000. If the status indicates a temporary fault, i.e, status 3, it’s likely due to the grid being down, although this assumption would need to be confirmed by checking the fault code. To avoid spending extra time checking the fault code, I can also monitor the grid frequency at 0x000E to determine if the grid is down and act accordingly.

Bonus : After dedicated effort, here are the holding registers details I discovered for 3000-G:

  • Inverter Operating State:
    • Register Address: 0x0000
    • Quantity: 1
  • PV1 Voltage:
    • Register Address: 0x0006
    • Quantity: 1
    • Scale: 0.1
  • PV1 Current:
    • Register Address: 0x0007
    • Quantity: 1
    • Scale: 0.01
  • PV1 Power:
    • Register Address: 0x000A
    • Quantity: 1
    • Scale: 0.01
  • Output Active Power:
    • Register Address: 0x000C
    • Quantity: 1
    • Scale: 0.01
  • Output Reactive Power:
    • Register Address: 0x000D
    • Quantity: 1
    • Scale: 0.01
  • Grid Frequency:
    • Register Address: 0x000E
    • Quantity: 1
    • Scale: 0.01
  • Total Production:
    • Register Address (High Byte): 0x0015
    • Register Address (Low Byte): 0x0016
    • Quantity: 1
  • Total Production Time:
    • Register Address (High Byte): 0x0017
    • Register Address (Low Byte): 0x0018
    • Quantity: 1
  • Today’s Production:
    • Register Address: 0x0019
    • Quantity: 1
    • Scale: 0.01
  • Today’s Production Time:
    • Register Address: 0x001A
    • Quantity: 1
  • Module Temperature:
    • Register Address: 0x001B
    • Quantity: 1
  • Inner Temperature:
    • Register Address: 0x001C
    • Quantity: 1

Window shadow Day1 production stats logged locally and pushed to Google Sheets

Your inverter model might need specific registry addresses to retrieve its details. Anyways, it’s possible to access this information locally and in real-time rather than depending on their api that does not give real-time data even if they display it as real-time on their web dashboards and apps.

To do : ~~ ~~Set up a dashboard w/ database to display the historical data I’ve collected, but this isn’t a priority at the moment since I can access this data on the cloud console anyway!

The Bonus

Public dashboard with historical data is up and running!

Window shadow Historical data is now available publicly

This post is licensed under CC BY 4.0 by the author.