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:
Hours or trial and error with modbus registers
The Solution
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
- Register Address:
- PV1 Voltage:
- Register Address:
0x0006
- Quantity: 1
- Scale: 0.1
- Register Address:
- PV1 Current:
- Register Address:
0x0007
- Quantity: 1
- Scale: 0.01
- Register Address:
- PV1 Power:
- Register Address:
0x000A
- Quantity: 1
- Scale: 0.01
- Register Address:
- Output Active Power:
- Register Address:
0x000C
- Quantity: 1
- Scale: 0.01
- Register Address:
- Output Reactive Power:
- Register Address:
0x000D
- Quantity: 1
- Scale: 0.01
- Register Address:
- Grid Frequency:
- Register Address:
0x000E
- Quantity: 1
- Scale: 0.01
- Register Address:
- Total Production:
- Register Address (High Byte):
0x0015
- Register Address (Low Byte):
0x0016
- Quantity: 1
- Register Address (High Byte):
- Total Production Time:
- Register Address (High Byte):
0x0017
- Register Address (Low Byte):
0x0018
- Quantity: 1
- Register Address (High Byte):
- Today’s Production:
- Register Address:
0x0019
- Quantity: 1
- Scale: 0.01
- Register Address:
- Today’s Production Time:
- Register Address:
0x001A
- Quantity: 1
- Register Address:
- Module Temperature:
- Register Address:
0x001B
- Quantity: 1
- Register Address:
- Inner Temperature:
- Register Address:
0x001C
- Quantity: 1
- Register Address:
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!