Skip to content

Add TS0041 signature variant for Tesla Smart TSL-SEN-BUTTON (_TZ3000_ajsypttg) - #5263

Open
sofger wants to merge 1 commit into
zigpy:devfrom
sofger:tesla-ts0041-ias-device-type
Open

Add TS0041 signature variant for Tesla Smart TSL-SEN-BUTTON (_TZ3000_ajsypttg)#5263
sofger wants to merge 1 commit into
zigpy:devfrom
sofger:tesla-ts0041-ias-device-type

Conversation

@sofger

@sofger sofger commented Aug 18, 2026

Copy link
Copy Markdown

Proposed change

The Tesla Smart TSL-SEN-BUTTON (a Tuya TS0041 rebrand, manufacturer _TZ3000_ajsypttg) 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 joins as unsupported.

This adds TuyaSmartRemote0041TIIAS, a variant of TuyaSmartRemote0041TI with the IAS ancillary control device type in the signature (replacement and triggers are identical).

Signature observed on the device:

SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=1025, device_version=1, input_clusters=[0, 1, 6, 10], output_clusters=[25])

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

  • 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

🤖 Generated with Claude Code

…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

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.61%. Comparing base (b139f81) to head (594b751).
⚠️ Report is 2 commits behind head on dev.

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.
📢 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.

@sofger

sofger commented Aug 18, 2026

Copy link
Copy Markdown
Author

Device diagnostics (redacted: IEEE/unique IDs by HA, plus timezone and custom component list; the static usb/zeroconf discovery matcher boilerplate is trimmed for brevity):

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 zigpy-review-bot added bot: ready PR generally in good state 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 Tuya Request/PR regarding a Tuya device bot: new variant PR only adds a new device model/variant to an existing quirk per LLM labels Aug 20, 2026
@TheJulianJES TheJulianJES added the bot-trigger: post review Pending/new review is posted some time after this label is applied label Aug 26, 2026

@zigpy-review-bot zigpy-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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_signature and ran it through the quirk registry: on dev it resolves to a plain zigpy.device.Device (no quirk at all); with this PR it resolves to zhaquirks.tuya.ts0041.TuyaSmartRemote0041TIIAS.
  • Same probe for the pre-existing variants — _TZ3000_awgcnkrh (device_type 0x0000) still resolves to TuyaSmartRemote0041TI, _TZ3000_pwgcnkrh still to TuyaSmartRemote0041TO — so the new signature shadows nothing.
  • zha.DeviceType.IAS_ANCILLARY_CONTROL == 0x0401 == 1025, matching the descriptor quoted in the description.
  • replacement and device_automation_triggers are identical to TuyaSmartRemote0041TI, and the three press constants line up with TuyaSmartRemoteOnOffCluster.press_type (0x00/0x01/0x02 -> short/double/long).
  • tests/test_tuya.py + tests/test_quirks.py: 3928 passed, 2 xfailed. ruff check and ruff format --check clean 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 name TuyaSmartRemote0041TIIAS, or any other IAS_ANCILLARY_CONTROL TS0041 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_ajsypttg or TSL-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 dev tip (b139f81), and neither changed file imports from zigpy.quirks.

Comment thread zhaquirks/tuya/ts0041.py
"""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]))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.)

Comment thread tests/test_tuya.py
(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"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@zigpy-review-bot zigpy-review-bot removed the bot-trigger: post review Pending/new review is posted some time after this label is applied label Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot: new variant PR only adds a new device model/variant to an existing quirk per LLM bot: ready PR generally in good state 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 Tuya Request/PR regarding a Tuya device

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants