Add TS0041 signature variant for Tesla Smart TSL-SEN-BUTTON (_TZ3000_ajsypttg) - #5263
Add TS0041 signature variant for Tesla Smart TSL-SEN-BUTTON (_TZ3000_ajsypttg)#5263sofger wants to merge 1 commit into
Conversation
…ajsypttg) The Tesla Smart TSL-SEN-BUTTON (a Tuya TS0041 rebrand) reports device_type=0x0401 (IAS_ANCILLARY_CONTROL) in its simple descriptor instead of 0x0000 (ON_OFF_SWITCH), so none of the existing TS0041 signatures match and the remote is unsupported. Add a variant of TuyaSmartRemote0041TI with the IAS ancillary device type.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #5263 +/- ##
==========================================
+ Coverage 92.60% 92.61% +0.01%
==========================================
Files 424 424
Lines 14667 14671 +4
==========================================
+ Hits 13582 13588 +6
+ Misses 1085 1083 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Device diagnostics (redacted: IEEE/unique IDs by HA, plus timezone and custom component list; the static zha-_TZ3000_ajsypttg TS0041 diagnostics{
"home_assistant": {
"arch": "x86_64",
"dev": false,
"docker": true,
"hassio": false,
"installation_type": "Home Assistant Container",
"os_name": "Linux",
"os_version": "6.8.0-136-generic",
"python_version": "3.14.6",
"timezone": "**REDACTED**",
"version": "2026.7.4",
"virtualenv": false,
"container_arch": "amd64",
"run_as_root": true
},
"custom_components": {},
"integration_manifest": {
"domain": "zha",
"name": "Zigbee Home Automation",
"after_dependencies": [
"hassio",
"onboarding",
"usb"
],
"codeowners": [
"dmulcahey",
"adminiuga",
"puddly",
"TheJulianJES"
],
"config_flow": true,
"dependencies": [
"file_upload",
"homeassistant_hardware"
],
"documentation": "https://www.home-assistant.io/integrations/zha",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": [
"aiosqlite",
"bellows",
"crccheck",
"pure_pcapy3",
"zhaquirks",
"zigpy",
"zigpy_deconz",
"zigpy_xbee",
"zigpy_zigate",
"zigpy_znp",
"zha",
"universal_silabs_flasher",
"serialx"
],
"requirements": [
"zha==2.0.1",
"zha-quirks==2.1.1"
],
"is_built_in": true,
"overwrites_built_in": false
},
"setup_times": {
"null": {
"setup": 5.3020892664790154e-05
},
"c6970b1bd49b7a187f307e105ddea854": {
"wait_import_platforms": -0.014086134964600205,
"config_entry_setup": 5.789520773105323
}
},
"data": {
"version": 2,
"ieee": "**REDACTED**",
"nwk": "0x91F8",
"manufacturer": "_TZ3000_ajsypttg",
"model": "TS0041",
"friendly_manufacturer": "_TZ3000_ajsypttg",
"friendly_model": "TS0041",
"name": "_TZ3000_ajsypttg TS0041",
"quirk_applied": true,
"quirk_class": "ts0041_tesla:TuyaSmartRemote0041TIIAS",
"exposes_features": [],
"manufacturer_code": 4098,
"power_source": "Battery or Unknown",
"lqi": 224,
"rssi": -44,
"last_seen": "2026-08-18T15:28:30.566551+00:00",
"available": true,
"device_type": "EndDevice",
"active_coordinator": false,
"node_descriptor": {
"logical_type": "EndDevice",
"complex_descriptor_available": false,
"user_descriptor_available": false,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 128,
"manufacturer_code": 4098,
"maximum_buffer_size": 82,
"maximum_incoming_transfer_size": 82,
"server_mask": 11264,
"maximum_outgoing_transfer_size": 82,
"descriptor_capability_field": 0
},
"endpoints": {
"1": {
"profile_id": 260,
"device_type": {
"name": "REMOTE_CONTROL",
"id": 6
},
"in_clusters": [
{
"cluster_id": "0x0000",
"endpoint_attribute": "basic",
"attributes": [
{
"id": "0x0001",
"name": "app_version",
"zcl_type": "uint8",
"value": 64
},
{
"id": "0x0004",
"name": "manufacturer",
"zcl_type": "string",
"value": "_TZ3000_ajsypttg"
},
{
"id": "0x0005",
"name": "model",
"zcl_type": "string",
"value": "TS0041"
}
]
},
{
"cluster_id": "0x0001",
"endpoint_attribute": "power",
"attributes": [
{
"id": "0x0021",
"name": "battery_percentage_remaining",
"zcl_type": "uint8",
"value": 200
},
{
"id": "0x0033",
"name": "battery_quantity",
"zcl_type": "uint8",
"unsupported": true
},
{
"id": "0x0031",
"name": "battery_size",
"zcl_type": "enum8",
"unsupported": true
},
{
"id": "0x0020",
"name": "battery_voltage",
"zcl_type": "uint8",
"value": 30
}
]
},
{
"cluster_id": "0x0006",
"endpoint_attribute": "TS004X_cluster",
"attributes": []
},
{
"cluster_id": "0x000a",
"endpoint_attribute": "time",
"attributes": []
}
],
"out_clusters": [
{
"cluster_id": "0x0019",
"endpoint_attribute": "ota",
"attributes": [
{
"id": "0x0002",
"name": "current_file_version",
"zcl_type": "uint32",
"value": 64
}
],
"last_query_cmd": {
"manufacturer_code": 4098,
"image_type": 5634,
"current_file_version": 64,
"hardware_version": null
}
}
]
}
},
"original_signature": {
"manufacturer": "_TZ3000_ajsypttg",
"model": "TS0041",
"node_desc": {
"logical_type": 2,
"complex_descriptor_available": 0,
"user_descriptor_available": 0,
"reserved": 0,
"aps_flags": 0,
"frequency_band": 8,
"mac_capability_flags": 128,
"manufacturer_code": 4098,
"maximum_buffer_size": 82,
"maximum_incoming_transfer_size": 82,
"server_mask": 11264,
"maximum_outgoing_transfer_size": 82,
"descriptor_capability_field": 0
},
"endpoints": {
"1": {
"profile_id": "0x0104",
"device_type": "0x0401",
"input_clusters": [
"0x0000",
"0x000a",
"0x0001",
"0x0006"
],
"output_clusters": [
"0x0019"
]
}
}
},
"zha_lib_entities": {
"sensor": [
{
"info_object": {
"fallback_name": null,
"unique_id": "**REDACTED**",
"migrate_unique_ids": [],
"platform": "sensor",
"class_name": "LQISensor",
"translation_key": "lqi",
"translation_placeholders": null,
"device_class": null,
"state_class": "measurement",
"entity_category": "diagnostic",
"entity_registry_enabled_default": false,
"enabled": true,
"primary": false,
"device_ieee": "**REDACTED**",
"endpoint_id": 1,
"available": true,
"group_id": null,
"suggested_display_precision": null,
"unit": null
},
"state": {
"class_name": "LQISensor",
"available": true,
"state": 224
}
},
{
"info_object": {
"fallback_name": null,
"unique_id": "**REDACTED**",
"migrate_unique_ids": [],
"platform": "sensor",
"class_name": "RSSISensor",
"translation_key": "rssi",
"translation_placeholders": null,
"device_class": "signal_strength",
"state_class": "measurement",
"entity_category": "diagnostic",
"entity_registry_enabled_default": false,
"enabled": true,
"primary": false,
"device_ieee": "**REDACTED**",
"endpoint_id": 1,
"available": true,
"group_id": null,
"suggested_display_precision": null,
"unit": "dBm"
},
"state": {
"class_name": "RSSISensor",
"available": true,
"state": -44
}
},
{
"info_object": {
"fallback_name": null,
"unique_id": "**REDACTED**",
"migrate_unique_ids": [],
"platform": "sensor",
"class_name": "Battery",
"translation_key": null,
"translation_placeholders": null,
"device_class": "battery",
"state_class": "measurement",
"entity_category": "diagnostic",
"entity_registry_enabled_default": true,
"enabled": true,
"primary": false,
"device_ieee": "**REDACTED**",
"endpoint_id": 1,
"available": true,
"group_id": null,
"suggested_display_precision": 0,
"unit": "%"
},
"state": {
"class_name": "Battery",
"available": true,
"state": 100.0,
"battery_voltage": 3.0
},
"extra_state_attributes": [
"battery_quantity",
"battery_size",
"battery_voltage"
]
}
],
"update": [
{
"info_object": {
"fallback_name": null,
"unique_id": "**REDACTED**",
"migrate_unique_ids": [],
"platform": "update",
"class_name": "FirmwareUpdateEntity",
"translation_key": null,
"translation_placeholders": null,
"device_class": "firmware",
"state_class": null,
"entity_category": "config",
"entity_registry_enabled_default": true,
"enabled": true,
"primary": false,
"device_ieee": "**REDACTED**",
"endpoint_id": 1,
"available": true,
"group_id": null,
"supported_features": 7
},
"state": {
"class_name": "FirmwareUpdateEntity",
"available": true,
"installed_version": "0x00000040",
"in_progress": false,
"update_percentage": null,
"latest_version": null,
"release_summary": null,
"release_notes": null,
"release_url": null
}
}
]
},
"neighbors": [],
"routes": []
},
"issues": []
} |
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Approve — clean, minimal signature variant. I verified both directions: the device genuinely cannot match any existing TS0041 quirk, and adding this one shadows none of them.
The attached diagnostics confirm the premise exactly — original_signature is profile 0x0104 / device_type 0x0401 / in [0x0000, 0x000a, 0x0001, 0x0006] / out [0x0019], i.e. TuyaSmartRemote0041TI's signature with IAS_ANCILLARY_CONTROL in place of ON_OFF_SWITCH. v1 signature matching compares device_type, so a separate class really is the only option here; there is no way to loosen the existing one.
Two nits inline, both optional. Nothing blocking.
Note for whoever merges: #4537 renames TuyaSmartRemoteOnOffCluster to TuyaOnOffCluster throughout zhaquirks/tuya/ts0041.py. Whichever of the two lands second needs the other's replacement dict updated to match — trivial, but easy to miss. Separately, there is no tests/data/devices/ snapshot in zigpy/zha for this variant yet; the dump posted in the PR comment is importable if a snapshot is wanted (low value here, since the quirk declares no v2 entities).
Optional, maintainer's call, no change requested: these TS0041 quirks key on MODEL alone with no manufacturer, and it is exactly that exact-signature matching which let this device fall through. A v2 QuirkBuilder("_TZ3000_ajsypttg", "TS0041") would match on manufacturer + model and be immune to further descriptor drift. The trade-off is losing the catch-all coverage the model-only v1 signatures provide, so staying v1 and consistent with the rest of the file (as #4063 did in February) is perfectly defensible — flagging it only so the choice is deliberate.
Verified (8 checks)
- Built a device from the diagnostics'
original_signatureand ran it through the quirk registry: ondevit resolves to a plainzigpy.device.Device(no quirk at all); with this PR it resolves tozhaquirks.tuya.ts0041.TuyaSmartRemote0041TIIAS. - Same probe for the pre-existing variants —
_TZ3000_awgcnkrh(device_type 0x0000) still resolves toTuyaSmartRemote0041TI,_TZ3000_pwgcnkrhstill toTuyaSmartRemote0041TO— so the new signature shadows nothing. zha.DeviceType.IAS_ANCILLARY_CONTROL == 0x0401 == 1025, matching the descriptor quoted in the description.replacementanddevice_automation_triggersare identical toTuyaSmartRemote0041TI, and the three press constants line up withTuyaSmartRemoteOnOffCluster.press_type(0x00/0x01/0x02-> short/double/long).tests/test_tuya.py+tests/test_quirks.py: 3928 passed, 2 xfailed.ruff checkandruff format --checkclean on both changed files, and Codecov reports the added lines covered.- Duplicate sweep over all 375 open PRs' file lists plus title/body searches: no other open or recently merged PR adds
_TZ3000_ajsypttg, the class nameTuyaSmartRemote0041TIIAS, or any otherIAS_ANCILLARY_CONTROLTS0041 signature. #4537 is the only other open PR touching this file (see note above); #4303 and #4309 only appear to touch it because their branches predate #5113. - Zigbee2MQTT has no converter entry for
_TZ3000_ajsypttgorTSL-SEN-BUTTON, so no cross-stack reference was available. Z2M matches TS0041 on model ID alone, so it would not hit this class of problem in the first place. - 2.0 quirks-API migration: nothing to do. The branch is based on the current
devtip (b139f81), and neither changed file imports fromzigpy.quirks.
| """Tuya 1-button remote device with time on in and IAS ancillary device type.""" | ||
|
|
||
| signature = { | ||
| # SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1025, device_version=1, input_clusters=[0, 1, 6, 10], output_clusters=[25])) |
There was a problem hiding this comment.
Nit: the pasted descriptor lists the input clusters as [0, 1, 6, 10], but the device reports them in the order [0, 10, 1, 6] (per original_signature in the diagnostics you attached). Cluster order has no effect on matching, so this is purely cosmetic — but these comments are meant to be the verbatim simple descriptor, so it would be nice for it to match. (The stray trailing ) is inherited from the class above; same category.)
| (zhaquirks.tuya.ts0041.TuyaSmartRemote0041TI, "_TZ3000_awgcnkrh"), | ||
| (zhaquirks.tuya.ts0041.TuyaSmartRemote0041TI, "_TZ3400_deyjhapk"), | ||
| (zhaquirks.tuya.ts0041.TuyaSmartRemote0041TI, "_some_random_manuf"), | ||
| (zhaquirks.tuya.ts0041.TuyaSmartRemote0041TIIAS, "_TZ3000_ajsypttg"), |
There was a problem hiding this comment.
Nit, optional: the sibling TuyaSmartRemote0041TI / ...TO rows each also carry a _some_random_manuf case, which is what this test is actually named for — these signatures key on MODEL alone, with no manufacturer. Adding one for the new class would assert that catch-all behaviour holds for it too. The real-manufacturer row you added is the more valuable of the two, so this is purely additive.
Proposed change
The Tesla Smart TSL-SEN-BUTTON (a Tuya TS0041 rebrand, manufacturer
_TZ3000_ajsypttg) reportsdevice_type=0x0401(IAS_ANCILLARY_CONTROL) in its simple descriptor instead of0x0000(ON_OFF_SWITCH), so none of the existing TS0041 signatures match and the remote joins as unsupported.This adds
TuyaSmartRemote0041TIIAS, a variant ofTuyaSmartRemote0041TIwith the IAS ancillary control device type in the signature (replacement and triggers are identical).Signature observed on the device:
Additional information
Running this quirk locally (via
custom_quirks_path): the device matches, and short/double/long press device triggers work.Device diagnostics
Attached in the comment below (redacted).
Checklist
pre-commitchecks pass / the code has been formatted using Black🤖 Generated with Claude Code