From 5784a706b191f0a724236b8903035a349f4cb77f Mon Sep 17 00:00:00 2001 From: Claude Opus 5 Date: Fri, 31 Jul 2026 11:41:37 +0200 Subject: [PATCH] feat(catalog): say whether a driver needs the internet, and who has to unlock it protocol names a transport and answers neither question an owner asks first. nibe_local and myuplink are both protocol: http and both read a NIBE heat pump, and they are opposites: one reads the pump over the LAN, the other reads NIBE's cloud. The catalog could not tell them apart. Two fields, orthogonal to protocol rather than folded into it. Splitting protocol into http_local/http_online was the obvious move and the wrong one: protocol feeds PROTOCOL_PERMISSIONS, so new values change what a package build grants, and it does not generalise -- MQTT can be a cloud broker too. connectivity: local | cloud where the driver talks while running setup: [...] what a human must obtain first, and from whom Backfilled across all 80 drivers from what the sources do. 77 local, 3 cloud: myuplink, easee_cloud and tibber are the only drivers that hardcode a vendor endpoint. Every other HTTP driver builds its base URL from config, and the Modbus, MQTT and serial drivers name a vendor only as homepage. setup is the second axis, and a driver can be local and still locked until somebody unlocks the interface: nibe_local is enabled in the pump's own installer menu -- no app, no account -- while sonnen and CTEK are enabled in the device's web UI, sma_pv needs an installer, and eight drivers need a bridge in between. 17 have a recorded gate; 63 do not, and absent means unrecorded. The page renders that as "Not recorded", never "Nothing to set up", and only setup: [none] claims a device needs nothing but an address. tests/test_connectivity.py stops connectivity being decorative. A cloud endpoint must be hardcoded -- there is nowhere else for it to come from -- while a LAN address comes from config, so the test reads that asymmetry in both directions and separates exactly the three from the other 77. Neither field reaches the signed artifact: _load_channel copies an explicit allowlist and these are not in it. So no driver version moves, unlike the ders correction in #53 that #55 had to version. Signed-off-by: Hugo Karlsson <48095810+HuggeK@users.noreply.github.com> Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com> --- CHANGELOG.md | 7 ++ index.yaml | 97 +++++++++++++++++++++++++ manifests/abb_meter.yaml | 1 + manifests/abb_terra.yaml | 1 + manifests/acrel.yaml | 1 + manifests/alfen.yaml | 1 + manifests/alphaess.yaml | 1 + manifests/ambibox.yaml | 1 + manifests/ambibox_v2x.yaml | 1 + manifests/atmoce.yaml | 1 + manifests/carlo_gavazzi.yaml | 1 + manifests/chint.yaml | 1 + manifests/circutor.yaml | 1 + manifests/ctek.yaml | 2 + manifests/ctek_hybrid.yaml | 2 + manifests/ctek_v2.yaml | 2 + manifests/deye.yaml | 1 + manifests/easee.yaml | 1 + manifests/easee_cloud.yaml | 2 + manifests/esphome-dsmr.yaml | 2 + manifests/esphome_dsmr.yaml | 2 + manifests/etrel.yaml | 1 + manifests/ferroamp.yaml | 1 + manifests/ferroamp_dc2_v2x.yaml | 1 + manifests/ferroamp_modbus.yaml | 1 + manifests/foxess.yaml | 1 + manifests/fronius.yaml | 1 + manifests/fronius_api.yaml | 1 + manifests/fronius_smart_meter.yaml | 1 + manifests/goe.yaml | 1 + manifests/goe_http.yaml | 1 + manifests/goodwe.yaml | 2 + manifests/growatt.yaml | 1 + manifests/hardybarth.yaml | 1 + manifests/heishamon.yaml | 2 + manifests/hello.yaml | 1 + manifests/huawei.yaml | 1 + manifests/janitza.yaml | 1 + manifests/keba.yaml | 1 + manifests/kostal.yaml | 1 + manifests/kstar.yaml | 1 + manifests/mennekes.yaml | 1 + manifests/myuplink.yaml | 2 + manifests/nibe_local.yaml | 2 + manifests/opendtu.yaml | 2 + manifests/opendtu_mqtt.yaml | 2 + manifests/openevse.yaml | 1 + manifests/p1_dsmr.yaml | 1 + manifests/p1_encrypted.yaml | 1 + manifests/p1_hdlc.yaml | 1 + manifests/p1_meter.yaml | 1 + manifests/pixii.yaml | 1 + manifests/pixii_pv.yaml | 1 + manifests/saj.yaml | 1 + manifests/schneider_meter.yaml | 1 + manifests/schrack_ev.yaml | 1 + manifests/sdm630.yaml | 1 + manifests/shelly.yaml | 1 + manifests/siemens_pac.yaml | 1 + manifests/sigenergy.yaml | 1 + manifests/sma.yaml | 1 + manifests/sma_pv.yaml | 2 + manifests/socomec.yaml | 1 + manifests/sofar.yaml | 1 + manifests/solaredge.yaml | 1 + manifests/solaredge_legacy.yaml | 1 + manifests/solaredge_pv.yaml | 1 + manifests/solax.yaml | 1 + manifests/solinteg.yaml | 1 + manifests/solis.yaml | 1 + manifests/solis_string.yaml | 1 + manifests/sonnen.yaml | 2 + manifests/sungrow.yaml | 1 + manifests/tesla_vehicle.yaml | 2 + manifests/tibber.yaml | 2 + manifests/varta.yaml | 1 + manifests/victron.yaml | 1 + manifests/victron_mqtt.yaml | 1 + manifests/wallbox.yaml | 1 + manifests/zap.yaml | 1 + manifests/zaptec.yaml | 1 + manifests/zuidwijk_p1.yaml | 2 + spec/manifest-v2.md | 59 ++++++++++++++- tests/test_connectivity.py | 113 +++++++++++++++++++++++++++++ tools/generate_index.py | 7 ++ tools/generate_site.py | 66 ++++++++++++++++- tools/new_driver.py | 7 ++ tools/validate_manifest.py | 46 +++++++++++- 88 files changed, 491 insertions(+), 8 deletions(-) create mode 100644 tests/test_connectivity.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0362f80..f9c2864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html ## [Unreleased] +### Added +- **Every manifest now states where its driver talks, and what a manufacturer demands before it will talk at all** — `connectivity: local | cloud` plus an optional `setup:` list. `protocol` answered neither question: `nibe_local` and `myuplink` are both `protocol: http` and both read a NIBE heat pump, and they are opposites — one reads the pump over the LAN, the other reads NIBE's cloud. Backfilled across all 80 drivers from what the sources do: **77 local, 3 cloud** (`myuplink`, `easee_cloud`, `tibber`, the only drivers that hardcode a vendor endpoint — every other HTTP driver builds its base URL from config, and all 51 Modbus, 8 MQTT and 4 serial drivers name a vendor only as `homepage`) +- `setup` is the second axis and the more interesting one, because a driver can be `local` and still locked until somebody unlocks the interface. `device_screen` (nibe_local, enabled in the pump's own installer menu — no app, no account, confirmed on an S735), `device_ui` (sonnen's JSON API and token, CTEK's `ModbusTCPEnable`), `installer` (sma_pv's "one-time installer step in the WebUI / Sunny Portal"), `vendor_portal` (the three cloud drivers), `bridge` (heishamon, opendtu ×2, esphome_dsmr ×2, tesla_vehicle's TeslaBleHttpProxy, zuidwijk_p1, and goodwe's required dongle). 17 drivers carry a recorded gate; 63 do not +- **An absent `setup` means unrecorded, and the page says exactly that.** It renders "Not recorded", never "Nothing to set up", and a driver with none drops out of the setup filter rather than passing as though it needed nothing. Only `setup: [none]` is a claim, and nothing claims it yet. The same reason `verification_status` is never flattened into a "verified" tick: the catalog reports what is known, not what would look complete +- `tests/test_connectivity.py` keeps `connectivity` checkable rather than decorative. A cloud endpoint has to be hardcoded — there is nowhere else for it to come from — while a LAN address arrives from config, so the test reads that asymmetry: a driver claiming `local` must build no request against a public hostname, and one claiming `cloud` must name the service it needs. On the current catalog it separates exactly the three cloud drivers from the other 77. It also requires a cloud driver to record what its vendor demands, so nobody meets the account requirement only after installing +- The catalog page gains a **Reach** column and two filter groups, putting "works without the internet" and "needs a bridge device" one click away. Neither field reaches the signed artifact — `_load_channel` copies an explicit allowlist and these are not in it — so unlike `ders`, correcting one costs no driver version + ### Fixed - **`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 diff --git a/index.yaml b/index.yaml index 088dea7..0cdbd20 100644 --- a/index.yaml +++ b/index.yaml @@ -4,6 +4,7 @@ drivers: version: "1.0.6" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 3865 @@ -12,6 +13,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 3779 @@ -20,6 +22,7 @@ drivers: version: "1.0.4" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 3412 @@ -28,6 +31,7 @@ drivers: version: "1.0.4" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 4649 @@ -36,6 +40,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 4031 @@ -44,6 +49,7 @@ drivers: version: "1.2.0" tier: core protocol: mqtt + connectivity: local ders: [battery, meter, v2x_charger] control: true size_bytes: 7203 @@ -52,6 +58,7 @@ drivers: version: "1.1.0" tier: core protocol: mqtt + connectivity: local ders: [v2x_charger] control: true size_bytes: 7506 @@ -60,6 +67,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 6790 @@ -68,6 +76,7 @@ drivers: version: "1.0.5" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 4491 @@ -76,6 +85,7 @@ drivers: version: "1.0.5" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 4047 @@ -84,6 +94,7 @@ drivers: version: "1.0.5" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 4749 @@ -92,6 +103,8 @@ drivers: version: "0.3.1" tier: core protocol: modbus + connectivity: local + setup: [device_ui] ders: [ev] control: true size_bytes: 14921 @@ -100,6 +113,8 @@ drivers: version: "0.3.1" tier: core protocol: modbus + connectivity: local + setup: [device_ui] ders: [ev] control: true size_bytes: 27291 @@ -108,6 +123,8 @@ drivers: version: "0.3.1" tier: core protocol: modbus + connectivity: local + setup: [device_ui] ders: [ev] control: true size_bytes: 12072 @@ -116,6 +133,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: false size_bytes: 23467 @@ -124,6 +142,7 @@ drivers: version: "1.0.4" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 4054 @@ -132,6 +151,8 @@ drivers: version: "1.1.0" tier: core protocol: http + connectivity: cloud + setup: [vendor_portal] ders: [ev] control: true size_bytes: 31312 @@ -140,6 +161,8 @@ drivers: version: "1.0.3" tier: community protocol: http + connectivity: local + setup: [bridge] ders: [meter] control: false size_bytes: 16772 @@ -148,6 +171,8 @@ drivers: version: "1.0.3" tier: core protocol: http + connectivity: local + setup: [bridge] ders: [meter] control: true size_bytes: 19353 @@ -156,6 +181,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 3545 @@ -164,6 +190,7 @@ drivers: version: "2.1.0" tier: core protocol: mqtt + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 55058 @@ -172,6 +199,7 @@ drivers: version: "2.1.0" tier: core protocol: mqtt + connectivity: local ders: [v2x_charger] control: true size_bytes: 13321 @@ -180,6 +208,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 14450 @@ -188,6 +217,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 6933 @@ -196,6 +226,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: false size_bytes: 12721 @@ -204,6 +235,7 @@ drivers: version: "1.0.2" tier: community protocol: http + connectivity: local ders: [pv, meter] control: false size_bytes: 2080 @@ -212,6 +244,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 7521 @@ -220,6 +253,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 3700 @@ -228,6 +262,7 @@ drivers: version: "1.0.1" tier: community protocol: http + connectivity: local ders: [v2x_charger] control: false size_bytes: 3340 @@ -236,6 +271,8 @@ drivers: version: "2.1.1" tier: community protocol: modbus + connectivity: local + setup: [bridge] ders: [pv, battery, meter] control: false size_bytes: 9543 @@ -244,6 +281,7 @@ drivers: version: "2.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 9353 @@ -252,6 +290,7 @@ drivers: version: "1.0.1" tier: community protocol: http + connectivity: local ders: [v2x_charger] control: false size_bytes: 2193 @@ -260,6 +299,8 @@ drivers: version: "0.5.0" tier: core protocol: mqtt + connectivity: local + setup: [bridge] ders: [heatpump] control: true size_bytes: 6973 @@ -268,6 +309,7 @@ drivers: version: "1.1.2" tier: community protocol: "" + connectivity: local ders: [meter] control: false size_bytes: 1362 @@ -276,6 +318,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 15257 @@ -284,6 +327,7 @@ drivers: version: "1.0.5" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 4524 @@ -292,6 +336,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 4620 @@ -300,6 +345,7 @@ drivers: version: "2.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 11199 @@ -308,6 +354,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 3615 @@ -316,6 +363,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 3766 @@ -324,6 +372,8 @@ drivers: version: "1.1.0" tier: core protocol: http + connectivity: cloud + setup: [vendor_portal] ders: [] control: true size_bytes: 15278 @@ -332,6 +382,8 @@ drivers: version: "1.1.1" tier: core protocol: http + connectivity: local + setup: [device_screen] ders: [heatpump] control: true size_bytes: 18227 @@ -340,6 +392,8 @@ drivers: version: "1.0.2" tier: community protocol: http + connectivity: local + setup: [bridge] ders: [pv, meter] control: false size_bytes: 3766 @@ -348,6 +402,8 @@ drivers: version: "1.0.3" tier: community protocol: mqtt + connectivity: local + setup: [bridge] ders: [pv, meter] control: false size_bytes: 4009 @@ -356,6 +412,7 @@ drivers: version: "1.0.1" tier: community protocol: http + connectivity: local ders: [v2x_charger] control: false size_bytes: 2446 @@ -364,6 +421,7 @@ drivers: version: "2.0.2" tier: core protocol: serial + connectivity: local ders: [meter] control: false size_bytes: 7201 @@ -372,6 +430,7 @@ drivers: version: "2.0.2" tier: core protocol: serial + connectivity: local ders: [meter] control: false size_bytes: 11071 @@ -380,6 +439,7 @@ drivers: version: "2.0.2" tier: core protocol: serial + connectivity: local ders: [meter] control: false size_bytes: 9203 @@ -388,6 +448,7 @@ drivers: version: "2.0.2" tier: core protocol: serial + connectivity: local ders: [meter] control: false size_bytes: 19344 @@ -396,6 +457,7 @@ drivers: version: "2.1.3" tier: core protocol: modbus + connectivity: local ders: [battery, meter] control: false size_bytes: 27853 @@ -404,6 +466,7 @@ drivers: version: "0.3.0" tier: core protocol: mqtt + connectivity: local ders: [pv, meter] control: true size_bytes: 6692 @@ -412,6 +475,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 4855 @@ -420,6 +484,7 @@ drivers: version: "1.0.5" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 4615 @@ -428,6 +493,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 3554 @@ -436,6 +502,7 @@ drivers: version: "2.0.1" tier: core protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 5488 @@ -444,6 +511,7 @@ drivers: version: "1.1.2" tier: community protocol: http + connectivity: local ders: [meter] control: false size_bytes: 11878 @@ -452,6 +520,7 @@ drivers: version: "1.0.5" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 4376 @@ -460,6 +529,7 @@ drivers: version: "1.1.2" tier: community protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 4841 @@ -468,6 +538,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: false size_bytes: 13951 @@ -476,6 +547,8 @@ drivers: version: "1.1.1" tier: core protocol: modbus + connectivity: local + setup: [installer] ders: [pv, meter] control: true size_bytes: 19567 @@ -484,6 +557,7 @@ drivers: version: "1.0.4" tier: community protocol: modbus + connectivity: local ders: [meter] control: false size_bytes: 3818 @@ -492,6 +566,7 @@ drivers: version: "2.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 9083 @@ -500,6 +575,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [meter, pv] control: false size_bytes: 21555 @@ -508,6 +584,7 @@ drivers: version: "0.3.2" tier: core protocol: modbus + connectivity: local ders: [pv] control: true size_bytes: 18246 @@ -516,6 +593,7 @@ drivers: version: "1.2.1" tier: core protocol: modbus + connectivity: local ders: [pv] control: true size_bytes: 12121 @@ -524,6 +602,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 5110 @@ -532,6 +611,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 8837 @@ -540,6 +620,7 @@ drivers: version: "2.1.1" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 17463 @@ -548,6 +629,7 @@ drivers: version: "1.1.1" tier: core protocol: modbus + connectivity: local ders: [pv] control: true size_bytes: 5784 @@ -556,6 +638,8 @@ drivers: version: "2.0.1" tier: community protocol: http + connectivity: local + setup: [device_ui] ders: [battery, meter] control: false size_bytes: 6158 @@ -564,6 +648,7 @@ drivers: version: "1.5.7" tier: core protocol: modbus + connectivity: local ders: [battery, meter, pv] control: true size_bytes: 48889 @@ -572,6 +657,8 @@ drivers: version: "0.2.0" tier: core protocol: http + connectivity: local + setup: [bridge] ders: [vehicle] control: true size_bytes: 23154 @@ -580,6 +667,8 @@ drivers: version: "1.1.0" tier: core protocol: http + connectivity: cloud + setup: [vendor_portal] ders: [meter] control: true size_bytes: 12410 @@ -588,6 +677,7 @@ drivers: version: "1.1.1" tier: community protocol: modbus + connectivity: local ders: [battery, meter] control: false size_bytes: 2794 @@ -596,6 +686,7 @@ drivers: version: "2.1.1" tier: community protocol: modbus + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 7998 @@ -604,6 +695,7 @@ drivers: version: "1.0.3" tier: community protocol: mqtt + connectivity: local ders: [pv, battery, meter] control: false size_bytes: 5779 @@ -612,6 +704,7 @@ drivers: version: "1.0.3" tier: community protocol: modbus + connectivity: local ders: [v2x_charger] control: false size_bytes: 2842 @@ -620,6 +713,7 @@ drivers: version: "2.0.1" tier: core protocol: http + connectivity: local ders: [meter, pv, battery, v2x_charger] control: true size_bytes: 30514 @@ -628,6 +722,7 @@ drivers: version: "1.0.1" tier: community protocol: http + connectivity: local ders: [v2x_charger] control: false size_bytes: 2281 @@ -636,6 +731,8 @@ drivers: version: "1.1.0" tier: core protocol: modbus + connectivity: local + setup: [bridge] ders: [meter] control: true size_bytes: 16217 diff --git a/manifests/abb_meter.yaml b/manifests/abb_meter.yaml index 738b4e1..c939464 100644 --- a/manifests/abb_meter.yaml +++ b/manifests/abb_meter.yaml @@ -3,6 +3,7 @@ version: "1.0.6" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/abb_terra.yaml b/manifests/abb_terra.yaml index 8fd6c82..99b9c90 100644 --- a/manifests/abb_terra.yaml +++ b/manifests/abb_terra.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/acrel.yaml b/manifests/acrel.yaml index 5f8c348..537279a 100644 --- a/manifests/acrel.yaml +++ b/manifests/acrel.yaml @@ -3,6 +3,7 @@ version: "1.0.4" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/alfen.yaml b/manifests/alfen.yaml index fd8cf69..5d78406 100644 --- a/manifests/alfen.yaml +++ b/manifests/alfen.yaml @@ -3,6 +3,7 @@ version: "1.0.4" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/alphaess.yaml b/manifests/alphaess.yaml index 58d6941..ecab864 100644 --- a/manifests/alphaess.yaml +++ b/manifests/alphaess.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/ambibox.yaml b/manifests/ambibox.yaml index 5842dfb..89f09c6 100644 --- a/manifests/ambibox.yaml +++ b/manifests/ambibox.yaml @@ -3,6 +3,7 @@ version: "1.2.0" tier: core author: "Sourceful Labs AB" protocol: mqtt +connectivity: local ders: [battery, meter, v2x_charger] control: true tested_devices: diff --git a/manifests/ambibox_v2x.yaml b/manifests/ambibox_v2x.yaml index 318f041..a426ab3 100644 --- a/manifests/ambibox_v2x.yaml +++ b/manifests/ambibox_v2x.yaml @@ -3,6 +3,7 @@ version: "1.1.0" tier: core author: "Sourceful Labs AB" protocol: mqtt +connectivity: local ders: [v2x_charger] control: true tested_devices: diff --git a/manifests/atmoce.yaml b/manifests/atmoce.yaml index f761875..3c778dd 100644 --- a/manifests/atmoce.yaml +++ b/manifests/atmoce.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/carlo_gavazzi.yaml b/manifests/carlo_gavazzi.yaml index a3dbcc7..c218f08 100644 --- a/manifests/carlo_gavazzi.yaml +++ b/manifests/carlo_gavazzi.yaml @@ -3,6 +3,7 @@ version: "1.0.5" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/chint.yaml b/manifests/chint.yaml index 9a432fb..622d13c 100644 --- a/manifests/chint.yaml +++ b/manifests/chint.yaml @@ -3,6 +3,7 @@ version: "1.0.5" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/circutor.yaml b/manifests/circutor.yaml index 4c22f76..0297174 100644 --- a/manifests/circutor.yaml +++ b/manifests/circutor.yaml @@ -3,6 +3,7 @@ version: "1.0.5" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/ctek.yaml b/manifests/ctek.yaml index beaab2e..50782cc 100644 --- a/manifests/ctek.yaml +++ b/manifests/ctek.yaml @@ -3,6 +3,8 @@ version: "0.3.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local +setup: [device_ui] ders: [ev] control: true tested_devices: diff --git a/manifests/ctek_hybrid.yaml b/manifests/ctek_hybrid.yaml index 594caee..f5c97e9 100644 --- a/manifests/ctek_hybrid.yaml +++ b/manifests/ctek_hybrid.yaml @@ -3,6 +3,8 @@ version: "0.3.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local +setup: [device_ui] ders: [ev] control: true tested_devices: diff --git a/manifests/ctek_v2.yaml b/manifests/ctek_v2.yaml index ec19184..e167712 100644 --- a/manifests/ctek_v2.yaml +++ b/manifests/ctek_v2.yaml @@ -3,6 +3,8 @@ version: "0.3.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local +setup: [device_ui] ders: [ev] control: true tested_devices: diff --git a/manifests/deye.yaml b/manifests/deye.yaml index 7c29124..2211f2a 100644 --- a/manifests/deye.yaml +++ b/manifests/deye.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: false tested_devices: diff --git a/manifests/easee.yaml b/manifests/easee.yaml index 6e52490..d6ba07c 100644 --- a/manifests/easee.yaml +++ b/manifests/easee.yaml @@ -3,6 +3,7 @@ version: "1.0.4" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/easee_cloud.yaml b/manifests/easee_cloud.yaml index 535c7ff..4916c5f 100644 --- a/manifests/easee_cloud.yaml +++ b/manifests/easee_cloud.yaml @@ -3,6 +3,8 @@ version: "1.1.0" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: cloud +setup: [vendor_portal] ders: [ev] control: true tested_devices: diff --git a/manifests/esphome-dsmr.yaml b/manifests/esphome-dsmr.yaml index de34f28..a3e28e0 100644 --- a/manifests/esphome-dsmr.yaml +++ b/manifests/esphome-dsmr.yaml @@ -3,6 +3,8 @@ version: "1.0.3" tier: community author: "FTW contributors" protocol: http +connectivity: local +setup: [bridge] ders: [meter] control: false tested_devices: diff --git a/manifests/esphome_dsmr.yaml b/manifests/esphome_dsmr.yaml index d772c0a..a6c0ade 100644 --- a/manifests/esphome_dsmr.yaml +++ b/manifests/esphome_dsmr.yaml @@ -3,6 +3,8 @@ version: "1.0.3" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: local +setup: [bridge] ders: [meter] control: true tested_devices: diff --git a/manifests/etrel.yaml b/manifests/etrel.yaml index 3ca702c..b908e63 100644 --- a/manifests/etrel.yaml +++ b/manifests/etrel.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/ferroamp.yaml b/manifests/ferroamp.yaml index 9c30a2e..38d6e7e 100644 --- a/manifests/ferroamp.yaml +++ b/manifests/ferroamp.yaml @@ -3,6 +3,7 @@ version: "2.1.0" tier: core author: "Sourceful Labs AB" protocol: mqtt +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/ferroamp_dc2_v2x.yaml b/manifests/ferroamp_dc2_v2x.yaml index da83ad9..9161c1b 100644 --- a/manifests/ferroamp_dc2_v2x.yaml +++ b/manifests/ferroamp_dc2_v2x.yaml @@ -3,6 +3,7 @@ version: "2.1.0" tier: core author: "Sourceful Labs AB" protocol: mqtt +connectivity: local ders: [v2x_charger] control: true tested_devices: diff --git a/manifests/ferroamp_modbus.yaml b/manifests/ferroamp_modbus.yaml index 5b499a1..5f93569 100644 --- a/manifests/ferroamp_modbus.yaml +++ b/manifests/ferroamp_modbus.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/foxess.yaml b/manifests/foxess.yaml index bee3b97..d717b13 100644 --- a/manifests/foxess.yaml +++ b/manifests/foxess.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/fronius.yaml b/manifests/fronius.yaml index b8cff23..2395f81 100644 --- a/manifests/fronius.yaml +++ b/manifests/fronius.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: false tested_devices: diff --git a/manifests/fronius_api.yaml b/manifests/fronius_api.yaml index f5ef108..88aa841 100644 --- a/manifests/fronius_api.yaml +++ b/manifests/fronius_api.yaml @@ -3,6 +3,7 @@ version: "1.0.2" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [pv, meter] control: false tested_devices: diff --git a/manifests/fronius_smart_meter.yaml b/manifests/fronius_smart_meter.yaml index 991088e..3d88d58 100644 --- a/manifests/fronius_smart_meter.yaml +++ b/manifests/fronius_smart_meter.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/goe.yaml b/manifests/goe.yaml index 2421310..ab17290 100644 --- a/manifests/goe.yaml +++ b/manifests/goe.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/goe_http.yaml b/manifests/goe_http.yaml index 79529e7..ab313bc 100644 --- a/manifests/goe_http.yaml +++ b/manifests/goe_http.yaml @@ -3,6 +3,7 @@ version: "1.0.1" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/goodwe.yaml b/manifests/goodwe.yaml index df4c59f..8eb6abd 100644 --- a/manifests/goodwe.yaml +++ b/manifests/goodwe.yaml @@ -3,6 +3,8 @@ version: "2.1.1" tier: community author: "frekes81 and Sourceful contributors" protocol: modbus +connectivity: local +setup: [bridge] ders: [pv, battery, meter] control: false tested_devices: [] diff --git a/manifests/growatt.yaml b/manifests/growatt.yaml index 3c9f73c..8375288 100644 --- a/manifests/growatt.yaml +++ b/manifests/growatt.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/hardybarth.yaml b/manifests/hardybarth.yaml index 498fd46..7288aa7 100644 --- a/manifests/hardybarth.yaml +++ b/manifests/hardybarth.yaml @@ -3,6 +3,7 @@ version: "1.0.1" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/heishamon.yaml b/manifests/heishamon.yaml index 85d0263..3363c4f 100644 --- a/manifests/heishamon.yaml +++ b/manifests/heishamon.yaml @@ -3,6 +3,8 @@ version: "0.5.0" tier: core author: "Sourceful Labs AB" protocol: mqtt +connectivity: local +setup: [bridge] ders: [heatpump] control: true tested_devices: diff --git a/manifests/hello.yaml b/manifests/hello.yaml index 5c98ab6..19bb79d 100644 --- a/manifests/hello.yaml +++ b/manifests/hello.yaml @@ -3,6 +3,7 @@ version: "1.1.2" tier: community author: "" protocol: "" +connectivity: local ders: [meter] control: false tested_devices: [] diff --git a/manifests/huawei.yaml b/manifests/huawei.yaml index 6842f9c..aef4e01 100644 --- a/manifests/huawei.yaml +++ b/manifests/huawei.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/janitza.yaml b/manifests/janitza.yaml index 6c06a97..ba1822e 100644 --- a/manifests/janitza.yaml +++ b/manifests/janitza.yaml @@ -3,6 +3,7 @@ version: "1.0.5" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/keba.yaml b/manifests/keba.yaml index 853bfcc..101f35d 100644 --- a/manifests/keba.yaml +++ b/manifests/keba.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/kostal.yaml b/manifests/kostal.yaml index 42730b2..501807e 100644 --- a/manifests/kostal.yaml +++ b/manifests/kostal.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/kstar.yaml b/manifests/kstar.yaml index c788ad1..d61c05c 100644 --- a/manifests/kstar.yaml +++ b/manifests/kstar.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/mennekes.yaml b/manifests/mennekes.yaml index 5935236..678a463 100644 --- a/manifests/mennekes.yaml +++ b/manifests/mennekes.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/myuplink.yaml b/manifests/myuplink.yaml index 17090d3..7dacb2e 100644 --- a/manifests/myuplink.yaml +++ b/manifests/myuplink.yaml @@ -3,6 +3,8 @@ version: "1.1.0" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: cloud +setup: [vendor_portal] ders: [] control: true tested_devices: diff --git a/manifests/nibe_local.yaml b/manifests/nibe_local.yaml index 5e9d478..7a11abb 100644 --- a/manifests/nibe_local.yaml +++ b/manifests/nibe_local.yaml @@ -3,6 +3,8 @@ version: "1.1.1" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: local +setup: [device_screen] ders: [heatpump] control: true tested_devices: diff --git a/manifests/opendtu.yaml b/manifests/opendtu.yaml index 09daee6..420a0f1 100644 --- a/manifests/opendtu.yaml +++ b/manifests/opendtu.yaml @@ -3,6 +3,8 @@ version: "1.0.2" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local +setup: [bridge] ders: [pv, meter] control: false tested_devices: diff --git a/manifests/opendtu_mqtt.yaml b/manifests/opendtu_mqtt.yaml index 3c33348..526f0a3 100644 --- a/manifests/opendtu_mqtt.yaml +++ b/manifests/opendtu_mqtt.yaml @@ -3,6 +3,8 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: mqtt +connectivity: local +setup: [bridge] ders: [pv, meter] control: false tested_devices: diff --git a/manifests/openevse.yaml b/manifests/openevse.yaml index 3838844..4b2aea7 100644 --- a/manifests/openevse.yaml +++ b/manifests/openevse.yaml @@ -3,6 +3,7 @@ version: "1.0.1" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/p1_dsmr.yaml b/manifests/p1_dsmr.yaml index 3b24ccc..b8c0ed6 100644 --- a/manifests/p1_dsmr.yaml +++ b/manifests/p1_dsmr.yaml @@ -3,6 +3,7 @@ version: "2.0.2" tier: core author: "Sourceful Labs AB" protocol: serial +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/p1_encrypted.yaml b/manifests/p1_encrypted.yaml index 6facc91..5eca352 100644 --- a/manifests/p1_encrypted.yaml +++ b/manifests/p1_encrypted.yaml @@ -3,6 +3,7 @@ version: "2.0.2" tier: core author: "Sourceful Labs AB" protocol: serial +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/p1_hdlc.yaml b/manifests/p1_hdlc.yaml index 4a50171..f171670 100644 --- a/manifests/p1_hdlc.yaml +++ b/manifests/p1_hdlc.yaml @@ -3,6 +3,7 @@ version: "2.0.2" tier: core author: "Sourceful Labs AB" protocol: serial +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/p1_meter.yaml b/manifests/p1_meter.yaml index b46ee78..1147434 100644 --- a/manifests/p1_meter.yaml +++ b/manifests/p1_meter.yaml @@ -3,6 +3,7 @@ version: "2.0.2" tier: core author: "Sourceful Labs AB" protocol: serial +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/pixii.yaml b/manifests/pixii.yaml index bcaa567..429fc58 100644 --- a/manifests/pixii.yaml +++ b/manifests/pixii.yaml @@ -3,6 +3,7 @@ version: "2.1.3" tier: core author: "Sourceful Labs AB and community contributors" protocol: modbus +connectivity: local ders: [battery, meter] control: false tested_devices: diff --git a/manifests/pixii_pv.yaml b/manifests/pixii_pv.yaml index 1fd21eb..26967cf 100644 --- a/manifests/pixii_pv.yaml +++ b/manifests/pixii_pv.yaml @@ -3,6 +3,7 @@ version: "0.3.0" tier: core author: "Sourceful Labs AB" protocol: mqtt +connectivity: local ders: [pv, meter] control: true tested_devices: diff --git a/manifests/saj.yaml b/manifests/saj.yaml index 3bd1483..0e7724a 100644 --- a/manifests/saj.yaml +++ b/manifests/saj.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/schneider_meter.yaml b/manifests/schneider_meter.yaml index cae861e..e98fcc1 100644 --- a/manifests/schneider_meter.yaml +++ b/manifests/schneider_meter.yaml @@ -3,6 +3,7 @@ version: "1.0.5" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/schrack_ev.yaml b/manifests/schrack_ev.yaml index ab443c5..be851a8 100644 --- a/manifests/schrack_ev.yaml +++ b/manifests/schrack_ev.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/sdm630.yaml b/manifests/sdm630.yaml index f48f800..2cf04a4 100644 --- a/manifests/sdm630.yaml +++ b/manifests/sdm630.yaml @@ -3,6 +3,7 @@ version: "2.0.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/shelly.yaml b/manifests/shelly.yaml index 43156be..9e500b2 100644 --- a/manifests/shelly.yaml +++ b/manifests/shelly.yaml @@ -3,6 +3,7 @@ version: "1.1.2" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/siemens_pac.yaml b/manifests/siemens_pac.yaml index a10fddc..1f60325 100644 --- a/manifests/siemens_pac.yaml +++ b/manifests/siemens_pac.yaml @@ -3,6 +3,7 @@ version: "1.0.5" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/sigenergy.yaml b/manifests/sigenergy.yaml index 1c318f6..dec5550 100644 --- a/manifests/sigenergy.yaml +++ b/manifests/sigenergy.yaml @@ -3,6 +3,7 @@ version: "1.1.2" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/sma.yaml b/manifests/sma.yaml index def2d36..7bd64fb 100644 --- a/manifests/sma.yaml +++ b/manifests/sma.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: false tested_devices: diff --git a/manifests/sma_pv.yaml b/manifests/sma_pv.yaml index 93bdf0d..703967b 100644 --- a/manifests/sma_pv.yaml +++ b/manifests/sma_pv.yaml @@ -3,6 +3,8 @@ version: "1.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local +setup: [installer] ders: [pv, meter] control: true tested_devices: diff --git a/manifests/socomec.yaml b/manifests/socomec.yaml index 73d6658..8653063 100644 --- a/manifests/socomec.yaml +++ b/manifests/socomec.yaml @@ -3,6 +3,7 @@ version: "1.0.4" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter] control: false tested_devices: diff --git a/manifests/sofar.yaml b/manifests/sofar.yaml index cd22b57..b14db25 100644 --- a/manifests/sofar.yaml +++ b/manifests/sofar.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/solaredge.yaml b/manifests/solaredge.yaml index e4d7063..992acb8 100644 --- a/manifests/solaredge.yaml +++ b/manifests/solaredge.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [meter, pv] control: false tested_devices: diff --git a/manifests/solaredge_legacy.yaml b/manifests/solaredge_legacy.yaml index b7ec48c..b9b04a7 100644 --- a/manifests/solaredge_legacy.yaml +++ b/manifests/solaredge_legacy.yaml @@ -3,6 +3,7 @@ version: "0.3.2" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv] control: true tested_devices: diff --git a/manifests/solaredge_pv.yaml b/manifests/solaredge_pv.yaml index 10d3d56..2f0f2c1 100644 --- a/manifests/solaredge_pv.yaml +++ b/manifests/solaredge_pv.yaml @@ -3,6 +3,7 @@ version: "1.2.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv] control: true tested_devices: diff --git a/manifests/solax.yaml b/manifests/solax.yaml index b2edd48..de5f487 100644 --- a/manifests/solax.yaml +++ b/manifests/solax.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/solinteg.yaml b/manifests/solinteg.yaml index 409a697..731ff4c 100644 --- a/manifests/solinteg.yaml +++ b/manifests/solinteg.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/solis.yaml b/manifests/solis.yaml index e3c2840..2421417 100644 --- a/manifests/solis.yaml +++ b/manifests/solis.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/solis_string.yaml b/manifests/solis_string.yaml index edbb628..8b0ba0c 100644 --- a/manifests/solis_string.yaml +++ b/manifests/solis_string.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv] control: true tested_devices: diff --git a/manifests/sonnen.yaml b/manifests/sonnen.yaml index f0ff588..5ecbfd3 100644 --- a/manifests/sonnen.yaml +++ b/manifests/sonnen.yaml @@ -3,6 +3,8 @@ version: "2.0.1" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local +setup: [device_ui] ders: [battery, meter] control: false tested_devices: diff --git a/manifests/sungrow.yaml b/manifests/sungrow.yaml index 88e5577..3d9e3fb 100644 --- a/manifests/sungrow.yaml +++ b/manifests/sungrow.yaml @@ -3,6 +3,7 @@ version: "1.5.7" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: diff --git a/manifests/tesla_vehicle.yaml b/manifests/tesla_vehicle.yaml index 73616b5..55d1fdc 100644 --- a/manifests/tesla_vehicle.yaml +++ b/manifests/tesla_vehicle.yaml @@ -3,6 +3,8 @@ version: "0.2.0" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: local +setup: [bridge] ders: [vehicle] control: true tested_devices: diff --git a/manifests/tibber.yaml b/manifests/tibber.yaml index 88feedf..7ed6da5 100644 --- a/manifests/tibber.yaml +++ b/manifests/tibber.yaml @@ -3,6 +3,8 @@ version: "1.1.0" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: cloud +setup: [vendor_portal] ders: [meter] control: true tested_devices: diff --git a/manifests/varta.yaml b/manifests/varta.yaml index 8df47af..887ba9c 100644 --- a/manifests/varta.yaml +++ b/manifests/varta.yaml @@ -3,6 +3,7 @@ version: "1.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter] control: false tested_devices: diff --git a/manifests/victron.yaml b/manifests/victron.yaml index 67e3149..e84e5b1 100644 --- a/manifests/victron.yaml +++ b/manifests/victron.yaml @@ -3,6 +3,7 @@ version: "2.1.1" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/victron_mqtt.yaml b/manifests/victron_mqtt.yaml index 37ddfae..f3d24e4 100644 --- a/manifests/victron_mqtt.yaml +++ b/manifests/victron_mqtt.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: mqtt +connectivity: local ders: [pv, battery, meter] control: false tested_devices: diff --git a/manifests/wallbox.yaml b/manifests/wallbox.yaml index b327d65..d705fa1 100644 --- a/manifests/wallbox.yaml +++ b/manifests/wallbox.yaml @@ -3,6 +3,7 @@ version: "1.0.3" tier: community author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/zap.yaml b/manifests/zap.yaml index 1254b3b..7e5610e 100644 --- a/manifests/zap.yaml +++ b/manifests/zap.yaml @@ -3,6 +3,7 @@ version: "2.0.1" tier: core author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [meter, pv, battery, v2x_charger] control: true tested_devices: diff --git a/manifests/zaptec.yaml b/manifests/zaptec.yaml index a090267..f0d57f6 100644 --- a/manifests/zaptec.yaml +++ b/manifests/zaptec.yaml @@ -3,6 +3,7 @@ version: "1.0.1" tier: community author: "Sourceful Labs AB" protocol: http +connectivity: local ders: [v2x_charger] control: false tested_devices: diff --git a/manifests/zuidwijk_p1.yaml b/manifests/zuidwijk_p1.yaml index 0d8245f..e35158f 100644 --- a/manifests/zuidwijk_p1.yaml +++ b/manifests/zuidwijk_p1.yaml @@ -3,6 +3,8 @@ version: "1.1.0" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local +setup: [bridge] ders: [meter] control: true tested_devices: diff --git a/spec/manifest-v2.md b/spec/manifest-v2.md index 702993c..c4288a4 100644 --- a/spec/manifest-v2.md +++ b/spec/manifest-v2.md @@ -11,6 +11,8 @@ 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 `""` | +| `connectivity` | string | Yes | `local` or `cloud` — where the driver talks while running | +| `setup` | list | No | What a human must obtain before it connects (see below) | | `ders` | list | Yes | DER types: `pv`, `battery`, `meter`, `v2x_charger` | | `control` | bool | Yes | Whether the driver supports EMS control commands | | `tested_devices` | list | No | Devices tested against (see below) | @@ -24,6 +26,52 @@ 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 | +## Connectivity and Setup + +`protocol` names a transport, which does not answer the two questions an owner +asks first. `nibe_local` and `myuplink` are both `protocol: http` and both read +a NIBE heat pump, and they are opposites: one reads the pump over the LAN, the +other reads NIBE's cloud. + +`connectivity` says where the driver talks while it is running. + +| Value | Meaning | +|-------|---------| +| `local` | Talks only to the device or a broker on the local network. Keeps working with the internet unplugged | +| `cloud` | Talks to a vendor service over the internet. No internet, no telemetry | + +`setup` says what a human must obtain **once**, and from whom, before the +driver can connect at all — which is a separate question, because a driver can +be `local` and still need a manufacturer to unlock the interface first. + +| Value | Meaning | +|-------|---------| +| `none` | Nothing beyond reaching the device on the network | +| `device_screen` | Enabled on the device's own display or keypad | +| `device_ui` | Enabled in the device's own local web interface | +| `vendor_app` | Requires the manufacturer's phone app | +| `vendor_portal` | Requires an account, API key or OAuth app from the vendor | +| `installer` | Requires installer-level access or a service partner | +| `vendor_approval` | The manufacturer must enable it for this site on request | +| `bridge` | Requires separate hardware or firmware in between | + +An **absent** `setup` means nobody has recorded one. It does not mean there is +nothing to do, and the catalog page renders it as "Not recorded" rather than +"Nothing to set up". Only `setup: [none]` claims a device needs nothing but an +address, so state it only when that has been confirmed. `none` cannot be +combined with another value. + +`bridge` is for a translator that is not the device and not the vendor's own +gateway product: a HeishaMon board, OpenDTU, an ESPHome build, TeslaBleHttpProxy, +a serial-to-Ethernet bridge. A vendor gateway the customer buys as part of the +system — the Sourceful Zap, a Ferroamp EnergyHub — is the device, not a bridge. + +Neither field reaches the signed driver artifact: `ftw_repository._load_channel` +copies an explicit allowlist (`ders`, `protocol`, `manufacturer`, `model_family` +and the `DRIVER` table) into the published metadata, and these two are not in +it. So correcting one does **not** need a version bump, unlike the fields listed +in CONTRIBUTING. + ## Tested Devices The `tested_devices` list describes the device models a driver has been verified against. @@ -48,6 +96,7 @@ version: "1.1.0" tier: core author: "Sourceful Labs AB" protocol: modbus +connectivity: local ders: [battery, meter, pv] control: true tested_devices: @@ -84,10 +133,12 @@ Version changes must be accompanied by a `CHANGELOG.md` entry under `[Unreleased 3. `tier` must be one of the three valid tiers 4. `protocol` must be a known protocol or empty string 5. All entries in `ders` must be valid DER types -6. `size_bytes` must be non-negative -7. `core` tier drivers must have an `author` -8. Every manifest must have a corresponding `.lua` file in `drivers/` -9. `tested_devices` entries must have `manufacturer` and `model_family` +6. `connectivity` must be `local` or `cloud` +7. `setup`, when present, must be a non-empty list of valid values, and `none` must stand alone +8. `size_bytes` must be non-negative +9. `core` tier drivers must have an `author` +10. Every manifest must have a corresponding `.lua` file in `drivers/` +11. `tested_devices` entries must have `manufacturer` and `model_family` ## Migration from V1 diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py new file mode 100644 index 0000000..7093d88 --- /dev/null +++ b/tests/test_connectivity.py @@ -0,0 +1,113 @@ +"""`connectivity` has to be checkable, or it is just a word in a file. + +A driver that reaches a vendor service has to name that service somewhere in +its source: it builds a URL from a hostname it hardcodes, because there is +nowhere else for a cloud endpoint to come from. A driver that only talks to +the LAN takes its address from config. That asymmetry is what these tests read, +so a manifest cannot claim `local` while the Lua calls an API on the internet. + +`setup` cannot be derived the same way -- nothing in the source proves a +manufacturer has to unlock an interface first -- so it is checked for shape +and for the one rule that carries meaning: absent is not `none`. +""" + +import re +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +MANIFESTS = sorted((ROOT / "manifests").glob("*.yaml")) + +# A URL literal in code, not in a comment and not the DRIVER table's homepage. +URL_RE = re.compile(r"""https?://([A-Za-z][A-Za-z0-9.-]*\.[A-Za-z]{2,})""") +COMMENT_RE = re.compile(r"^\s*--") +HOMEPAGE_RE = re.compile(r"^\s*homepage\s*=") + + +def manifest_field(text: str, field: str) -> str: + match = re.search(rf"^{field}:\s*(.+)$", text, re.MULTILINE) + return match.group(1).strip().strip('"') if match else "" + + +def manifest_list(text: str, field: str) -> list[str] | None: + match = re.search(rf"^{field}:\s*\[(.*)\]$", text, re.MULTILINE) + if not match: + return None + return [item.strip() for item in match.group(1).split(",") if item.strip()] + + +def called_hosts(driver_id: str) -> set[str]: + """Hostnames the driver hardcodes into a request, ignoring documentation.""" + source = ROOT / "drivers" / "lua" / f"{driver_id}.lua" + if not source.is_file(): + return set() + hosts: set[str] = set() + for line in source.read_text(encoding="utf-8").splitlines(): + if COMMENT_RE.match(line) or HOMEPAGE_RE.match(line): + continue + for host in URL_RE.findall(line): + # A link to a project page is provenance, not an endpoint. + if host.endswith("github.com") or host.startswith("developer."): + continue + hosts.add(host) + return hosts + + +IDS = [path.stem for path in MANIFESTS] + + +@pytest.mark.parametrize("driver_id", IDS) +def test_every_driver_states_where_it_talks(driver_id: str) -> None: + text = (ROOT / "manifests" / f"{driver_id}.yaml").read_text(encoding="utf-8") + assert manifest_field(text, "connectivity") in {"local", "cloud"}, ( + f"{driver_id}: connectivity must be local or cloud") + + +@pytest.mark.parametrize("driver_id", IDS) +def test_a_local_driver_does_not_call_the_internet(driver_id: str) -> None: + text = (ROOT / "manifests" / f"{driver_id}.yaml").read_text(encoding="utf-8") + if manifest_field(text, "connectivity") != "local": + return + hosts = called_hosts(driver_id) + assert not hosts, ( + f"{driver_id} claims connectivity: local but its source builds requests " + f"against {sorted(hosts)}") + + +@pytest.mark.parametrize("driver_id", IDS) +def test_a_cloud_driver_names_the_service_it_needs(driver_id: str) -> None: + text = (ROOT / "manifests" / f"{driver_id}.yaml").read_text(encoding="utf-8") + if manifest_field(text, "connectivity") != "cloud": + return + assert called_hosts(driver_id), ( + f"{driver_id} claims connectivity: cloud but hardcodes no vendor host; " + "if it reads a device on the LAN it is local") + + +@pytest.mark.parametrize("driver_id", IDS) +def test_setup_is_absent_or_meaningful(driver_id: str) -> None: + text = (ROOT / "manifests" / f"{driver_id}.yaml").read_text(encoding="utf-8") + setup = manifest_list(text, "setup") + if setup is None: + assert not re.search(r"^setup:", text, re.MULTILINE), ( + f"{driver_id}: setup must be an inline list, e.g. setup: [device_ui]") + return + assert setup, f"{driver_id}: omit setup rather than stating an empty list" + assert "none" not in setup or len(setup) == 1, ( + f"{driver_id}: setup 'none' cannot be combined with another requirement") + + +def test_a_cloud_driver_needs_something_from_its_vendor() -> None: + """Reaching a vendor service means holding a credential for it. + + Not a style rule: a cloud driver whose setup is unrecorded is the case + where an owner discovers only after installing that they need an account. + """ + for path in MANIFESTS: + text = path.read_text(encoding="utf-8") + if manifest_field(text, "connectivity") != "cloud": + continue + setup = manifest_list(text, "setup") or [] + assert setup and setup != ["none"], ( + f"{path.stem}: a cloud driver must record what its vendor requires") diff --git a/tools/generate_index.py b/tools/generate_index.py index 0901387..c6adf4e 100644 --- a/tools/generate_index.py +++ b/tools/generate_index.py @@ -53,6 +53,8 @@ def main(): "version": data.get("version", "1.0.0"), "tier": data.get("tier", "community"), "protocol": data.get("protocol", ""), + "connectivity": data.get("connectivity", ""), + "setup": data.get("setup", []), "ders": data.get("ders", []), "control": data.get("control", False), "size_bytes": size_bytes, @@ -68,6 +70,11 @@ def main(): lines.append(f' version: "{e["version"]}"') lines.append(f' tier: {e["tier"]}') lines.append(f' protocol: {e["protocol"]}' if e["protocol"] else ' protocol: ""') + lines.append(f' connectivity: {e["connectivity"]}') + # Omitted rather than empty: no recorded setup requirement is not a + # claim that a device needs none. + if e["setup"]: + lines.append(f' setup: {yaml_list_inline(e["setup"])}') lines.append(f' ders: {yaml_list_inline(e["ders"])}') lines.append(f' control: {"true" if e["control"] else "false"}') lines.append(f' size_bytes: {e["size_bytes"]}') diff --git a/tools/generate_site.py b/tools/generate_site.py index 36ca766..55427d2 100644 --- a/tools/generate_site.py +++ b/tools/generate_site.py @@ -41,6 +41,27 @@ "v2x_charger": "V2X charger", "vehicle": "Vehicle", } +# Where the driver talks while it runs. The question behind this filter is +# "does this keep working when the internet is down?", so the labels answer +# that rather than naming a transport. +CONNECTIVITY_LABELS = { + "local": "Local only", + "cloud": "Needs the internet", + "": "Unspecified", +} +# What somebody has to obtain before any of it works, even when the driver +# itself never leaves the LAN. A driver with no entry has had none recorded, +# which the page says instead of implying there is nothing to do. +SETUP_LABELS = { + "none": "Nothing to set up", + "device_screen": "Enable on the device screen", + "device_ui": "Enable in the device web UI", + "vendor_app": "Manufacturer app", + "vendor_portal": "Manufacturer account", + "installer": "Installer access", + "vendor_approval": "Manufacturer must enable it", + "bridge": "Needs a bridge device", +} PROTOCOL_LABELS = { "modbus": "Modbus", "mqtt": "MQTT", @@ -224,6 +245,8 @@ def collect_drivers() -> list[dict]: "version": data.get("version", ""), "tier": data.get("tier", "community"), "protocol": data.get("protocol", ""), + "connectivity": data.get("connectivity", ""), + "setup": data.get("setup", []) or [], "ders": data.get("ders", []) or [], "control": bool(data.get("control", False)), "author": data.get("author", ""), @@ -275,6 +298,8 @@ def build_catalog() -> dict: "ders": DER_LABELS, "protocols": PROTOCOL_LABELS, "tiers": TIER_LABELS, + "connectivity": CONNECTIVITY_LABELS, + "setup": SETUP_LABELS, }, "totals": { "drivers": len(drivers), @@ -385,7 +410,7 @@ def embed_json(payload: dict) -> str: .catalog-body { padding-top: 30px; } .driver { border: 1px solid var(--line); background: var(--surface); margin-bottom: -1px; } .driver.is-open { border-color: var(--ink); position: relative; z-index: 1; } -.driver-head { width: 100%; display: grid; grid-template-columns: minmax(0, 3fr) 90px minmax(0, 1.6fr) 76px 92px 74px 22px; align-items: center; gap: 14px; padding: 15px 18px; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; } +.driver-head { width: 100%; display: grid; grid-template-columns: minmax(0, 3fr) 90px 74px minmax(0, 1.6fr) 76px 92px 74px 22px; align-items: center; gap: 14px; padding: 15px 18px; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; } .driver-head:hover { background: var(--signal-light); } .driver-id { display: block; color: var(--ink); font-family: var(--mono); font-size: 14px; font-weight: 700; } .driver-title { display: block; margin-top: 2px; color: var(--muted); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } @@ -394,6 +419,9 @@ def embed_json(payload: dict) -> str: .badge.tier-core { border-color: var(--ink); background: var(--ink); color: var(--cream); } .badge.control-yes { border-color: var(--signal); color: var(--signal); font-weight: 700; } .badge.control-no { color: var(--muted); opacity: 0.7; } +/* Local-first is the default this project is built for, so the badge that + earns attention is the one that says a device stops without the internet. */ +.badge.conn-cloud { border-style: dotted; color: var(--muted); } .ders { display: flex; gap: 5px; flex-wrap: wrap; } .ders .badge { border-style: dashed; } .version { color: var(--ink); font-family: var(--mono); font-size: 12px; } @@ -480,7 +508,8 @@ def embed_json(payload: dict) -> str: var drivers = CATALOG.drivers; var labels = CATALOG.labels; var state = { view: "drivers", query: "", ders: new Set(), protocols: new Set(), - tiers: new Set(), control: false, verified: false }; + tiers: new Set(), connectivity: new Set(), setup: new Set(), + control: false, verified: false }; var listEl = document.getElementById("list"); var countEl = document.getElementById("result-count"); @@ -494,12 +523,16 @@ def embed_json(payload: dict) -> str: function derLabel(d) { return labels.ders[d] || d; } function protocolLabel(p) { return labels.protocols[p] || p; } function tierLabel(t) { return labels.tiers[t] || t; } + function connectivityLabel(c) { return labels.connectivity[c] || c; } + function setupLabel(s) { return labels.setup[s] || s; } function bytes(n) { return n >= 1024 ? (n / 1024).toFixed(1) + " kB" : n + " B"; } // One flat haystack per driver so a search matches a model number or a // manufacturer, not only the driver id. drivers.forEach(function (d) { var parts = [d.id, d.title, d.description, d.protocol, d.tier, d.author]; + parts.push(d.connectivity, connectivityLabel(d.connectivity)); + d.setup.forEach(function (x) { parts.push(x, setupLabel(x)); }); d.ders.forEach(function (x) { parts.push(x, derLabel(x)); }); d.manufacturers.forEach(function (m) { parts.push(m); }); d.tested_devices.forEach(function (dev) { @@ -515,6 +548,13 @@ def embed_json(payload: dict) -> str: if (state.verified && !d.hardware_verified) return false; if (state.tiers.size && !state.tiers.has(d.tier)) return false; if (state.protocols.size && !state.protocols.has(d.protocol)) return false; + if (state.connectivity.size && !state.connectivity.has(d.connectivity)) return false; + // A driver with no recorded setup requirement cannot answer this filter, + // so it drops out rather than passing as if it needed nothing. + if (state.setup.size) { + var setupHit = d.setup.some(function (x) { return state.setup.has(x); }); + if (!setupHit) return false; + } if (state.ders.size) { var hit = d.ders.some(function (x) { return state.ders.has(x); }); if (!hit) return false; @@ -562,6 +602,10 @@ def embed_json(payload: dict) -> str: var control = d.control ? 'Control' : 'Read only'; + var reach = d.connectivity + ? '' + + (d.connectivity === "cloud" ? "Cloud" : "Local") + "" + : ""; // The driver's own verification_status, rendered as what it actually says. // Most drivers are ports awaiting hardware, and the page must not dress // that up as a test result. @@ -579,6 +623,10 @@ def embed_json(payload: dict) -> str: var facts = [ ["Tier", esc(tierLabel(d.tier))], ["Protocol", esc(protocolLabel(d.protocol))], + ["Reach", esc(connectivityLabel(d.connectivity))], + ["To set up", d.setup.length + ? d.setup.map(function (x) { return esc(setupLabel(x)); }).join(", ") + : "Not recorded"], ["Catalog version", esc(d.version)], ["Min host", esc(d.min_host_version || "—")], ["Source size", bytes(d.size_bytes)], @@ -594,6 +642,7 @@ def embed_json(payload: dict) -> str: '