Skip to content

Commit

Permalink
fix: Add occupancy timeout for Tuya IH012-RT01/ZMS-102 (#8333)
Browse files Browse the repository at this point in the history
  • Loading branch information
srett authored Dec 15, 2024
1 parent 6ed8eb3 commit 7a14d5b
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 10 deletions.
10 changes: 6 additions & 4 deletions src/devices/tuya.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1851,12 +1851,14 @@ const definitions: DefinitionWithExtend[] = [
model: 'IH012-RT01',
vendor: 'Tuya',
description: 'Motion sensor',
fromZigbee: [fz.ias_occupancy_alarm_1, fz.ignore_basic_report, fz.ZM35HQ_attr, fz.battery],
fromZigbee: [fz.ignore_basic_report, fz.ZM35HQ_attr, fz.battery],
toZigbee: [tz.ZM35HQ_attr],
extend: [quirkCheckinInterval(15000)],
extend: [
quirkCheckinInterval(15000),
// Occupancy reporting interval is 60s, so allow for one dropped update plus a small safety margin of 5s
iasZoneAlarm({zoneType: 'occupancy', zoneAttributes: ['alarm_1', 'battery_low'], keepAliveTimeout: 125}),
],
exposes: [
e.occupancy(),
e.battery_low(),
e.battery(),
e.battery_voltage(),
e.enum('sensitivity', ea.ALL, ['low', 'medium', 'high']).withDescription('PIR sensor sensitivity'),
Expand Down
29 changes: 23 additions & 6 deletions src/lib/modernExtend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,7 @@ export interface IasArgs {
zoneType: iasZoneType;
zoneAttributes: iasZoneAttribute[];
alarmTimeout?: boolean;
keepAliveTimeout?: number;
zoneStatusReporting?: boolean;
description?: string;
manufacturerZoneAttributes?: manufacturerZoneAttribute[];
Expand Down Expand Up @@ -1489,7 +1490,8 @@ export function iasZoneAlarm(args: IasArgs): ModernExtend {
globalStore.putValue(msg.endpoint, 'timer', timer);
}
}
const zoneStatus = msg.type === 'commandStatusChangeNotification' ? msg.data.zonestatus : msg.data.zoneStatus;
const isChange = msg.type === 'commandStatusChangeNotification';
const zoneStatus = isChange ? msg.data.zonestatus : msg.data.zoneStatus;
if (zoneStatus !== undefined) {
let payload = {};
if (args.zoneAttributes.includes('tamper')) {
Expand Down Expand Up @@ -1525,13 +1527,28 @@ export function iasZoneAlarm(args: IasArgs): ModernExtend {
alarm2Payload = !alarm2Payload;
}

if (bothAlarms) {
// Can't just alarm1Payload || alarm2Payload as an unused alarm's bit might be always 1 or random in the received data
let addTimeout = false;
if (args.zoneAttributes.includes('alarm_1')) {
payload = {[alarm1Name]: alarm1Payload, ...payload};
addTimeout ||= alarm1Payload;
}
if (args.zoneAttributes.includes('alarm_2')) {
payload = {[alarm2Name]: alarm2Payload, ...payload};
} else if (args.zoneAttributes.includes('alarm_1')) {
payload = {[alarm1Name]: alarm1Payload, ...payload};
} else if (args.zoneAttributes.includes('alarm_2')) {
payload = {[alarm2Name]: alarm2Payload, ...payload};
addTimeout ||= alarm2Payload;
}
if (isChange && args.keepAliveTimeout > 0) {
// This sensor continuously sends occupation updates as long as motion is detected; (re)start a timeout
// each time we receive one, in case the clearance message gets lost. Normally, these kinds of sensors
// send a clearance message, so this is an additional safety measure.
clearTimeout(globalStore.getValue(msg.endpoint, 'timeout'));
if (addTimeout) {
// At least one zone active
const timer = setTimeout(() => publish({[alarm1Name]: false, [alarm2Name]: false}), args.keepAliveTimeout * 1000);
globalStore.putValue(msg.endpoint, 'timeout', timer);
} else {
globalStore.clearValue(msg.endpoint, 'timeout');
}
}

if (args.manufacturerZoneAttributes)
Expand Down

0 comments on commit 7a14d5b

Please sign in to comment.