Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ jobs:
- run: git diff --exit-code -- index.yaml
- run: uv run --no-sync python tools/generate_devices.py
- run: git diff --exit-code -- devices.yaml
- run: uv run --no-sync python tools/generate_support_status.py
- run: git diff --exit-code -- support-status.json SUPPORT_STATUS.md
- run: uv run --no-sync pytest -q drivers/tests tests

packages:
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html

## [Unreleased]

### Changed
- **pixii** 1.2.1 — First public-source package version; control remains disabled
- **sdm630** 1.1.2 — First public-source package version for FTW and Blixt
- **sungrow** 1.3.1 — First public-source package version; control remains disabled pending FTW process isolation and physical HIL
- Repository provenance now binds public source commits instead of the former private source repository

### Added
- **pixii** 1.2.0 — Staged FTW control v2 package that keeps battery and site-meter telemetry live while calibration marks the battery unavailable for dispatch; control stays disabled pending runtime isolation and physical HIL
- **sungrow** 1.3.0 — Staged FTW control v2 adapter with checked Modbus writes, exact readback, structured results and vendor-auto default mode; the FTW target stays disabled pending runtime isolation and physical HIL
Expand All @@ -30,7 +36,7 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
- Versioning policy in manifest spec
- This changelog

### Changed
### Earlier changes
- All manifests: `tested_devices.model` renamed to `tested_devices.model_family`
- All manifests: version bumped from 1.0.0 to 1.1.0
- Manifest spec (`spec/manifest-v2.md`) updated with new tested_devices fields
Expand Down
22 changes: 12 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,34 @@ ARTIFACT_DIR ?= .artifacts/$(ID)
.PHONY: bootstrap new-driver test-driver package-driver check boundary

bootstrap:
uv sync --extra package --extra dev
uv sync --frozen --extra package --extra dev

new-driver:
uv run --extra package --extra dev python tools/new_driver.py --id "$(ID)" --protocol "$(PROTOCOL)" --kind "$(KIND)"
uv run --frozen --extra package --extra dev python tools/new_driver.py --id "$(ID)" --protocol "$(PROTOCOL)" --kind "$(KIND)"

test-driver:
test -n "$(ID)"
bash tools/build_luac.sh --with-interpreter ./luac55
./luac55 -p "drivers/lua/$(ID).lua"
bash tools/check_sandbox.sh "drivers/lua/$(ID).lua"
uv run --extra package --extra dev python tools/validate_manifest.py "manifests/$(ID).yaml"
uv run --extra package --extra dev pytest -q drivers/tests -k "$(ID)"
uv run --frozen --extra package --extra dev python tools/validate_manifest.py "manifests/$(ID).yaml"
uv run --frozen --extra package --extra dev pytest -q drivers/tests -k "$(ID)"

package-driver:
test -n "$(ID)"
uv run --extra package --extra dev python tools/build_candidate.py --id "$(ID)" --target "$(TARGET)" --output-dir "$(ARTIFACT_DIR)"
uv run --frozen --extra package --extra dev python tools/build_candidate.py --id "$(ID)" --target "$(TARGET)" --output-dir "$(ARTIFACT_DIR)"

boundary:
uv run --extra package --extra dev python tools/check_public_boundary.py
uv run --frozen --extra package --extra dev python tools/check_public_boundary.py

check: boundary
bash tools/build_luac.sh --with-interpreter ./luac55
uv run --extra package --extra dev python tools/validate_manifest.py
uv run --extra package --extra dev python tools/generate_index.py
uv run --frozen --extra package --extra dev python tools/validate_manifest.py
uv run --frozen --extra package --extra dev python tools/generate_index.py
git diff --exit-code -- index.yaml
uv run --extra package --extra dev python tools/generate_devices.py
uv run --frozen --extra package --extra dev python tools/generate_devices.py
git diff --exit-code -- devices.yaml
uv run --frozen --extra package --extra dev python tools/generate_support_status.py
git diff --exit-code -- support-status.json SUPPORT_STATUS.md
bash tools/check_sandbox.sh
uv run --extra package --extra dev pytest -q drivers/tests tests
uv run --frozen --extra package --extra dev pytest -q drivers/tests tests
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ beta from an exact reviewed commit through the private release service.

```text
public PR -> public CI -> reviewed commit -> private signer -> beta package
-> supervised site test -> exact-byte stable promotion
-> supervised site test -> stable envelope over the same artifacts
```

FTW and Blixt consume the signed package index. A public source pin in a host
Expand All @@ -58,6 +58,11 @@ release. Refreshing an index never installs or activates code. Each host keeps
its own safety and activation authority. Zap is a future target, not a current
production target. Hugin has no active runtime or registry role.

The catalog is not an install claim. See [SUPPORT_STATUS.md](SUPPORT_STATUS.md)
for source, target conformance, signed beta, HIL, stable and legacy parity per
driver and target. Nova and fleet inventory use the same driver, package and
target identities from [support-status.json](support-status.json).

## License

Sourceful-authored code is licensed under Apache-2.0. Vendored Lua 5.5 source
Expand Down
6 changes: 6 additions & 0 deletions SOURCE_IMPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,11 @@ The import includes driver source, public manifests, public contracts, package
recipes, tests and local validation tools. It excludes the service API,
database, admin UI, deployment, cloud release code and signing material.

The initial import also contains the reviewed path fixes and repository URL
changes listed in `source-import-delta.json`. It includes one behavior delta
for Ferroamp DC2 V2X field names. A focused host-contract test pins that change;
no Ferroamp package recipe or release is part of this cutover, and physical HIL
remains required before any signed Ferroamp artifact.

From this import onward, this public repository is the editable source. The
private service consumes a locked commit and must reject local source drift.
128 changes: 128 additions & 0 deletions SUPPORT_STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Driver support status

Catalog source is not proof that a target can install or run a driver.

| Driver | Catalog | Target | Conformance | Candidate | Signed beta | HIL | Stable | Legacy parity | Control |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| abb_meter | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| abb_meter | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| abb_terra | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| abb_terra | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| acrel | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| acrel | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| alfen | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| alfen | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| alphaess | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| alphaess | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ambibox | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ambibox | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| atmoce | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| atmoce | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| carlo_gavazzi | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| carlo_gavazzi | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| chint | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| chint | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| circutor | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| circutor | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| deye | 1.2.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| deye | 1.2.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| easee | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| etrel | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| etrel | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp_dc2_v2x | 1.0.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp_dc2_v2x | 1.0.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp_modbus | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| ferroamp_modbus | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| foxess | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| foxess | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius | 1.2.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius | 1.2.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius_api | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius_api | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius_smart_meter | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| fronius_smart_meter | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| goe | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| goe | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| goe_http | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| goe_http | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| goodwe | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| goodwe | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| growatt | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| growatt | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| hardybarth | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| hardybarth | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| hello | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| hello | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| huawei | 1.3.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| huawei | 1.3.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| janitza | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| janitza | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| keba | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| keba | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| kostal | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| kostal | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| kstar | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| kstar | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| mennekes | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| mennekes | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| opendtu | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| opendtu | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| opendtu_mqtt | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| opendtu_mqtt | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| openevse | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| openevse | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_dsmr | 2.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_dsmr | 2.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_encrypted | 2.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_encrypted | 2.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_hdlc | 2.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_hdlc | 2.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_meter | 2.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| p1_meter | 2.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| pixii | 1.1.0 | ftw-core | staged | 1.2.1 | — | required | — | blocked | no |
| pixii | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| saj | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| saj | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| schneider_meter | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| schneider_meter | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| schrack_ev | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| schrack_ev | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sdm630 | 1.1.2 | ftw-core | contract_passed | 1.1.2 | 1.1.1 | required_for_public_source_candidate | — | not_assessed | no |
| sdm630 | 1.1.2 | blixt-l1 | contract_passed | 1.1.2 | 1.1.1 | required_for_public_source_candidate | — | not_applicable_new_pilot | no |
| shelly | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| shelly | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| siemens_pac | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| siemens_pac | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sigenergy | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sigenergy | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sma | 1.2.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sma | 1.2.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| socomec | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| socomec | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sofar | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sofar | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solaredge | 1.2.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solaredge | 1.2.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solax | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solax | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solinteg | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solinteg | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solis | 1.2.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| solis | 1.2.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sonnen | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sonnen | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| sungrow | 1.2.0 | ftw-core | staged_control_off | 1.3.1 | 1.3.0 | required | — | not_assessed | no |
| sungrow | 1.2.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| varta | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| varta | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| victron | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| victron | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| victron_mqtt | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| victron_mqtt | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| wallbox | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| wallbox | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
| zaptec | 1.0.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no |
| zaptec | 1.0.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no |
2 changes: 1 addition & 1 deletion devices.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ manufacturers:
protocols:
- protocol: modbus
driver: "sdm630"
version: "1.1.1"
version: "1.1.2"
ders: [meter]
control: false
firmware_versions: ""
Expand Down
4 changes: 2 additions & 2 deletions drivers/lua/sdm630.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ DRIVER = {
id = "sdm630",
name = "Eastron SDM630 meter",
manufacturer = "Eastron",
version = "1.1.1",
version = "1.1.2",
protocols = { "modbus" },
capabilities = { "meter" },
read_only = true,
Expand All @@ -32,7 +32,7 @@ DRIVER = {

DRIVER_MANIFEST = {
name = "sdm630",
version = "1.1.1",
version = "1.1.2",
role = "meter",
requires = {},
options = {},
Expand Down
19 changes: 19 additions & 0 deletions drivers/tests/test_ferroamp_dc2_v2x_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Pin the reviewed Ferroamp DC2 V2X import delta to the host field contract."""

from conftest import read_driver


def test_ferroamp_dc2_uses_host_v2x_power_limit_fields() -> None:
code = read_driver("ferroamp_dc2_v2x")
expected = {
'charger.charge_power_max_W = snum("ev/limits/max_power")',
'charger.charge_power_min_W = snum("ev/limits/min_power")',
'charger.discharge_power_max_W = snum("ev/limits/max_discharge_power")',
}
legacy = {
"charger.ev_max_power_W",
"charger.ev_min_power_W",
"charger.ev_max_discharge_power_W",
}
assert all(field in code for field in expected)
assert all(field not in code for field in legacy)
Loading