zha-device-handlers
zha-device-handlers copied to clipboard
[Device Support Request] NOUS E6 TS0601 _TZE200_nnrfa68v
Is your feature request related to a problem? Please describe. The NOUS E6 Thermometer and Humidity sensor does not provide any values, as well as the time on it is not synced. I have modified the current sensor quirk a bit and made temperature, humidity and battery level work. Whatever I do though the time synchronization has no effect. In this issue I will provide the code I have now, maybe someone will have some ideas :)
I have also checked the tuya zigbee communication spec but I didn't find anything useful there.
Describe the solution you'd like I would like for the device to be officially supported :). And maybe additionally to suport the time sync feature.
Device 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": 260,
"device_type": "0x0302",
"in_clusters": [
"0x0001",
"0x0402",
"0x0405",
"0xef00"
],
"out_clusters": [
"0x000a",
"0x0019"
]
}
},
"manufacturer": "_TZE200_nnrfa68v",
"model": "TS0601",
"class": "ts0601_sensor_cst.TuyaNousE6TempHumiditySensor"
}
Diagnostic information
{
"home_assistant": {
"installation_type": "Home Assistant OS",
"version": "2022.8.5",
"dev": false,
"hassio": true,
"virtualenv": false,
"python_version": "3.10.5",
"docker": true,
"arch": "x86_64",
"timezone": "Europe/Warsaw",
"os_name": "Linux",
"os_version": "5.15.55",
"supervisor": "2022.08.3",
"host_os": "Home Assistant OS 8.4",
"docker_version": "20.10.14",
"chassis": "vm",
"run_as_root": true
},
"custom_components": {
"hacs": {
"version": "1.26.2",
"requirements": [
"aiogithubapi>=22.2.4"
]
},
"ping_arp": {
"version": "0.1.0",
"requirements": []
}
},
"integration_manifest": {
"domain": "zha",
"name": "Zigbee Home Automation",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zha",
"requirements": [
"bellows==0.32.0",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.78",
"zigpy-deconz==0.18.0",
"zigpy==0.49.1",
"zigpy-xbee==0.15.0",
"zigpy-zigate==0.9.1",
"zigpy-znp==0.8.1"
],
"usb": [
{
"vid": "10C4",
"pid": "EA60",
"description": "*2652*",
"known_devices": [
"slae.sh cc2652rb stick"
]
},
{
"vid": "10C4",
"pid": "EA60",
"description": "*sonoff*plus*",
"known_devices": [
"sonoff zigbee dongle plus"
]
},
{
"vid": "10C4",
"pid": "EA60",
"description": "*tubeszb*",
"known_devices": [
"TubesZB Coordinator"
]
},
{
"vid": "1A86",
"pid": "7523",
"description": "*tubeszb*",
"known_devices": [
"TubesZB Coordinator"
]
},
{
"vid": "1A86",
"pid": "7523",
"description": "*zigstar*",
"known_devices": [
"ZigStar Coordinators"
]
},
{
"vid": "1CF1",
"pid": "0030",
"description": "*conbee*",
"known_devices": [
"Conbee II"
]
},
{
"vid": "10C4",
"pid": "8A2A",
"description": "*zigbee*",
"known_devices": [
"Nortek HUSBZB-1"
]
},
{
"vid": "0403",
"pid": "6015",
"description": "*zigate*",
"known_devices": [
"ZiGate+"
]
},
{
"vid": "10C4",
"pid": "EA60",
"description": "*zigate*",
"known_devices": [
"ZiGate"
]
},
{
"vid": "10C4",
"pid": "8B34",
"description": "*bv 2010/10*",
"known_devices": [
"Bitron Video AV2010/10"
]
}
],
"codeowners": [
"@dmulcahey",
"@adminiuga",
"@puddly"
],
"zeroconf": [
{
"type": "_esphomelib._tcp.local.",
"name": "tube*"
},
{
"type": "_zigate-zigbee-gateway._tcp.local.",
"name": "*zigate*"
}
],
"after_dependencies": [
"onboarding",
"usb",
"zeroconf"
],
"iot_class": "local_polling",
"loggers": [
"aiosqlite",
"bellows",
"crccheck",
"pure_pcapy3",
"zhaquirks",
"zigpy",
"zigpy_deconz",
"zigpy_xbee",
"zigpy_zigate",
"zigpy_znp"
],
"is_built_in": true
},
"data": {
"ieee": "**REDACTED**",
"nwk": 50565,
"manufacturer": "_TZE200_nnrfa68v",
"model": "TS0601",
"name": "_TZE200_nnrfa68v TS0601",
"quirk_applied": true,
"quirk_class": "ts0601_sensor_cst.TuyaNousE6TempHumiditySensor",
"manufacturer_code": 4417,
"power_source": "Battery or Unknown",
"lqi": 112,
"rssi": null,
"last_seen": "2022-08-21T22:13:32",
"available": true,
"device_type": "EndDevice",
"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": 260,
"device_type": "0x0302",
"in_clusters": [
"0x0001",
"0x0402",
"0x0405",
"0xef00"
],
"out_clusters": [
"0x000a",
"0x0019"
]
}
}
},
"active_coordinator": false,
"entities": [
{
"entity_id": "sensor.nous_e6_kuchnia_battery",
"name": "_TZE200_nnrfa68v TS0601"
},
{
"entity_id": "sensor.nous_e6_kuchnia_temperature",
"name": "_TZE200_nnrfa68v TS0601"
},
{
"entity_id": "sensor.nous_e6_kuchnia_humidity",
"name": "_TZE200_nnrfa68v TS0601"
}
],
"neighbors": [],
"endpoint_names": [
{
"name": "TEMPERATURE_SENSOR"
}
],
"user_given_name": "NOUS E6 Kuchnia",
"device_reg_id": "44dcd0624ecf150b39fd626e1efd208c",
"area_id": "kuchnia",
"cluster_details": {
"1": {
"device_type": {
"name": "TEMPERATURE_SENSOR",
"id": 770
},
"profile_id": 260,
"in_clusters": {
"0xef00": {
"endpoint_attribute": "tuya_manufacturer",
"attributes": {
"0xef00": {
"attribute_name": "mcu_version",
"value": "1.0.0"
}
},
"unsupported_attributes": {}
},
"0x0402": {
"endpoint_attribute": "temperature",
"attributes": {
"0x0000": {
"attribute_name": "measured_value",
"value": 2370
}
},
"unsupported_attributes": {}
},
"0x0405": {
"endpoint_attribute": "humidity",
"attributes": {
"0x0000": {
"attribute_name": "measured_value",
"value": 7100
}
},
"unsupported_attributes": {}
},
"0x0001": {
"endpoint_attribute": "power",
"attributes": {
"0x0021": {
"attribute_name": "battery_percentage_remaining",
"value": 200
}
},
"unsupported_attributes": {}
}
},
"out_clusters": {
"0x0019": {
"endpoint_attribute": "ota",
"attributes": {},
"unsupported_attributes": {}
},
"0x000a": {
"endpoint_attribute": "time",
"attributes": {},
"unsupported_attributes": {}
}
}
}
}
}
}
Additional logs
Here are the time_set logs that don't work
2022-08-21 23:56:06.587 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Received ZCL frame: b'\x09\x12\x24\x0D\x00'
2022-08-21 23:56:06.588 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Decoded ZCL frame header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=0, direction=<Direction.Client_to_Server: 1>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False, *is_reply=True), tsn=18, command_id=36, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2022-08-21 23:56:06.588 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Decoded ZCL frame: TemperatureHumidityManufCluster:set_time_request(data=[13, 0])
2022-08-21 23:56:06.588 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Received command 0x24 (TSN 18): set_time_request(data=[13, 0])
2022-08-21 23:56:06.589 WARNING (MainThread) [zigpy.zcl] [0xC585:1:0xef00] HANDLE SET TIME! NOT WORKING YET
2022-08-21 23:56:06.591 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Sending request header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.CLUSTER_COMMAND: 1>, is_manufacturer_specific=True, direction=<Direction.Server_to_Client: 0>, disable_default_response=0, reserved=0, *is_cluster=True, *is_general=False, *is_reply=False), manufacturer=4417, tsn=100, command_id=36, *direction=<Direction.Server_to_Client: 0>, *is_reply=False)
2022-08-21 23:56:06.592 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Sending request: set_time(time=[13, 0, 99, 2, 169, 246, 99, 2, 198, 22])
2022-08-21 23:56:06.592 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Sending reply header: ZCLHeader(frame_control=FrameControl(frame_type=<FrameType.GLOBAL_COMMAND: 0>, is_manufacturer_specific=False, direction=<Direction.Client_to_Server: 1>, disable_default_response=1, reserved=0, *is_cluster=False, *is_general=True, *is_reply=True), tsn=18, command_id=<GeneralCommand.Default_Response: 11>, *direction=<Direction.Client_to_Server: 1>, *is_reply=True)
2022-08-21 23:56:06.593 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Sending reply: Default_Response(command_id=36, status=<Status.SUCCESS: 0>)
2022-08-21 23:56:06.593 DEBUG (MainThread) [zigpy.device] [0xc585] Extending timeout for 0x64 request
2022-08-21 23:56:06.595 DEBUG (MainThread) [zigpy_znp.api] Sending request: AF.DataRequestExt.Req(DstAddrModeAddress=AddrModeAddress(mode=<AddrMode.NWK: 2>, address=0xC585), DstEndpoint=1, DstPanId=0x0000, SrcEndpoint=1, ClusterId=61184, TSN=100, Options=<TransmitOptions.SUPPRESS_ROUTE_DISC_NETWORK|ACK_REQUEST: 48>, Radius=30, Data=b'\x05\x41\x11\x64\x24\x00\x0A\x0D\x00\x63\x02\xA9\xF6\x63\x02\xC6\x16')
Additional context Here is the page with the device info: https://noussmart.pl/product/e6.html Here is the code I for the test quirk:
"""Tuya temp and humidity sensor with a screen."""
from typing import Dict
################## clean this up
import zigpy.types as t
from zigpy.zcl import foundation
from zhaquirks.tuya import TuyaTimePayload, TuyaCommand
import datetime
##################
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SKIP_CONFIGURATION,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster
TUYA_SET_TIME = 0x24
# NOTES:
# The data comes in as a string on cluster, if there is nothing set up you may see these lines in the logs:
# Unknown message (b'19830100a40102000400000118') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 28.0 degrees
# Unknown message (b'19840100a5020200040000022c') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 55.6% humid
# Unknown message (b'19850100a60402000400000064') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 100% battery
class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
"""Tuya local TemperatureMeasurement cluster."""
class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
"""Tuya local RelativeHumidity cluster."""
class TemperatureHumidityManufCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Temperature and Humidity data points."""
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"measured_value",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x * 10, # decidegree to centidegree
),
2: DPToAttributeMapping(
TuyaRelativeHumidity.ep_attribute,
"measured_value",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x * 100, # 0.01 to 1.0
),
4: DPToAttributeMapping(
TuyaPowerConfigurationCluster2AAA.ep_attribute,
"battery_percentage_remaining",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x * 2, # reported percentage is doubled
),
}
set_time_offset = 1970
data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
4: "_dp_2_attr_update",
}
def handle_set_data(self, command: TuyaCommand) -> foundation.Status:
return foundation.Status.SUCCESS
def handle_set_time_request(self, sequence_number: t.uint16_t) -> foundation.Status:
self.warning("HANDLE SET TIME! NOT WORKING YET")
payload = TuyaTimePayload()
#utc_timestamp = int((datetime.datetime.utcnow()).timestamp())
#local_timestamp = int(datetime.datetime.now().timestamp())
utc_now = datetime.datetime.utcnow()
now = datetime.datetime.now()
offset_time = datetime.datetime(self.set_time_offset, 1, 1)
utc_timestamp = int((utc_now - offset_time).total_seconds())
local_timestamp = int((now - offset_time).total_seconds())
payload.extend(sequence_number[0].to_bytes(1, "big", signed=False))
payload.extend(sequence_number[1].to_bytes(1, "big", signed=False))
payload.extend(utc_timestamp.to_bytes(4, "big", signed=False))
payload.extend(local_timestamp.to_bytes(4, "big", signed=False))
self.create_catching_task(
self.command(TUYA_SET_TIME, payload, expect_reply=True, tsn = self.endpoint.device.application.get_sequence())
)
return foundation.Status.SUCCESS
class TuyaNousE6TempHumiditySensor(CustomDevice):
"""Custom device representing tuya temp and humidity sensor with a screen (NOUS E6)."""
signature = {
# <SimpleDescriptor endpoint=1, profile=260, device_type=81
# device_version=1
# input_clusters=[4, 5, 61184, 0]
# output_clusters=[25, 10]>
MODELS_INFO: [("_TZE200_nnrfa68v", "TS0601")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG, # this is how the device reports itself
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TemperatureHumidityManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
replacement = {
SKIP_CONFIGURATION: True,
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
INPUT_CLUSTERS: [
TemperatureHumidityManufCluster, # Single bus for temp, humidity, and battery
TuyaTemperatureMeasurement,
TuyaRelativeHumidity,
TuyaPowerConfigurationCluster2AAA,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
I want to check this issue but I will be away from my computer until the end of August.
Im also really interested. Thanks for your hard work.
The first thing to say is that my knowledge of Python is quite basic, so I recommend interpreting my comments and instructions with caution.
In principle the functionality is implemented in another class: https://github.com/zigpy/zha-device-handlers/blob/dev/zhaquirks/tuya/init.py#L377-L398
Also important, this comment: https://github.com/zigpy/zha-device-handlers/blob/dev/zhaquirks/tuya/init.py#L283-L296
And: https://developer.tuya.com/en/docs/iot/tuya-zigbee-module-uart-communication-protocol?id=K9ear5khsqoty#title-18-Sync%20clock%20time
The proposed code mostly looks correct to me, but there are a couple of differences that might be worth testing.
This code is not in the 'original' implementation:
payload.extend(sequence_number[0].to_bytes(1, "big", signed=False))
payload.extend(sequence_number[1].to_bytes(1, "big", signed=False))
I suppose that wants to put the sequence in the response payload, but there is also a sequence in the command:
self.command(TUYA_SET_TIME, payload, expect_reply=True, tsn = self.endpoint.device.application.get_sequence())
Maybe we could do without these parts (any of both).
Other diference with the original is the command call. The original is like this (the super()
part):
self.create_catching_task(
super().command(TUYA_SET_TIME, payload, expect_reply=False)
)
I would try with a more similar implementation to try to make it work (without the sequence_number[].to_bytes
and the tsn = self.endpoint.device.application.get_sequence()
) and then I would try to put the rest.
There are also the offsets (your code don't implement the set_time_local_offset
one). Maybe to configure this values is relevant for this device. I'm not familiar with this part and I can't say which values could be needed here.
Looking at the code there are some values that I would try:
set_time_offset = 2000
set_time_local_offset = 1970
set_time_offset = 1970
I suppose that the probe that code isn't working is that the device time is not synchronized. And that there is any info about it in the logs. Isn't it?
I also would keep the expect_reply=True
to see which is the device response to command. I suppose that with your actual code there is no response from device.
Yeah the sequence_number was an addition by me based on some Tuya spec I've found bo it didn't work. I will check if I get the sensor to respond to my set time command. The reason why I am not using set_time_local_offset is because the base class for this request is not implementing any code to synchronize the time, so it won't make a difference, but I will try with various offsets. Thanks, I will let you know if I have some progress.
@javicalle hhmm whatever I do I seem to get the following result:
2022-09-06 21:13:08.479 DEBUG (MainThread) [zigpy.zcl] [0xC585:1:0xef00] Decoded ZCL frame: TemperatureHumidityManufCluster:Default_Response(command_id=36, status=<Status.UNSUP_MANUF_CLUSTER_COMMAND: 131>)
It seems that either some sort of validation is failing on the MCU side or it just supports some different weird kind of setting time... Any ideas? :)
The device is not liking the command we sent it. Could you include the full log? I want to see the device request and the response.
Also you can try with this code:
self.create_catching_task(
super().command(
TUYA_SET_TIME,
payload,
manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID,
expect_reply=False
)
)
Wow, it was the manufacturer parameter! Now it works flawlessly. How did you know? :D
Here is the current version of the quirk (which additionally is able to parse temperature_sensitivity etc, I am also trying to make setting of that param work):
"""Tuya temp and humidity sensor with screen."""
from typing import Dict
################## clean this up
import zigpy.types as t
from zigpy.zcl import foundation
from zhaquirks.tuya import TuyaTimePayload, TuyaCommand
import datetime
from typing import Tuple, Optional, Union
##################
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time, AnalogOutput
from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SKIP_CONFIGURATION,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaDPType, TuyaMCUCluster
TUYA_SET_TIME = 0x24
# NOTES:
# The data comes in as a string on cluster, if there is nothing set up you may see these lines in the logs:
# Unknown message (b'19830100a40102000400000118') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 28.0 degrees
# Unknown message (b'19840100a5020200040000022c') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 55.6% humid
# Unknown message (b'19850100a60402000400000064') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 100% battery
class TemperatureUnitConvert(t.enum8):
"""Tuya Temp unit convert enum."""
Celsius = 0x00
Fahrenheit = 0x01
class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
"""Tuya local TemperatureMeasurement cluster."""
attributes = TemperatureMeasurement.attributes.copy()
attributes.update(
{
0x8001: ("temp_unit_convert", t.enum8),
0x8002: ("alarm_max_temperature", t.Single),
0x8003: ("alarm_min_temperature", t.Single),
0x8004: ("temperature_sensitivity", t.Single),
}
)
class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
"""Tuya local RelativeHumidity cluster."""
class TemperatureHumidityManufCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Temperature and Humidity data points."""
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"measured_value",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x * 10, # decidegree to centidegree
),
2: DPToAttributeMapping(
TuyaRelativeHumidity.ep_attribute,
"measured_value",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x * 100, # 0.01 to 1.0
),
4: DPToAttributeMapping(
TuyaPowerConfigurationCluster2AAA.ep_attribute,
"battery_percentage_remaining",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x * 2, # reported percentage is doubled
),
9: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"temp_unit_convert",
dp_type=TuyaDPType.ENUM,
converter=lambda x: TemperatureUnitConvert(x)
),
10: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"alarm_max_temperature",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x / 10
),
11: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"alarm_min_temperature",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x / 10
),
19: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"temperature_sensitivity",
dp_type=TuyaDPType.VALUE,
converter=lambda x: x / 10
)
}
set_time_offset = 1970
set_time_local_offset = 1970
data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
4: "_dp_2_attr_update",
9: "_dp_2_attr_update",
10: "_dp_2_attr_update",
11: "_dp_2_attr_update",
19: "_dp_2_attr_update",
}
def handle_set_time_request(self, sequence_number: t.uint16_t) -> foundation.Status:
payload = TuyaTimePayload()
utc_now = datetime.datetime.utcnow()
now = datetime.datetime.now()
offset_time = datetime.datetime(self.set_time_offset, 1, 1)
offset_time_local = datetime.datetime(self.set_time_local_offset, 1, 1)
utc_timestamp = int((utc_now - offset_time).total_seconds())
local_timestamp = int((now - offset_time).total_seconds())
payload.extend(utc_timestamp.to_bytes(4, "big", signed=False))
payload.extend(local_timestamp.to_bytes(4, "big", signed=False))
self.create_catching_task(
self.command(TUYA_SET_TIME, payload, manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID, expect_reply=False)
)
return foundation.Status.SUCCESS
class TuyaNousE6TempHumiditySensor(CustomDevice):
"""Custom device representing tuya temp and humidity sensor with a screen (NOUS E6)."""
signature = {
# <SimpleDescriptor endpoint=1, profile=260, device_type=81
# device_version=1
# input_clusters=[4, 5, 61184, 0]
# output_clusters=[25, 10]>
MODELS_INFO: [("_TZE200_nnrfa68v", "TS0601")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG, # this is how the device reports itself
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TemperatureHumidityManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
replacement = {
SKIP_CONFIGURATION: True,
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
INPUT_CLUSTERS: [
Basic.cluster_id,
TemperatureHumidityManufCluster, # Single bus for temp, humidity, and battery
TuyaTemperatureMeasurement,
TuyaRelativeHumidity,
TuyaPowerConfigurationCluster2AAA,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
Which param are you trying to set?
And where do these attributes come from?:
0x8001: ("temp_unit_convert", t.enum8),
0x8002: ("alarm_max_temperature", t.Single),
0x8003: ("alarm_min_temperature", t.Single),
0x8004: ("temperature_sensitivity", t.Single),
Any settings you want to modify must be done through the TemperatureHumidityManufCluster
cluster. The rest of the clusters are fake
clusters (hence TuyaLocalCluster
)
PS: you can color your code if:
```python
here your code
```
Ummmm, I think I see what you're trying to...
Add the attributes to the cluster TemperatureHumidityManufCluster
and adapt the code.
class TemperatureHumidityManufCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Temperature and Humidity data points."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
0x8001: ("temp_unit_convert", t.enum8),
0x8002: ("alarm_max_temperature", t.Single),
0x8003: ("alarm_min_temperature", t.Single),
0x8004: ("temperature_sensitivity", t.Single),
}
)
.../...
Device report logs with the datapoints may be helpful.
Hi, also i'm a senior developper i started the zigbee and homeassistant adventure only yesterday, so i apologize if i'mbeing noob.
I installed the above quirk in haos 8.5 and it works thank you very much. Without you i should have returned my device.
However i was wondering why did i have to go to the device and make the pairing again so it began to work. before that, the humidy and temperature where marked as unavailable. is it specific to haos ? When we edit a quirk, beside reloading haos must i always redo the pairing ?
thank you again.
If the device never have reported one attribute ZHA is not knowing it. The Humidity is not sent as Zigbee attribute its tuya DP commands and need the quirk for converting it to Zigbee attribute. Then preparing the device the tuya MCU is sending all DPs its using and the quirk is updating the attribute, very likely the same is happening if waiting some time and the system is getting it from the device.
Normal is not needed repairing device but repower can being good for most devices with Exceptions.
With python you shall always deleting the generated cash folder then editing the file before restarting HA.
Thanks a lot for the quirk, I see now correct sensor values from the NOUS E6 thermostat. I just copied the provided quirk.
Sadly I only get few updates... The temperature on the device has to get at least 0.6 °C lower or higher for device to notify HA with an updated value. Same for humidity sensor, few updates. Is there a way to configure the device regarding sensitivity ?
Thanks a lot for the quirk, I see now correct sensor values from the NOUS E6 thermostat. I just copied the provided quirk.
Sadly I only get few updates... The temperature on the device has to get at least 0.6 °C lower or higher for device to notify HA with an updated value. Same for humidity sensor, few updates. Is there a way to configure the device regarding sensitivity ?
Hello mfeledyn,
I have the same issue leading to something not really usable to handle my heater. I'm using the above quirk (thanks for that RhavoX!)
In TuyaTemperatureMeasurement I found the temperature_sensitivity attribut which was set to 0.6. I modified it to 0.1 but without success, the temperature stills updated only when it is modified from 0.6°C at least.
Has anybody an idea to improve the frequency of the update?
Trying to configure temperature sensitivity in device management, I select "TuyaTemperatureMeasurement (Endpoint id: 1, Id: 0x0402, Type: in)" cluster, then "temperature_sensitivity (id: 0x8004)" in attributes list, set value to 0.1 but I miss the manufacturer replacing code on the next input. May be it is not updated because I leave it blank or set wrong code. I have searched that code with no luck so far. Anyone would have this NOUS E6 manufacturer code ? May be specific to that input ?... Thx!
Has anybody an idea to improve the frequency of the update?
I have just tried to update the sensitivity (using the quirk from @RhavoX - great stuff, thanks!) and it works more or less fine for me. It seems not to always update after a 0.1 degree change, but much more often as can be seen in the screenshots
I have now set it to 0.2 and will let let it run for a few days.
Which value returns the cluster if you try to read the attribute again? It returns the same you set?
Which value returns the cluster if you try to read the attribute again? It returns the same you set?
Yes, I tried this for both devices. It always returns the value I have set. E.g. first I tried READ ATTRIBUTE
which got the initial value 0.5, then I tried WRITE ATTRIBUTE
for the values 0.1 and READ ATTRIBUTE
which gave again 0.1. The same I tried for 0.2. So it seems the value is correctly set.
I've tried that and at least for me it does not work. It reports 0.2 in the read value but it's still at the default 0.6 if I look at the logs.
Tried this as well: https://community.home-assistant.io/t/report-configuration-of-a-sleepy-device-with-zha-toolkit/384617 Configuring the reporting interval via zha_toolkit. Maybe this helps someone to find a solution.
My service call looks like this:
service: zha_toolkit.conf_report
data:
ieee: sensor.thermometer_01_temperature
endpoint: 1
cluster: 0x0402
attribute: 0
min_interval: 5
max_interval: 300
reportable_change: 10
tries: 100
event_done: zha_done
Thanks @RhavoX for complete current version of the quirk above, works flawlessly.
Thanks @spandi for the sensitivity tweak above. It looks like it kinda worked (for test change of "0.2 C" it immediately showed in the dashboard), but sometimes temperature data just isn't being reported or is reported with a delay.
FWIW, it looks like humidity metrics reporting is dragging a bit too (75% -> 74% -> 72% is yet to be reported as I'm writing this).
Also I don't see any automatic changes to sensitivity parameter as reported above (at least as seen from HASS). Probably different device revisions somehow?
@spandi, per your comment with tests, am I missing something and there's humidity sensitivity parameter, or do you assume that temperature_sensitivity controls bove humidity and temperature?
@markuskonojacki I have the same behaviour. I think "writing" the 0.2 value in temperature_sensitivity is just doing it locally in HA memory state for the device. When using tre "out" clusters I can see HA issuing some commands to the device, but temperature is an "in" cluster and does not send any command/attribute update ?...
Hello, I am brand new to Home Assistant. I just manage to get things working which do work ^out of the box^. However sadly enough, this device does not :(
The device seems to be developed for tuya, what is as I see it now is a bad thing (I do not like to have my home controlled from china). I have a clean install of Home Assistant without any connection which a cloud service and I would like to keep it that way.
I have a ^clean^ Home Assistant system with an USB-zigbee-stick. The device is recognized on zigbee level, but does not work.
So what to do:
- where can I find the latest code version? (here in this treat, or as a file in git?)
- which steps should I take to integrate the code (and how)
Could some one give me a hand / explain?
Hello @LouisAtGH , I think the latest version of the code is here in that thread. I have not heard of a git or something else. In order to install the quirk manually you need to create the "quirks" sub-directory in your HomeConfig directory. Then in "quirks" dir create a file, say "nous_e6.py", and paste the quirk. Save the file. Restart HA. You should then see correct values from the NOUS E6 temperature sensor.
Thanks, but probably very stupid, but I really have no idea how to do that from the GUI :(
@LouisAtGH AFAIK it's impossible to do from GUI: you have to access your server files directly.
Here's a guide on how to do this: https://github.com/home-assistant/home-assistant.io/pull/23884/files#diff-f6bd4d42fd2565238968333a466474a25384fb4eecddc0235902e5ff5e798c8dR422-R435
I was afraid of that. In general ..... the Home Assistant GUI could use ^some^ improvements.
So I need SSH. I already did a first trail to se up SSH. But I am afraid that I have to do that from the command line as well. Including generating a key etc.
After searching the internet, I will probably manage that, but oh oh oh oh ..... it could and should be so much simpler ...
Well, it's not an issue of Home Assistant per se (apart from lack of docs may be)
The thing is that the device in question (NOUS E6) is quite new and, on top of that, doesn't follow the standard way for communication. And obviously HA (or zha-device-handlers
for that matter) can't handle every single edge case.
Hence, HA clearly (at least in from my perspective) separates between "supported" and "do-it-yourself" kind of integrations, with former being supported in GUI quite well, and the latter requering some direct tinkering with underlying configuration files.
It shouldn't be hard to do by yourself, so give it a shot. The instructions above and complete quirk code from here should be enough to do that.
(Sorry for a bit of off-topic everyone)
Thanks after taking a lot of steps, I managed to get this device working. I will describe those steps in the forum as reaction on my help request today. https://community.home-assistant.io/t/how-to-define-an-unsupported-device-starting-with-a-fresh-home-assistant-install/510150.
However one thing is still unclear to me that is every thing written here about "Manage Zigbee Device and clusters. I searched in the gui and on the internet .... can not find how to reach those settings ....
So far I used https://github.com/zigpy/zha-device-handlers/issues/1702#issuecomment-1238612059 but the most recent Home Assistant update (2023.3.0) seems to have broken it. Thanks to the error I got my whole ZigBee integration stopped working so I removed the quirk for now.
This was the error I got:
ImportError: cannot import name 'TuyaDPType' from 'zhaquirks.tuya.mcu' (/usr/lib/python3.10/site-packages/zhaquirks/tuya/mcu/__init__.py)
Does anyone know whats up with that? I'm using the linuxserver/homeassistant:latest
image.
If this is the wrong project/issue to report this in, please let me know and I'll go where-ever else is appropriate. It's just that I originally found the quirk here and it worked perfectly for so long :D
Hi, yep got hit by that too :) ZHA did some small changes here and there so the quirk had to be adjusted. I think I have to finally try to push this to the mainline and add other features later if necessary. For now the following updated quirk can be used (2023.03 compatible)
"""Tuya temp and humidity sensor with screen."""
from typing import Dict
################## clean this up
import zigpy.types as t
from zigpy.zcl import foundation
from zhaquirks.tuya import TuyaTimePayload, TuyaCommand, TuyaDPType
import datetime
from typing import Tuple, Optional, Union
##################
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time, AnalogOutput
from zigpy.zcl.clusters.measurement import RelativeHumidity, TemperatureMeasurement
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SKIP_CONFIGURATION,
)
from zhaquirks.tuya import TuyaLocalCluster, TuyaPowerConfigurationCluster2AAA
from zhaquirks.tuya.mcu import DPToAttributeMapping, TuyaMCUCluster
TUYA_SET_TIME = 0x24
# NOTES:
# The data comes in as a string on cluster, if there is nothing set up you may see these lines in the logs:
# Unknown message (b'19830100a40102000400000118') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 28.0 degrees
# Unknown message (b'19840100a5020200040000022c') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 55.6% humid
# Unknown message (b'19850100a60402000400000064') on cluster 61184: unknown endpoint or cluster id: 'No cluster ID 0xef00 on (a4:c1:38:d0:18:8b:64:aa, 1)'
# 100% battery
class TemperatureUnitConvert(t.enum8):
"""Tuya Temp unit convert enum."""
Celsius = 0x00
Fahrenheit = 0x01
class TuyaTemperatureMeasurement(TemperatureMeasurement, TuyaLocalCluster):
"""Tuya local TemperatureMeasurement cluster."""
attributes = TemperatureMeasurement.attributes.copy()
attributes.update(
{
0x8001: ("temp_unit_convert", t.enum8),
0x8002: ("alarm_max_temperature", t.Single),
0x8003: ("alarm_min_temperature", t.Single),
0x8004: ("temperature_sensitivity", t.Single),
}
)
class TuyaRelativeHumidity(RelativeHumidity, TuyaLocalCluster):
"""Tuya local RelativeHumidity cluster."""
class TemperatureHumidityManufCluster(TuyaMCUCluster):
"""Tuya Manufacturer Cluster with Temperature and Humidity data points."""
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"measured_value",
converter=lambda x: x * 10, # decidegree to centidegree
),
2: DPToAttributeMapping(
TuyaRelativeHumidity.ep_attribute,
"measured_value",
converter=lambda x: x * 100, # 0.01 to 1.0
),
4: DPToAttributeMapping(
TuyaPowerConfigurationCluster2AAA.ep_attribute,
"battery_percentage_remaining",
converter=lambda x: x * 2, # reported percentage is doubled
),
9: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"temp_unit_convert",
converter=lambda x: TemperatureUnitConvert(x)
),
10: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"alarm_max_temperature",
converter=lambda x: x / 10
),
11: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"alarm_min_temperature",
converter=lambda x: x / 10
),
19: DPToAttributeMapping(
TuyaTemperatureMeasurement.ep_attribute,
"temperature_sensitivity",
converter=lambda x: x / 10
)
}
set_time_offset = 1970
set_time_local_offset = 1970
data_point_handlers = {
1: "_dp_2_attr_update",
2: "_dp_2_attr_update",
4: "_dp_2_attr_update",
9: "_dp_2_attr_update",
10: "_dp_2_attr_update",
11: "_dp_2_attr_update",
19: "_dp_2_attr_update",
}
def handle_set_time_request(self, sequence_number: t.uint16_t) -> foundation.Status:
payload = TuyaTimePayload()
utc_now = datetime.datetime.utcnow()
now = datetime.datetime.now()
offset_time = datetime.datetime(self.set_time_offset, 1, 1)
offset_time_local = datetime.datetime(self.set_time_local_offset, 1, 1)
utc_timestamp = int((utc_now - offset_time).total_seconds())
local_timestamp = int((now - offset_time).total_seconds())
payload.extend(utc_timestamp.to_bytes(4, "big", signed=False))
payload.extend(local_timestamp.to_bytes(4, "big", signed=False))
self.create_catching_task(
self.command(TUYA_SET_TIME, payload, manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID, expect_reply=False)
)
return foundation.Status.SUCCESS
class TuyaNousE6TempHumiditySensor(CustomDevice):
"""Custom device representing tuya temp and humidity sensor with a screen (NOUS E6)."""
signature = {
# <SimpleDescriptor endpoint=1, profile=260, device_type=81
# device_version=1
# input_clusters=[4, 5, 61184, 0]
# output_clusters=[25, 10]>
MODELS_INFO: [("_TZE200_nnrfa68v", "TS0601")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG, # this is how the device reports itself
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TemperatureHumidityManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
replacement = {
SKIP_CONFIGURATION: True,
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.TEMPERATURE_SENSOR,
INPUT_CLUSTERS: [
Basic.cluster_id,
TemperatureHumidityManufCluster, # Single bus for temp, humidity, and battery
TuyaTemperatureMeasurement,
TuyaRelativeHumidity,
TuyaPowerConfigurationCluster2AAA,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id],
}
},
}
Wow, that was fast. It works too! Thank you :smile: