Skip to content

Some registers can only be read as a block on my GW5000D-NS inverter #126

Description

@kieranclancy

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions