Replies: 5 comments 18 replies
-
Managed to grab some logs from the device after rebooting HA with more loggers enabled:
The battery seems to be there in the cluster 0xEF00 at dp=7, but I can't read it ! When trying an attribute read with ZHA Toolkit, either at 0xEF00 with attribute index 0x0007 or 0xEF07, it always returns an error ! |
Beta Was this translation helpful? Give feedback.
-
I don't understand why there are so many devices with the same part number (SAS980SWT-7-Z01) with different vendor ID and different clusters allocation, while they all seem to have the same DPs. |
Beta Was this translation helpful? Give feedback.
-
OK, I made a lot of trial and error with my main HA instance (too hard to implement the conbee on my development instance) and I managed to get the battery reporting to work correctly. Had to hack a bit and overload the handle_message() method into my MCU cluster to be able to use the battery bus and update the correct attribute from power configuration cluster. Here is the file : ts0601_TZE200_akjefhj5_valve.py"""Tuya Valve."""
import logging
from typing import Dict
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time, OnOff
from zigpy.zcl.clusters.smartenergy import Metering
from zhaquirks import Bus, LocalDataCluster, DoublingPowerConfigurationCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import (
TuyaLocalCluster,
LocalDataCluster
)
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaDPType,
TuyaMCUCluster,
TuyaOnOff,
)
_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel(logging.DEBUG)
class TuyaValveWaterConsumed(Metering, TuyaLocalCluster):
"""Tuya Valve Water consumed cluster."""
VOLUME_LITERS = 0x0007
"""Setting unit of measurement."""
_CONSTANT_ATTRIBUTES = {0x0300: VOLUME_LITERS}
class NewDoublingPowerConfigurationCluster(LocalDataCluster, DoublingPowerConfigurationCluster):
"""Doubling Power Configuration cluster to receive reports that are sent to the MCU cluster."""
cluster_id = DoublingPowerConfigurationCluster.cluster_id
BATTERY_ID = 0x0021
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.battery_bus.add_listener(self)
def battery_reported(self, value):
"""Battery reported."""
self._update_attribute(self.BATTERY_ID, value)
class TuyaValveManufCluster(TuyaMCUCluster):
"""On/Off Tuya cluster with extra device attributes."""
def handle_message(
self,
hdr,
args,
*,
dst_addressing,
):
if getattr(getattr(args, "data", None), "dp", None) == 7:
_LOGGER.debug(
"%s - TuyaValveManufCluster handling message triggers"
"battery_reported event with value=%s",
self.endpoint.device.nwk,
args.data.data.payload
)
self.endpoint.device.battery_bus.listener_event("battery_reported",
args.data.data.payload
)
return super().handle_message(hdr, args, dst_addressing=dst_addressing)
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
# 7: ("battery_percentage_remaining", t.uint16_t, True),
0xEF0B: ("time_left", t.uint16_t, True),
0xEF0C: ("state", t.enum8, True),
0xEF0F: ("last_valve_open_duration", t.uint16_t, True),
}
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaOnOff.ep_attribute,
"on_off",
dp_type=TuyaDPType.BOOL,
),
5: DPToAttributeMapping(
TuyaValveWaterConsumed.ep_attribute,
"current_summ_delivered",
TuyaDPType.VALUE,
),
7: DPToAttributeMapping(
# DoublingPowerConfigurationCluster.ep_attribute,
NewDoublingPowerConfigurationCluster.ep_attribute,
"battery_percentage_remaining",
TuyaDPType.VALUE,
),
11: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"time_left",
TuyaDPType.VALUE,
),
12: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"state",
TuyaDPType.VALUE,
),
15: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"last_valve_open_duration",
TuyaDPType.VALUE,
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
5: "_dp_2_attr_update",
7: "_dp_2_attr_update",
11: "_dp_2_attr_update",
12: "_dp_2_attr_update",
15: "_dp_2_attr_update",
}
class TuyaValve(CustomDevice):
"""Tuya valve device."""
def __init__(self, *args, **kwargs):
"""Init."""
self.battery_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
MODELS_INFO: [("_TZE200_akjefhj5", "TS0601")],
# SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1,
# input_clusters=[0, 1, 4, 5, 6, 1794, 61184], output_clusters=[25, 10])
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
# PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
# OnOff.cluster_id,
TuyaValveManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
# PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaOnOff,
TuyaValveWaterConsumed, # attr 0 = last volume delivered
NewDoublingPowerConfigurationCluster,
#DoublingPowerConfigurationCluster,
# OnOff.cluster_id,
TuyaValveManufCluster,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
} Now, I need to understand how to make my |
Beta Was this translation helpful? Give feedback.
-
I had to update the file as TuyaDPType has moved in ZHA. Updated file
"""Tuya Valve."""
import logging
from typing import Dict
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time, OnOff
from zigpy.zcl.clusters.smartenergy import Metering
from zhaquirks import Bus, LocalDataCluster, DoublingPowerConfigurationCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import (
TuyaLocalCluster,
LocalDataCluster,
TuyaDPType,
)
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaMCUCluster,
TuyaOnOff,
)
_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel(logging.DEBUG)
class TuyaValveWaterConsumed(Metering, TuyaLocalCluster):
"""Tuya Valve Water consumed cluster."""
VOLUME_LITERS = 0x0007
"""Setting unit of measurement."""
_CONSTANT_ATTRIBUTES = {0x0300: VOLUME_LITERS}
class NewDoublingPowerConfigurationCluster(LocalDataCluster, DoublingPowerConfigurationCluster):
"""Doubling Power Configuration cluster to receive reports that are sent to the MCU cluster."""
cluster_id = DoublingPowerConfigurationCluster.cluster_id
BATTERY_ID = 0x0021
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.battery_bus.add_listener(self)
def battery_reported(self, value):
"""Battery reported."""
self._update_attribute(self.BATTERY_ID, value)
class TuyaValveManufCluster(TuyaMCUCluster):
"""On/Off Tuya cluster with extra device attributes."""
def handle_message(
self,
hdr,
args,
*,
dst_addressing,
):
if getattr(getattr(args, "data", None), "dp", None) == 7:
_LOGGER.debug(
"%s - TuyaValveManufCluster handling message triggers"
"battery_reported event with value=%s",
self.endpoint.device.nwk,
args.data.data.payload
)
self.endpoint.device.battery_bus.listener_event("battery_reported",
args.data.data.payload
)
return super().handle_message(hdr, args, dst_addressing=dst_addressing)
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
# 7: ("battery_percentage_remaining", t.uint16_t, True),
0xEF0B: ("timer_duration", t.uint32_t, True),
0xEF0C: ("state", t.enum8, True),
0xEF0F: ("last_valve_open_duration", t.uint32_t, True),
}
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaOnOff.ep_attribute,
"on_off",
TuyaDPType.BOOL,
),
5: DPToAttributeMapping(
TuyaValveWaterConsumed.ep_attribute,
"current_summ_delivered",
lambda x: x /10.0,
# TuyaDPType.VALUE,
),
7: DPToAttributeMapping(
# DoublingPowerConfigurationCluster.ep_attribute,
NewDoublingPowerConfigurationCluster.ep_attribute,
"battery_percentage_remaining",
TuyaDPType.VALUE,
),
11: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"timer_duration",
TuyaDPType.VALUE,
),
12: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"state",
TuyaDPType.VALUE,
),
15: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"last_valve_open_duration",
TuyaDPType.VALUE,
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
5: "_dp_2_attr_update",
7: "_dp_2_attr_update",
11: "_dp_2_attr_update",
12: "_dp_2_attr_update",
15: "_dp_2_attr_update",
}
class TuyaValve(CustomDevice):
"""Tuya valve device."""
def __init__(self, *args, **kwargs):
"""Init."""
self.battery_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
MODELS_INFO: [("_TZE200_akjefhj5", "TS0601")],
# SizePrefixedSimpleDescriptor(endpoint=1, profile=260, device_type=81, device_version=1,
# input_clusters=[0, 1, 4, 5, 6, 1794, 61184], output_clusters=[25, 10])
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
# PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
# OnOff.cluster_id,
TuyaValveManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
# PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaOnOff,
TuyaValveWaterConsumed, # attr 0 = last volume delivered
NewDoublingPowerConfigurationCluster,
#DoublingPowerConfigurationCluster,
# OnOff.cluster_id,
TuyaValveManufCluster,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
} |
Beta Was this translation helpful? Give feedback.
-
For info, I finally decided to add a second ZigBee controller (Sonoff Dongle) on my raspberry and manage it with ZigBee2MQTT through Docker. I added my valves to Z2M and kept ZHA for the other devices. And now everything is working perfectly fine except for the timers (but they don't even work correctly in Tuya Smart Life app, the time seems to be out of sync); I don't use the timers anyway, so I don't care about the timers, I prefer to automate everything with HA. |
Beta Was this translation helpful? Give feedback.
-
Hello,
I need some help to develop the quirk for the SASWELL SAS980SWT-7-Z01 irrigation timer valve, with the vendor ID _TZE200_akjefhj5 and model TS0601.
https://www.saswell.com/smart-irrigation-wifi-water-timer-sas980swt-7-z01_p147.html
I own two units, and I can control it through Tuya SmartLife app, but I'd like to get rid of Tuya app and use Home Assistant instead through ZHA integration.
I've seen that this valve is already available with different vendors (RTX ZVG1 for example), and I could find in ZHA that there is a quirk that looks very similar to the result the scan of my valves but with different vendor ID :
MODELS_INFO: [("_TZE200_81isopgh", "TS0601")],
#1556This valve exists in the ts0601_valve.py file in ZHA device handlers, but unfortunately it seems it doesn't fully matches my valve's description, changing only the vendor ID didn't work, and I had to make some modifications based on some extractions from Tuya logs.
I could grab the following data from my valves :
In the tuya logs I could extract the DP codes using Chrome developers tool, but unfortunately, it doesn't indicates in which cluster the data are available (in bold those showing data received from valves in Tuya developers platform):
and in the debug tool from the Tuya developers platform, I could get the data types
I made some trials copying the TuyaValve class from ts0601_valve.py and made some changes, on the
The full file
With the file as shown above I can get the valve in HA as below :
And regarding what works and what doesn't work :
smartenergy_metering
sensor is supposed to report the volume of water during the last opening of the valve, but it reports garbage. I could fill 5 liters in a bucket, and it indicated 114, then I added 5 more liters and it said 55 ; while it said 615 and 543 when I added some water in my pool (around 150/160 liters both time, in 30mn).TuyaValveManufCluster
:time_left
attribute by writing the value in the field and using 1 in the replacement code before performing the write. Once the new time value written, it remains set up when reading the attribute, and this value is used when switching the valve on. Once the valve is switched off (either manually or after timer end), the value is then reset to 600 seconds.The doubling power management cluster doesn't seem to have the correct battery level configured, but I can't figure where this data is located in the clusters available in the data from the valve to change it in the quirk.
Could someone please help me to understand how to find the correct data point for battery level and water volume consumed and how to make the attributes be reported as sensor entities in HA ? When looking at the python code from the quirks, it looks really abstract to me, despite my knowledge in python. The behavior of the quirk looks strange, when I try to enable the clusters 1 (power configuration) and 6 (on_off) it breaks the quirk, and the modified clusters like
TuyaValveManufCluster
disappear from the interface. But it seems thePowerConfiguration
cluster should be closer to the one used in the valve, or am I wrong ?Thanks for your help.
Beta Was this translation helpful? Give feedback.
All reactions