I was trying to determine why I couldn't read a number of registers relating to Power Limits on my GW5000D-NS over UDP, but the GoodWe SolarGo app was able to read the values without problems.
DEBUG:goodwe:Connected to inverter GW5000D-NS, S/N:55000DSN20******.
Reading grid_export
DEBUG:goodwe.protocol:Socket closed.
DEBUG:goodwe.protocol:Sending: READ register 40327 (7f039d8700011191)
DEBUG:goodwe.protocol:Received: aa557f03020000904e
Reading grid_export_limit
DEBUG:goodwe.protocol:Socket closed.
DEBUG:goodwe.protocol:Sending: READ 2 registers from 40328 (7f039d8800026193)
DEBUG:goodwe.protocol:Received: aa557f0304ffffffff65a0
As you can see, the grid_export_limit isn't being read properly.
By recording the packets sent by the SolarGo app, I observed that on the "Operating Parameters" screen, it sends a UDP packet to read 10 registers at once starting from 40327:
SolarGo -> inverter: 7f039d87000a5056
inverter -> SolarGo: aa557f031400000000012c0c2900540c2c0053fffffffffffff500
I can reproduce this in Python if I copy this approach and read 10 registers at once:
await inverter._read_from_socket(inverter._read_command(40327, 10))
DEBUG:goodwe.protocol:Sending: READ 10 registers from 40327 (7f039d87000a5056)
DEBUG:goodwe.protocol:Received: aa557f031400000000012c0c2900540c2c0053fffffffffffff500
Here's a table comparing individual reads (or a 2-register read in the case of 40328) versus reading them as a 10-register block:
| Register |
When read individually |
When read as 10-register block |
| 40327 (grid export limit enabled) |
0x0000 |
0x0000 |
| 40328 (export limit, high bits) |
0xffff |
0x0000 |
| 40329 (export limit, low bits) |
0xffff |
0x012c (300W) |
| 40330 (start) |
0xffff |
0x0c29 |
| 40331 (stop) |
0xffff |
0x0054 |
| 40332 (restart) |
0xffff |
0x0c2c |
| 40333 (?) |
0xffff |
0x0053 |
| 40334 (?) |
0xffff |
0xffff |
| 40335 (?) |
0x0000 |
0xffff |
| 40336 (?) |
0xffff |
0xffff |
I'm not sure why SolarGo reads 10 registers here; it doesn't look like anything after 40329 is especially useful (and I assume 40330-40332 are meant to only be written to?). It's also a bit of a puzzle that 40335 reads back as 0x0000 if read on its own but as 0xffff if read as a block.
In my case, I am trying to read and dynamically set the export limit. Because of this issue though, I need to use a workaround of:
# Always returns 0
# value = await inverter.read_setting('grid_export_limit')
# Returns correct value
result = await inverter._read_from_socket(inverter._read_command(40327, 3))
result_b = result.read(6)
value = int.from_bytes(result_b[2:6], byteorder='big', signed=False)
As far as a fix for the library might go, maybe it would be possible to declare the sensor like:
# dt.py
Long("grid_export_limit", 40328, "Grid Export Limit", "W", Kind.GRID, read_as_block=(40327, 3))
This would still know that it's 4 bytes long, but that for reads it needs to request 3 registers from 40327, then seek to the appropriate offset in the data to read the 4 bytes. For writing, it doesn't need to overwrite the whole block; it only needs to write the registers for that value, and this is consistent with how SolarGo updates the value (it just sets 40328 and 40329 in one write when you change the export limit, not 40327).
I don't love the thought of needing to add a new kwarg like this to Long and other sensors though... Architecturally, it could be nice to separate the responsibility of reading/writing the data (content - what register(s) and how) from the calculations being done on the raw values (presentation - scaling, association with units, even association with ids/names/kinds etc). That might be quite a refactor though.
This also got me thinking, maybe there are other registers that can't be read on this inverter if reading them individually, but reading them as part of a larger block might be possible. I mapped the following new registers or blocks of registers by capturing packets from the SolarGo app:
# Authentication password (e.g. from SolarGo app - note: client side authentication check)
# Must be read as a block of 16 registers on my inverter
40000: First char pair (ASCII) e.g. 0x3132 = "12"
40001: Second char pair (ASCII) e.g. 0x3334 = "34"
40002: Third char pair (ASCII) e.g. 0x3536 = "56"
40003: Fourth char pair (ASCII) e.g. 0x3738 = "78"
40004: 0x0000 on mine, maybe because password is only 8 chars long?
40005: 0x0000 on mine
40006: 0x0000 on mine
40007: 0x0000 on mine
40008: First char pair of some other passcode, maybe for non-installer user? (ASCII) e.g. 0x3132 = "12"
40009: Second char pair ...
40010: 0x0000 on mine
...
40015: 0x0000 on mine
# Characteristic parameters
# Must be read as a block of 5 registers on my inverter
40322: ISO (Minimum DC insulation resistance) (x 1 kOhm) e.g. 0x0032 = 50 kOhm
40323: LVRT (low voltage ride-through) (1=on, 0=off)
40324: HVRT (high voltage ride-through) (1=on, 0=off)
40325: ? 0x0000 on mine
40326: Shadow scan enabled (1=on, 0=off) (dt.py currently says this is a "Status" but it's actually RW)
# Must be read as a block of 5 registers on my inverter
# These are shown when SolarGo is showing values for Active power %
# (which apparently takes a value between 0% and 100%) and for
# Reactive power % (-60% to 60% apparently, according to SEMS portal)
# but I don't really want to mess with the settings to find out which
# is which or how it's encoded (e.g. x .1%, signed for reactive?).
40480: ? 0x0000 on mine
40481: Power factor (x .01) e.g. 0x0064 = 1.00
40482: ? 0x0000 on mine
40483: ? 0x0000 on mine
40484: ? 0x0000 on mine
# This one isn't in a block, just missing in dt.py
40486: Reconnection time (how long to wait after being off or recovering from fault before trying to start again?) (x 1s) e.g. 0x03c = 60 seconds
# On-grid Jump Off Settings
# Must be read as a block of 17 registers on my inverter
40501: Over-voltage stage 1 trigger value (x .1%) e.g. 0x0480 = 115.2%
40502: Over-voltage stage 1 trip time (x .02s) e.g. 0x004b = 1.5s
40503: Over-voltage stage 2 trigger value (x .1%) e.g. 0x0480 = 115.2%
40504: Over-voltage stage 2 trip time (x .02s) e.g. 0x0005 = 0.1s
40505: Under-voltage stage 1 trigger value (x .1%) e.g. 0x030e = 78.2%
40506: Under-voltage stage 1 trip time (x .02s) e.g. 0x020d = 10.5s
40507: Under-voltage stage 2 trigger value (x .1%) e.g. 0x030e = 78.2%
40508: Under-voltage stage 2 trip time (x .02s) e.g. 0x004b = 1.5s
40509: 10-minute over-voltage protection (x .1%) e.g. 0x0461 = 112.1%
40510: Over-frequency stage 1 trigger value (x .01Hz) e.g. 0x1450 = 52.00Hz
40511: Over-frequency stage 1 trip time (x .02s) e.g. 0x0005 = 0.1s
40512: Over-frequency stage 2 trigger value (x .01Hz) e.g. 0x1450 = 52.00Hz
40513: Over-frequency stage 2 trip time (x .02s) e.g. 0x0005 = 0.1s
40514: Under-frequency stage 1 trigger value (x .01Hz) e.g. 0x125c = 47.00Hz
40515: Under-frequency stage 1 trip time (x .02s) e.g. 0x004b = 1.5s
40516: Under-frequency stage 2 trigger value (x .01Hz) e.g. 0x125c = 47.00Hz
40517: Under-frequency stage 2 trip time (x .02s) e.g. 0x004b = 1.5s
I was trying to determine why I couldn't read a number of registers relating to Power Limits on my GW5000D-NS over UDP, but the GoodWe SolarGo app was able to read the values without problems.
As you can see, the grid_export_limit isn't being read properly.
By recording the packets sent by the SolarGo app, I observed that on the "Operating Parameters" screen, it sends a UDP packet to read 10 registers at once starting from 40327:
I can reproduce this in Python if I copy this approach and read 10 registers at once:
Here's a table comparing individual reads (or a 2-register read in the case of 40328) versus reading them as a 10-register block:
I'm not sure why SolarGo reads 10 registers here; it doesn't look like anything after 40329 is especially useful (and I assume 40330-40332 are meant to only be written to?). It's also a bit of a puzzle that 40335 reads back as 0x0000 if read on its own but as 0xffff if read as a block.
In my case, I am trying to read and dynamically set the export limit. Because of this issue though, I need to use a workaround of:
As far as a fix for the library might go, maybe it would be possible to declare the sensor like:
This would still know that it's 4 bytes long, but that for reads it needs to request 3 registers from 40327, then seek to the appropriate offset in the data to read the 4 bytes. For writing, it doesn't need to overwrite the whole block; it only needs to write the registers for that value, and this is consistent with how SolarGo updates the value (it just sets 40328 and 40329 in one write when you change the export limit, not 40327).
I don't love the thought of needing to add a new kwarg like this to
Longand other sensors though... Architecturally, it could be nice to separate the responsibility of reading/writing the data (content - what register(s) and how) from the calculations being done on the raw values (presentation - scaling, association with units, even association with ids/names/kinds etc). That might be quite a refactor though.This also got me thinking, maybe there are other registers that can't be read on this inverter if reading them individually, but reading them as part of a larger block might be possible. I mapped the following new registers or blocks of registers by capturing packets from the SolarGo app: