squishykid / solax

🌞 Solax Inverter API Wrapper
MIT License
100 stars 57 forks source link

Add support for Q.Volt HYB-G3-1P #94

Open TheRealZago opened 1 year ago

TheRealZago commented 1 year ago

Hi all, After working a bit with this inverter for a quick standalone real time data monitor, I feel confident in sharing with you the schema I managed to interpret.

The API can be accessed either when connected directly to the PocketWiFi dongle, or when all connected to a network. The API endpoint is the same as the 3-phase bigger brother (Q.Volt HYB-G3-3P), so a POST request with body optType=ReadRealTimeData&pwd={pwd} to the dongle's IP address, querying port 80 for HTTP. Too bad they don't share a single array position...

Here's the sample JSON output

```json { "sn": "[WiFi dongle SN]", "ver": "3.001.03", "type": 15, "Data": [ 2372, 14, 312, 4995, 2355, 2363, 7, 6, 181, 164, 2, 19104, 0, 63, 31660, 0, 0, 27, 96, 3212, 0, 4313, 0, 88, 100, 0, 29, 3678, 0, 0, 0, 0, 0, 0, 52344, 1, 1255, 0, 312, 35, 256, 3504, 2400, 74, 300, 208, 196, 33, 33, 84, 1, 1, 0, 0, 20874, 0, 65223, 65535, 65387, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2460, 0, 62722, 65535, 3600, 0, 61936, 65535, 120, 0, 40, 0, 0, 0, 0, 95, 14, 45, 0, 0, 350, 0, 230, 0, 0, 0, 10, 0, 0, 9782, 271, 5643, 1620, 778, 14135, 14135, 14135, 0, 0, 0, 1, 3195, 0, 0, 3334, 3319, 62928, 20, 20564, 12339, 18497, 12866, 18736, 12356, 13105, 20564, 12339, 18498, 12355, 18740, 12356, 13873, 20564, 12339, 18498, 12866, 18740, 12356, 12849, 20564, 12339, 18754, 12849, 18742, 13124, 12338, 0, 0, 0, 0, 0, 0, 0, 1, 2050, 4097, 258, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], "Information": [ 6, 15, "[inverter SN]", 8, 1.29, 0, 1.27, 1.03, 0, 1 ] } ```

So far, I've been able to identify the following fields

{
    "Network Voltage": (0, Units.V, div10),
    "Inverter output current": (1, Units.A, twoway_div10),
    "Inverter output power": (2, Units.W, to_signed),
    "Grid Frequency": (3, Units.HZ, div100),
    "PV1 Voltage": (4, Units.V, div10),
    "PV2 Voltage": (5, Units.V, div10),
    "PV1 Current": (6, Units.A, div10),
    "PV2 Current": (7, Units.A, div10),
    "PV1 Power": (8, Units.W),
    "PV2 Power": (9, Units.W),
    "Inverter Operation mode": (10, Units.NONE, cls.Processors.inverter_modes),
    "Total inverter yield": (pack_u16(11, 12), Total(Units.KWH), div10),
    "Today's inverter output yield": (13, Units.KWH, div10),
    "Battery Voltage": (14, Units.V, div100),
    "Battery Current": (15, Units.A, twoway_div100),
    "Battery Power": (16, Units.W, to_signed),
    "Battery Temperature": (17, Units.C),
    "Battery Remaining Capacity": (18, Units.PERCENT),
    # 19: seemingly monotonic, slowly growing
    # 20: always 0
    # 21: always 4313
    # 22: always 0
    "Battery Remaining Energy": (23, Units.KWH, div10),
    # 24: always 100
    # 25: always 0
    # 26-27: jumping around
    # 28-31: always 0
    # 32-33: realtime feed-in/grid power?
    "Total output to grid": (pack_u16(34, 35), Total(Units.KWH), div100),
    "Total input from grid": (pack_u16(36, 37), Total(Units.KWH), div100),
    "Current power usage": (38, Units.W, to_signed),
    # 39-115: mix of steady values, 0s, FFFFs and changing mysteries
    "Total battery energy throughput": (pack_u16(116, 117), Total(Units.WH)),
    # 118-124: BMS serial number, ASCII, little-endian, 2 chars per register
    # 125-131: Battery 1 serial number, ASCII, little-endian, 2 chars per register
    # 132-138: Battery 2 serial number, ASCII, little-endian, 2 chars per register
    # 139-145: Battery 3 serial number, ASCII, little-endian, 2 chars per register
    # 146-152: Battery 4 serial number, ASCII, little-endian, 2 chars per register
    # 153-156: ???
    "Battery Operation mode": (157, Units.NONE, cls.Processors.battery_modes),
    # 158 - 199: always 0
}

This is an afternoon's worth of data collection and interpretation. I've also managed to pick the worst day by having very little solar production, so I'll keep it running for a couple more days. I'll open a pull request as soon as I'm done with these last sanity checks.

TheRealZago commented 1 year ago

Alright, I've figured out a few more numbers. Pull request incoming.

One more question. I'm seeing a weird behavior with the discovery logic, specifically it's getting stuck for 5 minutes on the X3 data query, before aiohttp decides it's had enough and raises a asyncio.exceptions.TimeoutError. Wouldn't it be advisable to set the API response timeout to something more realistic (idk, 30 seconds)? Is there any known condition where a response would take more than that to be processed? From what I can tell, there's no explicit timeout set for the HTTP request itself, so I think it's rolling with the default OS' socket timeout.

I can circumvent the lockup by moving the HYB-G3-1P before the X3, but I have no idea how other inverters could behave.

Here's the full traceback:

zago@MintBox:~/repo/solax$ python3 test.py
Trying as <class 'solax.inverters.x_hybrid.XHybrid'>
Trying as <class 'solax.inverters.x3.X3'>
Traceback (most recent call last):
  File "test.py", line 10, in <module>
    data = loop.run_until_complete(work())
  File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "test.py", line 5, in work
    r = await solax.real_time_api('172.16.1.215', 80, "solarone")
  File "/home/zago/repo/solax/solax/__init__.py", line 36, in real_time_api
    i = await discover(ip_address, port, pwd)
  File "/home/zago/repo/solax/solax/discovery.py", line 42, in discover
    await i.get_data()
  File "/home/zago/repo/solax/solax/inverter.py", line 51, in get_data
    data = await self.make_request(self.host, self.port, self.pwd)
  File "/home/zago/repo/solax/solax/inverter.py", line 151, in make_request
    async with session.post(url, headers=headers) as req:
  File "/home/zago/.local/lib/python3.8/site-packages/aiohttp/client.py", line 1141, in __aenter__
    self._resp = await self._coro
  File "/home/zago/.local/lib/python3.8/site-packages/aiohttp/client.py", line 560, in _request
    await resp.start(conn)
  File "/home/zago/.local/lib/python3.8/site-packages/aiohttp/client_reqrep.py", line 914, in start
    self._continue = None
  File "/home/zago/.local/lib/python3.8/site-packages/aiohttp/helpers.py", line 720, in __exit__
    raise asyncio.TimeoutError from None
asyncio.exceptions.TimeoutError