From bf9cfa079fa984751c39944dbabc8b42493c98b6 Mon Sep 17 00:00:00 2001 From: Joost Loohuis Date: Fri, 20 Sep 2024 14:34:19 +0200 Subject: [PATCH 1/4] Unify camera and doorbell implementation --- app.json | 510 +++++++++++++++++- drivers/camera/device.ts | 180 +------ drivers/camera/driver.flow.compose.json | 7 + drivers/camera/driver.ts | 95 +--- drivers/doorbell/device.ts | 28 +- drivers/doorbell/driver.compose.json | 8 + drivers/doorbell/driver.flow.compose.json | 201 ++++++- drivers/doorbell/driver.settings.compose.json | 192 +++++++ drivers/doorbell/driver.ts | 22 +- .../camera/TuyaCameraConstants.ts | 0 lib/camera/device.ts | 187 +++++++ lib/camera/driver.ts | 96 ++++ {drivers => lib}/camera/translations.json | 0 13 files changed, 1208 insertions(+), 318 deletions(-) rename {drivers => lib}/camera/TuyaCameraConstants.ts (100%) create mode 100644 lib/camera/device.ts create mode 100644 lib/camera/driver.ts rename {drivers => lib}/camera/translations.json (100%) diff --git a/app.json b/app.json index 540520da..0e7c93f9 100644 --- a/app.json +++ b/app.json @@ -780,6 +780,19 @@ } ] }, + { + "id": "camera_doorbell_rang", + "title": { + "en": "Doorbell rang" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=camera&capabilities=hidden.doorbell" + } + ] + }, { "id": "dimmer_sub_switch_1_turned_off", "title": { @@ -927,10 +940,88 @@ } ] }, + { + "id": "doorbell_alarm_crying_child_true", + "title": { + "en": "A crying child was detected" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_crying_child" + } + ] + }, + { + "id": "doorbell_alarm_crying_child_false", + "title": { + "en": "Crying is no longer detected" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_crying_child" + } + ] + }, + { + "id": "doorbell_alarm_pet_true", + "title": { + "en": "A pet was detected" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_pet" + } + ] + }, + { + "id": "doorbell_alarm_pet_false", + "title": { + "en": "Pet is no longer detected" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_pet" + } + ] + }, + { + "id": "doorbell_alarm_sound_true", + "title": { + "en": "A sound was detected" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_sound" + } + ] + }, + { + "id": "doorbell_alarm_sound_false", + "title": { + "en": "Sound is no longer detected" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_sound" + } + ] + }, { "id": "doorbell_rang", "title": { - "en": "The doorbell rang" + "en": "Doorbell rang" }, "args": [ { @@ -1776,6 +1867,210 @@ } ] }, + { + "id": "doorbell_cruise_switch", + "title": { + "en": "Set auto patrol" + }, + "titleFormatted": { + "en": "Set auto patrol [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=cruise_switch" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_siren_switch", + "title": { + "en": "Set siren" + }, + "titleFormatted": { + "en": "Set siren [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=siren_switch" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_motion_switch", + "title": { + "en": "Set motion detection" + }, + "titleFormatted": { + "en": "Set motion detection [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_motion" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_motion_tracking", + "title": { + "en": "Set motion tracking" + }, + "titleFormatted": { + "en": "Set motion tracking [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_decibel_switch", + "title": { + "en": "Set sound detection" + }, + "titleFormatted": { + "en": "Set sound detection [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_sound" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_cry_detection_switch", + "title": { + "en": "Set crying baby detection" + }, + "titleFormatted": { + "en": "Set crying baby detection [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_crying_child" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_pet_detection", + "title": { + "en": "Set pet detection" + }, + "titleFormatted": { + "en": "Set pet detection [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell&capabilities=alarm_pet" + }, + { + "name": "value", + "type": "checkbox", + "title": { + "en": "Value" + } + } + ] + }, + { + "id": "doorbell_basic_nightvision", + "title": { + "en": "Set night mode" + }, + "titleFormatted": { + "en": "Set night mode to [[value]]" + }, + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=doorbell" + }, + { + "name": "value", + "type": "dropdown", + "title": { + "en": "Value" + }, + "values": [ + { + "id": "0", + "label": { + "en": "Auto" + } + }, + { + "id": "1", + "label": { + "en": "Off" + } + }, + { + "id": "2", + "label": { + "en": "On" + } + } + ] + } + ] + }, { "id": "fan_light_on", "title": { @@ -3458,7 +3753,10 @@ ] }, { - "capabilities": [], + "capabilities": [ + "alarm_motion", + "alarm_sound" + ], "connectivity": [ "cloud" ], @@ -3505,8 +3803,216 @@ "en": "Doorbell", "nl": "Deurbel" }, + "capabilitiesOptions": { + "alarm_motion": { + "title": { + "en": "Motion Detected" + } + } + }, "id": "doorbell", "settings": [ + { + "type": "group", + "label": { + "en": "NOTE: Some settings are not supported by every doorbell." + }, + "children": [ + { + "id": "motion_switch", + "type": "checkbox", + "label": { + "en": "Motion Detection" + }, + "value": false + }, + { + "id": "motion_tracking", + "type": "checkbox", + "label": { + "en": "Motion Tracking" + }, + "value": false + }, + { + "id": "decibel_switch", + "type": "checkbox", + "label": { + "en": "Sound Detection" + }, + "value": false + }, + { + "id": "cry_detection_switch", + "type": "checkbox", + "label": { + "en": "Crying Baby Detection" + }, + "value": false + }, + { + "id": "pet_detection", + "type": "checkbox", + "label": { + "en": "Pet Detection" + }, + "value": false + }, + { + "id": "motion_sensitivity", + "type": "dropdown", + "label": { + "en": "Motion Sensitivity" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Low" + } + }, + { + "id": "1", + "label": { + "en": "Medium" + } + }, + { + "id": "2", + "label": { + "en": "High" + } + } + ] + }, + { + "id": "decibel_sensitivity", + "type": "dropdown", + "label": { + "en": "Sound Sensitivity" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Low" + } + }, + { + "id": "1", + "label": { + "en": "High" + } + } + ] + }, + { + "id": "basic_nightvision", + "type": "dropdown", + "label": { + "en": "Night Mode" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Auto" + } + }, + { + "id": "1", + "label": { + "en": "Off" + } + }, + { + "id": "2", + "label": { + "en": "On" + } + } + ] + }, + { + "id": "basic_device_volume", + "type": "number", + "label": { + "en": "Device Volume" + }, + "value": 8, + "min": 1, + "max": 10, + "step": 1 + }, + { + "id": "basic_anti_flicker", + "type": "dropdown", + "label": { + "en": "Anti-Flicker" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Disabled" + } + }, + { + "id": "1", + "label": { + "en": "50 Hz" + } + }, + { + "id": "2", + "label": { + "en": "60 Hz" + } + } + ] + }, + { + "id": "basic_osd", + "type": "checkbox", + "label": { + "en": "Video Timestamp" + }, + "value": true + }, + { + "id": "basic_flip", + "type": "checkbox", + "label": { + "en": "Flip Video" + }, + "value": false + }, + { + "id": "basic_indicator", + "type": "checkbox", + "label": { + "en": "Status Indicator" + }, + "value": false + } + ] + }, + { + "id": "alarm_timeout", + "type": "number", + "label": { + "en": "Alarm Timeout" + }, + "value": 10, + "min": 1, + "units": { + "en": "seconds" + } + }, { "id": "deviceSpecification", "type": "label", diff --git a/drivers/camera/device.ts b/drivers/camera/device.ts index 36b6c6b5..4b9a7750 100644 --- a/drivers/camera/device.ts +++ b/drivers/camera/device.ts @@ -1,179 +1,5 @@ -import * as TuyaOAuth2Util from '../../lib/TuyaOAuth2Util'; -import { constIncludes, getFromMap } from '../../lib/TuyaOAuth2Util'; -import { SettingsEvent, TuyaStatus } from '../../types/TuyaTypes'; -import { - CAMERA_ALARM_EVENT_CAPABILITIES, - CAMERA_SETTING_LABELS, - HomeyCameraSettings, - SIMPLE_CAMERA_CAPABILITIES, - TuyaCameraSettings, -} from './TuyaCameraConstants'; -import TuyaTimeOutAlarmDevice from '../../lib/TuyaTimeOutAlarmDevice'; +import TuyaDeviceWithCamera from '../../lib/camera/device'; -module.exports = class TuyaOAuth2DeviceCamera extends TuyaTimeOutAlarmDevice { - async onOAuth2Init(): Promise { - await super.onOAuth2Init(); - - for (const capability of this.getCapabilities()) { - // Basic capabilities - if (constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_write, capability)) { - this.registerCapabilityListener(capability, value => - this.sendCommand({ - code: capability, - value: value, - }), - ); - } - - // PTZ control - if (capability === 'ptz_control_vertical') { - this.registerCapabilityListener(capability, value => this.ptzCapabilityListener(value, '0', '4')); - } - if (capability === 'ptz_control_horizontal') { - this.registerCapabilityListener(capability, value => this.ptzCapabilityListener(value, '6', '2')); - } - - if (capability === 'ptz_control_zoom') { - this.registerCapabilityListener(capability, value => this.zoomCapabilityListener(value)); - } - - // Other capabilities - if (capability === 'onoff') { - this.registerCapabilityListener(capability, value => - this.sendCommand({ - code: 'basic_private', - value: !value, - }), - ); - } - } - - // Reset alarms in case a timeout was interrupted - for (const tuyaCapability in CAMERA_ALARM_EVENT_CAPABILITIES) { - const capability = getFromMap(CAMERA_ALARM_EVENT_CAPABILITIES, tuyaCapability); - if (capability && this.hasCapability(capability)) { - await this.setCapabilityValue(capability, false); - } - } - } - - async onTuyaStatus(status: TuyaStatus, changed: string[]): Promise { - await super.onTuyaStatus(status, changed); - - for (const statusKey in status) { - const value = status[statusKey]; - - // Basic capabilities - if ( - constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_write, statusKey) || - constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_only, statusKey) - ) { - await this.setCapabilityValue(statusKey, value).catch(this.error); - } - - if (constIncludes(SIMPLE_CAMERA_CAPABILITIES.setting, statusKey)) { - await this.setSettings({ - [statusKey]: value, - }).catch(this.error); - } - - // PTZ control - if ( - (statusKey === 'ptz_stop' && value && changed.includes('ptz_stop')) || - (statusKey === 'ptz_control' && value === '8' && changed.includes('ptz_control')) - ) { - await this.setCapabilityValue('ptz_control_horizontal', 'idle').catch(this.error); - await this.setCapabilityValue('ptz_control_vertical', 'idle').catch(this.error); - } - - if (statusKey === 'zoom_stop' && value && changed.includes('zoom_stop')) { - await this.setCapabilityValue('ptz_control_zoom', 'idle').catch(this.error); - } - - // Other capabilities - if (statusKey === 'basic_private') { - await this.setCapabilityValue('onoff', !value).catch(this.error); - } - - if (statusKey === 'wireless_electricity') { - await this.setCapabilityValue('measure_battery', value).catch(this.error); - } - - // Event messages - if (statusKey === 'initiative_message' && changed.includes('initiative_message')) { - // Event messages are base64 encoded JSON - const encoded = status[statusKey] as string; - const decoded = Buffer.from(encoded, 'base64'); - const data = JSON.parse(decoded.toString()); - const notificationType = data.cmd; - const dataType = data.type; - this.log('Notification:', notificationType, dataType); - - // Check if the event is for a known alarm - if (notificationType in CAMERA_ALARM_EVENT_CAPABILITIES) { - const alarmCapability = getFromMap(CAMERA_ALARM_EVENT_CAPABILITIES, notificationType); - if (!alarmCapability) { - continue; - } - - if (!this.hasCapability(alarmCapability)) { - await this.addCapability(alarmCapability).catch(this.error); - } - await this.setAlarm(alarmCapability); - } - } - } - } - - async setAlarm(capability: string): Promise { - await super.setAlarm( - capability, - async () => { - const deviceTriggerCard = this.homey.flow.getDeviceTriggerCard(`camera_${capability}_true`); - await deviceTriggerCard.trigger(this).catch(this.error); - await this.setCapabilityValue(capability, true).catch(this.error); - }, - async () => { - const deviceTriggerCard = this.homey.flow.getDeviceTriggerCard(`camera_${capability}_false`); - await deviceTriggerCard.trigger(this).catch(this.error); - await this.setCapabilityValue(capability, false).catch(this.error); - }, - ); - } - - // Map from up/idle/down to commands so the ternary UI shows arrows - async ptzCapabilityListener(value: 'up' | 'idle' | 'down', up: string, down: string): Promise { - if (value === 'idle') { - await this.sendCommand({ code: 'ptz_stop', value: true }); - } else { - await this.sendCommand({ code: 'ptz_control', value: value === 'up' ? up : down }); - } - } - - async zoomCapabilityListener(value: 'up' | 'idle' | 'down'): Promise { - if (value === 'idle') { - await this.sendCommand({ code: 'zoom_stop', value: true }); - } else { - await this.sendCommand({ code: 'zoom_control', value: value === 'up' ? '1' : '0' }); - } - } - - async onSettings(event: SettingsEvent): Promise { - const tuyaSettingsEvent = TuyaOAuth2Util.filterTuyaSettings(event, [ - 'motion_switch', - 'motion_tracking', - 'decibel_switch', - 'cry_detection_switch', - 'pet_detection', - 'motion_sensitivity', - 'decibel_sensitivity', - 'basic_nightvision', - 'basic_device_volume', - 'basic_anti_flicker', - 'basic_osd', - 'basic_flip', - 'basic_indicator', - ]); - return await TuyaOAuth2Util.onSettings(this, tuyaSettingsEvent, CAMERA_SETTING_LABELS); - } +module.exports = class TuyaOAuth2DeviceCamera extends TuyaDeviceWithCamera { + DOORBELL_TRIGGER_FLOW = 'camera_doorbell_rang'; }; diff --git a/drivers/camera/driver.flow.compose.json b/drivers/camera/driver.flow.compose.json index a3a8f08a..3953eb40 100644 --- a/drivers/camera/driver.flow.compose.json +++ b/drivers/camera/driver.flow.compose.json @@ -198,6 +198,13 @@ "en": "Sound is no longer detected" }, "$filter": "capabilities=alarm_sound" + }, + { + "id": "camera_doorbell_rang", + "title": { + "en": "Doorbell rang" + }, + "$filter": "capabilities=hidden.doorbell" } ] } diff --git a/drivers/camera/driver.ts b/drivers/camera/driver.ts index c36f8736..a7512d97 100644 --- a/drivers/camera/driver.ts +++ b/drivers/camera/driver.ts @@ -1,94 +1,3 @@ -import { DEVICE_CATEGORIES } from '../../lib/TuyaOAuth2Constants'; -import TuyaOAuth2Driver, { ListDeviceProperties } from '../../lib/TuyaOAuth2Driver'; -import { constIncludes, getFromMap } from '../../lib/TuyaOAuth2Util'; -import { - type TuyaDeviceDataPointResponse, - TuyaDeviceResponse, - TuyaDeviceSpecificationResponse, -} from '../../types/TuyaApiTypes'; -import type { StandardFlowArgs } from '../../types/TuyaTypes'; -import { - CAMERA_ALARM_CAPABILITIES, - CAMERA_SETTING_LABELS, - COMPLEX_CAMERA_CAPABILITIES, - SIMPLE_CAMERA_CAPABILITIES, - SIMPLE_CAMERA_FLOWS, -} from './TuyaCameraConstants'; +import TuyaDriverWithCamera from '../../lib/camera/driver'; -module.exports = class TuyaOAuth2DriverCamera extends TuyaOAuth2Driver { - TUYA_DEVICE_CATEGORIES = [DEVICE_CATEGORIES.SECURITY_VIDEO_SURV.SMART_CAMERA] as const; - - async onInit(): Promise { - await super.onInit(); - - for (const capability of SIMPLE_CAMERA_FLOWS.read_write) { - this.homey.flow.getActionCard(`camera_${capability}`).registerRunListener(async (args: StandardFlowArgs) => { - await args.device.triggerCapabilityListener(capability, args.value); - }); - } - - // Apply the same way as in onSettings, but for an individual value - for (const setting of SIMPLE_CAMERA_FLOWS.setting) { - this.addSettingFlowHandler(setting, CAMERA_SETTING_LABELS); - } - } - - onTuyaPairListDeviceProperties( - device: TuyaDeviceResponse, - specifications?: TuyaDeviceSpecificationResponse, - dataPoints?: TuyaDeviceDataPointResponse, - ): ListDeviceProperties { - const props = super.onTuyaPairListDeviceProperties(device, specifications, dataPoints); - - for (const status of device.status) { - const capability = status.code; - - // Basic capabilities - if ( - constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_write, capability) || - constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_only, capability) - ) { - props.store.tuya_capabilities.push(capability); - props.capabilities.push(capability); - } - - // More complicated capabilities - if (constIncludes(COMPLEX_CAMERA_CAPABILITIES, capability)) { - props.store.tuya_capabilities.push(capability); - } - } - - // Add battery capacity if supported - if (props.store.tuya_capabilities.includes('wireless_electricity')) { - props.capabilities.push('measure_battery'); - } - - // Add privacy mode control if supported - if (props.store.tuya_capabilities.includes('basic_private')) { - props.capabilities.push('onoff'); - } - - // Add camera movement control capabilities if supported - if (props.store.tuya_capabilities.includes('ptz_control') && props.store.tuya_capabilities.includes('ptz_stop')) { - props.capabilities.push('ptz_control_horizontal', 'ptz_control_vertical'); - } - - if (props.store.tuya_capabilities.includes('zoom_control') && props.store.tuya_capabilities.includes('zoom_stop')) { - props.capabilities.push('ptz_control_zoom'); - } - - // Add alarm event capabilities if supported, based on the toggles that are available - // e.g. motion_switch means alarm_motion gets added - if (props.store.tuya_capabilities.includes('initiative_message')) { - // Add the alarm capabilities based on the toggles that are available - for (const capability of props.store.tuya_capabilities) { - const alarmCapability = getFromMap(CAMERA_ALARM_CAPABILITIES, capability); - if (alarmCapability) { - props.capabilities.push(alarmCapability); - } - } - } - - return props; - } -}; +module.exports = class TuyaOAuth2DriverCamera extends TuyaDriverWithCamera {}; diff --git a/drivers/doorbell/device.ts b/drivers/doorbell/device.ts index 0eede4e4..f19cbcc3 100644 --- a/drivers/doorbell/device.ts +++ b/drivers/doorbell/device.ts @@ -1,27 +1,5 @@ -import TuyaOAuth2Device from '../../lib/TuyaOAuth2Device'; -import { TuyaStatus } from '../../types/TuyaTypes'; +import TuyaDeviceWithCamera from '../../lib/camera/device'; -module.exports = class TuyaOAuth2DeviceDoorbell extends TuyaOAuth2Device { - async onTuyaStatus(status: TuyaStatus, changedStatusCodes: string[]): Promise { - await super.onTuyaStatus(status, changedStatusCodes); - - if (changedStatusCodes.includes('alarm_message')) { - try { - const decoded = JSON.parse(Buffer.from(status['alarm_message'] as string, 'base64').toString('utf8')); - this.log(`Decoded Message: ${JSON.stringify(decoded)}`); - - if (decoded.cmd === 'ipc_doorbell') { - this.log('Doorbell Rang!', decoded); - - this.homey.flow - .getDeviceTriggerCard('doorbell_rang') - .trigger(this) - .catch((err: Error) => this.error(`Error Triggering Doorbell Rang: ${err.message}`)); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - this.error(`Error Parsing Message: ${err.message}`); - } - } - } +module.exports = class TuyaOAuth2DeviceDoorbell extends TuyaDeviceWithCamera { + DOORBELL_TRIGGER_FLOW = 'doorbell_rang'; }; diff --git a/drivers/doorbell/driver.compose.json b/drivers/doorbell/driver.compose.json index 935e6214..f6acaa7b 100644 --- a/drivers/doorbell/driver.compose.json +++ b/drivers/doorbell/driver.compose.json @@ -4,5 +4,13 @@ "name": { "en": "Doorbell", "nl": "Deurbel" + }, + "capabilities": ["alarm_motion", "alarm_sound"], + "capabilitiesOptions": { + "alarm_motion": { + "title": { + "en": "Motion Detected" + } + } } } diff --git a/drivers/doorbell/driver.flow.compose.json b/drivers/doorbell/driver.flow.compose.json index c6958e6b..c4c62fc3 100644 --- a/drivers/doorbell/driver.flow.compose.json +++ b/drivers/doorbell/driver.flow.compose.json @@ -1,9 +1,208 @@ { + "actions": [ + { + "id": "doorbell_cruise_switch", + "$filter": "capabilities=cruise_switch", + "title": { + "en": "Set auto patrol" + }, + "titleFormatted": { + "en": "Set auto patrol [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_siren_switch", + "$filter": "capabilities=siren_switch", + "title": { + "en": "Set siren" + }, + "titleFormatted": { + "en": "Set siren [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_motion_switch", + "$filter": "capabilities=alarm_motion", + "title": { + "en": "Set motion detection" + }, + "titleFormatted": { + "en": "Set motion detection [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_motion_tracking", + "title": { + "en": "Set motion tracking" + }, + "titleFormatted": { + "en": "Set motion tracking [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_decibel_switch", + "$filter": "capabilities=alarm_sound", + "title": { + "en": "Set sound detection" + }, + "titleFormatted": { + "en": "Set sound detection [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_cry_detection_switch", + "$filter": "capabilities=alarm_crying_child", + "title": { + "en": "Set crying baby detection" + }, + "titleFormatted": { + "en": "Set crying baby detection [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_pet_detection", + "$filter": "capabilities=alarm_pet", + "title": { + "en": "Set pet detection" + }, + "titleFormatted": { + "en": "Set pet detection [[value]]" + }, + "args": [ + { + "name": "value", + "type": "checkbox", + "title": { "en": "Value" } + } + ] + }, + { + "id": "doorbell_basic_nightvision", + "title": { + "en": "Set night mode" + }, + "titleFormatted": { + "en": "Set night mode to [[value]]" + }, + "args": [ + { + "name": "value", + "type": "dropdown", + "title": { "en": "Value" }, + "values": [ + { + "id": "0", + "label": { + "en": "Auto" + } + }, + { + "id": "1", + "label": { + "en": "Off" + } + }, + { + "id": "2", + "label": { + "en": "On" + } + } + ] + } + ] + } + ], + "conditions": [], "triggers": [ + { + "id": "doorbell_alarm_crying_child_true", + "title": { + "en": "A crying child was detected" + }, + "$filter": "capabilities=alarm_crying_child" + }, + { + "id": "doorbell_alarm_crying_child_false", + "title": { + "en": "Crying is no longer detected" + }, + "$filter": "capabilities=alarm_crying_child" + }, + { + "id": "doorbell_alarm_pet_true", + "title": { + "en": "A pet was detected" + }, + "$filter": "capabilities=alarm_pet" + }, + { + "id": "doorbell_alarm_pet_false", + "title": { + "en": "Pet is no longer detected" + }, + "$filter": "capabilities=alarm_pet" + }, + { + "id": "doorbell_alarm_sound_true", + "title": { + "en": "A sound was detected" + }, + "$filter": "capabilities=alarm_sound" + }, + { + "id": "doorbell_alarm_sound_false", + "title": { + "en": "Sound is no longer detected" + }, + "$filter": "capabilities=alarm_sound" + }, { "id": "doorbell_rang", "title": { - "en": "The doorbell rang" + "en": "Doorbell rang" } } ] diff --git a/drivers/doorbell/driver.settings.compose.json b/drivers/doorbell/driver.settings.compose.json index dceebc0d..6d29c7f8 100644 --- a/drivers/doorbell/driver.settings.compose.json +++ b/drivers/doorbell/driver.settings.compose.json @@ -1,4 +1,196 @@ [ + { + "type": "group", + "label": { + "en": "NOTE: Some settings are not supported by every doorbell." + }, + "children": [ + { + "id": "motion_switch", + "type": "checkbox", + "label": { + "en": "Motion Detection" + }, + "value": false + }, + { + "id": "motion_tracking", + "type": "checkbox", + "label": { + "en": "Motion Tracking" + }, + "value": false + }, + { + "id": "decibel_switch", + "type": "checkbox", + "label": { + "en": "Sound Detection" + }, + "value": false + }, + { + "id": "cry_detection_switch", + "type": "checkbox", + "label": { + "en": "Crying Baby Detection" + }, + "value": false + }, + { + "id": "pet_detection", + "type": "checkbox", + "label": { + "en": "Pet Detection" + }, + "value": false + }, + { + "id": "motion_sensitivity", + "type": "dropdown", + "label": { + "en": "Motion Sensitivity" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Low" + } + }, + { + "id": "1", + "label": { + "en": "Medium" + } + }, + { + "id": "2", + "label": { + "en": "High" + } + } + ] + }, + { + "id": "decibel_sensitivity", + "type": "dropdown", + "label": { + "en": "Sound Sensitivity" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Low" + } + }, + { + "id": "1", + "label": { + "en": "High" + } + } + ] + }, + { + "id": "basic_nightvision", + "type": "dropdown", + "label": { + "en": "Night Mode" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Auto" + } + }, + { + "id": "1", + "label": { + "en": "Off" + } + }, + { + "id": "2", + "label": { + "en": "On" + } + } + ] + }, + { + "id": "basic_device_volume", + "type": "number", + "label": { + "en": "Device Volume" + }, + "value": 8, + "min": 1, + "max": 10, + "step": 1 + }, + { + "id": "basic_anti_flicker", + "type": "dropdown", + "label": { + "en": "Anti-Flicker" + }, + "value": "0", + "values": [ + { + "id": "0", + "label": { + "en": "Disabled" + } + }, + { + "id": "1", + "label": { + "en": "50 Hz" + } + }, + { + "id": "2", + "label": { + "en": "60 Hz" + } + } + ] + }, + { + "id": "basic_osd", + "type": "checkbox", + "label": { + "en": "Video Timestamp" + }, + "value": true + }, + { + "id": "basic_flip", + "type": "checkbox", + "label": { + "en": "Flip Video" + }, + "value": false + }, + { + "id": "basic_indicator", + "type": "checkbox", + "label": { + "en": "Status Indicator" + }, + "value": false + } + ] + }, + { + "$extends": "alarmTimeout" + }, { "$extends": "deviceSpecification" } diff --git a/drivers/doorbell/driver.ts b/drivers/doorbell/driver.ts index 0d26159c..f0669006 100644 --- a/drivers/doorbell/driver.ts +++ b/drivers/doorbell/driver.ts @@ -1,21 +1,3 @@ -import { DEVICE_CATEGORIES } from '../../lib/TuyaOAuth2Constants'; -import TuyaOAuth2Driver from '../../lib/TuyaOAuth2Driver'; -import { TuyaDeviceResponse } from '../../types/TuyaApiTypes'; +import TuyaDriverWithCamera from '../../lib/camera/driver'; -// TODO refactor to be in line with other drivers -module.exports = class TuyaOAuth2DriverDoorbell extends TuyaOAuth2Driver { - TUYA_DEVICE_CATEGORIES = [DEVICE_CATEGORIES.SECURITY_VIDEO_SURV.SMART_CAMERA] as const; - - onTuyaPairListDeviceFilter(device: TuyaDeviceResponse): boolean { - if (!super.onTuyaPairListDeviceFilter(device)) return false; - - // Require a doorbell capability - return !!device.status.find(status => status.code === 'doorbell_active'); - } - - // onTuyaPairListDeviceProperties(device) { - // const props = super.onTuyaPairListDeviceProperties(device); - - // return props; - // } -}; +module.exports = class TuyaOAuth2DriverDoorbell extends TuyaDriverWithCamera {}; diff --git a/drivers/camera/TuyaCameraConstants.ts b/lib/camera/TuyaCameraConstants.ts similarity index 100% rename from drivers/camera/TuyaCameraConstants.ts rename to lib/camera/TuyaCameraConstants.ts diff --git a/lib/camera/device.ts b/lib/camera/device.ts new file mode 100644 index 00000000..8dc1da13 --- /dev/null +++ b/lib/camera/device.ts @@ -0,0 +1,187 @@ +import * as TuyaOAuth2Util from '../TuyaOAuth2Util'; +import { constIncludes, getFromMap } from '../TuyaOAuth2Util'; +import { SettingsEvent, TuyaStatus } from '../../types/TuyaTypes'; +import { + CAMERA_ALARM_EVENT_CAPABILITIES, + CAMERA_SETTING_LABELS, + HomeyCameraSettings, + SIMPLE_CAMERA_CAPABILITIES, + TuyaCameraSettings, +} from './TuyaCameraConstants'; +import TuyaTimeOutAlarmDevice from '../TuyaTimeOutAlarmDevice'; + +abstract class TuyaOAuth2DeviceWithCamera extends TuyaTimeOutAlarmDevice { + abstract DOORBELL_TRIGGER_FLOW: string; + + async onOAuth2Init(): Promise { + await super.onOAuth2Init(); + + for (const capability of this.getCapabilities()) { + // Basic capabilities + if (constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_write, capability)) { + this.registerCapabilityListener(capability, value => + this.sendCommand({ + code: capability, + value: value, + }), + ); + } + + // PTZ control + if (capability === 'ptz_control_vertical') { + this.registerCapabilityListener(capability, value => this.ptzCapabilityListener(value, '0', '4')); + } + if (capability === 'ptz_control_horizontal') { + this.registerCapabilityListener(capability, value => this.ptzCapabilityListener(value, '6', '2')); + } + + if (capability === 'ptz_control_zoom') { + this.registerCapabilityListener(capability, value => this.zoomCapabilityListener(value)); + } + + // Other capabilities + if (capability === 'onoff') { + this.registerCapabilityListener(capability, value => + this.sendCommand({ + code: 'basic_private', + value: !value, + }), + ); + } + } + + // Reset alarms in case a timeout was interrupted + for (const tuyaCapability in CAMERA_ALARM_EVENT_CAPABILITIES) { + const capability = getFromMap(CAMERA_ALARM_EVENT_CAPABILITIES, tuyaCapability); + if (capability && this.hasCapability(capability)) { + await this.setCapabilityValue(capability, false); + } + } + } + + async onTuyaStatus(status: TuyaStatus, changed: string[]): Promise { + await super.onTuyaStatus(status, changed); + + for (const statusKey in status) { + const value = status[statusKey]; + + // Basic capabilities + if ( + constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_write, statusKey) || + constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_only, statusKey) + ) { + await this.setCapabilityValue(statusKey, value).catch(this.error); + } + + if (constIncludes(SIMPLE_CAMERA_CAPABILITIES.setting, statusKey)) { + await this.setSettings({ + [statusKey]: value, + }).catch(this.error); + } + + // PTZ control + if ( + (statusKey === 'ptz_stop' && value && changed.includes('ptz_stop')) || + (statusKey === 'ptz_control' && value === '8' && changed.includes('ptz_control')) + ) { + await this.setCapabilityValue('ptz_control_horizontal', 'idle').catch(this.error); + await this.setCapabilityValue('ptz_control_vertical', 'idle').catch(this.error); + } + + if (statusKey === 'zoom_stop' && value && changed.includes('zoom_stop')) { + await this.setCapabilityValue('ptz_control_zoom', 'idle').catch(this.error); + } + + // Other capabilities + if (statusKey === 'basic_private') { + await this.setCapabilityValue('onoff', !value).catch(this.error); + } + + if (statusKey === 'wireless_electricity') { + await this.setCapabilityValue('measure_battery', value).catch(this.error); + } + + // Event messages + if ( + (statusKey === 'initiative_message' && changed.includes('initiative_message')) || + (statusKey === 'alarm_message' && changed.includes('alarm_message')) + ) { + if (statusKey === 'initiative_message' && changed.includes('initiative_message')) { + // Event messages are base64 encoded JSON + const encoded = status[statusKey] as string; + const decoded = Buffer.from(encoded, 'base64'); + const data = JSON.parse(decoded.toString()); + const notificationType = data.cmd; + const dataType = data.type; + this.log('Notification:', notificationType, dataType); + + // Check if the event is for a known alarm + if (notificationType in CAMERA_ALARM_EVENT_CAPABILITIES) { + const alarmCapability = getFromMap(CAMERA_ALARM_EVENT_CAPABILITIES, notificationType); + if (!alarmCapability) { + continue; + } + + if (!this.hasCapability(alarmCapability)) { + await this.addCapability(alarmCapability).catch(this.error); + } + await this.setAlarm(alarmCapability); + } + } + } + } + + async setAlarm(capability: string): Promise { + await super.setAlarm( + capability, + async () => { + const deviceTriggerCard = this.homey.flow.getDeviceTriggerCard(`${this.driver.id}_${capability}_true`); + await deviceTriggerCard.trigger(this).catch(this.error); + await this.setCapabilityValue(capability, true).catch(this.error); + }, + async () => { + const deviceTriggerCard = this.homey.flow.getDeviceTriggerCard(`${this.driver.id}_${capability}_false`); + await deviceTriggerCard.trigger(this).catch(this.error); + await this.setCapabilityValue(capability, false).catch(this.error); + }, + ); + } + + // Map from up/idle/down to commands so the ternary UI shows arrows + async ptzCapabilityListener(value: 'up' | 'idle' | 'down', up: string, down: string): Promise { + if (value === 'idle') { + await this.sendCommand({ code: 'ptz_stop', value: true }); + } else { + await this.sendCommand({ code: 'ptz_control', value: value === 'up' ? up : down }); + } + } + + async zoomCapabilityListener(value: 'up' | 'idle' | 'down'): Promise { + if (value === 'idle') { + await this.sendCommand({ code: 'zoom_stop', value: true }); + } else { + await this.sendCommand({ code: 'zoom_control', value: value === 'up' ? '1' : '0' }); + } + } + + async onSettings(event: SettingsEvent): Promise { + const tuyaSettingsEvent = TuyaOAuth2Util.filterTuyaSettings(event, [ + 'motion_switch', + 'motion_tracking', + 'decibel_switch', + 'cry_detection_switch', + 'pet_detection', + 'motion_sensitivity', + 'decibel_sensitivity', + 'basic_nightvision', + 'basic_device_volume', + 'basic_anti_flicker', + 'basic_osd', + 'basic_flip', + 'basic_indicator', + ]); + return await TuyaOAuth2Util.onSettings(this, tuyaSettingsEvent, CAMERA_SETTING_LABELS); + } +} + +export default TuyaOAuth2DeviceWithCamera; diff --git a/lib/camera/driver.ts b/lib/camera/driver.ts new file mode 100644 index 00000000..7742b147 --- /dev/null +++ b/lib/camera/driver.ts @@ -0,0 +1,96 @@ +import { DEVICE_CATEGORIES } from '../TuyaOAuth2Constants'; +import TuyaOAuth2Driver, { ListDeviceProperties } from '../TuyaOAuth2Driver'; +import { constIncludes, getFromMap } from '../TuyaOAuth2Util'; +import { + type TuyaDeviceDataPointResponse, + TuyaDeviceResponse, + TuyaDeviceSpecificationResponse, +} from '../../types/TuyaApiTypes'; +import type { StandardFlowArgs } from '../../types/TuyaTypes'; +import { + CAMERA_ALARM_CAPABILITIES, + CAMERA_SETTING_LABELS, + COMPLEX_CAMERA_CAPABILITIES, + SIMPLE_CAMERA_CAPABILITIES, + SIMPLE_CAMERA_FLOWS, +} from './TuyaCameraConstants'; + +class TuyaOAuth2DriverWithCamera extends TuyaOAuth2Driver { + TUYA_DEVICE_CATEGORIES = [DEVICE_CATEGORIES.SECURITY_VIDEO_SURV.SMART_CAMERA] as const; + + async onInit(): Promise { + await super.onInit(); + + for (const capability of SIMPLE_CAMERA_FLOWS.read_write) { + this.homey.flow.getActionCard(`${this.id}_${capability}`).registerRunListener(async (args: StandardFlowArgs) => { + await args.device.triggerCapabilityListener(capability, args.value); + }); + } + + // Apply the same way as in onSettings, but for an individual value + for (const setting of SIMPLE_CAMERA_FLOWS.setting) { + this.addSettingFlowHandler(setting, CAMERA_SETTING_LABELS); + } + } + + onTuyaPairListDeviceProperties( + device: TuyaDeviceResponse, + specifications?: TuyaDeviceSpecificationResponse, + dataPoints?: TuyaDeviceDataPointResponse, + ): ListDeviceProperties { + const props = super.onTuyaPairListDeviceProperties(device, specifications, dataPoints); + + for (const status of device.status) { + const capability = status.code; + + // Basic capabilities + if ( + constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_write, capability) || + constIncludes(SIMPLE_CAMERA_CAPABILITIES.read_only, capability) + ) { + props.store.tuya_capabilities.push(capability); + props.capabilities.push(capability); + } + + // More complicated capabilities + if (constIncludes(COMPLEX_CAMERA_CAPABILITIES, capability)) { + props.store.tuya_capabilities.push(capability); + } + } + + // Add battery capacity if supported + if (props.store.tuya_capabilities.includes('wireless_electricity')) { + props.capabilities.push('measure_battery'); + } + + // Add privacy mode control if supported + if (props.store.tuya_capabilities.includes('basic_private')) { + props.capabilities.push('onoff'); + } + + // Add camera movement control capabilities if supported + if (props.store.tuya_capabilities.includes('ptz_control') && props.store.tuya_capabilities.includes('ptz_stop')) { + props.capabilities.push('ptz_control_horizontal', 'ptz_control_vertical'); + } + + if (props.store.tuya_capabilities.includes('zoom_control') && props.store.tuya_capabilities.includes('zoom_stop')) { + props.capabilities.push('ptz_control_zoom'); + } + + // Add alarm event capabilities if supported, based on the toggles that are available + // e.g. motion_switch means alarm_motion gets added + if (props.store.tuya_capabilities.includes('initiative_message')) { + // Add the alarm capabilities based on the toggles that are available + for (const capability of props.store.tuya_capabilities) { + const alarmCapability = getFromMap(CAMERA_ALARM_CAPABILITIES, capability); + if (alarmCapability) { + props.capabilities.push(alarmCapability); + } + } + } + + return props; + } +} + +export default TuyaOAuth2DriverWithCamera; diff --git a/drivers/camera/translations.json b/lib/camera/translations.json similarity index 100% rename from drivers/camera/translations.json rename to lib/camera/translations.json From b36509e4f20bee0812b41b1014d74634317a1660 Mon Sep 17 00:00:00 2001 From: Joost Loohuis Date: Fri, 20 Sep 2024 15:20:24 +0200 Subject: [PATCH 2/4] Fix doorbell event support --- lib/camera/TuyaCameraConstants.ts | 1 + lib/camera/device.ts | 15 +++++++++++---- lib/camera/driver.ts | 5 ++++- lib/webhooks/TuyaWebhookParser.ts | 16 ++++++++++++---- types/TuyaTypes.ts | 3 ++- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/camera/TuyaCameraConstants.ts b/lib/camera/TuyaCameraConstants.ts index 570f92f1..7c8b8a82 100644 --- a/lib/camera/TuyaCameraConstants.ts +++ b/lib/camera/TuyaCameraConstants.ts @@ -53,6 +53,7 @@ export const CAMERA_ALARM_CAPABILITIES = { decibel_switch: 'alarm_sound', cry_detection_switch: 'alarm_crying_child', pet_detection: 'alarm_pet', + doorbell_active: 'hidden.doorbell', } as const; // Map from an event to an alarm capability diff --git a/lib/camera/device.ts b/lib/camera/device.ts index 8dc1da13..18d1af2e 100644 --- a/lib/camera/device.ts +++ b/lib/camera/device.ts @@ -102,10 +102,17 @@ abstract class TuyaOAuth2DeviceWithCamera extends TuyaTimeOutAlarmDevice { } // Event messages - if ( - (statusKey === 'initiative_message' && changed.includes('initiative_message')) || - (statusKey === 'alarm_message' && changed.includes('alarm_message')) - ) { + if (statusKey === 'event_message' && changed.includes('event_message')) { + const event_message = value as { etype: string }; + if (event_message.etype === 'ac_doorbell') { + if (!this.hasCapability('hidden.doorbell')) { + await this.addCapability('hidden.doorbell').catch(this.error); + } else { + await this.homey.flow.getDeviceTriggerCard(this.DOORBELL_TRIGGER_FLOW).trigger(this).catch(this.error); + } + } + } + if (statusKey === 'initiative_message' && changed.includes('initiative_message')) { // Event messages are base64 encoded JSON const encoded = status[statusKey] as string; diff --git a/lib/camera/driver.ts b/lib/camera/driver.ts index 7742b147..3af5fa2d 100644 --- a/lib/camera/driver.ts +++ b/lib/camera/driver.ts @@ -53,7 +53,10 @@ class TuyaOAuth2DriverWithCamera extends TuyaOAuth2Driver { } // More complicated capabilities - if (constIncludes(COMPLEX_CAMERA_CAPABILITIES, capability)) { + if ( + constIncludes(COMPLEX_CAMERA_CAPABILITIES, capability) || + constIncludes(SIMPLE_CAMERA_CAPABILITIES.setting, capability) + ) { props.store.tuya_capabilities.push(capability); } } diff --git a/lib/webhooks/TuyaWebhookParser.ts b/lib/webhooks/TuyaWebhookParser.ts index d516e787..70ff2d64 100644 --- a/lib/webhooks/TuyaWebhookParser.ts +++ b/lib/webhooks/TuyaWebhookParser.ts @@ -18,8 +18,15 @@ type IotCoreStatusEvent = { properties?: Array>; }; }; +type EventEvent = { + event: 'event'; + data: { + etype: string; + edata: string; + }; +}; -type TuyaWebhookData = OnlineEvent | OfflineEvent | StatusEvent | IotCoreStatusEvent; +export type TuyaWebhookData = OnlineEvent | OfflineEvent | StatusEvent | IotCoreStatusEvent | EventEvent; export default class TuyaWebhookParser { private readonly logContext; @@ -39,14 +46,15 @@ export default class TuyaWebhookParser { break; case 'status': statusUpdate = convertStatusArrayToStatusObject(message.data.deviceStatus); - break; case 'iot_core_status': statusUpdate = convertStatusArrayToStatusObject(message.data.properties); - + break; + case 'event': + statusUpdate = { event_message: message.data }; break; default: - throw new Error(`Unknown Webhook Event: ${message}`); + throw new Error(`Unknown Webhook Event: ${JSON.stringify(message)}`); } const changedStatusCodes = Object.keys(statusUpdate); diff --git a/types/TuyaTypes.ts b/types/TuyaTypes.ts index e119e157..ef595059 100644 --- a/types/TuyaTypes.ts +++ b/types/TuyaTypes.ts @@ -1,7 +1,8 @@ import type TuyaOAuth2Device from '../lib/TuyaOAuth2Device'; +import { TuyaWebhookData } from '../lib/webhooks/TuyaWebhookParser'; export type TuyaStatus = Record; -export type TuyaStatusSource = 'sync' | 'online' | 'offline' | 'status' | 'iot_core_status'; +export type TuyaStatusSource = 'sync' | TuyaWebhookData['event']; // Legacy status update export type TuyaStatusUpdate = { From 2537f96369d1e34c1664abfce9e7dd85d94eb82b Mon Sep 17 00:00:00 2001 From: Joost Loohuis Date: Mon, 23 Sep 2024 16:17:51 +0200 Subject: [PATCH 3/4] Improve camera logging --- lib/camera/device.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/camera/device.ts b/lib/camera/device.ts index 18d1af2e..df181f8f 100644 --- a/lib/camera/device.ts +++ b/lib/camera/device.ts @@ -103,7 +103,8 @@ abstract class TuyaOAuth2DeviceWithCamera extends TuyaTimeOutAlarmDevice { // Event messages if (statusKey === 'event_message' && changed.includes('event_message')) { - const event_message = value as { etype: string }; + const event_message = value as { etype: string; edata: string }; + this.log('Event message:', event_message.etype, event_message.edata); if (event_message.etype === 'ac_doorbell') { if (!this.hasCapability('hidden.doorbell')) { await this.addCapability('hidden.doorbell').catch(this.error); @@ -120,7 +121,7 @@ abstract class TuyaOAuth2DeviceWithCamera extends TuyaTimeOutAlarmDevice { const data = JSON.parse(decoded.toString()); const notificationType = data.cmd; const dataType = data.type; - this.log('Notification:', notificationType, dataType); + this.log('Initiative message:', notificationType, dataType); // Check if the event is for a known alarm if (notificationType in CAMERA_ALARM_EVENT_CAPABILITIES) { From 0eefe4031e7dd927d1c3ba25a4e7cda7310285ea Mon Sep 17 00:00:00 2001 From: Joost Loohuis Date: Mon, 23 Sep 2024 17:18:55 +0200 Subject: [PATCH 4/4] Apply PR suggestions --- lib/camera/device.ts | 4 ++-- lib/webhooks/TuyaWebhookParser.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/camera/device.ts b/lib/camera/device.ts index df181f8f..6db4c8d3 100644 --- a/lib/camera/device.ts +++ b/lib/camera/device.ts @@ -9,6 +9,7 @@ import { TuyaCameraSettings, } from './TuyaCameraConstants'; import TuyaTimeOutAlarmDevice from '../TuyaTimeOutAlarmDevice'; +import { EventEvent } from '../webhooks/TuyaWebhookParser'; abstract class TuyaOAuth2DeviceWithCamera extends TuyaTimeOutAlarmDevice { abstract DOORBELL_TRIGGER_FLOW: string; @@ -103,8 +104,7 @@ abstract class TuyaOAuth2DeviceWithCamera extends TuyaTimeOutAlarmDevice { // Event messages if (statusKey === 'event_message' && changed.includes('event_message')) { - const event_message = value as { etype: string; edata: string }; - this.log('Event message:', event_message.etype, event_message.edata); + const event_message = value as EventEvent['data']; if (event_message.etype === 'ac_doorbell') { if (!this.hasCapability('hidden.doorbell')) { await this.addCapability('hidden.doorbell').catch(this.error); diff --git a/lib/webhooks/TuyaWebhookParser.ts b/lib/webhooks/TuyaWebhookParser.ts index 70ff2d64..ea62209d 100644 --- a/lib/webhooks/TuyaWebhookParser.ts +++ b/lib/webhooks/TuyaWebhookParser.ts @@ -18,7 +18,7 @@ type IotCoreStatusEvent = { properties?: Array>; }; }; -type EventEvent = { +export type EventEvent = { event: 'event'; data: { etype: string;