Convert the ZLinky_TIC quirk to v2 and expose tariff period sensors - #5245
Convert the ZLinky_TIC quirk to v2 and expose tariff period sensors#5245yoda-jm wants to merge 5 commits into
Conversation
The four v1 subclasses matched firmware variants by exact cluster list: the base signature, plus PowerConfiguration on v12, Time and the Tuya cluster on v14, and a different device type on v15. Matching on manufacturer and model covers all of them, so ZLinkyTIC, ZLinkyTICFWV12, ZLinkyTICFWV14 and ZLinkyTICFWV15 collapse into a single QuirkBuilder. PowerConfiguration is added unconditionally, as the v1 replacement already did for every variant. The two ZLinky entries in the test_suspicious_cluster_moves allow list go away with the v1 classes. No functional change intended: same clusters replaced, same entities.
The quirk decodes the whole manufacturer specific cluster 0xFF66, but none of its attributes reach Home Assistant: only the six Metering tier summations do, and those are registered by the ZHA library itself. Users who want to automate on their tariff period have no entity to work with. Expose six of them as sensors: - PTEC (linky_tariff_period), the tariff period currently in effect, reported in both TIC modes from firmware v15. "TH.." on the Base tariff, "HC.."/"HP.." on the off-peak/peak tariff. - OPTARIF, the subscribed tariff option. - ADPS, the subscribed power exceeding warning, in amperes. - HHPHC, MOTDETAT and the TIC mode, as diagnostic sensors. Attributes a meter does not support are skipped by ZHA rather than producing empty entities, so no firmware version filtering is needed, matching the existing tier summation sensors. The entity list is based on the work in zigpy#3456 by @blauret, which was closed unmerged when its author moved away from ZHA.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #5245 +/- ##
==========================================
- Coverage 92.60% 92.59% -0.02%
==========================================
Files 424 424
Lines 14667 14647 -20
==========================================
- Hits 13582 13562 -20
Misses 1085 1085 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR migrates the LiXee ZLinky_TIC handler from a legacy (v1) quirk to a v2 QuirkBuilder definition so manufacturer-specific attributes on cluster 0xFF66 can be exposed as Home Assistant entities (notably the current tariff period), and adds a small test suite to assert the v2 quirk behavior.
Changes:
- Convert
zhaquirks/lixee/zlinky.pyfrom multiple v1CustomDevicesubclasses to a single v2QuirkBuilder-based quirk. - Expose six manufacturer-cluster attributes as HA sensors (tariff period, tariff option, overload warning current, and diagnostic attributes).
- Update/add tests to cover v2 cluster replacement and entity metadata registration.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
zhaquirks/lixee/zlinky.py |
Migrates ZLinky quirk to v2 and defines new manufacturer-cluster sensors. |
tests/test_quirks.py |
Removes v1-only special casing for the now-deleted ZLinky v1 subclasses. |
tests/test_lixee.py |
Adds v2-focused tests asserting cluster replacement and exposed sensor metadata. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Firmware v14 and later report a Tuya cluster in both directions that the device does not implement. The v1 quirk dropped it by listing it in the FWV14 and FWV15 signatures but not in their replacements, which is what the two ZLinky entries in the test_suspicious_cluster_moves allow list were about. The v2 conversion lost that, so restore it explicitly. RemoveCluster pops with a default, so this is a no-op on firmware that does not report the cluster, and a test now pins the behaviour rather than an allow list entry.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/test_lixee.py:90
- This test only exercises removal of the Tuya cluster when it is present as a server/input cluster. The assertion that it is absent from
endpoint.out_clusterswill always pass in this setup because the cluster is never added as a client/output cluster in the first place, so it doesn’t actually cover the.removes(..., cluster_type=ClusterType.Client)behavior. Add a second device setup (or a second test) that places the Tuya cluster inout_clusters(ClusterType.Client) and assert it gets removed there too.
cluster_ids={
1: {
ZLINKY_MANUFACTURER_CLUSTER_ID: None,
TuyaManufCluster.cluster_id: ClusterType.Server,
}
tests/test_lixee.py:41
zigpy_device_from_v2_quirk()expects cluster IDs to map to aClusterType, but this test passesNone. That works accidentally (it falls back to adding the cluster as a server/input cluster), but it obscures intent and makes the test inconsistent with the fixture’s contract. UseClusterType.Serverexplicitly for clarity and to match the annotated type.
cluster_ids={
1: {
Metering.cluster_id: None,
ZLINKY_MANUFACTURER_CLUSTER_ID: None,
}
The repository squash-merges, so commit messages do not reach the branch history. Move the parts worth keeping into the code itself: what distinguished the four v1 firmware subclasses and why matching on manufacturer and model replaces them, and why the two ZLinky entries in the test_suspicious_cluster_moves allow list existed, now recorded in the docstring of the test that replaces them. Comments only, no behaviour change.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
tests/test_lixee.py:96
- This test only seeds the Tuya cluster as an input (server) cluster; the assertion about
endpoint.out_clusterswill always pass even if the.removes(..., cluster_type=ClusterType.Client)operation regresses. Seed a second device with the cluster as a client cluster so both removal paths are actually exercised.
device = zigpy_device_from_v2_quirk(
LIXEE,
"ZLinky_TIC",
cluster_ids={
1: {
ZLINKY_MANUFACTURER_CLUSTER_ID: None,
TuyaManufCluster.cluster_id: ClusterType.Server,
}
},
)
endpoint = device.endpoints[1]
assert TuyaManufCluster.cluster_id not in endpoint.in_clusters
assert TuyaManufCluster.cluster_id not in endpoint.out_clusters
tests/test_lixee.py:41
zigpy_device_from_v2_quirkexpects eachcluster_idsentry to map to aClusterType. UsingNoneworks only accidentally (it falls through to the server-cluster branch) and violates the annotated type, which can break type checking and makes the test intent unclear.
cluster_ids={
1: {
Metering.cluster_id: None,
ZLINKY_MANUFACTURER_CLUSTER_ID: None,
}
},
The test seeded the Tuya cluster as a server cluster only, so the assertion on out_clusters passed whatever the client-side removal did: dropping that .removes() call left the suite green. Parametrizing over both cluster types makes each removal fail on its own. Also pass ClusterType.Server explicitly instead of None. The fixture annotates cluster_ids as dict[int, dict[int, ClusterType]] and None only worked by falling through to the server branch.
|
Both suppressed Copilot comments were right, and the first one was a real defect. Fixed in 024a7fc. The
Worth noting the suppressed comment caught something the visible review did not: the Tuya fix itself was correct, but the test pinning it was only half testing it. |
Proposed change
Expose the ZLinky_TIC tariff period in Home Assistant.
The quirk already decodes the whole manufacturer specific cluster
0xFF66—42 attributes — but none of them reach Home Assistant. The only ZLinky sensors
that exist are the six Metering tier summations, and those are registered by the
ZHA library itself. A user on an off-peak/peak tariff has no entity telling them
which period is currently in effect, so automations have to hardcode a schedule.
In France that is increasingly wrong: the off-peak windows are being reshuffled
by Enedis, with afternoon slots that vary seasonally.
A v1 quirk cannot fix this. Legacy quirks are compiled into registry entries
with
zha_device_factory=None, so there is nowhere to attach entity metadata.Exposing an attribute from the quirk requires the file to be v2. That is the
only reason the conversion is in here, and it is why the commits are split:
Convert the ZLinky_TIC quirk to v2— the four v1 subclasses matchedfirmware variants by exact cluster list; matching on manufacturer and model
covers all of them, so they collapse into a single
QuirkBuilder. Thiscommit is intentionally identical to the lixee hunk of Migrate almost all v1 quirks to v2 #4499.
Expose ZLinky_TIC tariff period and meter status sensors— the actualfeature, six sensors on the manufacturer cluster.
Keep removing the Tuya cluster the ZLinky_TIC does not implement— afix on review. The conversion was not inert as first claimed: firmware v14
and later report a Tuya cluster the device does not implement, which the v1
quirk dropped by listing it in the FWV14/FWV15 signatures but not in their
replacements. That is what the two ZLinky entries in the
test_suspicious_cluster_movesallow list recorded, and removing thoseentries without replacing the behaviour lost it — confirmed on hardware,
0xEF00came back on endpoint 1 in both directions. It is now removedexplicitly and pinned by a test. The lixee hunk of Migrate almost all v1 quirks to v2 #4499 has the same
omission.
Record why the v1 subclasses collapse and the allow list entries go—comments only.
Every other v1/v2 difference was checked and is a non-issue:
Time, the v15DIMMABLE_LIGHTdevice type and the standard clusters were re-listed by the v1replacements only because those lists are exhaustive. A v2 quirk states
differences, so anything untouched is preserved as the device reports it.
Reviewing the commits separately should make it easy to see what the conversion
changes before looking at what it enables.
Additional information
Relationship to #4499. That PR already migrates
zhaquirks/lixee/zlinky.py,and the first commit here is deliberately the same change. It is currently
mergeable_state: dirtyacross 158 files with no activity since 2026-07-21, sorather than wait on it — or pile a feature onto a 158-file migration — this
keeps one device and one problem together in a reviewable PR. Happy to drop the
first commit and rebase on #4499 instead if you would rather land that first;
just say which you prefer. If this lands first, lixee can be dropped from #4499.
Relationship to #3456. @blauret converted this quirk to v2 and added
sensors back in 2024. It was tested by users and closed unmerged only because
its author moved to Zigbee2MQTT. The entity list here is based on that work and
credited in the commit message. I deliberately kept a smaller set than #3456:
only attributes I could read back from real hardware, all on the manufacturer
cluster. The
ElectricalMeasurementandMeteringadditions from that PR areleft for a follow-up.
Sensors added, with the values read from a v15 meter on the Base tariff:
linky_tariff_periodTH..hist_tariff_option_or_std_supplier_price_schedule_nameBASEhist_subscribed_power_exceeding_warning0hist_schedule_peak_hours_off_peak_hours0linky_status000000linky_mode0No firmware version filtering. Attributes a meter does not support are
skipped by ZHA's
_is_supported()rather than producing empty entities, whichis also why the existing tier summation sensors carry no version guard. The
From V13/From V15notes in the attribute definitions stay comments.One known limitation, reported on #3456 and not fixed here: the tariff
period does not appear to refresh on its own, only on an explicit read. I could
not reproduce it either way — my meter is on the Base tariff, so the value never
changes. If it is confirmed, a
reporting_configon the sensor would be the fixand I am happy to add it in this PR.
Device diagnostics
zha-01K3QV44R6NV3V8EP1TMKCFKGE-LiXee ZLinky_TIC-9ef86ccabdca67c2652627c1fbd79218.json
Attached. One caveat:
quirk_classreadszlinky_tarif:(LiXee / ZLinky_TIC)because the diagnostics were captured with this change loaded as a custom quirk
via
custom_quirks_path, which is how it was tested on hardware. The clusterand attribute contents are the ones this PR produces.
Checklist
pre-commitchecks pass / the code has been formatted using Black