Skip to content

Commit

Permalink
Add Giex GX03 Valve
Browse files Browse the repository at this point in the history
  • Loading branch information
prairiesnpr committed Oct 10, 2024
1 parent fa0d9fa commit a725b64
Show file tree
Hide file tree
Showing 2 changed files with 315 additions and 1 deletion.
35 changes: 35 additions & 0 deletions tests/test_tuya_valve.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from tests.common import ClusterListener, wait_for_zigpy_tasks
import zhaquirks
import zhaquirks.tuya
import zhaquirks.tuya.ts0601_valve

zhaquirks.setup()

Expand Down Expand Up @@ -114,3 +116,36 @@ async def test_report_values_psbzs(zigpy_device_from_quirk, quirk):
assert tuya_listener.attribute_updates[2][1] == 0 # frost lock state is inverted
assert tuya_listener.attribute_updates[3][0] == 0xEF13
assert tuya_listener.attribute_updates[3][1] == 1 # frost lock state is inverted


@pytest.mark.parametrize(
"model,manuf",
[
("_TZE284_8zizsafo", "TS0601"),
],
)
async def test_giex_03_quirk(zigpy_device_from_v2_quirk, model, manuf):
"""Test Giex GX03 Valve Quirk."""

quirked_device = zigpy_device_from_v2_quirk(model, manuf)
tuya_cluster = quirked_device.endpoints[1].tuya_manufacturer

with mock.patch.object(
tuya_cluster.endpoint, "request", return_value=foundation.Status.SUCCESS
) as m1:
(status,) = await tuya_cluster.write_attributes(
{
"valve_one_duration": 10,
}
)
await wait_for_zigpy_tasks()
m1.assert_called_with(
61184,
1,
b"\x01\x01\x00\x00\x01\x19\x02\x00\x04\x00\x00\x00\x0a",
expect_reply=False,
command_id=0,
)
assert status == [
foundation.WriteAttributesStatusRecord(foundation.Status.SUCCESS)
]
281 changes: 280 additions & 1 deletion zhaquirks/tuya/ts0601_valve.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.quirks.v2 import EntityPlatform, EntityType, QuirkBuilder
from zigpy.quirks.v2.homeassistant import UnitOfTime
from zigpy.quirks.v2.homeassistant.sensor import SensorDeviceClass, SensorStateClass
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import Basic, Groups, Identify, OnOff, Ota, Scenes, Time
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Identify,
OnOff,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zigpy.zcl.clusters.smartenergy import Metering

from zhaquirks import DoublingPowerConfigurationCluster
Expand Down Expand Up @@ -467,3 +479,270 @@ class GiexValveVar02(CustomDevice):
}
}
}


class GiexPowerConfigurationCluster4AA(TuyaPowerConfigurationCluster):
"""PowerConfiguration cluster for devices with 4 AA."""

_CONSTANT_ATTRIBUTES = {
PowerConfiguration.AttributeDefs.battery_size.id: 3,
PowerConfiguration.AttributeDefs.battery_rated_voltage.id: 15,
PowerConfiguration.AttributeDefs.battery_quantity.id: 4,
}


class GiexValveManufClusterModel03(TuyaMCUCluster):
"""GiEX valve manufacturer cluster - two outlet version (GX03)."""

MAX_DURATION_MIN = 1440
VALVE_ONE_SW_ATTR = 0xEF01
VALVE_TWO_SW_ATTR = 0xEF02
VALVE_ONE_CNT_DWN_ATTR = 0xEF0D
VALVE_TWO_CNT_DWN_ATTR = 0xEF0E
VALVE_ONE_DUR_ATTR = 0xEF19
VALVE_TWO_DUR_ATTR = 0xEF1A
VALVE_ONE_WX_ATTR = 0xEF25
VALVE_TWO_WX_ATTR = 0xEF6A
VALVE_ONE_STAT_ATTR = 0xEF68
VALVE_TWO_STAT_ATTR = 0xEF69
VALVE_FAULT_ATTR = 0xEF67

class GiexIrrigationStatus(t.enum8):
"""Giex Irrigation Status Enum."""

Manual = 0x00
Auto = 0x01
Idle = 0x02

class GiexIrrigationWeatherDelay(t.enum8):
"""Giex Irrigation Weather Delay Enum."""

NoDelay = 0x00
OneDayDelay = 0x01
TwoDayDelay = 0x02
ThreeDayDelay = 0x03
FourDayDelay = 0x04
FiveDayDelay = 0x05
SixDayDelay = 0x06
SevenDayDelay = 0x07

attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
VALVE_ONE_SW_ATTR: ("valve_one_on_off", t.Bool, True),
VALVE_TWO_SW_ATTR: ("valve_two_on_off", t.Bool, True),
VALVE_ONE_CNT_DWN_ATTR: (
"valve_one_countdown",
t.uint16_t,
True,
),
VALVE_TWO_CNT_DWN_ATTR: ("valve_two_countdown", t.uint16_t, True),
VALVE_ONE_DUR_ATTR: (
"valve_one_duration",
t.uint32_t,
True,
),
VALVE_TWO_DUR_ATTR: ("valve_two_duration", t.uint32_t, True),
VALVE_ONE_WX_ATTR: (
"valve_one_weather_delay",
GiexIrrigationWeatherDelay,
True,
),
VALVE_TWO_WX_ATTR: (
"valve_two_weather_delay",
GiexIrrigationWeatherDelay,
True,
),
VALVE_ONE_STAT_ATTR: ("valve_one_status", GiexIrrigationStatus, True),
VALVE_TWO_STAT_ATTR: ("valve_two_status", GiexIrrigationStatus, True),
VALVE_FAULT_ATTR: ("valve_fault", t.uint8_t, True),
}
)

dp_to_attribute: dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_on_off",
),
2: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_on_off",
),
13: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_countdown",
),
14: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_countdown",
),
25: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_duration",
),
26: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_duration",
),
37: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_weather_delay",
),
59: DPToAttributeMapping(
GiexPowerConfigurationCluster4AA.ep_attribute,
"battery_percentage_remaining",
),
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_ptds",
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_xhds",
),
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_fault",
),
104: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_one_status",
),
105: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_status",
),
106: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_weather_delay",
),
107: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_ptds",
),
108: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"valve_two_xhds",
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
13: "_dp_2_attr_update",
14: "_dp_2_attr_update",
25: "_dp_2_attr_update",
26: "_dp_2_attr_update",
37: "_dp_2_attr_update",
59: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
103: "_dp_2_attr_update",
104: "_dp_2_attr_update",
105: "_dp_2_attr_update",
106: "_dp_2_attr_update",
107: "_dp_2_attr_update",
108: "_dp_2_attr_update",
}

async def write_attributes(self, attributes, manufacturer=None):
"""Overwrite to force manufacturer code."""

return await super().write_attributes(
attributes, manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID
)


(
QuirkBuilder("_TZE284_8zizsafo", "TS0601")
.replaces(GiexValveManufClusterModel03)
.adds(GiexPowerConfigurationCluster4AA)
.switch(
"valve_one_on_off",
GiexValveManufClusterModel03.cluster_id,
translation_key="valve_one_on_off",
fallback_name="Valve 1",
)
.switch(
"valve_two_on_off",
GiexValveManufClusterModel03.cluster_id,
translation_key="valve_two_on_off",
fallback_name="Valve 2",
)
.number(
"valve_one_countdown",
GiexValveManufClusterModel03.cluster_id,
device_class=SensorDeviceClass.DURATION,
unit=UnitOfTime.MINUTES,
min_value=0,
max_value=GiexValveManufClusterModel03.MAX_DURATION_MIN,
step=1,
translation_key="valve_one_countdown",
fallback_name="Irrigation Time 1",
)
.number(
"valve_two_countdown",
GiexValveManufClusterModel03.cluster_id,
device_class=SensorDeviceClass.DURATION,
unit=UnitOfTime.MINUTES,
min_value=0,
max_value=GiexValveManufClusterModel03.MAX_DURATION_MIN,
step=1,
translation_key="valve_two_countdown",
fallback_name="Irrigation Time 2",
)
.enum(
"valve_one_weather_delay",
GiexValveManufClusterModel03.GiexIrrigationWeatherDelay,
GiexValveManufClusterModel03.cluster_id,
translation_key="weather_delay",
fallback_name="Weather Delay 1",
initially_disabled=True,
)
.enum(
"valve_two_weather_delay",
GiexValveManufClusterModel03.GiexIrrigationWeatherDelay,
GiexValveManufClusterModel03.cluster_id,
translation_key="weather_delay",
fallback_name="Weather Delay 2",
initially_disabled=True,
)
.sensor(
"valve_one_duration",
GiexValveManufClusterModel03.cluster_id,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.DURATION,
unit=UnitOfTime.SECONDS,
entity_type=EntityType.STANDARD,
translation_key="irrigation_duration",
fallback_name="Irrigation Duration 1",
)
.sensor(
"valve_two_duration",
GiexValveManufClusterModel03.cluster_id,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.DURATION,
unit=UnitOfTime.SECONDS,
entity_type=EntityType.STANDARD,
translation_key="irrigation_duration",
fallback_name="Irrigation Duration 2",
)
.enum(
"valve_one_status",
GiexValveManufClusterModel03.GiexIrrigationStatus,
GiexValveManufClusterModel03.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
translation_key="valve_one_status",
fallback_name="Status 1",
)
.enum(
"valve_two_status",
GiexValveManufClusterModel03.GiexIrrigationStatus,
GiexValveManufClusterModel03.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
translation_key="valve_two_status",
fallback_name="Status 2",
)
.add_to_registry()
)

0 comments on commit a725b64

Please sign in to comment.