Repository: zigpy/zha-device-handlers (python)

Issue:
New Ikea Somrig button, not able to connect zha 
### Problem description

Hello :slight_smile:
I bought the new Ikea Somrig buttons and would like to connect them to my HomeAssistant with Zigbee Dongle.
During the search of a new device, HA identifies the button but is not able to configure it.
Although the configuration process does not finish, the device is part of the device list. Sometimes there is a battery and identify entity and sometimes not. I can not control any entity.

### Solution description

 Is anybody aware if there is a way to connect a device which is currently not fully supported or do you know if there will be an integration?

### Screenshots/Video

<img width="425" alt="Bildschirm­foto 2023-12-12 um 20 17 57" src="https://github.com/zigpy/zha-device-handlers/assets/153435366/3fe2e683-adf7-4d38-ab56-332d9c3334fd">
<img width="299" alt="Bildschirm­foto 2023-12-12 um 20 20 21" src="https://github.com/zigpy/zha-device-handlers/assets/153435366/47bc0a97-bd70-401e-ae24-481fce65d8ca">


### Device signature

IEEE: 5c:c7:c1:ff:fe:87:35:e2
Nwk: 0x046e
Device Type: EndDevice
LQI: 108
RSSI: Unbekannt
Zuletzt gesehen: 2023-12-12T20:13:52
Energiequelle: Battery or Unknown

### Diagnostic information

[zha-e958911dd7b4b0c8793c91dc810d16d3-IKEA of Sweden SOMRIG shortcut button-7dfa14459bf34ecb1709bcf1e8686b31.json.txt](https://github.com/zigpy/zha-device-handlers/files/13652515/zha-e958911dd7b4b0c8793c91dc810d16d3-IKEA.of.Sweden.SOMRIG.shortcut.button-7dfa14459bf34ecb1709bcf1e8686b31.json.txt)


### Logs

<details><summary>Logs</summary>

```python
[Paste the logs here]
```

</details>


### Custom quirk

<details><summary>Custom quirk</summary>

```python
[Paste your custom quirk here]
```

</details>


### Additional information

_No response_

Pull request description:
Add support for IKEA Somrig shortcut button
## Proposed change
Quirk for new SOMRIG smart button from IKEA. I based everything off of the Symfonysk remote v2.


## Additional information
Fixes #2835


## Checklist
<!--
  Put an 'x' in all boxes that apply.
  Note: You do not need to tick all boxes before creating a PR.
-->

- [x] The changes are tested and work correctly
- [x] `pre-commit` checks pass / the code has been formatted using Black
- [ ] Tests have been added to verify that the new code works


Patch:
diff --git a/zhaquirks/ikea/somrigsmartbtn.py b/zhaquirks/ikea/somrigsmartbtn.py
new file mode 100644
index 0000000..412249c
--- /dev/null
+++ b/zhaquirks/ikea/somrigsmartbtn.py
@@ -0,0 +1,165 @@
+"""Device handler for IKEA of Sweden SOMRIG shortcut button"""
+from zigpy.profiles import zha
+from zigpy.quirks import CustomDevice
+from zigpy.zcl.clusters.general import (
+    Basic,
+    Groups,
+    Identify,
+    LevelControl,
+    OnOff,
+    Ota,
+    PollControl,
+    PowerConfiguration,
+)
+from zigpy.zcl.clusters.lightlink import LightLink
+
+from zhaquirks.const import (
+    BUTTON_1,
+    BUTTON_2,
+    COMMAND,
+    COMMAND_M_INITIAL_PRESS,
+    COMMAND_M_LONG_PRESS,
+    COMMAND_M_LONG_RELEASE,
+    COMMAND_M_MULTI_PRESS_COMPLETE,
+    COMMAND_M_SHORT_RELEASE,
+    DEVICE_TYPE,
+    DOUBLE_PRESS,
+    ENDPOINT_ID,
+    ENDPOINTS,
+    INPUT_CLUSTERS,
+    LONG_PRESS,
+    LONG_RELEASE,
+    MODELS_INFO,
+    OUTPUT_CLUSTERS,
+    PRESSED,
+    PROFILE_ID,
+    SHORT_PRESS,
+)
+from zhaquirks.ikea import (
+    IKEA,
+    IKEA_CLUSTER_ID,
+    PowerConfig1AAACluster,
+    ShortcutV2Cluster,
+)
+
+
+class IkeaSomrigSmartButton(CustomDevice):
+    """Custom device representing IKEA SOMRIG shortcut button"""
+
+    signature = {
+        # <SimpleDescriptor endpoint=1 profile=260, device_type=6
+        # device_version=1
+        # input_clusters=[0, 1, 3, 4, 32, 4096, 64636, 64640]
+        # output_clusters=[3, 4, 6, 8, 25, 4096, 64640]>
+        MODELS_INFO: [(IKEA, "SOMRIG shortcut button")],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    PowerConfiguration.cluster_id,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    PollControl.cluster_id,
+                    LightLink.cluster_id,
+                    IKEA_CLUSTER_ID,
+                    ShortcutV2Cluster.cluster_id,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    Ota.cluster_id,
+                    LightLink.cluster_id,
+                    ShortcutV2Cluster.cluster_id,
+                ],
+            },
+            # <SimpleDescriptor endpoint=2  profile=260  device_type=6
+            # device_version=1
+            # input_clusters=[0, 3, 4, 64640]
+            # output_clusters=[3, 4, 6, 8, 64640]>
+            2: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    ShortcutV2Cluster.cluster_id,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    ShortcutV2Cluster.cluster_id,
+                ],
+            },
+        },
+    }
+
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    PowerConfig1AAACluster,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    PollControl.cluster_id,
+                    LightLink.cluster_id,
+                    IKEA_CLUSTER_ID,
+                    ShortcutV2Cluster,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    Ota.cluster_id,
+                    LightLink.cluster_id,
+                    ShortcutV2Cluster,
+                ],
+            },
+            2: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.REMOTE_CONTROL,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    ShortcutV2Cluster,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,
+                    Groups.cluster_id,
+                    OnOff.cluster_id,
+                    LevelControl.cluster_id,
+                    ShortcutV2Cluster,
+                ],
+            },
+        },
+    }
+
+    device_automation_triggers = {
+        (PRESSED, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_M_INITIAL_PRESS},
+        (SHORT_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_M_SHORT_RELEASE},
+        (DOUBLE_PRESS, BUTTON_1): {
+            ENDPOINT_ID: 1,
+            COMMAND: COMMAND_M_MULTI_PRESS_COMPLETE,
+        },
+        (LONG_PRESS, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_M_LONG_PRESS},
+        (LONG_RELEASE, BUTTON_1): {ENDPOINT_ID: 1, COMMAND: COMMAND_M_LONG_RELEASE},
+        (PRESSED, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: COMMAND_M_INITIAL_PRESS},
+        (SHORT_PRESS, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: COMMAND_M_SHORT_RELEASE},
+        (DOUBLE_PRESS, BUTTON_2): {
+            ENDPOINT_ID: 2,
+            COMMAND: COMMAND_M_MULTI_PRESS_COMPLETE,
+        },
+        (LONG_PRESS, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: COMMAND_M_LONG_PRESS},
+        (LONG_RELEASE, BUTTON_2): {ENDPOINT_ID: 2, COMMAND: COMMAND_M_LONG_RELEASE},
+    }
diff --git a/zhaquirks/sonoff/snzb01p.py b/zhaquirks/sonoff/snzb01p.py
new file mode 100644
index 0000000..5248f09
--- /dev/null
+++ b/zhaquirks/sonoff/snzb01p.py
@@ -0,0 +1,91 @@
+"""Sonoff Smart Button SNZB-01P"""
+from zigpy.profiles import zha
+from zigpy.quirks import CustomDevice
+from zigpy.zcl.clusters.general import (
+    Basic,
+    Identify,
+    OnOff,
+    Ota,
+    PollControl,
+    PowerConfiguration,
+)
+
+from zhaquirks.const import (
+    BUTTON,
+    CLUSTER_ID,
+    COMMAND,
+    COMMAND_OFF,
+    COMMAND_ON,
+    COMMAND_TOGGLE,
+    DEVICE_TYPE,
+    DOUBLE_PRESS,
+    ENDPOINT_ID,
+    ENDPOINTS,
+    INPUT_CLUSTERS,
+    LONG_PRESS,
+    MODELS_INFO,
+    OUTPUT_CLUSTERS,
+    PROFILE_ID,
+    SHORT_PRESS,
+)
+
+SONOFF_CLUSTER_ID = 0xFC57
+
+
+class SonoffSmartButtonSNZB01P(CustomDevice):
+    """Sonoff smart button remote - model SNZB-01P"""
+
+    signature = {
+        #  <SimpleDescriptor endpoint=1 profile=260 device_type=0
+        #  device_version=1
+        #  input_clusters=[0, 1, 3, 32, 64599]
+        #  output_clusters=[3, 6, 25]>
+        MODELS_INFO: [
+            ("eWeLink", "SNZB-01P"),
+        ],
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    PowerConfiguration.cluster_id,
+                    Identify.cluster_id,
+                    PollControl.cluster_id,
+                    SONOFF_CLUSTER_ID,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,
+                    OnOff.cluster_id,
+                    Ota.cluster_id,
+                ],
+            },
+        },
+    }
+
+    replacement = {
+        ENDPOINTS: {
+            1: {
+                PROFILE_ID: zha.PROFILE_ID,
+                DEVICE_TYPE: zha.DeviceType.ON_OFF_SWITCH,
+                INPUT_CLUSTERS: [
+                    Basic.cluster_id,
+                    PowerConfiguration.cluster_id,
+                    Identify.cluster_id,
+                    PollControl.cluster_id,
+                    SONOFF_CLUSTER_ID,
+                ],
+                OUTPUT_CLUSTERS: [
+                    Identify.cluster_id,
+                    OnOff.cluster_id,
+                    Ota.cluster_id,
+                ],
+            },
+        },
+    }
+
+    device_automation_triggers = {
+        (SHORT_PRESS, BUTTON): {COMMAND: COMMAND_TOGGLE, CLUSTER_ID: 6, ENDPOINT_ID: 1},
+        (DOUBLE_PRESS, BUTTON): {COMMAND: COMMAND_ON, CLUSTER_ID: 6, ENDPOINT_ID: 1},
+        (LONG_PRESS, BUTTON): {COMMAND: COMMAND_OFF, CLUSTER_ID: 6, ENDPOINT_ID: 1},
+    }
diff --git a/zhaquirks/tuya/ts0601_gas.py b/zhaquirks/tuya/ts0601_gas.py
index d1e6885..5670426 100644
--- a/zhaquirks/tuya/ts0601_gas.py
+++ b/zhaquirks/tuya/ts0601_gas.py
@@ -79,7 +79,10 @@ class TuyaGasDetector0601(CustomDevice):
         super().__init__(*args, **kwargs)
 
     signature = {
-        MODELS_INFO: [("_TZE200_ggev5fsl", "TS0601")],
+        MODELS_INFO: [
+            ("_TZE200_ggev5fsl", "TS0601"),
+            ("_TZE200_rjxqso4a", "TS0601"),
+        ],
         ENDPOINTS: {
             1: {
                 PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
diff --git a/zhaquirks/tuya/ts0601_trv.py b/zhaquirks/tuya/ts0601_trv.py
index 1400888..d480b9c 100644
--- a/zhaquirks/tuya/ts0601_trv.py
+++ b/zhaquirks/tuya/ts0601_trv.py
@@ -1536,6 +1536,7 @@ class MoesHY368_Type1(TuyaThermostat):
             ("_TZE200_9sfg7gm0", "TS0601"),
             ("_TZE200_8whxpsiw", "TS0601"),
             ("_TZE200_8thwkzxl", "TS0601"),  # Tervix Pro Line EVA 2
+            ("_TZE200_xby0s3ta", "TS0601"),  # Sandy Beach HY367
         ],
         ENDPOINTS: {
             1: {


Test patch:
diff --git a/tests/test_sonoff.py b/tests/test_sonoff.py
new file mode 100644
index 0000000..2e5b47a
--- /dev/null
+++ b/tests/test_sonoff.py
@@ -0,0 +1,30 @@
+"""Tests for Sonoff quirks."""
+
+
+import zhaquirks
+import zhaquirks.sonoff.snzb01p
+
+zhaquirks.setup()
+
+
+def test_sonoff_snzb01p(assert_signature_matches_quirk):
+    """Test 'Sonoff SNZB-01P smart button' signature is matched to its quirk."""
+
+    signature = {
+        "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.EndDevice: 2>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress: 128>, manufacturer_code=4742, maximum_buffer_size=82, maximum_incoming_transfer_size=255, server_mask=11264, maximum_outgoing_transfer_size=255, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)",
+        "endpoints": {
+            "1": {
+                "profile_id": 260,
+                "device_type": "0x0000",
+                "in_clusters": ["0x0000", "0x0001", "0x0003", "0x0020", "0xfc57"],
+                "out_clusters": ["0x0003", "0x0006", "0x0019"],
+            }
+        },
+        "manufacturer": "eWeLink",
+        "model": "SNZB-01P",
+        "class": "sonoff.snzb01p.SonoffSmartButtonSNZB01P",
+    }
+
+    assert_signature_matches_quirk(
+        zhaquirks.sonoff.snzb01p.SonoffSmartButtonSNZB01P, signature
+    )