-
Notifications
You must be signed in to change notification settings - Fork 991
Labels
device support requestThis requests support for a new deviceThis requests support for a new device
Description
Problem description
Since 2026.2.0, my zha-toolkit read attributes commands fail with a backtrace in the logs that shows an invalid manufacturer code.
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/utils.py", line 1211, in cluster_read_attributes
return await cluster.read_attributes(attrs, manufacturer=manufacturer)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/zigpy/zcl/__init__.py", line 965, in read_attributes
attr_def = self.find_attribute(attribute, manufacturer_code=manufacturer)
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/zigpy/zcl/__init__.py", line 513, in find_attribute
raise KeyError(manufacturer_code)
KeyError: 0
Solution description
I figured that the attributes in error where attributes of the cluster Metering of the ZLinky_TIC and added them in a custom_quirk.
It solved my problem.
Screenshots/Video
Screenshots/Video
None available.
Diagnostics information
zha-4e7438fded8a244ee2b9a6fade3a1023-LiXee ZLinky_TIC-dc486567f5fcf9be70dd10399a1ebaaf(1).json
Device signature
Device signature
{
"node_descriptor": {
"logical_type": 1,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 142,
"manufacturer_code": 4151,
"maximum_buffer_size": 127,
"maximum_incoming_transfer_size": 200,
"server_mask": 11264,
"maximum_outgoing_transfer_size": 200,
"descriptor_capability_field": 0
},
"endpoints": {
"1": {
"profile_id": "0x0104",
"device_type": "0x0053",
"input_clusters": [
"0x0000",
"0x0001",
"0x0003",
"0x0702",
"0x0b01",
"0x0b04",
"0xff66"
],
"output_clusters": [
"0x0019"
]
},
"242": {
"profile_id": "0xa1e0",
"device_type": "0x0061",
"input_clusters": [
"0x0021"
],
"output_clusters": [
"0x0021"
]
}
},
"manufacturer": "LiXee",
"model": "ZLinky_TIC",
"class": "zlinky.ZLinkyTICFWV12"
}
Logs
Logs
2026-02-07 14:19:31.061 ERROR (MainThread) [homeassistant.helpers.script.websocket_api_script] websocket_api script: Error executing scrip\
t. Unexpected error for call_service at pos 1: 0
Traceback (most recent call last):
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/homeassistant/helpers/script.py", line 531, in _async_step
await getattr(self, handler)()
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/homeassistant/helpers/script.py", line 1018, in _async_step_call_service
response_data = await self._async_run_long_action(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<9 lines>...
)
^
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/homeassistant/helpers/script.py", line 631, in _async_run_long_action
return await long_task
^^^^^^^^^^^^^^^
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/homeassistant/core.py", line 2817, in async_call
response_data = await coro
^^^^^^^^^^
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/homeassistant/core.py", line 2860, in _execute_service
return await target(service_call)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/__init__.py", line 819, in toolkit_service
raise handler_exception
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/__init__.py", line 771, in toolkit_service
handler_result = await handler(
^^^^^^^^^^^^^^
...<8 lines>...
)
^
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/__init__.py", line 906, in command_handler_default
return await default.default(
^^^^^^^^^^^^^^^^^^^^^^
app, listener, ieee, cmd, data, service, params, event_data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/default.py", line 51, in default
await handler(app, listener, ieee, cmd, data, service, params, event_data)
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/zcl_attr.py", line 311, in attr_read
await attr_write(*args, **kwargs)
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/zcl_attr.py", line 371, in attr_write
result_read = await u.cluster_read_attributes(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<4 lines>...
)
^
File "/opt/homeassistant/.homeassistant/custom_components/zha_toolkit/utils.py", line 1211, in cluster_read_attributes
return await cluster.read_attributes(attrs, manufacturer=manufacturer)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/zigpy/zcl/__init__.py", line 965, in read_attributes
attr_def = self.find_attribute(attribute, manufacturer_code=manufacturer)
File "/opt/homeassistant/venv-3.13/lib/python3.13/site-packages/zigpy/zcl/__init__.py", line 513, in find_attribute
raise KeyError(manufacturer_code)
KeyError: 0
Custom quirk
Custom quirk
"""Quirk for ZLinky_TIC."""
from copy import deepcopy
from typing import Final
from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
Basic,
GreenPowerProxy,
Identify,
Ota,
PowerConfiguration,
Time,
)
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement, MeterIdentification
from zigpy.zcl.clusters.smartenergy import Metering
from zigpy.zcl.foundation import BaseAttributeDefs, ZCLAttributeDef
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
#AAU#from zhaquirks.lixee import LIXEE, ZLINKY_MANUFACTURER_CLUSTER_ID
import logging
_LOGGER = logging.getLogger(__name__)
LIXEE = "LiXee"
ZLINKY_MANUFACTURER_CLUSTER_ID = 0xFF66
from zigpy.quirks import DEVICE_REGISTRY
# other existing imports from custom quirk
# remove upstream quirk from registry
try:
#_LOGGER.error("Registry:\n %s", ',\n '.join(str(DEVICE_REGISTRY._registry_v1).split(',')))
for model_registry in DEVICE_REGISTRY._registry_v1.values():
#_LOGGER.error("model_registry:\n %s", ',\n '.join(str(model_registry).split(',')))
#for key, value in model_registry.items():
# _LOGGER.error("model_registry.name: %s", key)
# _LOGGER.error("model_registry.value:\n %s", ',\n '.join(str(value).split(',')))
if 'ZLinky_TIC' in model_registry:
del model_registry['ZLinky_TIC']
_LOGGER.error("'ZLinky_TIC' removed.")
except Exception as e:
_LOGGER.error("Exception: %s", str(e))
try:
del DEVICE_REGISTRY._registry_v2[(LIXEE, "ZLinky_TIC")]
except Exception as e:
_LOGGER.warning("Exception: %s", str(e))
#AAU#
from zhaquirks.tuya import TuyaManufCluster
class ZLinkyTICManufacturerCluster(CustomCluster):
"""ZLinkyTICManufacturerCluster manufacturer cluster."""
cluster_id = ZLINKY_MANUFACTURER_CLUSTER_ID
name = "ZLinky_TIC Manufacturer specific"
ep_attribute = "zlinky_manufacturer_specific"
# The attribute comments below are in French to match the reference documentation,
# see https://github.com/fairecasoimeme/Zlinky_TIC/tree/v9.0#synth%C3%A8se-d%C3%A9veloppeur
# and https://github.com/fairecasoimeme/Zlinky_TIC/blob/v9.0/ZLinky/Source/LixeeCluster.h
class AttributeDefs(BaseAttributeDefs):
"""Attribute definitions."""
# Historical mode: OPTARIF "Option tarifaire" / String 4 car
# Standard mode: NGTF "Nom du calendrier tarifaire fournisseur" / String 16 car
hist_tariff_option_or_std_supplier_price_schedule_name: Final = ZCLAttributeDef(
id=0x0000, type=t.LimitedCharString(16), is_manufacturer_specific=True
)
# Historical mode: DEMAIN "Couleur du lendemain" / String 4 car
hist_tomorrow_color: Final = ZCLAttributeDef(
id=0x0001, type=t.LimitedCharString(4), is_manufacturer_specific=True
)
# Historical mode: HHPHC "Horaire Heure Pleines Heures Creuses" / Uint8 1 car
hist_schedule_peak_hours_off_peak_hours: Final = ZCLAttributeDef(
id=0x0002, type=t.uint8_t, is_manufacturer_specific=True
)
# Historical mode: PPOT "Présence des potentiels" (Triphasé) / Uint8 2 car
hist_potentials_presence: Final = ZCLAttributeDef(
id=0x0003, type=t.uint8_t, is_manufacturer_specific=True
)
# Historical mode: PEJP "Préavis début EJP(30min)" / Uint8 2 car
hist_ejp_start_notice: Final = ZCLAttributeDef(
id=0x0004, type=t.uint8_t, is_manufacturer_specific=True
)
# Historical mode: ADPS "Avertissement de Dépassement De Puissance Souscrite" / Uint16 3 car
hist_subscribed_power_exceeding_warning: Final = ZCLAttributeDef(
id=0x0005, type=t.uint16_t, is_manufacturer_specific=True
)
# Historical mode: ADIR1 "Avertissement de Dépassement D'intensité phase 1" / Uint16 3 car
hist_current_exceeding_warning_phase_1: Final = ZCLAttributeDef(
id=0x0006, type=t.uint16_t, is_manufacturer_specific=True
)
# Historical mode: ADIR2 "Avertissement de Dépassement D'intensité phase 2" / Uint16 3 car
hist_current_exceeding_warning_phase_2: Final = ZCLAttributeDef(
id=0x0007, type=t.uint16_t, is_manufacturer_specific=True
)
# Historical mode: ADIR3 "Avertissement de Dépassement D'intensité phase 3" / Uint16 3 car
hist_current_exceeding_warning_phase_3: Final = ZCLAttributeDef(
id=0x0008, type=t.uint16_t, is_manufacturer_specific=True
)
# Historical mode: MOTDETAT "Etat du Linky (From V13)" / String 6 car
linky_status: Final = ZCLAttributeDef(
id=0x0009, type=t.LimitedCharString(6), is_manufacturer_specific=True
)
# Historical and standard mode: "Tariff Period (From V15)" / String 16 car
linky_tariff_period: Final = ZCLAttributeDef(
id=0x0010, type=t.LimitedCharString(16), is_manufacturer_specific=True
)
# Historical and Standard mode: "Linky acquisition time (From V7)"" / Uint8 1 car
linky_acquisition_time: Final = ZCLAttributeDef(
id=0x0100, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: LTARF "Libellé tarif fournisseur en cours" / String 16 car
std_current_supplier_price_description: Final = ZCLAttributeDef(
id=0x0200, type=t.LimitedCharString(16), is_manufacturer_specific=True
)
# Standard mode: NTARF "Numéro de l'index tarifaire en cours" / Uint8 2 car
std_current_tariff_index_number: Final = ZCLAttributeDef(
id=0x0201, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: DATE "Date et heure courant" / String 10 car
std_current_date_and_time: Final = ZCLAttributeDef(
id=0x0202, type=t.LimitedCharString(10), is_manufacturer_specific=True
)
# Standard mode: EASD01 "Energie active soutirée Distributeur, index 01" / Uint32 9 car
std_active_energy_withdrawn_distributor_index_01: Final = ZCLAttributeDef(
id=0x0203, type=t.uint32_t, is_manufacturer_specific=True
)
# Standard mode: EASD02 "Energie active soutirée Distributeur, index 02" / Uint32 9 car
std_active_energy_withdrawn_distributor_index_02: Final = ZCLAttributeDef(
id=0x0204, type=t.uint32_t, is_manufacturer_specific=True
)
# Standard mode: EASD03 "Energie active soutirée Distributeur, index 03" / Uint32 9 car
std_active_energy_withdrawn_distributor_index_03: Final = ZCLAttributeDef(
id=0x0205, type=t.uint32_t, is_manufacturer_specific=True
)
# Standard mode: EASD04 "Energie active soutirée Distributeur, index 04" / Uint32 9 car
std_active_energy_withdrawn_distributor_index_04: Final = ZCLAttributeDef(
id=0x0206, type=t.uint32_t, is_manufacturer_specific=True
)
# Standard mode: SINSTI "Puissance app. Instantanée injectée" (Production) / Uint16 5 car
std_apparent_power_injected_instantaneous: Final = ZCLAttributeDef(
id=0x0207, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: SMAXIN "Puissance app max. injectée n" (Production) / Uint16 5 car
std_apparent_power_injected_max: Final = ZCLAttributeDef(
id=0x0208, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: SMAXIN-1 "Puissance app max. injectée n-1" (Production) / Uint16 5 car
std_apparent_power_injected_max_1: Final = ZCLAttributeDef(
id=0x0209, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: CCAIN "Point n de la courbe de charge active injectée" (Production) / Uint16 5 car
std_injected_active_load_curve_point_n: Final = ZCLAttributeDef(
id=0x0210, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: CCAIN-1 "Point n-1 de la courbe de charge active injectée" (Production) / Uint16 5 car
std_injected_active_load_curve_point_n_1: Final = ZCLAttributeDef(
id=0x0211, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: SMAXN-1 "Puissance app. max. soutirée n-1" (Monophasé) / Uint16 5 car
# Standard mode: SMAXN1-1 "Puissance app. max. soutirée n-1 ph.1" (Triphasé) / Uint16 5 car
std_apparent_power_withdrawn_max_phase_1_n_1: Final = ZCLAttributeDef(
id=0x0212, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: SMAXN2-1 "Puissance app. max. soutirée n-1 ph. 2" (Triphasé) / Uint16 5 car
std_apparent_power_withdrawn_max_phase_2_n_1: Final = ZCLAttributeDef(
id=0x0213, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: SMAXN3-1 "Puissance app. max. soutirée n-1 ph. 3" (Triphasé) / Uint16 5 car
std_apparent_power_withdrawn_max_phase_3_n_1: Final = ZCLAttributeDef(
id=0x0214, type=t.uint16_t, is_manufacturer_specific=True
)
# Standard mode: MSG1 "Message court" / String 32 car
std_message_short: Final = ZCLAttributeDef(
id=0x0215, type=t.LimitedCharString(32), is_manufacturer_specific=True
)
# Standard mode: MSG2 "Message ultra court" / String 16 car
std_message_ultra_short: Final = ZCLAttributeDef(
id=0x0216, type=t.LimitedCharString(16), is_manufacturer_specific=True
)
# Standard mode: STGE "Registre de Statuts" / String 8 car /* codespell:ignore */
std_status_register: Final = ZCLAttributeDef(
id=0x0217, type=t.LimitedCharString(8), is_manufacturer_specific=True
)
# Standard mode: DPM1 "Début Pointe Mobile 1" / Uint8 2 car
std_mobile_peak_start_1: Final = ZCLAttributeDef(
id=0x0218, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: FPM1 "Fin Pointe Mobile 1" / Uint8 2 car
std_mobile_peak_end_1: Final = ZCLAttributeDef(
id=0x0219, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: DPM2 "Début Pointe Mobile 2" / Uint8 2 car
std_mobile_peak_start_2: Final = ZCLAttributeDef(
id=0x0220, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: FPM2 "Fin Pointe Mobile 2" / Uint8 2 car
std_mobile_peak_end_2: Final = ZCLAttributeDef(
id=0x0221, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: DPM3 "Début Pointe Mobile 3" / Uint8 2 car
std_mobile_peak_start_3: Final = ZCLAttributeDef(
id=0x0222, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: FPM3 "Fin Pointe Mobile 3" / Uint8 2 car
std_mobile_peak_end_3: Final = ZCLAttributeDef(
id=0x0223, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: RELAIS "RELAIS" / Uint16 3 car
std_relay: Final = ZCLAttributeDef(
id=0x0224, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: NJOURF "Numéro du jour en cours calendrier fournisseur" / Uint8 2 car
std_supplier_calendar_current_day_number: Final = ZCLAttributeDef(
id=0x0225, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: NJOURF+1 "Numéro du prochain jour calendrier fournisseur" / Uint8 2 car
std_supplier_calendar_next_day_number: Final = ZCLAttributeDef(
id=0x0226, type=t.uint8_t, is_manufacturer_specific=True
)
# Standard mode: PJOURF+1 "Profil du prochain jour calendrier fournisseur" / String 98 car
std_supplier_calendar_next_day_profile: Final = ZCLAttributeDef(
id=0x0227, type=t.LimitedCharString(98), is_manufacturer_specific=True
)
# Standard mode: PPOINTE1 "Profil du prochain jour de pointe" / String 98 car
std_next_peak_day_profile: Final = ZCLAttributeDef(
id=0x0228, type=t.LimitedCharString(98), is_manufacturer_specific=True
)
# Historical and Standard mode: - "Linky Mode (From V4)" / Uint8 1 car
linky_mode: Final = ZCLAttributeDef(
id=0x0300, type=t.uint8_t, is_manufacturer_specific=True
)
class ZLinkyTICMetering(CustomCluster, Metering):
"""ZLinky_TIC custom metring cluster."""
# ZLinky_TIC reports current_summ_delivered in Wh
# Home Assistant expects kWh (1kWh = 1000 Wh)
MULTIPLIER = 0x0301
DIVISOR = 0x0302
_CONSTANT_ATTRIBUTES = {MULTIPLIER: 1, DIVISOR: 1000}
# The attribute comments below are in French to match the reference documentation,
# see https://github.com/fairecasoimeme/Zlinky_TIC/tree/v9.0#synth%C3%A8se-d%C3%A9veloppeur
# and https://github.com/fairecasoimeme/Zlinky_TIC/blob/v9.0/ZLinky/Source/LixeeCluster.h
class AttributeDefs(Metering.AttributeDefs):
"""Attribute additions."""
# Standard mode: EAIT "Energie active injectée totale" (Production) / Int48 9 car
std_total_injected_active_energy: Final = ZCLAttributeDef(
id=0x0001, type=t.uint48_t, is_manufacturer_specific=True
)
# Standard mode: PTEC "Période tarifaire en cours" / String 4 car
hist_current_tarif_period: Final = ZCLAttributeDef(
id=0x0020, type=t.LimitedCharString(4), is_manufacturer_specific=True
)
class ZLinkyTIC(CustomDevice):
"""ZLinky_TIC from LiXee."""
signature = {
MODELS_INFO: [(LIXEE, "ZLinky_TIC")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.METER_INTERFACE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Metering.cluster_id,
MeterIdentification.cluster_id,
ElectricalMeasurement.cluster_id,
ZLinkyTICManufacturerCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.METER_INTERFACE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
ZLinkyTICMetering,
MeterIdentification.cluster_id,
ElectricalMeasurement.cluster_id,
ZLinkyTICManufacturerCluster,
],
OUTPUT_CLUSTERS: [Ota.cluster_id],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
},
},
}
class ZLinkyTICFWV12(ZLinkyTIC):
"""ZLinky_TIC from LiXee with firmware v12.0 & v13.0."""
signature = deepcopy(ZLinkyTIC.signature)
# Insert PowerConfiguration cluster in signature for devices with firmware v12.0 & v13.0
signature[ENDPOINTS][1][INPUT_CLUSTERS].insert(1, PowerConfiguration.cluster_id)
class ZLinkyTICFWV14(ZLinkyTICFWV12):
"""ZLinky_TIC from LiXee with firmware v14.0+."""
signature = deepcopy(ZLinkyTICFWV12.signature)
replacement = deepcopy(ZLinkyTICFWV12.replacement)
# Insert Time configuration cluster in signature for devices with firmware v14.0+
signature[ENDPOINTS][1][INPUT_CLUSTERS].insert(1, Time.cluster_id)
# Insert Tuya cluster in signature for devices with firmware v14.0+
signature[ENDPOINTS][1][INPUT_CLUSTERS].insert(7, TuyaManufCluster.cluster_id)
signature[ENDPOINTS][1][OUTPUT_CLUSTERS].insert(1, TuyaManufCluster.cluster_id)
replacement[ENDPOINTS][1][INPUT_CLUSTERS].insert(1, Time.cluster_id)
class ZLinkyTICFWV15(ZLinkyTICFWV14):
"""ZLinky_TIC from LiXee with firmware v15.0+."""
signature = deepcopy(ZLinkyTICFWV14.signature)
replacement = deepcopy(ZLinkyTICFWV14.replacement)
signature[ENDPOINTS][1][DEVICE_TYPE] = zha.DeviceType.DIMMABLE_LIGHT
replacement[ENDPOINTS][1][DEVICE_TYPE] = zha.DeviceType.DIMMABLE_LIGHTAdditional information
FWIW, I've provided the custom_quirk that solves my issue.
Obliously, it's only the addition of AttributeDefs in the ZLinkyTICMetering that is relevant.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
device support requestThis requests support for a new deviceThis requests support for a new device