Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
### 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.

### Changed
- **`foxess_h3_smart` 0.9.1** — `verification_status` moves `experimental` → `production` ("verified on hardware" in the FTW UI), with `verification_notes` recording exactly what was verified and on what: telemetry, battery dispatch in all three regimes, and PV curtailment on a 1K5-HI-10-V1 over multi-day operation — and that the H3-Smart family shares the map but remains untested on H3-Smart hardware. Header and description drop the "local control build" framing that stopped being true when the channel shipped it

### 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
Expand Down
4 changes: 2 additions & 2 deletions SUPPORT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +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_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 |
| foxess_h3_smart | 0.9.1 | ftw-core | not_assessed | 0.9.1 | — | not_recorded | — | not_assessed | yes |
| foxess_h3_smart | 0.9.1 | blixt-l1 | not_assessed | 0.9.1 | — | 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
4 changes: 2 additions & 2 deletions devices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ manufacturers:
protocols:
- protocol: modbus
driver: "foxess_h3_smart"
version: "0.9.0"
version: "0.9.1"
ders: [pv, battery, meter]
control: true
firmware_versions: ""
Expand All @@ -404,7 +404,7 @@ manufacturers:
protocols:
- protocol: modbus
driver: "foxess_h3_smart"
version: "0.9.0"
version: "0.9.1"
ders: [pv, battery, meter]
control: true
firmware_versions: ""
Expand Down
16 changes: 10 additions & 6 deletions drivers/lua/foxess_h3_smart.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
-- The distinct H1/H3 (11000-range) register map lives in the separate
-- `foxess` driver.
--
-- LOCAL CONTROL BUILD (operator's own risk, not the signed channel):
-- battery dispatch through the vendor remote-control block.
-- CONTROL DRIVER: battery dispatch and PV curtailment through the
-- vendor remote-control block. Ships through the signed channel with
-- control intact (the catalog marks it control: true); it began as a
-- local operator override and was hardware-validated in that role
-- before publishing.
--
-- ============================ SEMANTICS ============================
-- The setpoint at 46003/46004 is the INVERTER'S AC ACTIVE POWER,
Expand Down Expand Up @@ -128,15 +131,16 @@ DRIVER = {
id = "foxess_h3_smart",
name = "FoxESS H3-Smart / 1K5",
manufacturer = "Fox ESS",
version = "0.9.0",
version = "0.9.1",
host_api_min = 1,
host_api_max = 2,
protocols = { "modbus" },
capabilities = { "pv", "battery", "meter", "pv-curtail" },
description = "Fox ESS H3-Smart register map: 1K5-HI series and H3-Smart three-phase hybrids. Modbus-TCP port 502, unit 247. Local control build: battery dispatch via the remote-control block.",
description = "Fox ESS H3-Smart register map: 1K5-HI series and H3-Smart three-phase hybrids. Modbus-TCP port 502, unit 247. Battery dispatch and PV curtailment via the vendor remote-control block.",
authors = { "Sourceful Labs AB" },
tested_models = { "1K5-HI-10-V1" },
verification_status = "experimental",
verification_status = "production",
verification_notes = "Verified on 1K5-HI-10-V1 hardware over multi-day operation: telemetry (PV per-string, battery, per-phase grid CT, energy counters), battery dispatch in charge, discharge and hold, and PV curtailment. H3-Smart family shares the register map but has not been tested on H3-Smart hardware.",
read_only = false,
}

Expand All @@ -150,7 +154,7 @@ PROTOCOL = "modbus"
-- other field here.
DRIVER_MANIFEST = {
name = "foxess_h3_smart",
version = "0.9.0",
version = "0.9.1",
role = "inverter",
requires = {},
options = {},
Expand Down
6 changes: 3 additions & 3 deletions index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,14 @@ drivers:
size_bytes: 14450
sha256: "8be39a1d41b18b1e12fb9b669e6fe11c93775abfdb4f41338f9dc79fe66fd2d9"
- name: "foxess_h3_smart"
version: "0.9.0"
version: "0.9.1"
tier: community
protocol: modbus
connectivity: local
ders: [pv, battery, meter]
control: true
size_bytes: 33379
sha256: "e4d50a700df28bcb43d4251996b294274cb50d04e6e47d3047470200a90c1e74"
size_bytes: 33859
sha256: "1b6769f4e2583866603ba2fa71f6d3eaa972cc14f5556f99fe9b04c9a194a0b5"
- name: "fronius"
version: "2.1.1"
tier: core
Expand Down
6 changes: 3 additions & 3 deletions manifests/foxess_h3_smart.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "foxess_h3_smart"
version: "0.9.0"
version: "0.9.1"
tier: community
author: "Sourceful Labs AB"
protocol: modbus
Expand Down Expand Up @@ -27,9 +27,9 @@ upstream_docs:
kind: register_map
url_stability: stable
min_host_version: "1.5.0"
size_bytes: 33379
size_bytes: 33859
dkb_id: ""
sha256: "e4d50a700df28bcb43d4251996b294274cb50d04e6e47d3047470200a90c1e74"
sha256: "1b6769f4e2583866603ba2fa71f6d3eaa972cc14f5556f99fe9b04c9a194a0b5"
signature: ""
bytecode_sha256: ""
bytecode_signature: ""
Expand Down
2 changes: 1 addition & 1 deletion packages/v1/foxess_h3_smart/package-source.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": "sourceful.driver-package-source/v1",
"package_id": "com.sourceful.driver.foxess-h3-smart",
"version": "0.9.0",
"version": "0.9.1",
"channel": "beta",
"display_name": "FoxESS H3-Smart / 1K5",
"identity": {
Expand Down
6 changes: 3 additions & 3 deletions support-status.json
Original file line number Diff line number Diff line change
Expand Up @@ -646,12 +646,12 @@
},
{
"catalog_source": true,
"catalog_version": "0.9.0",
"catalog_version": "0.9.1",
"driver_id": "foxess_h3_smart",
"package_id": "com.sourceful.driver.foxess-h3-smart",
"targets": {
"blixt-l1": {
"candidate_package_version": "0.9.0",
"candidate_package_version": "0.9.1",
"control_enabled": true,
"hil": "not_recorded",
"historical_signed_beta_version": null,
Expand All @@ -661,7 +661,7 @@
"target_conformance": "not_assessed"
},
"ftw-core": {
"candidate_package_version": "0.9.0",
"candidate_package_version": "0.9.1",
"control_enabled": true,
"hil": "not_recorded",
"historical_signed_beta_version": null,
Expand Down