Tuya TS0203 _TZ3000_x8q36xwf "closed" after 1:02:59
What happened?
Hi, I bought another Tuya door sensor to monitor if chicken doors are closed. I received _TZ3000_x8q36xwf from Ali (with blue LED), paired it to Z2M, mounted and left it in open state. After 1h 2m 59s sensor state is "closed" .. I tried it multiple times - same time result (sometimes +-3s seconds). So I decided to order another one thinking this one is "broken". But same situation with new sensor, after this period of time sensor is "closed" and never report open again (only if magnet is reattached again). I assumed that there could be some problem with my Z2M network in grandma's house (2.2.1, EmberZNet, 7.4.4). So I paired it to home Z2M (2.2.1, EZSP v8, 6.10.3.0 build 297) but even with difference in drivers same situation - closed after 1:02:29 :D
I am already using TS0203 - two _TZ3000_6zvw8ham (green LED) and they are without problems. I also discovered that I am also using one _TZ3000_x8q36xwf but for front doors and they won't be opened for more than 5 mins so this situation would never happen.
What did you expect to happen?
Real open status not only 1:02:59
How to reproduce it (minimal and precise)
Pair TS0203 _TZ3000_x8q36xwf and keep it in opened state for more than one hour.
Zigbee2MQTT version
2.2.1
Adapter firmware version
7.4.4 [GA] / 6.10.3.0 build 297
Adapter
EmberZNet / EZSP v8
Setup
Two Z2M instances - one running in HA as plugin and second instance in Docker at grandma's house connected with VPN. Same two efr32mg21 zigbee dongles, but different drivers (as wroted)
Debug log
No response
I just installed zigbee2mqtt, and have the same problem with my one Tuya TS0203 sensor (blue LED). In the log below the sensor was not closed, yet reported "contact" after about 1h and 30min respectively.
10:54:40.171 AtMostOnce {"battery":100,"battery_low":false,"contact":false,"linkquality":104,"tamper":false,"voltage":3000}
12:00:11.070 AtMostOnce {"battery":100,"battery_low":false,"contact":true,"linkquality":128,"tamper":false,"voltage":3000}
..
12:00:11.958 AtMostOnce {"battery":100,"battery_low":false,"contact":false,"linkquality":120,"tamper":false,"voltage":3000}
12:27:14.294 AtMostOnce {"battery":100,"battery_low":false,"contact":true,"linkquality":168,"tamper":false,"voltage":3000}
I have the same issue with _TZ3000_bpkijo14 and apparently ZHA does not show the same symptom, which may indicate an issue with Z2M.
I noticed that if I add the attribute "contact" to the filtered attribute from cache list, then "contact" comes out as null. This seems like a battery status report that the device does not include contact attribute with it and somehow z2m defaults it to true.
The false closed (contact: true) is reported between 30 and 60 minutes, most of the time.
[2025-05-03 03:41:32] info: z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/0xXXXXXXXXXX', payload '{"battery":100,"battery_low":null,"contact":null,"linkquality":144,"voltage":3000}'
I tried to set "Retain" to true, so maybe that would resolve what looks like a caching bug, but that didn't work either. I also noticed this from time to time, but I guess it makes sense because it is likely sleeping:
[2025-05-06 00:34:38] error: zh:controller:device: Handling of poll check-in from 0xXXXXXXXXXX failed (ZCL command 0xXXXXXXXXXX/1 genPollCtrl.checkinRsp({"startFastPolling":false,"fastPollTimeout":0}, {"timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":false,"direction":0,"reservedBits":0,"writeUndiv":false,"sendPolicy":"immediate"}) failed ({"target":36231,"apsFrame":{"profileId":260,"clusterId":32,"sourceEndpoint":1,"destinationEndpoint":1,"options":4416,"groupId":0,"sequence":240},"zclSequence":182,"commandIdentifier":11} timed out after 10000ms))
Most notable is "disableDefaultResponse":false. I was thinking if that could be set to true, then it could help.
But I am new to Z2M (and Zigbee protocol itself) so I still don't know what comes from where and how exactly these messages get constructed before they are published to the topic.
So I don't really know if what I am saying is just nonsense or how to influence these settings (could not find any documentation that I could relate to this last log message).
I read people that had this issue resolved by changing to ZHA. Before I try moving it over to ZHA, I wanted to see where this leads to here, because there is less than a week that I first tried Zigbee with HA, so I may need some pointers to be able to investigate further in a timely manner.
Edit: Apparently this is not new, also happens in #24979, but that never was looked into.
I tried using a custom converter, to see if I would get anywhere, but I didn't.
I won't have time to go in depth here, so I will leave here what I have tried, if anyone would like to pick up on it.
The documentation on how to do this is not very helpful. It is still confusing because the documentation talks about .js files and the examples are .mjs. I don't know if it actually supports ES modules as all I got with .mjs was:
error to z2m: ReferenceError: module is not defined in ES module scope
at file:///app/dist/external_converters/ts0203_ignore_null_attribute.mjs:33:1
at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26)
at ExternalConverters.loadFiles (/app/lib/extension/externalJS.ts:210:29)
at ExternalConverters.start (/app/lib/extension/externalJS.ts:54:9)
at Controller.startExtension (/app/lib/controller.ts:271:13)
at Controller.start (/app/lib/controller.ts:151:13)
at start (/app/index.js:149:5)
at Timeout.restart [as _onTimeout] (/app/index.js:46:5)
I then switched to .js and saw it in the logs that it had been loaded (requires restart).
If you use home assistant, you just need to add a .js script file to the folder config/zigbee2mqtt/external_converters (documentation was not clear about this, so I lost some time figuring this out).
import {definitions} from "zigbee-herdsman-converters/devices/tuya";
import {logger} from "zigbee-herdsman-converters/lib/logger"
// Find the TS0203 definition
const base = definitions.find(d => d.model === 'TS0203');
// Clone and override fromZigbee
export default [{
...base.filter(f => f.name !== 'fromZigbee'),
fromZigbee: [
{
...base.fromZigbee.find(f => f.type === "commandStatusChangeNotification").filter(f => f.name !== 'convert'),
cluster: "ssIasZone",
type: "commandStatusChangeNotification",
convert: (model, msg, publish, options, meta) => {
try {
logger.debug('Custom commandStatusChangeNotification for TS0203:', msg);
const zoneStatus = msg.data.zonestatus;
if (!zoneStatus || zoneStatus === 0) {
return {};
}
const contactProperty = postfixWithEndpointName("contact", msg, model, meta);
const tamperProperty = postfixWithEndpointName("tamper", msg, model, meta);
const batteryLowProperty = postfixWithEndpointName("battery_low", msg, model, meta);
return {
[contactProperty]: !((zoneStatus & 1) > 0),
[tamperProperty]: (zoneStatus & (1 << 2)) > 0,
[batteryLowProperty]: (zoneStatus & (1 << 3)) > 0,
};
} catch (error) {
logger.error('Error in commandStatusChangeNotification for TS0203:', error);
return {};
}
}
},
{
...base.fromZigbee.find(f => f.type === "attributeReport").filter(f => f.name !== 'convert'),
cluster: "ssIasZone",
type: "attributeReport",
convert: (model, msg, publish, options, meta) => {
try {
logger.debug('Custom attributeReport for TS0203:', msg);
const zoneStatus = msg.data.zoneStatus;
if (!zoneStatus || zoneStatus === 0) {
return {};
}
const contactProperty = postfixWithEndpointName("contact", msg, model, meta);
const tamperProperty = postfixWithEndpointName("tamper", msg, model, meta);
const batteryLowProperty = postfixWithEndpointName("battery_low", msg, model, meta);
return {
[contactProperty]: !((zoneStatus & 1) > 0),
[tamperProperty]: (zoneStatus & (1 << 2)) > 0,
[batteryLowProperty]: (zoneStatus & (1 << 3)) > 0,
};
} catch (error) {
logger.error('Error in attributeReport for TS0203:', error);
return {};
}
}
},
...base.fromZigbee.filter(f => f.name !== 'ias_contact_alarm_1_report' && f.name !== 'ias_contact_alarm_1'),
]
}];
I tried changing the logic, and put a catch all just in case. but I could never see a change in behavior (or the log messages I added), so I can only assume, it was not being matched with the device.
Since there has been no reaction from anyone that understands a bit better I will move over to ZHA, but will be interested to see if anyone can figure this out.
I can confirm that with ZHA, the device behaves as expected. Although the pairing experience has been a lot worse and it doesn't seem to appropriately use the range from my router bulbs. So, one problem out, another in. May revisit this if perhaps @Koenkk can give some pointers.
Could you provide the debug log when it (incorrectly) goes to closed?
See this on how to enable debug logging.
Yes, I have been diving into the logs to see what goes wrong, but couldn't find anything else with the knowledge I have. I did notice though, that when this happened, I would get two topic publishes at the same time with contact: "true":
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA]
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA ackNum=7](ackRx=7 frmTx=7)
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA ackNum=7 frmNum=0](frmRx=0) Added to rxQueue
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: ---> [FRAME type=ACK frmRx=1](ackRx=7)
[2025-05-08 00:09:50] debug: zh:ember:ezsp: <=== [CBFRAME: ID=89:"INCOMING_ROUTE_RECORD_HANDLER" Seq=46 Len=24]
[2025-05-08 00:09:50] debug: zh:ember:ezsp: ezspIncomingRouteRecordHandler: source=37337 sourceEui=0xa4c1383371784add lastHopLqi=220 lastHopRssi=-45 relayCount=3 relayList=31312,51909,58259
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA]
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA ackNum=7](ackRx=7 frmTx=7)
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA ackNum=7 frmNum=1](frmRx=1) Added to rxQueue
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: ---> [FRAME type=ACK frmRx=2](ackRx=7)
[2025-05-08 00:09:50] debug: zh:ember:ezsp: <=== [CBFRAME: ID=69:"INCOMING_MESSAGE_HANDLER" Seq=46 Len=32]
[2025-05-08 00:09:50] debug: zh:ember:ezsp: ezspIncomingMessageHandler: type=UNICAST apsFrame={"profileId":260,"clusterId":1280,"sourceEndpoint":1,"destinationEndpoint":1,"options":256,"groupId":0,"sequence":121} packetInfo:{"senderShortId":37337,"senderLongId":"0xFFFFFFFFFFFFFFFF","bindingIndex":255,"addressIndex":255,"lastHopLqi":220,"lastHopRssi":-45,"lastHopTimestamp":0} messageContents=185f0a01f02001
[2025-05-08 00:09:50] debug: zh:controller: Received payload: clusterID=1280, address=37337, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=220, frame={"header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":true,"reservedBits":0},"transactionSequenceNumber":95,"commandIdentifier":10},"payload":[{"attrId":61441,"dataType":32,"attrData":1}],"command":{"ID":10,"name":"report","parameters":[{"name":"attrId","type":33},{"name":"dataType","type":32},{"name":"attrData","type":1000}]}}
[2025-05-08 00:09:50] debug: z2m: Received Zigbee message from 'Front Door Contact Sensor', type 'attributeReport', cluster 'ssIasZone', data '{"61441":1}' from endpoint 1 with groupID 0
[2025-05-08 00:09:50] info: z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/Front Door Contact Sensor', payload '{"battery":100,"battery_low":false,"contact":true,"last_seen":"2025-05-08T00:09:50+02:00","linkquality":220,"tamper":false,"voltage":3000}'
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA]
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA ackNum=7](ackRx=7 frmTx=7)
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: <--- [FRAME type=DATA ackNum=7 frmNum=2](frmRx=2) Added to rxQueue
[2025-05-08 00:09:50] debug: zh:ember:uart:ash: ---> [FRAME type=ACK frmRx=3](ackRx=7)
[2025-05-08 00:09:50] debug: zh:ember:ezsp: <=== [CBFRAME: ID=69:"INCOMING_MESSAGE_HANDLER" Seq=46 Len=32]
[2025-05-08 00:09:50] debug: zh:ember:ezsp: ezspIncomingMessageHandler: type=UNICAST apsFrame={"profileId":260,"clusterId":1280,"sourceEndpoint":1,"destinationEndpoint":1,"options":256,"groupId":0,"sequence":122} packetInfo:{"senderShortId":37337,"senderLongId":"0xFFFFFFFFFFFFFFFF","bindingIndex":255,"addressIndex":255,"lastHopLqi":220,"lastHopRssi":-45,"lastHopTimestamp":0} messageContents=18600a13002001
[2025-05-08 00:09:50] debug: zh:controller: Received payload: clusterID=1280, address=37337, groupID=0, endpoint=1, destinationEndpoint=1, wasBroadcast=false, linkQuality=220, frame={"header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":true,"reservedBits":0},"transactionSequenceNumber":96,"commandIdentifier":10},"payload":[{"attrId":19,"dataType":32,"attrData":1}],"command":{"ID":10,"name":"report","parameters":[{"name":"attrId","type":33},{"name":"dataType","type":32},{"name":"attrData","type":1000}]}}
[2025-05-08 00:09:50] debug: z2m: Received Zigbee message from 'Front Door Contact Sensor', type 'attributeReport', cluster 'ssIasZone', data '{"currentZoneSensitivityLevel":1}' from endpoint 1 with groupID 0
[2025-05-08 00:09:50] info: z2m:mqtt: MQTT publish: topic 'zigbee2mqtt/Front Door Contact Sensor', payload '{"battery":100,"battery_low":false,"contact":true,"last_seen":"2025-05-08T00:09:50+02:00","linkquality":220,"tamper":false,"voltage":3000}'
I am no ZHA at the moment, so can't reproduce without reverting the setup, but can go ahead if some more data points will help.
Fixed the issue!
Changes will be available in the dev branch in a few hours from now and in the next release which is every 1st of the month.
Ah, nice. So my suspicion was correct after all. Too bad I could not make the external converter work to validate it. Would be nice to know what I done wrong, so next time I can try to submit a PR
I can confirm that sensors are now working as expected with newest bugfix. Thanks Koen and FΓ‘bio!
Works here too. Thanks!
I've got a couple of these, and I am seeing a similar issue I think, in that they revert to closed after a varying amount of time, but typically no longer than an hour.
I am behind on my zb2mqtt updates since the breaking changes for v1->2 as I haven't had time to work it through.
Was this issue fixed in v2?
Trying to decide if I should just return them..