Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tuya smoke detector TS0205 PST-YG500A #2991

Merged
merged 10 commits into from
Aug 27, 2024
68 changes: 68 additions & 0 deletions tests/test_tuya_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Tests for Tuya Smoke Detector."""

import pytest
from zigpy.zcl.clusters.security import IasZone

from tests.common import ClusterListener
import zhaquirks.tuya.ts0205

zhaquirks.setup()


def test_ts0205_signature(assert_signature_matches_quirk):
"""Test signature."""

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=4417, maximum_buffer_size=66, maximum_incoming_transfer_size=66, server_mask=10752, maximum_outgoing_transfer_size=66, 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": 0x0104,
"device_type": "0x0402",
"in_clusters": ["0x0000", "0x0001", "0x0004", "0x0005", "0x0500"],
"out_clusters": [
"0x0003",
"0x0004",
"0x0006",
"0x000a",
"0x0019",
"0x1000",
],
}
},
"manufacturer": "_TZ3210_up3pngle",
"model": "TS0205",
"class": "ts0205.TuyaSmokeDetectorTS0205",
}
assert_signature_matches_quirk(
zhaquirks.tuya.ts0205.TuyaSmokeDetectorTS0205, signature
)


@pytest.mark.parametrize("quirk", (zhaquirks.tuya.ts0205.TuyaSmokeDetectorTS0205,))
async def test_tuya_smoke_sensor_attribute_update(zigpy_device_from_quirk, quirk):
"""Test update_attribute on Tuya smoke sensor."""

device = zigpy_device_from_quirk(quirk)

opple_cluster = device.endpoints[1].tuya_manufacturer
opple_listener = ClusterListener(opple_cluster)

ias_cluster = device.endpoints[1].ias_zone
ias_listener = ClusterListener(ias_cluster)

zone_status_id = IasZone.AttributeDefs.zone_status.id

# check that updating smoke attribute also updates zone status on the Ias Zone cluster

# turn on smoke alarm
opple_cluster.update_attribute(0x0401, 1)
assert len(opple_listener.attribute_updates) == 1
assert len(ias_listener.attribute_updates) == 1
assert ias_listener.attribute_updates[0][0] == zone_status_id
assert ias_listener.attribute_updates[0][1] == 0
# turn off smoke alarm
opple_cluster.update_attribute(0x0401, 0)
assert len(opple_listener.attribute_updates) == 2
assert len(ias_listener.attribute_updates) == 2
assert ias_listener.attribute_updates[1][0] == zone_status_id
assert ias_listener.attribute_updates[1][1] == IasZone.ZoneStatus.Alarm_1
111 changes: 111 additions & 0 deletions zhaquirks/tuya/ts0205.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Tuya TS0205 smoke detector."""

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Identify,
OnOff,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zigpy.zcl.clusters.lightlink import LightLink
from zigpy.zcl.clusters.security import IasZone
from zigpy.zcl.foundation import BaseAttributeDefs, ZCLAttributeDef

from zhaquirks import LocalDataCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import TuyaManufClusterAttributes


class TuyaSmokeDetectorCluster(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the TS0205 smoke detector."""

class AttributeDefs(BaseAttributeDefs):
"""Attribute definitions."""

smoke_detected = ZCLAttributeDef(
id=0x0401, # [0]/[1] [Detected]/[Clear]
type=t.uint8_t,
is_manufacturer_specific=True,
)

def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == self.AttributeDefs.smoke_detected.id:
if value == 0:
self.endpoint.ias_zone.update_attribute(
IasZone.AttributeDefs.zone_status.id, IasZone.ZoneStatus.Alarm_1
)
else:
self.endpoint.ias_zone.update_attribute(
IasZone.AttributeDefs.zone_status.id, 0
)


class TuyaIasZone(LocalDataCluster, IasZone):
"""IAS Zone."""

_CONSTANT_ATTRIBUTES = {
IasZone.AttributeDefs.zone_type.id: IasZone.ZoneType.Fire_Sensor
}


class TuyaSmokeDetectorTS0205(CustomDevice):
"""Tuya smoke sensor PST-YG500A."""

signature = {
MODELS_INFO: [("_TZ3210_up3pngle", "TS0205")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
IasZone.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
OnOff.cluster_id,
Time.cluster_id,
Ota.cluster_id,
LightLink.cluster_id,
],
}
},
}

replacement = {
ENDPOINTS: {
1: {
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaIasZone,
TuyaSmokeDetectorCluster,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
Ota.cluster_id,
],
}
}
}
Loading