-
Notifications
You must be signed in to change notification settings - Fork 6
fix(drivers): expose device limits and measured control response #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
405ca9a
c795e42
aa9099e
d74ace6
df00393
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ DRIVER = { | |
| id = "sungrow", | ||
| name = "Sungrow SH Hybrid Inverter", | ||
| manufacturer = "Sungrow", | ||
| version = "1.5.9", | ||
| version = "1.5.10", | ||
| protocols = { "modbus" }, | ||
| capabilities = { "meter", "pv", "battery", "pv-curtail" }, | ||
| description = "Sungrow SH-series hybrid inverters with LFP battery, via Modbus TCP.", | ||
|
|
@@ -535,6 +535,7 @@ function driver_poll() | |
|
|
||
| host.emit("pv", { | ||
| w = -pv_w, -- negative = generation (EMS convention) | ||
| control_power_available = pv_regs ~= nil or mppt_regs ~= nil, | ||
| mppt1_v = mppt1_v, | ||
| mppt1_a = mppt1_a, | ||
| mppt2_v = mppt2_v, | ||
|
|
@@ -640,13 +641,35 @@ function driver_poll() | |
| end | ||
| end | ||
|
|
||
| -- EMS state diagnostics — what the inverter *actually* has latched | ||
| -- in its control registers right now. With the #164 write-order fix | ||
| -- these should track whatever the dispatcher sent last tick; any | ||
| -- drift between target and ems_force_w points at external writers | ||
| -- (iSolarCloud, HA integration, another EMS) racing the driver. | ||
| local emsd = nil | ||
| if hybrid_block_worth_reading() then | ||
| emsd = optional_read(13049, 3, "holding") | ||
| end | ||
| if emsd then | ||
| host.emit_metric("sungrow_ems_mode", emsd[1]) -- 0=self, 2=forced, 3=ext | ||
| host.emit_metric("sungrow_force_cmd", emsd[2]) -- 0xAA=170 chg, 0xBB=187 dis, 0xCC=204 stop | ||
| host.emit_metric("sungrow_force_w", emsd[3]) | ||
| end | ||
|
|
||
| if bat_regs then | ||
| local battery = { | ||
| w = bat_w, | ||
| v = bat_v, | ||
| a = bat_a, | ||
| soc = bat_soc, | ||
| } | ||
| -- The holding registers, not the last command, supply this value. | ||
| -- Force power is inactive in self-consumption and external EMS modes. | ||
| if emsd and emsd[1] == 2 then | ||
| if emsd[2] == 0xAA then battery.setpoint_w = emsd[3] | ||
| elseif emsd[2] == 0xBB then battery.setpoint_w = -emsd[3] | ||
| elseif emsd[2] == 0xCC then battery.setpoint_w = 0 end | ||
| end | ||
|
|
||
| -- Energy counters are separate reads. Add each only if it answered: | ||
| -- a counter reported as zero would look like a reset meter. | ||
|
|
@@ -667,21 +690,6 @@ function driver_poll() | |
| host.emit_metric("battery_dc_a", bat_a) | ||
| end | ||
|
|
||
| -- EMS state diagnostics — what the inverter *actually* has latched | ||
| -- in its control registers right now. With the #164 write-order fix | ||
| -- these should track whatever the dispatcher sent last tick; any | ||
| -- drift between target and ems_force_w points at external writers | ||
| -- (iSolarCloud, HA integration, another EMS) racing the driver. | ||
| local emsd = nil | ||
| if hybrid_block_worth_reading() then | ||
| emsd = optional_read(13049, 3, "holding") | ||
| end | ||
| if emsd then | ||
| host.emit_metric("sungrow_ems_mode", emsd[1]) -- 0=self, 2=forced, 3=ext | ||
| host.emit_metric("sungrow_force_cmd", emsd[2]) -- 0xAA=170 chg, 0xBB=187 dis, 0xCC=204 stop | ||
| host.emit_metric("sungrow_force_w", emsd[3]) | ||
| end | ||
|
|
||
| -- Grid meter power: 5600-5601, I32 LE, watts (positive=import, negative=export) | ||
| local mw_regs = optional_read(5600, 2, "input") | ||
| local meter_w = 0 | ||
|
|
@@ -732,8 +740,16 @@ function driver_poll() | |
| end | ||
| end | ||
|
|
||
| -- 5600-5601 relay the external meter on the known SH register map. Only an | ||
| -- installed meter reads anything there: a meterless install reads zero | ||
| -- power and zero phase current, so it claims no separate meter. | ||
| local meter_origin = nil | ||
| local meter_reads = mw_regs ~= nil and (meter_w ~= 0 or l1_a + l2_a + l3_a > 0) | ||
| if model_family == "hybrid" and meter_reads then meter_origin = "external_meter" end | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This condition covers every device classified as a AGENTS.md reference: AGENTS.md:L17-L20 Useful? React with 👍 / 👎. |
||
| host.emit("meter", { | ||
| w = meter_w, | ||
| power_origin = meter_origin, | ||
| control_power_available = mw_regs ~= nil, | ||
| l1_w = l1_w, | ||
| l2_w = l2_w, | ||
| l3_w = l3_w, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the primary PV register returns zero because of the documented firmware quirk and the MPPT read times out, this
orstill marks control power available even though the fallback ladder emits zero without a usable source. Core can consequently accept a false zero-power response while the array is still generating; availability should require whichever source actually determinedpv_wto have answered.AGENTS.md reference: AGENTS.md:L18-L20
Useful? React with 👍 / 👎.