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: 0 additions & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
* @srcfl/core
/packages/ @srcfl/core
/spec/ @srcfl/core
/spec/control-runtime-v2.md @srcfl/core
/spec/schemas/sourceful.driver-command.v1.schema.json @srcfl/core
/spec/schemas/sourceful.driver-command-result.v1.schema.json @srcfl/core
/drivers/lua/ @srcfl/core
/.github/ @srcfl/core
12 changes: 3 additions & 9 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,16 @@ See CONTRIBUTING.md. -->
- [ ] Cached telemetry becomes stale instead of being re-emitted as fresh.
- [ ] No credentials, serial numbers, private addresses or site data are included.

## Package or promotion evidence
## Control evidence

Complete this part when a package version, target, control contract or release
candidate changes. Use `not applicable` for a driver-only read-only change.
Complete this part when a control path changes. Use `not applicable` for a
read-only change.

- Public source SHA:
- Package ID and new version:
- Target matrix:
- Material and candidate artifact SHA-256:
- HIL evidence or `required`:
- Control enabled: `false` unless all control gates have passed
- [ ] Control changes include default-mode, lease-expiry and HIL evidence.

## Checks

- [ ] Commits include `Signed-off-by`.
- [ ] `make test-driver ID=<id>`
- [ ] `make package-driver ID=<id> TARGET=<target>`
- [ ] `make check`
19 changes: 0 additions & 19 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,6 @@ jobs:
- run: git diff --exit-code -- support-status.json SUPPORT_STATUS.md
- run: uv run --no-sync pytest -q drivers/tests tests

packages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 1
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
with:
python-version: "3.12"
- run: uv sync --frozen --extra package --extra dev
- name: Build each unsigned package candidate
run: |
for source in packages/v1/*/package-source.json; do
id="$(basename "$(dirname "$source")")"
uv run --no-sync python tools/build_candidate.py \
--id "$id" \
--output-dir "/tmp/device-driver-candidates/$id"
done

# The sign-off certifies authored content, so this asks it of the commits that
# carry some, and skips merge commits.
#
Expand Down
31 changes: 12 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Device driver repository guide

This public repository is the only editable source for shared Sourceful device
drivers and the main driver source for FTW. It publishes FTW's signed driver
channel from reviewed commits. Device Support may later consume a locked commit
for other products or support levels, but it does not own a second source tree.
This public repository is FTW's driver repository and the only editable source
for its device drivers. It publishes FTW's signed driver channel from reviewed
commits.

## FTW product direction

Expand All @@ -30,33 +29,31 @@ and control acceptance gates below.
- Keep signing keys out of source, logs and build output.
- Public pull-request builds stay unsigned. The release workflow signs the FTW
channel only after a reviewed change reaches `main`.
- A catalog or package build never grants activation or control authority.
- A catalog build never grants activation or control authority.
- New drivers start read-only.
- Control needs a safe default mode, bounded leases, structured results and HIL
acceptance for every target host.

## Where a driver change has to land

A driver can exist in three places. Fixing one and leaving the others is how a
A driver can exist in two places. Fixing one and leaving the other is how a
fixed bug comes back.

1. **`drivers/lua/<id>.lua`** — the catalog driver. This is what the signed
channel publishes and what FTW bundles. Every fix starts here.
2. **`packages/v1/<id>/targets/*.lua`** — a separate file, not generated from
the catalog driver. Only some drivers have one. A package target carries its
own version line and can drift from the catalog copy without any check
noticing. If the driver you are fixing has one, fix both.
3. **FTW's `drivers/`** — a recovery snapshot, generated from this repository
channel publishes and what FTW bundles. Every fix starts here, and it is
the only copy of a driver in this repository.
2. **FTW's `drivers/`** — a recovery snapshot, generated from this repository
at the commit pinned in FTW's `drivers/BUNDLED_SOURCE.json`. Never edit a
driver there; FTW's own CI rejects the drift. But note the reverse: merging
here does **not** reach that snapshot. Someone has to move the pin and run
FTW's `scripts/sync-bundled-drivers.sh`. Until then a gateway booting
offline still runs the old driver.

Pixii's flap on register 40288 is the worked example. It was fixed in #16,
survived in the package target, was reverted in the catalog driver by #27, and
reached customer hardware a second time. See the entries for **pixii** 2.1.1
and **solaredge_legacy** 0.3.1 in `CHANGELOG.md`.
survived in a separate package-target copy, was reverted in the catalog driver
by #27, and reached customer hardware a second time. See the entries for
**pixii** 2.1.1 and **solaredge_legacy** 0.3.1 in `CHANGELOG.md`. Those copies
have since been removed.

### Editing a driver that came from FTW

Expand All @@ -82,17 +79,13 @@ rule may be right and the driver wrong, or the check itself may be wrong.
caught there rather than by a wrong value on a customer's site. A document
behind a login cannot be watched; reference it in a driver comment instead.
See `docs/WRITING-A-DRIVER.md`.
- Keep Lua compatible with every runtime declared in the package recipe.
- Package id, version, read-only state and target metadata must match the Lua
`DRIVER` block.

## Checks

Run the narrow driver command while editing, then the full check:

```bash
make test-driver ID=example
make package-driver ID=example TARGET=ftw-core
make check
```

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html

## [Unreleased]

### Removed

- **The Device Support package pilot and its format.** This is FTW's driver repository, and the pilot gave each packaged driver a third version line beside the catalog `DRIVER` version and the manifest version, under a package id that could not contain `_` where the catalog id does. Nothing published or consumed it: the release workflow never called the packager, CI built unsigned candidates only, and no release carries a package or index asset. Removed: `packages/v1/` (recipes for esphome-dsmr, foxess_h3_smart, pixii, sdm630 and sungrow, and the separate `pixii` and `sungrow` target files), `tools/driver_package.py`, `tools/build_candidate.py`, `tools/derive_sungrow_ftw_observe.py`, `make package-driver`, the `packages` CI job, the package-v1 specs and every `sourceful.driver-*` JSON schema, the driver inventory and Blixt reference specs, `package_id`, `candidate_package_version`, `stable_package_version` and `control_enabled` from `support-status.json`, and the four `support-status-overrides.json` entries (pixii, goodwe, sdm630, sungrow) that described the pilot's own package targets and candidate versions. The catalog page loses its always-empty "Stable package" column and those notes. `spec/control-runtime-v2.md` keeps only FTW Core's v2 command interface, and `spec/signing.md` only the FTW channel. Tests that ran a rule against both a catalog driver and its package target now run it against the catalog driver only. No driver source or version changed. A package can be rebuilt from the catalog driver the day one is needed; git history keeps the pilot at 489c937

### Added
- **nibe_local** 1.2.0 — the driver's first write path, and deliberately its only one: the pump's native **Solar PV surplus feed** ([srcfl/ftw#537](https://github.com/srcfl/ftw/issues/537)). The S-series was built to take a live "available solar power" number from NIBE's Modbus accessory (registers 2107/2109) and soak the surplus into heating and hot water using owner-tuned offsets; FTW now acts as that accessory. Control-by-hint: the pump's firmware decides what to do with the number, so a wrong value degrades to wasted comfort, never to unsafe operation. Off by default and triple-gated — host `capabilities.http.allow_write`, driver `write.solar_pv: true` with a mandatory `write.max_w` clamp ceiling, and the owner-side enable on the pump itself. The pump's timeout for a silently stopped feed is undocumented, so the driver does not lean on it: a dead-man's switch clears the feed when commands stop, `driver_default_mode` clears it on watchdog/stale-meter/stop, and a startup sweep clears a feed a crashed run left behind — and the sweep stays armed even when a config mistake (missing `max_w`) refuses new writes. All of that runs only while FTW runs, so the driver header documents the decommission step (turn 2107 off, or set the API read-only in menu 7.5.15) for the day it does not. The write API's most dangerous habit is covered too: the Local REST API rejects writes *inside an HTTP 200* ("error: read only value"), which the driver surfaces as an actionable error naming the installer menu (7.5.15) instead of reporting success. Requires `host.http_patch` from the FTW core; on older cores the driver states so and stays read-only. The `DRIVER` block declares the path as `write_capabilities = { "solar_pv" }`, which is what lets a host offer a switch for it: FTW's Settings screen renders the feed's controls only for a driver that states it has a write path, so the alternative was an owner hand-editing two keys in `config.yaml` to use the feature at all

Expand Down
11 changes: 3 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,12 @@ git checkout -b add-example-meter
make new-driver ID=example PROTOCOL=modbus KIND=meter
```

The generated package is read-only and targets FTW and Blixt through the shared
Lua 5.1 source profile. Edit the generated files rather than creating a second
manifest elsewhere.
The generated driver is read-only and comes with its manifest. Edit the
generated files rather than creating a second manifest elsewhere.

Every driver must:

- declare `DRIVER` metadata whose id and version match its package;
- declare `DRIVER` metadata whose id and version match its manifest;
- implement `driver_init`, `driver_poll`, `driver_cleanup` and a safe
`driver_default_mode` when control is later added;
- translate vendor signs at the driver boundary;
Expand Down Expand Up @@ -140,13 +139,9 @@ make history
```bash
make bootstrap
make test-driver ID=example
make package-driver ID=example TARGET=ftw-core
make check
```

The package command creates an unsigned candidate under `.artifacts/`. It does
not grant release or signing rights.

The FTW release build turns each source file into a signed, read-only Lua
artifact. The channel includes the full catalog and checks every generated
artifact against the FTW v1 lifecycle and host API. A merged source change
Expand Down
8 changes: 1 addition & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
ID ?=
PROTOCOL ?= modbus
KIND ?= meter
TARGET ?= ftw-core
ARTIFACT_DIR ?= .artifacts/$(ID)

LEVEL ?= patch

.PHONY: bootstrap new-driver test-driver package-driver check boundary \
.PHONY: bootstrap new-driver test-driver check boundary \
refused-write-report absent-register-report \
sync-manifests bump-driver history ftw-baseline ftw-baseline-report \
host-api site watch-upstream-docs
Expand Down Expand Up @@ -56,10 +54,6 @@ test-driver:
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 --frozen --extra package --extra dev python tools/build_candidate.py --id "$(ID)" --target "$(TARGET)" --output-dir "$(ARTIFACT_DIR)"

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

Expand Down
38 changes: 11 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Sourceful Device Drivers

This public repository is the source of truth for Sourceful device driver
code, package metadata, compatibility contracts and tests. It is also FTW's
main driver source. FTW installs signed, content-addressed release assets from
this repository. It never runs raw code from `main`.

Device Support may later consume an exact public commit to build packages for
other products or a higher support level. It does not own a second editable
copy of the source and is not FTW's normal driver source.
This public repository is FTW's driver repository: the source of truth for its
device driver code, catalog metadata and tests. FTW installs signed,
content-addressed release assets from this repository. It never runs raw code
from `main`.

## Browse the catalog

Expand Down Expand Up @@ -59,9 +55,7 @@ New drivers start with telemetry only. Control support needs a later,
separate review with a safe default mode, a bounded command lease, structured
results and supervised hardware-in-the-loop evidence.

Read [CONTRIBUTING.md](CONTRIBUTING.md) and
[spec/driver-package-v1.md](spec/driver-package-v1.md) before changing a
package contract.
Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.

## Scope

Expand All @@ -84,10 +78,6 @@ host provides, which `tools/host_api_check.py` enforces. Converting drivers to
a single spelling was tried and abandoned: it changed 196 lines across 36
field-proven drivers without changing what any of them does.

Zap is built on a separate track that compiles from this source. Its
constraints do not shape the drivers here, and it is not a target in these
package recipes.

What a driver may call is defined in [spec/host-api-profile.json](spec/host-api-profile.json)
and enforced by `make check`. A function outside the profile is not available,
whichever host it was tested against.
Expand All @@ -100,13 +90,12 @@ integrity, never hardware coverage.

This repository contains only public source and validation code:

- `drivers/lua` — shared Lua driver source;
- `drivers/lua` — Lua driver source;
- `manifests` — public catalog metadata and tested models;
- `packages/v1` — signed-package build recipes and host adapters;
- `spec` — package, inventory and command contracts;
- `drivers/tests` and `tests` — driver and package tests;
- `spec` — driver, host API, manifest and signing contracts;
- `drivers/tests` and `tests` — driver and tooling tests;
- `ftw-channel.json` — the rules for FTW's signed, read-only channel;
- `tools` — local validation, FTW release builds and unsigned package builds.
- `tools` — local validation and FTW release builds.

Private keys, credentials, cloud roles and service code stay outside this
repository. A pull request can produce unsigned test output only. GitHub
Expand Down Expand Up @@ -138,14 +127,9 @@ uv run python tools/ftw_download_stats.py

GitHub counts asset downloads, not unique users or active installs.

The separate package-v1 work remains available for Blixt and later Device
Support use. It can consume the same public commit without changing FTW's
default source.

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).
for target conformance, signed beta, HIL and legacy parity per driver and
target.

## License

Expand Down
5 changes: 2 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ advisory flow for this repository.
Never include credentials, signing material, private addresses, device serial
numbers or site telemetry in an issue, pull request, fixture or log.

The public build creates unsigned candidates. Only the private Device Support
release service holds release credentials and signs packages from reviewed,
locked commits.
Pull-request builds are unsigned. Only the release workflow on `main` signs
FTW's driver channel, with credentials held as GitHub Actions secrets.
5 changes: 2 additions & 3 deletions SOURCE_IMPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ 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.
From this import onward, this public repository is the editable source.

## FTW bundled drivers

Expand All @@ -25,7 +24,7 @@ hash, FTW driver id and version. `make check` verifies those hashes, so a
baseline cannot be edited by hand.

A baseline is a record, not a driver. Nothing under `baselines` reaches the
catalog, a package recipe or the signed channel, and every entry stays
catalog or the signed channel, and every entry stays
`live_activation: blocked`.

Re-import after a change in FTW:
Expand Down
Loading
Loading