From bdac8a6a41d435c51f19170d9a5eae183c8d6e48 Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Fri, 31 Jul 2026 12:23:59 +0200 Subject: [PATCH] fix(myuplink): file the heat pump as a heat pump myuplink declared ders: [], so the one MyUplink cloud heat-pump driver was absent from the Heat pump filter and from every other device type on the catalog page -- unreachable by the thing an owner actually searches for. It is now ders: [heatpump]. This revisits a deliberate choice. #27 widened the DER vocabulary and set myuplink to none, reading ders as the emit stream a driver produces rather than the device it covers. #53 settled it the other way for nibe_local, citing heishamon. All three report a pump through host.emit_metric and never call host.emit; myuplink and heishamon emit the same hp_* metric names. Two of the three already declared heatpump, so myuplink was the one left behind rather than the one holding a line. 1.1.0 -> 1.1.1 because ders is copied into the signed artifact's capabilities, so this changes the published bytes with no Lua moved -- the same reason nibe_local took 1.1.1. Unlike #53 the channel never got the chance to refuse it: the preflight check answers on the pull request, and putting the version back to 1.1.0 there reproduces the refusal. spec/manifest-v2.md documented four DER types where validate_manifest.py accepts seven. ev, heatpump and vehicle have been valid since #27 and are used by seven manifests, but the field table a driver author reads never listed them -- and the missing vocabulary is exactly what myuplink needed. The table is now complete and says what the field means for a metrics-only driver, which is the ambiguity that produced three different answers for three drivers doing the same thing. Signed-off-by: HuggeK <48095810+HuggeK@users.noreply.github.com> Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ SUPPORT_STATUS.md | 4 ++-- devices.yaml | 4 ++-- index.yaml | 4 ++-- manifests/myuplink.yaml | 4 ++-- spec/manifest-v2.md | 27 ++++++++++++++++++++++++++- support-status.json | 2 +- 7 files changed, 39 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9f379..d418e6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html - **`nibe_local` was filed as a meter.** Its manifest declared `ders: [meter]`; it reads a NIBE S-series heat pump. The driver emits nothing but metrics — `hp_power_w`, `hp_used_power_w`, the temperatures and the lifetime energy counters, all through `host.emit_metric` and never through `host.emit("meter", …)` — which its header states is deliberate, so that a pump's electrical draw cannot double-count against the site's real grid meter. The catalog was advertising a DER the driver does not provide, and the published page (#51) badges and filters on exactly that field: the pump sat under **Meter** and was absent from **Heat pump**, where an owner looking for it would start. Now `ders: [heatpump]`, matching `heishamon` — the other metric-only heat pump, which has been filed that way since it landed. The Lua source and its `sha256` are untouched, so the driver a host runs is byte-for-byte the one it ran before - **`nibe_local`** 1.1.0 → 1.1.1 — **the version has to move anyway, because the signed artifact carries this metadata.** `ders` is not catalog-only decoration: `_load_channel` copies it into the artifact's `capabilities` and the channel publishes source and metadata as one signed file, so correcting the field changed the published bytes. The channel then refused the release — `error: nibe_local: changed artifact needs a higher version than 1.1.0` — and it was right to, since that immutability is what lets an operator name the bytes a site is running. A patch rather than a minor: no register, field or emitted metric changed, only the DER the catalog states. The `DRIVER` table is deliberately left alone, keeping the driver byte-identical to its FTW baseline the way #29 established +- **`myuplink` was filed under no device type at all.** Its manifest declared `ders: []`, so the one MyUplink cloud heat-pump driver was absent from **Heat pump** and from every other filter on the catalog page — unreachable by device type, which is how an owner looks for their pump. It is now `ders: [heatpump]`, matching the other two heat pumps. This revisits a deliberate choice: when #27 widened the DER vocabulary it set `myuplink` to none, reading `ders` as the emit stream a driver produces rather than the device it covers. #53 then settled the question the other way for `nibe_local`, citing `heishamon` — and the three drivers are the same case. All three report a pump through `host.emit_metric` and never call `host.emit`, `myuplink` and `heishamon` emit the same `hp_*` metric names, and two of the three already declared `heatpump`. `myuplink` was the one left behind, not the one holding the line +- **`myuplink`** 1.1.0 → 1.1.1 — the version has to move, for the reason `nibe_local` 1.1.1 did: `ders` is copied into the signed artifact's `capabilities`, so this edit changes the published bytes even though no Lua moved. Unlike #53 the channel never got the chance to refuse it — the `signed channel accepts this tree` check answered on the pull request, and putting the version back to 1.1.0 there reproduces `myuplink: changed artifact needs a higher version than 1.1.0 (published)`. A patch: no register, parameter or emitted metric changed, only the device type the catalog files it under. The `DRIVER` table is left alone, keeping the driver byte-identical to its FTW baseline the way #29 established +- **`spec/manifest-v2.md` documented four DER types where the validator accepts seven.** `ev`, `heatpump` and `vehicle` have been valid since #27 and are used by seven manifests, but the field table a driver author reads never listed them — so the spec silently disagreed with `tools/validate_manifest.py`, and the vocabulary that was missing is exactly the one `myuplink` needed. The table is now complete and says what the field means for a metrics-only driver, which is the ambiguity that produced three different answers for three drivers doing the same thing. It also records that `ders` reaches the signed artifact, so changing it costs a version + ### Added - **A published driver catalog at [srcfl.github.io/device-drivers](https://srcfl.github.io/device-drivers/)** — all 80 drivers, searchable by manufacturer or model number and filterable by device type, protocol, tier and control. `tools/generate_site.py` builds it from the manifests and the Lua sources on every push to `main`, so no copy of this data is checked in and the page cannot fall behind what a host installs. It reports hardware evidence the way the repository does rather than the way a catalog usually does: 5 drivers state they have been confirmed against physical hardware and 28 state that they have not, and `tests/test_site.py` fails the build if a driver's `verification_status` is ever rendered as a stronger claim than the driver makes diff --git a/SUPPORT_STATUS.md b/SUPPORT_STATUS.md index c7e2421..e7c4672 100644 --- a/SUPPORT_STATUS.md +++ b/SUPPORT_STATUS.md @@ -84,8 +84,8 @@ Catalog source is not proof that a target can install or run a driver. | kstar | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | mennekes | 1.0.3 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | mennekes | 1.0.3 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | -| myuplink | 1.1.0 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | -| myuplink | 1.1.0 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | +| myuplink | 1.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | +| myuplink | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | nibe_local | 1.1.1 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | | nibe_local | 1.1.1 | blixt-l1 | not_assessed | — | — | not_recorded | — | not_assessed | no | | opendtu | 1.0.2 | ftw-core | not_assessed | — | — | not_recorded | — | not_assessed | no | diff --git a/devices.yaml b/devices.yaml index f0028ff..9946885 100644 --- a/devices.yaml +++ b/devices.yaml @@ -963,8 +963,8 @@ manufacturers: protocols: - protocol: http driver: "myuplink" - version: "1.1.0" - ders: [] + version: "1.1.1" + ders: [heatpump] control: true firmware_versions: "" notes: "Read-only heat-pump telemetry via MyUplink Cloud REST API v2: compressor power + hot-water/indoor/outdoor temperatures. Observe-only — no control. OAuth: authorization-code + refresh-token (connect in Settings → Devices)." diff --git a/index.yaml b/index.yaml index 088dea7..17978c4 100644 --- a/index.yaml +++ b/index.yaml @@ -321,10 +321,10 @@ drivers: size_bytes: 3766 sha256: "5f985b8917aea7b08fba13c02506b45e40232a763393dc6af7c6eeb6ab5853af" - name: "myuplink" - version: "1.1.0" + version: "1.1.1" tier: core protocol: http - ders: [] + ders: [heatpump] control: true size_bytes: 15278 sha256: "d754fd4af96c2047f9d9e458a776ad8f10510569836618f4631dd70e2510f404" diff --git a/manifests/myuplink.yaml b/manifests/myuplink.yaml index 00c8cd9..5f82e6d 100644 --- a/manifests/myuplink.yaml +++ b/manifests/myuplink.yaml @@ -1,9 +1,9 @@ name: "myuplink" -version: "1.1.0" +version: "1.1.1" tier: core author: "Sourceful Labs AB" protocol: http -ders: [] +ders: [heatpump] control: true tested_devices: - manufacturer: "MyUplink (NIBE, Bosch, Atlantic, Daikin, ...)" diff --git a/spec/manifest-v2.md b/spec/manifest-v2.md index 9167013..c1c76a6 100644 --- a/spec/manifest-v2.md +++ b/spec/manifest-v2.md @@ -11,7 +11,7 @@ Each driver has a YAML manifest in `manifests/` that describes its metadata, cap | `tier` | string | Yes | One of: `core`, `community`, `oem` | | `author` | string | Yes (core) | Author name or organization | | `protocol` | string | Yes | `modbus`, `mqtt`, `serial`, `standalone`, or `""` | -| `ders` | list | Yes | DER types: `pv`, `battery`, `meter`, `v2x_charger` | +| `ders` | list | Yes | DER types the driver covers (see below) | | `control` | bool | Yes | Whether the driver supports EMS control commands | | `tested_devices` | list | No | Devices tested against (see below) | | `upstream_docs` | list | No | Vendor reference documents to watch for register/protocol changes (see below) | @@ -25,6 +25,31 @@ Each driver has a YAML manifest in `manifests/` that describes its metadata, cap | `bytecode_size` | int | No | Size of the `.luac` bytecode file in bytes | | `changelog` | string | No | Version-specific release notes | +## DER Types + +`ders` states which kinds of distributed energy resource a driver covers. + +| Value | Device | +|-------|--------| +| `pv` | Solar PV | +| `battery` | Battery storage | +| `meter` | Electricity meter | +| `ev` | EV charger | +| `v2x_charger` | Bidirectional (V2X) charger | +| `heatpump` | Heat pump | +| `vehicle` | Vehicle | + +It describes **the device, not the emit function used to report it.** A driver +that reports a heat pump only through `host.emit_metric`, without ever calling +`host.emit("heatpump", …)`, still declares `heatpump`: the field is how the +catalog files a driver, and an owner looking for their pump searches by the +device they own. Declaring the empty list files a driver under nothing, so it +cannot be found by device type at all — that is for a driver which genuinely +covers no DER, not for one whose telemetry is metrics-only. + +`ders` reaches the signed artifact as its `capabilities`, so changing it changes +the published bytes and requires a version bump like any other source change. + ## Tested Devices The `tested_devices` list describes the device models a driver has been verified against. diff --git a/support-status.json b/support-status.json index d124f51..06b9aa3 100644 --- a/support-status.json +++ b/support-status.json @@ -1122,7 +1122,7 @@ }, { "catalog_source": true, - "catalog_version": "1.1.0", + "catalog_version": "1.1.1", "driver_id": "myuplink", "package_id": null, "targets": {