diff --git a/SUPPORTED_DEVICES.md b/SUPPORTED_DEVICES.md index 7b31689a..a496e903 100644 --- a/SUPPORTED_DEVICES.md +++ b/SUPPORTED_DEVICES.md @@ -107,7 +107,7 @@ Most category code is pinyin abbreviation of Chinese name. | Name | Name (zh) | Code | Homebridge Service | Supported | Links | | ---- | ---- | ---- | ---- | ---- | ---- | -| Alarm Host | 报警主机 | mal | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf) | +| Alarm Host | 报警主机 | mal | Security System | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorymal?id=Kaiuz33clqxaf) | | Smart Camera | 智能摄像机 | sp | Motion Sensor
Doorbell | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorysp?id=Kaiuz35leyo12) | | Siren Alarm | 声光报警传感器 | sgbj | | | [Documentation](https://developer.tuya.com/en/docs/iot/categorysgbj?id=Kaiuz37tlpbnu) | | Gas Alarm | 燃气报警传感器 | rqbj | Leak Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryrqbj?id=Kaiuz3d162ubw) | diff --git a/package-lock.json b/package-lock.json index fb06cdfb..fe135e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@0x5e/homebridge-tuya-platform", - "version": "1.7.0-beta.29", + "version": "1.7.0-beta.30", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@0x5e/homebridge-tuya-platform", - "version": "1.7.0-beta.29", + "version": "1.7.0-beta.30", "funding": [ { "type": "paypal", diff --git a/package.json b/package.json index f03c75ae..03a5d60f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0x5e/homebridge-tuya-platform", - "version": "1.7.0-beta.29", + "version": "1.7.0-beta.30", "description": "Fork version of official Tuya Homebridge plugin. Brings a bunch of bug fix and new device support.", "license": "MIT", "repository": { diff --git a/src/accessory/AccessoryFactory.ts b/src/accessory/AccessoryFactory.ts index c782a941..ea265468 100644 --- a/src/accessory/AccessoryFactory.ts +++ b/src/accessory/AccessoryFactory.ts @@ -37,6 +37,7 @@ import AirConditionerAccessory from './AirConditionerAccessory'; import IRControlHubAccessory from './IRControlHubAccessory'; import IRGenericAccessory from './IRGenericAccessory'; import IRAirConditionerAccessory from './IRAirConditionerAccessory'; +import SecuritySystemAccessory from './SecuritySystemAccessory'; export default class AccessoryFactory { @@ -176,6 +177,9 @@ export default class AccessoryFactory { case 'jtmspro': handler = new LockAccessory(platform, accessory); break; + case 'mal': + handler = new SecuritySystemAccessory(platform, accessory); + break; // Other case 'scene': diff --git a/src/accessory/SecuritySystemAccessory.ts b/src/accessory/SecuritySystemAccessory.ts new file mode 100644 index 00000000..22fa5c2e --- /dev/null +++ b/src/accessory/SecuritySystemAccessory.ts @@ -0,0 +1,29 @@ +import BaseAccessory from './BaseAccessory'; +import { configureSecuritySystemCurrentState, configureSecuritySystemTargetState } from './characteristic/SecuritySystemState'; +import { configureName } from './characteristic/Name'; + +const SCHEMA_CODE = { + MASTER_MODE: ['master_mode'], + SOS_STATE: ['sos_state'], +}; + +export default class SecuritySystemAccessory extends BaseAccessory { + + requiredSchema() { + return [SCHEMA_CODE.MASTER_MODE, SCHEMA_CODE.SOS_STATE]; + } + + isNightArm = false; + + configureServices() { + const service = this.accessory.getService(this.Service.SecuritySystem) + || this.accessory.addService(this.Service.SecuritySystem); + + configureName(this, service, this.device.name); + + configureSecuritySystemCurrentState(this, service, this.getSchema(...SCHEMA_CODE.MASTER_MODE), + this.getSchema(...SCHEMA_CODE.SOS_STATE)); + configureSecuritySystemTargetState(this, service, this.getSchema(...SCHEMA_CODE.MASTER_MODE), + this.getSchema(...SCHEMA_CODE.SOS_STATE)); + } +} diff --git a/src/accessory/characteristic/SecuritySystemState.ts b/src/accessory/characteristic/SecuritySystemState.ts new file mode 100644 index 00000000..32b62327 --- /dev/null +++ b/src/accessory/characteristic/SecuritySystemState.ts @@ -0,0 +1,91 @@ +import { Service } from 'homebridge'; +import { TuyaDeviceSchema } from '../../device/TuyaDevice'; +import BaseAccessory from '../BaseAccessory'; +import SecuritySystemAccessory from '../SecuritySystemAccessory'; + +const TUYA_CODES = { + MASTER_MODE: { + ARMED: 'arm', + DISARMED: 'disarmed', + HOME: 'home', + }, +}; + +function getTuyaHomebridgeMap(accessory: BaseAccessory) { + const tuyaHomebridgeMap = new Map(); + + tuyaHomebridgeMap.set(TUYA_CODES.MASTER_MODE.ARMED, accessory.Characteristic.SecuritySystemCurrentState.AWAY_ARM); + tuyaHomebridgeMap.set(TUYA_CODES.MASTER_MODE.DISARMED, accessory.Characteristic.SecuritySystemCurrentState.DISARMED); + tuyaHomebridgeMap.set(TUYA_CODES.MASTER_MODE.HOME, accessory.Characteristic.SecuritySystemCurrentState.STAY_ARM); + tuyaHomebridgeMap.set(accessory.Characteristic.SecuritySystemCurrentState.AWAY_ARM, TUYA_CODES.MASTER_MODE.ARMED); + tuyaHomebridgeMap.set(accessory.Characteristic.SecuritySystemCurrentState.DISARMED, TUYA_CODES.MASTER_MODE.DISARMED); + tuyaHomebridgeMap.set(accessory.Characteristic.SecuritySystemCurrentState.STAY_ARM, TUYA_CODES.MASTER_MODE.HOME); + tuyaHomebridgeMap.set(accessory.Characteristic.SecuritySystemCurrentState.NIGHT_ARM, TUYA_CODES.MASTER_MODE.HOME); + + return tuyaHomebridgeMap; +} + +export function configureSecuritySystemCurrentState(accessory: SecuritySystemAccessory, service: Service, + masterModeSchema?: TuyaDeviceSchema, sosStateSchema?: TuyaDeviceSchema) { + if (!masterModeSchema || !sosStateSchema) { + return; + } + + const tuyaHomebridgeMap = getTuyaHomebridgeMap(accessory); + + service.getCharacteristic(accessory.Characteristic.SecuritySystemCurrentState) + .onGet(() => { + const alarmTriggered = accessory.getStatus(sosStateSchema.code)!.value; + + if (alarmTriggered) { + return accessory.Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + } else { + const currentState = accessory.getStatus(masterModeSchema.code)!.value; + if (currentState === TUYA_CODES.MASTER_MODE.HOME) { + return accessory.isNightArm ? accessory.Characteristic.SecuritySystemCurrentState.NIGHT_ARM : + accessory.Characteristic.SecuritySystemCurrentState.STAY_ARM; + } + + return tuyaHomebridgeMap.get(currentState); + } + }); +} + +export function configureSecuritySystemTargetState(accessory: SecuritySystemAccessory, service: Service, + masterModeSchema?: TuyaDeviceSchema, sosStateSchema?: TuyaDeviceSchema) { + if (!masterModeSchema || !sosStateSchema) { + return; + } + + const tuyaHomebridgeMap = getTuyaHomebridgeMap(accessory); + + service.getCharacteristic(accessory.Characteristic.SecuritySystemTargetState) + .onGet(() => { + const currentState = accessory.getStatus(masterModeSchema.code)!.value; + if (currentState === TUYA_CODES.MASTER_MODE.HOME) { + return accessory.isNightArm ? accessory.Characteristic.SecuritySystemCurrentState.NIGHT_ARM : + accessory.Characteristic.SecuritySystemCurrentState.STAY_ARM; + } + + return tuyaHomebridgeMap.get(currentState); + }) + .onSet(value => { + + const sosState = accessory.getStatus(sosStateSchema.code)?.value; + + // If we received a request to disarm the alarm, we make sure sos_state is set to false + if (sosState && value === accessory.Characteristic.SecuritySystemTargetState.DISARM) { + accessory.sendCommands([{ + code: sosStateSchema.code, + value: false, + }], true); + } + + accessory.isNightArm = value === accessory.Characteristic.SecuritySystemTargetState.NIGHT_ARM; + + accessory.sendCommands([{ + code: masterModeSchema.code, + value: tuyaHomebridgeMap.get(value), + }], true); + }); +}