Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6448401
wip(foxess_h3_smart): 0.2.0 local control build — hardware-validated …
Leitet Aug 3, 2026
4425888
fix(foxess_h3_smart): the remote-control setpoint is grid power, not …
Leitet Aug 5, 2026
6a2792e
Revert "fix(foxess_h3_smart): the remote-control setpoint is grid pow…
Leitet Aug 5, 2026
c5f369d
fix(foxess_h3_smart): the setpoint is inverter AC power, not battery …
Leitet Aug 5, 2026
205855d
feat(foxess_h3_smart): guarded charge path, ported from the reference…
Leitet Aug 5, 2026
c7cd55a
fix(foxess_h3_smart): a zero command holds the battery at zero, it do…
Leitet Aug 5, 2026
6f57027
chore(foxess_h3_smart): sync manifest and declare the package as control
Leitet Aug 5, 2026
d516d56
chore(foxess_h3_smart): manifest control flag + regenerate derived ca…
Leitet Aug 5, 2026
35ee43c
feat(foxess_h3_smart): per-phase meter power and amps for the fuse bars
Leitet Aug 5, 2026
c67f3b1
feat(foxess_h3_smart): close the maturity gap with the sungrow driver
Leitet Aug 5, 2026
6a3b502
fix(foxess_h3_smart): distinct emit variables and updated fixtures fo…
Leitet Aug 5, 2026
4f62191
fix(foxess_h3_smart): derate PV to AC-achievable in the setpoint tran…
Leitet Aug 5, 2026
831705a
fix(foxess_h3_smart): calibrate PV_AC_EFF from two hardware points
Leitet Aug 6, 2026
c224de0
chore(foxess_h3_smart): align manifest and package versions at 0.7.2
Leitet Aug 6, 2026
48b7ac7
chore(foxess_h3_smart): regenerate catalogs for 0.7.2
Leitet Aug 6, 2026
017ae50
Merge branch 'main' into local-foxess-control-build
Leitet Aug 6, 2026
52582af
feat(foxess_h3_smart): pv-curtail via the AC-setpoint ceiling (0.8.0)
Leitet Aug 6, 2026
eb5021d
chore(foxess_h3_smart): regenerate support-status for 0.8.0
Leitet Aug 6, 2026
d5a9127
feat(foxess_h3_smart): control v2 entrypoints (0.9.0)
Leitet Aug 6, 2026
71c2c24
chore: remove the untested foxess H1/H3 driver, keeping its record
Leitet Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html

## [Unreleased]

### Removed
- **`foxess` (H1/H3 11000-range map) — removed untested.** Every tested_devices entry said "Community driver, untested", and no hardware has ever validated the map. This is not a clean supersession: `foxess_h3_smart` covers the 1K5-HI and H3-Smart families, not the H1 / H3 / H3-PRO / AIO-H3 families the old driver claimed — those lose their only, unvalidated, listing. The removal is still right on this vendor's hardware behaviour: a Fox ESS inverter answers unknown registers with silence, so a wrong-map driver produces no error, only timeouts — a real 1K5 answered none of the 11000-range map, and the catalog offering it anyway cost that operator a full "device not supported" detour. A catalog that reports what is known must not list a map nobody has seen answer. H1-range coverage can return the way H3-Smart did: a driver written against hardware someone actually has.

### Added
- **`foxess_h3_smart` 0.9.0** — battery control through the vendor remote-control block, hardware-validated in charge, discharge and hold on a 1K5-HI-10-V1. The setpoint at 46003/46004 is the inverter's AC active power, export-positive — not battery power and not a grid target — so the driver translates `vendor = pv × PV_AC_EFF − battery_target` and guards the charge path: BMS ceiling (46018/46019) minus a 200 W margin, a daylight split on PV string voltage, a one-cycle 0 W pause on import/export sign crossings, and charge refusal at SoC ≥ 99% (the inverter ignores its own Max SoC under remote control). Zero is a held setpoint, not a release — releasing let native self-use surge charging back against the host's ceiling in a ~90 s limit cycle. Two dead-man's switches: the vendor timeout at 46002 (≥ 60 s — the master samples that block slowly and a shorter session expires unseen) and a 60 s driver-side command lease; `driver_default_mode` releases remote control explicitly and SELF_USE is the only fallback work mode. `PV_AC_EFF = 0.977` is calibrated from two held-zero hardware points (−70 W residual at 2455 W PV, +71 W at 4025 W) — the efficiency curve rises with load, so one constant lands within ±25 W across the daytime range, erring toward a few watts of charge rather than a steady drain. 0.8.0 adds `pv-curtail` (`curtail` / `curtail_disable`), gated on the operator's `supports_pv_curtail` opt-in: the cap is a ceiling on the same AC setpoint — on a hybrid, PV beyond the cap charges the battery first (up to the live BMS limit) and genuine curtailment begins past that, which is the ordering the negative-export guard wants; a curtail-only session holds battery-at-zero under the ceiling in daylight and stands down at night so it cannot block self-use discharge. 0.9.0 implements the **control v2 contract** (srcfl/ftw#738/#741) — `driver_command_v2` / `driver_default_mode_v2` return structured results whose "applied"/"defaulted" claims the host verifies against its own write-scope evidence (write ack + setpoint readback), and default mode always writes the release and reads it back; the v1 entrypoints remain for local operator builds, whose default mode keeps the skip-if-not-ours courtesy toward FoxESS-app schedule periods. The migration also surfaced that the host's write bindings return error strings rather than raising, so a bare `pcall` around a write reports failure as success — every write now checks both layers. `drivers/tests/test_foxess_h3_smart_control.py` holds the v2 results to the contract: statuses, codes, device_state, evidence lists, and the registers actually written. Also since 0.1.0: per-phase grid CT (voltage, power, amps per phase), lifetime energy counters, inverter temperature and state metrics, and a fault-code latch on 39067..39069
- **A driver-authoring rule for the hybrid inverter that has no battery** — *A hybrid inverter may have no battery* in `docs/WRITING-A-DRIVER.md`, and rule 8 in `drivers/lua/GUIDELINES.md`. The existing rules cover a read that **failed**; this is the case where nothing failed — the device is healthy, every register answered, and the battery still is not there. Nearly every hybrid inverter is sold both with storage and without it under one model number and one register map, so a PV-only site is not an edge case, it is half the product line. The SG12RT already cited at the top of that document is this same fact arriving as an outage rather than as a wrong number. The rule: fill each battery field only from a register that answered, emit the DER only when at least one did, and **detect** it rather than reading it off the model number or asking the operator to declare it — the site nobody told the driver about is exactly the one that reports wrong. Records how the absence actually arrives, which is vendor-specific and cannot be assumed from one example: registers that go silent (Sigenergy), that answer a plain zero, that answer `0xFFFF`/`0x7FFFFFFF`/NaN (`sma` and `solis` already carry sentinel helpers), or that fault
- The rule is stated with the catalog measured rather than asserted: **24 drivers emit both `pv` and `battery`, and 20 of them emit the battery DER with no guard on whether any battery register answered.** Two of the 20 (`ferroamp`, `zap`) gate it on configuration or on API discovery instead — better than nothing, and still not detection, which is why the rule names the difference. `sigenergy` 1.1.3 below is the worked example of the fix. The other 19 are not touched here: each is its own driver, its own register map and its own version, and a sweep that changes nineteen drivers at once is not reviewable
- **`ders` deliberately stays as it is** in all of them. It describes what a driver can produce, not what one site has, and it reaches the signed artifact — so removing `battery` from a hybrid's manifest would cost a version and describe the driver less accurately than before
Expand Down
6 changes: 2 additions & 4 deletions SUPPORT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ Catalog source is not proof that a target can install or run a driver.
| ferroamp_dc2_v2x | 2.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp_modbus | 2.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp_modbus | 2.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| foxess | 1.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| foxess | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| foxess_h3_smart | 0.1.0 | ftw-core | not_assessed | 0.1.0 | — | not_recorded | — | not_assessed | no |
| foxess_h3_smart | 0.1.0 | blixt-l1 | not_assessed | 0.1.0 | — | not_recorded | — | not_assessed | no |
| foxess_h3_smart | 0.9.0 | ftw-core | not_assessed | 0.9.0 | — | not_recorded | — | not_assessed | yes |
| foxess_h3_smart | 0.9.0 | blixt-l1 | not_assessed | 0.9.0 | — | not_recorded | — | not_assessed | yes |
| fronius | 2.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius | 2.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius_api | 1.0.2 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
Expand Down
65 changes: 5 additions & 60 deletions devices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -393,77 +393,22 @@ manufacturers:
protocols:
- protocol: modbus
driver: "foxess_h3_smart"
version: "0.1.0"
version: "0.9.0"
ders: [pv, battery, meter]
control: false
firmware_versions: ""
notes: "Telemetry validated on 1K5-HI-10-V1 hardware: PV string power agrees with V*A, and pv + battery + load balances the grid CT. Read-only."
- name: "AIO-H3 (All-in-One)"
variants: [AIO-H3-10.0, AIO-H3-3.0, AIO-H3-5.0, AIO-H3-6.0, AIO-H3-8.0]
regions: []
protocols:
- protocol: modbus
driver: "foxess"
version: "1.1.1"
ders: [pv, battery, meter]
control: false
firmware_versions: ""
notes: "Community driver, untested"
- name: "H1 (Single-Phase Hybrid)"
variants: [H1-3.0, H1-3.7, H1-4.6, H1-5.0, H1-6.0]
regions: []
protocols:
- protocol: modbus
driver: "foxess"
version: "1.1.1"
ders: [pv, battery, meter]
control: false
firmware_versions: ""
notes: "Community driver, untested"
- name: "H3 (Three-Phase Hybrid)"
variants: [H3-10.0, H3-12.0, H3-5.0, H3-6.0, H3-8.0]
regions: []
protocols:
- protocol: modbus
driver: "foxess"
version: "1.1.1"
ders: [pv, battery, meter]
control: false
firmware_versions: ""
notes: "Community driver, untested"
- name: "H3 PRO"
variants: [H3-PRO-10.0, H3-PRO-15.0, H3-PRO-20.0]
regions: []
protocols:
- protocol: modbus
driver: "foxess"
version: "1.1.1"
ders: [pv, battery, meter]
control: false
control: true
firmware_versions: ""
notes: "Community driver, untested"
notes: "Telemetry and battery control validated on 1K5-HI-10-V1 hardware: PV string power agrees with V*A, pv + battery + load balances the grid CT, and battery dispatch through the remote-control block is validated in charge, discharge and hold."
- name: "H3-Smart"
variants: []
regions: []
protocols:
- protocol: modbus
driver: "foxess_h3_smart"
version: "0.1.0"
version: "0.9.0"
ders: [pv, battery, meter]
control: false
control: true
firmware_versions: ""
notes: "Shares the 1K5 register map; not yet tested on H3-Smart hardware."
- name: "KH Series"
variants: [KH-3.0, KH-3.7, KH-5.0]
regions: []
protocols:
- protocol: modbus
driver: "foxess"
version: "1.1.1"
ders: [pv, battery, meter]
control: false
firmware_versions: ""
notes: "Community driver, untested"
- name: "Fronius"
model_families:
- name: "Fronius Primo (Classic)"
Expand Down
241 changes: 0 additions & 241 deletions drivers/lua/foxess.lua

This file was deleted.

Loading