Skip to content

Convert the ZLinky_TIC quirk to v2 and expose tariff period sensors - #5245

Open
yoda-jm wants to merge 5 commits into
zigpy:devfrom
yoda-jm:zlinky-tariff-entities
Open

Convert the ZLinky_TIC quirk to v2 and expose tariff period sensors#5245
yoda-jm wants to merge 5 commits into
zigpy:devfrom
yoda-jm:zlinky-tariff-entities

Conversation

@yoda-jm

@yoda-jm yoda-jm commented Aug 12, 2026

Copy link
Copy Markdown

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:

  1. Convert the ZLinky_TIC quirk to v2 — the four v1 subclasses matched
    firmware variants by exact cluster list; matching on manufacturer and model
    covers all of them, so they collapse into a single QuirkBuilder. This
    commit is intentionally identical to the lixee hunk of Migrate almost all v1 quirks to v2 #4499.
  2. Expose ZLinky_TIC tariff period and meter status sensors — the actual
    feature, six sensors on the manufacturer cluster.
  3. Keep removing the Tuya cluster the ZLinky_TIC does not implement — a
    fix 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_moves allow list recorded, and removing those
    entries without replacing the behaviour lost it — confirmed on hardware,
    0xEF00 came back on endpoint 1 in both directions. It is now removed
    explicitly and pinned by a test. The lixee hunk of Migrate almost all v1 quirks to v2 #4499 has the same
    omission.
  4. 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 v15
DIMMABLE_LIGHT device type and the standard clusters were re-listed by the v1
replacements 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: dirty across 158 files with no activity since 2026-07-21, so
rather 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 ElectricalMeasurement and Metering additions from that PR are
left for a follow-up.

Sensors added, with the values read from a v15 meter on the Base tariff:

Attribute TIC code Value read Entity
linky_tariff_period PTEC TH.. Tariff period
hist_tariff_option_or_std_supplier_price_schedule_name OPTARIF BASE Tariff option
hist_subscribed_power_exceeding_warning ADPS 0 Subscribed power exceeding warning
hist_schedule_peak_hours_off_peak_hours HHPHC 0 Off-peak hours schedule (diagnostic)
linky_status MOTDETAT 000000 Meter status (diagnostic)
linky_mode 0 TIC mode (diagnostic)

No firmware version filtering. Attributes a meter does not support are
skipped by ZHA's _is_supported() rather than producing empty entities, which
is also why the existing tier summation sensors carry no version guard. The
From V13 / From V15 notes 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_config on the sensor would be the fix
and I am happy to add it in this PR.

Device diagnostics

zha-01K3QV44R6NV3V8EP1TMKCFKGE-LiXee ZLinky_TIC-9ef86ccabdca67c2652627c1fbd79218.json

Attached. One caveat: quirk_class reads zlinky_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 cluster
and attribute contents are the ones this PR produces.

Checklist

  • The changes are tested and work correctly
  • pre-commit checks pass / the code has been formatted using Black
  • Tests have been added to verify that the new code works
  • Device diagnostics data has been attached

yoda-jm added 2 commits August 12, 2026 12:38
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.
Copilot AI lite review requested due to automatic review settings August 12, 2026 10:45
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.59%. Comparing base (b139f81) to head (024a7fc).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py from multiple v1 CustomDevice subclasses to a single v2 QuirkBuilder-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.

Comment thread zhaquirks/lixee/zlinky.py
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.
Copilot AI review requested due to automatic review settings August 12, 2026 11:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_clusters will 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 in out_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 a ClusterType, but this test passes None. 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. Use ClusterType.Server explicitly 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.
Copilot AI review requested due to automatic review settings August 12, 2026 11:12
@yoda-jm yoda-jm changed the title Expose ZLinky_TIC tariff period and meter status sensors Convert the ZLinky_TIC quirk to v2 and expose tariff period sensors Aug 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_clusters will 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_quirk expects each cluster_ids entry to map to a ClusterType. Using None works 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.
Copilot AI review requested due to automatic review settings August 12, 2026 11:23
@yoda-jm

yoda-jm commented Aug 12, 2026

Copy link
Copy Markdown
Author

Both suppressed Copilot comments were right, and the first one was a real defect. Fixed in 024a7fc.

The out_clusters assertion was vacuous. I checked by mutation rather than by reading: deleting the .removes(..., cluster_type=ClusterType.Client) call left the whole suite green, because the test only ever seeded the Tuya cluster as a server cluster. The test is now parametrized over both cluster types, and breaking either removal fails on its own:

suppression Client cassee -> FAILED tests/test_lixee.py::test_zlinky_tuya_cluster_removed[1]
suppression Server cassee -> FAILED tests/test_lixee.py::test_zlinky_tuya_cluster_removed[0]

None in cluster_ids is also fixed. The fixture annotates it as dict[int, dict[int, ClusterType]], and None only worked by falling through to the server branch. Every entry now passes ClusterType.Server explicitly.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@zigpy-review-bot zigpy-review-bot added bot: nearly ready PR is good but needs a few minor tweaks per LLM bot: 2.0 migration done PR is on the ZHA/quirks 2.0.0 API (migrated or written for 2.0) per LLM enhancement Improve an existing quirk bot: needs maintainer PR does something questionable that needs a maintainer decision per LLM labels Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot: nearly ready PR is good but needs a few minor tweaks per LLM bot: needs maintainer PR does something questionable that needs a maintainer decision per LLM bot: 2.0 migration done PR is on the ZHA/quirks 2.0.0 API (migrated or written for 2.0) per LLM enhancement Improve an existing quirk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants