@zswuwing/homebridge-wyze-smart-home
Version:
Wyze Smart Home plugin for Homebridge
665 lines (611 loc) • 27.8 kB
JavaScript
const { Service, Characteristic } = require('../types')
// Responses from the Wyze API can lag a little after a new value is set
const UPDATE_THROTTLE_MS = 1000
module.exports = class WyzeAccessory {
constructor (plugin, homeKitAccessory) {
this.updating = false
this.lastTimestamp = null
this.plugin = plugin
this.homeKitAccessory = homeKitAccessory
}
// Default Prop
get display_name () { return this.homeKitAccessory.displayName }
get mac () { return this.homeKitAccessory.context.mac }
get product_type () { return this.homeKitAccessory.context.product_type }
get product_model () { return this.homeKitAccessory.context.product_model }
// Wall Switch Prop
get single_press_type () { return this.homeKitAccessory.context.device_params?.single_press_type }
get double_press_type () { return this.homeKitAccessory.context.device_params?.double_press_type }
get triple_press_type () { return this.homeKitAccessory.context.device_params?.triple_press_type }
get long_press_type () { return this.homeKitAccessory.context.device_params?.long_press_type }
get iot_state () { return this.homeKitAccessory.context.device_params?.iot_state } //Device online / offline
get switch_power () { return this.homeKitAccessory.context.device_params?.switch_power } //Power on / Power off
// Lock Prop - only add get if you need to access them
// From Device List
get switch_state () { return this.homeKitAccessory.context.device_params.switch_state }
get open_close_state () { return this.homeKitAccessory.context.device_params.open_close_state }
// from lockInfo
get lockPower () { return this.homeKitAccessory.context.device_params?.power }
get lockDoorOpenStatus () { return this.homeKitAccessory.context.device_params?.door_open_status }
get lockOnoffLine () { return this.homeKitAccessory.context.device_params?.onoff_line }
get lockTrashMode () { return this.homeKitAccessory.context.device_params?.trash_mode }
get lockKeypadEnableStatus () { return this.homeKitAccessory.context.device_params?.keypad_enable_status }
get lockDoorSensor () { return this.homeKitAccessory.context.device_params?.door_sensor }
get lockKeypadPower () { return this.homeKitAccessory.context.device_params?.keypadPower }
get lockKeypadOnoffLine () { return this.homeKitAccessory.context.device_params?.keypadOnoff_line }
get lockKeypadMac () { return this.homeKitAccessory.context.device_params?.keypadMac }
get lockLockerStatusDoor () { return this.homeKitAccessory.context.device_params?.door }
get lockLockerStatusHardlock () { return this.homeKitAccessory.context.device_params?.hardlock }
// from Camera
get cameraPowerSwitch () { return this.homeKitAccessory.context.device_params.power_switch}
get cameraMotionSwitch () { return this.homeKitAccessory.context.device_params.motion_alarm_switch}
get cameraNotification () { return this.homeKitAccessory.context.device_params?.notification}
get cameraOn () { return this.homeKitAccessory.context.device_params?.on}
get cameraAvailable () { return this.homeKitAccessory.context.device_params?.available}
get cameraSiren () { return this.homeKitAccessory.context.device_params?.cameraSiren}
get cameraFloodLight () { return this.homeKitAccessory.context.device_params?.floodLight}
// from HMS
get hmsHmsID () { return this.homeKitAccessory.context.device_params?.hmsId}
get hmsStatus () {return this.homeKitAccessory.context.device_params?.hmsStatus}
// from thermostat
get thermostatTemperature() { return this.homeKitAccessory.context.device_params?.temperature }
set thermostatTemperature(value){ this.homeKitAccessory.context.device_params.temperature = value }
get thermostatModeSys() { return this.homeKitAccessory.context.device_params?.mode_sys }
set thermostatModeSys(value) { this.homeKitAccessory.context.device_params.mode_sys = value }
get thermostatWorkingState() { return this.homeKitAccessory.context.device_params?.working_state }
set thermostatWorkingState(value) { this.homeKitAccessory.context.device_params.working_state = value }
get thermostatCoolSetpoint() { return this.homeKitAccessory.context.device_params?.cool_sp }
set thermostatCoolSetpoint(value) { this.homeKitAccessory.context.device_params.cool_sp = value}
get thermostatHeatSetpoint() { return this.homeKitAccessory.context.device_params?.heat_sp }
set thermostatHeatSetpoint(value) { this.homeKitAccessory.context.device_params.heat_sp = value }
get thermostatTempUnit() { return this.homeKitAccessory.context.device_params?.temp_unit }
set thermostatTempUnit(value) { this.homeKitAccessory.context.device_params.temp_unit = value }
get thermostatTime2Temp() { return this.homeKitAccessory.context.device_params?.time2temp_val }
set thermostatTime2Temp(value) { this.homeKitAccessory.context.device_params.time2temp_val = value }
get thermostatConnState() { return this.homeKitAccessory.context.conn_state }
/** Determines whether this accessory matches the given Wyze device */
matches (device) {
return this.mac === device.mac
}
async update (device, timestamp) {
const productType = device.product_type
const productModel = device.product_model
switch (productType) {
case "OutdoorPlug":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
switch_state: device.device_params.switch_state,
photosensitive_switch: device.device_params.photosensitive_switch,
ssid : device.device_params.ssid,
ip : device.device_params.ip,
rssi : device.device_params.rssi,
},
}
break
case "Plug":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
switch_state: device.device_params.switch_state,
ssid : device.device_params.ssid,
ip : device.device_params.ip,
rssi : device.device_params.rssi,
},
}
break
case "Common":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
switch_state: device.device_params.switch_state,
connection_state: device.device_params.connection_state,
switch_power: this.switch_power,
iot_state: this.iot_state,
double_press_type: this.double_press_type,
single_press_type: this.single_press_type,
},
}
break
case "Camera":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
device_params: device.device_params = {
ssid : device.device_params.ssid,
ip : device.device_params.ip,
power_switch : device.device_params.power_switch,
notification : this.cameraNotification,
on : this.cameraOn,
available : this.cameraAvailable,
siren : this.cameraSiren,
floodLight : this.cameraFloodLight,
},
}
break
case "LightStrip":
case "Light":
case "MeshLight":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
switch_state : device.device_params.switch_state,
ssid : device.device_params.ssid,
ip : device.device_params.ip,
rssi : device.device_params.rssi,
}
}
break
case "TemperatureHumidity":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
th_sensor_temperature : device.device_params.th_sensor_temperature,
th_sensor_humidity: device.device_params.th_sensor_humidity,
voltage : device.device_params.voltage,
is_low_battery : device.device_params.is_low_battery,
rssi : device.device_params.rssi,
}
}
break
case "MotionSensor":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
motion_state : device.device_params.motion_state,
motion_state_ts: device.device_params.motion_state_ts,
voltage : device.device_params.voltage,
is_low_battery : device.device_params.is_low_battery,
rssi : device.device_params.rssi,
}
}
break
case "LeakSensor":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
ws_detect_state : device.device_params.ws_detect_state,
ws_detect_state_ts: device.device_params.ws_detect_state_ts,
voltage : device.device_params.voltage,
is_low_battery : device.device_params.is_low_battery,
rssi : device.device_params.rssi,
}
}
break
case "ContactSensor":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
open_close_state : device.device_params.open_close_state,
open_close_state_ts: device.device_params.open_close_state_ts,
voltage : device.device_params.voltage,
is_low_battery : device.device_params.is_low_battery,
rssi : device.device_params.rssi,
}
}
break
case "Lock":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
switch_state : device.device_params.switch_state,
open_close_state: device.device_params.open_close_state,
onoff_line: this.lockOnoffLine,
power: this.lockPower,
door_open_status: this.lockDoorOpenStatus,
trash_mode: this.lockTrashMode,
keypad_enable_status: this.lockKeypadEnableStatus,
door_sensor: this.lockDoorSensor,
keypadPower: this.lockKeypadPower,
keypadOnoff_line: this.lockKeypadOnoffLine,
keypadMac: this.lockKeypadMac,
door: this.lockLockerStatusDoor,
hardlock: this.lockLockerStatusHardlock,
}
}
break
case "Thermostat":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
conn_state: device.conn_state,
push_switch: device.push_switch,
device_params: device.device_params = {
temperature: this.thermostatTemperature,
cool_sp: this.thermostatCoolSetpoint,
heat_sp: this.thermostatHeatSetpoint,
working_state: this.thermostatWorkingState,
temp_unit: this.thermostatTempUnit,
mode_sys: this.thermostatModeSys,
time2temp_val: this.thermostatTime2Temp
}
}
break
case "S1Gateway":
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
device_params: device.device_params = {
battery: device.device_params.battery,
indicator_light_switch: device.device_params.indicator_light_switch,
power_source: device.device_params.power_source,
ssid: device.device_params.ssid,
ip: device.device_params.ip,
rssi: device.device_params.rssi,
hmsID: this.hmsHmsID,
hmsStatus: this.hmsStatus
}
}
break
default:
this.homeKitAccessory.context = {
mac: device.mac,
product_type: device.product_type,
product_model: device.product_model,
nickname: device.nickname,
}
break
}
this.homeKitAccessory.getService(Service.AccessoryInformation)
.updateCharacteristic(Characteristic.Name, device.nickname)
.updateCharacteristic(Characteristic.Manufacturer, 'Wyze')
.updateCharacteristic(Characteristic.Model, device.product_model)
.updateCharacteristic(Characteristic.SerialNumber, device.mac)
if (this.shouldUpdateCharacteristics(timestamp)) {
await this.updateCharacteristics(device)
}
}
shouldUpdateCharacteristics (timestamp) {
if (this.updating) {
return false
}
if (this.lastTimestamp && timestamp <= (this.lastTimestamp + UPDATE_THROTTLE_MS)) {
return false
}
return true
}
updateCharacteristics (device) {
//
}
async getPropertyList () {
const response = await this.plugin.client.getPropertyList(this.mac, this.product_model)
return response
}
async getLockInfo () {
const response = await this.plugin.client.getLockInfo(this.mac, this.product_model)
return response
}
async lockGetProperty() {
this.updating = true
const propertyList = await this.plugin.client.getLockInfo(this.mac, this.product_model)
let lockProperties = propertyList.device
if (lockProperties == null) {
this.updating = false
return
}
const prop_key = Object.keys(lockProperties)
for (const element of prop_key) {
const prop = element;
switch (prop) {
case "onoff_line":
this.homeKitAccessory.context.device_params.onoff_line = lockProperties[prop]
continue
case "power":
this.homeKitAccessory.context.device_params.power = lockProperties[prop]
continue
case "door_open_status":
this.homeKitAccessory.context.device_params.door_open_status = lockProperties[prop]
continue
case "trash_mode":
this.homeKitAccessory.context.device_params.trash_mode = lockProperties[prop]
continue
case "keypad_enable_status":
this.homeKitAccessory.context.device_params.keypad_enable_status = lockProperties[prop]
continue
case "door_sensor":
this.homeKitAccessory.context.device_params.door_sensor = lockProperties[prop]
continue
}
}
let lockerStatusProperties = propertyList.device.locker_status
const prop_keyLock = Object.keys(lockerStatusProperties)
for (const element of prop_keyLock) {
const prop = element;
switch (prop){
case "door":
this.homeKitAccessory.context.device_params.door = lockerStatusProperties[prop]
continue
case "hardlock":
this.homeKitAccessory.context.device_params.hardlock = lockerStatusProperties[prop]
continue
}
}
let lockKeypadProperties = propertyList.device.keypad
if (lockKeypadProperties != null) {
const prop_keypad = Object.keys(lockKeypadProperties)
for (const element of prop_keypad) {
const prop = element
switch (prop ) {
case "power":
this.homeKitAccessory.context.device_params.keypadPower = lockKeypadProperties[prop]
continue
case "onoff_line":
this.homeKitAccessory.context.device_params.keypadOnoff_line = lockKeypadProperties[prop]
continue
}
}
let lockKeypadProp = propertyList.device.keypad.hardware_info
const prop_keypadProp = Object.keys(lockKeypadProp)
for (const element of prop_keypadProp) {
const prop = element
switch (prop ) {
case "mac":
this.homeKitAccessory.context.device_params.keypadMac = lockKeypadProp[prop]
continue
}
}
}
this.updating = false
}
// Wall Switch Can we move this to its own class - wallSwitch
async wallSwitchSetIotProp(deviceMac, productModel, prop, value) {
let response
try {
this.updating = true
response = await this.plugin.client.setIotProp(deviceMac, productModel, prop, value)
this.lastTimestamp = response.ts
} finally {
this.updating = false
return response
}
}
async wallSwitchGetIotProp() {
let keys = "iot_state,switch-power,switch-iot,single_press_type, double_press_type, triple_press_type, long_press_type"
let response
try {
this.updating = true
response = await this.plugin.client.getIotProp(this.mac, keys)
let properties = response.data.props
const prop_key = Object.keys(properties);
for (const element of prop_key) {
const prop = element;
if (prop === 'iot_state') {
this.homeKitAccessory.context.device_params.iot_state = properties[prop]
} else if (prop == 'single_press_type') {
this.homeKitAccessory.context.device_params.single_press_type = properties[prop]
} else if (prop == 'double_press_type') {
this.homeKitAccessory.context.device_params.double_press_type = properties[prop]
} else if (prop == 'triple_press_type') {
this.homeKitAccessory.context.device_params.triple_press_type = properties[prop]
} else if (prop == 'long_press_type') {
this.homeKitAccessory.context.device_params.long_press_type = properties[prop]
} else if (prop == 'switch-power'){
this.homeKitAccessory.context.device_params.switch_power = properties[prop]
}
}
this.lastTimestamp = response.ts
} finally {
this.updating = false
return response
}
}
async power_onoff(value) {
const response = await this.wallSwitchSetIotProp(this.mac, this.product_model, 'switch-power', value)
return response
}
async iot_onoff(value) {
const response = await this.wallSwitchSetIotProp(this.mac, this.product_model, 'switch-iot', value)
return response
}
//Camera
async cameraTurnOn() { await this.runActionList(this.mac, this.product_model, "P3", 'power_on')}
async cameraTurnOff() { await this.runActionList(this.mac, this.product_model, "P3", 'power_off')}
async cameraSirenOn() { await this.runActionList(this.mac, this.product_model, "P1049", 'siren_on')}
async cameraSirenOff() { await this.runActionList(this.mac, this.product_model, "P1049", 'siren_off')}
async cameraFloodLightOn() { await this.setProperty(this.mac, this.product_model, "P1049", "1")}
async cameraFloodLightOff() { await this.setProperty(this.mac, this.product_model, "P1049", "2")}
async turnOnNotifications() { await this.setProperty(this.mac, this.product_model, "P1", "1")}
async turnOffNotifications() { await this.setProperty(this.mac, this.product_model, "P1", "0")}
async getCameraPropertyList () {
const cameraProperty = {
NOTIFICATION : "P1",
ON : "P3",
AVAILABLE : "P5",
CAMERA_SIREN : "P1049",
FLOOD_LIGHT : "P1056",
}
const propertyList = await this.getPropertyList(this.mac, this.product_model)
for (const property of propertyList.data.property_list) {
switch (property.pid) {
case cameraProperty.NOTIFICATION:
this.homeKitAccessory.context.device_params.notification = property.value
case cameraProperty.ON:
this.homeKitAccessory.context.device_params.on = property.value
case cameraProperty.AVAILABLE:
this.homeKitAccessory.context.device_params.available = property.value
case cameraProperty.CAMERA_SIREN:
this.homeKitAccessory.context.device_params.cameraSiren = property.value
case cameraProperty.FLOOD_LIGHT:
this.homeKitAccessory.context.device_params.floodLight = property.value
}
}
}
//WyzeLight
async lightTurnOn() { await this.setProperty(this.mac, this.product_model, "P3", "0")}
async lightTurnOff() { await this.setProperty(this.mac, this.product_model, "P3", "1")}
async lightSetBrightness(value) { await this.setProperty(this.mac, this.product_model, "P1501", value)}
async setColorTemperature(value) { await this.setProperty(this.mac, this.product_model, "P1502", value)}
//HMS
async getHmsID() {
const response = await this.plugin.client.getPlanBindingListByUser()
this.homeKitAccessory.context.device_params.hmsId = response.data[0].deviceList[0].device_id
}
async setHMSState(hms_id, mode) {
if(this.plugin.config.logging == "debug") this.plugin.log("HMSID = "+hms_id + " Mode = " + mode)
let responseDisable
let response
if(mode == "off") {
responseDisable = await this.plugin.client.disableRemeAlarm(hms_id)
response = await this.plugin.client.monitoringProfileActive(hms_id, 0, 0)
} else if( mode === "away" ) {
response = await this.plugin.client.monitoringProfileActive(hms_id, 0, 1)
} else if( mode === "home" ) {
response = await this.plugin.client.monitoringProfileActive(hms_id, 1, 0)
}
return response
}
async getHmsUpdate(hms_id) {
const response = await this.plugin.client.monitoringProfileStateStatus(hms_id)
this.homeKitAccessory.context.device_params.hmsStatus = response.message
}
async setProperty (property, value) {
try {
this.updating = true
const response = await this.plugin.client.setProperty(this.mac, this.product_model, property, value)
this.lastTimestamp = response.ts
} finally {
this.updating = false
}
}
async runActionList (property, value) {
try {
this.updating = true
const response = await this.plugin.client.runActionList(this.mac, this.product_model, property, value, 'set_mesh_property')
this.lastTimestamp = response.ts
} finally {
this.updating = false
}
}
async runActionListOnOff (property, value, actionKey) {
try {
this.updating = true
if(this.plugin.config.logging == "debug") this.plugin.log(`Setting runActionList Power State ${this.homeKitAccessory.context.mac} (${this.homeKitAccessory.context.nickname}) to ${value}`)
const response = await this.plugin.client.runActionList(this.mac, this.product_model, property, value, actionKey)
this.lastTimestamp = response.ts
} finally {
this.updating = false
}
}
// Thermostat Methods
async thermostatGetIotProp() {
let keys = "trigger_off_val,emheat,temperature,humidity,time2temp_val,protect_time,mode_sys,heat_sp,cool_sp, current_scenario,config_scenario,temp_unit,fan_mode,iot_state,w_city_id,w_lat,w_lon,working_state, dev_hold,dev_holdtime,asw_hold,app_version,setup_state,wiring_logic_id,save_comfort_balance, kid_lock,calibrate_humidity,calibrate_temperature,fancirc_time,query_schedule"
let response
try {
this.updating = true
response = await this.plugin.client.thermostatGetIotProp(this.mac, keys)
let properties = response.data.props
const prop_key = Object.keys(properties);
for (const element of prop_key) {
const prop = element;
switch (prop) {
case 'temperature':
this.homeKitAccessory.context.device_params.temperature = Math.round(properties[prop])
continue
case 'cool_sp':
this.homeKitAccessory.context.device_params.cool_sp = Math.round(properties[prop])
continue
case 'heat_sp':
this.homeKitAccessory.context.device_params.heat_sp = Math.round(properties[prop])
continue
case 'working_state':
this.homeKitAccessory.context.device_params.working_state = properties[prop]
continue
case 'temp_unit':
this.homeKitAccessory.context.device_params.temp_unit = properties[prop]
continue
case 'mode_sys':
this.homeKitAccessory.context.device_params.mode_sys = properties[prop]
continue
case 'iot_state':
this.homeKitAccessory.context.conn_state = properties[prop]
continue
case 'time2temp_val':
this.homeKitAccessory.context.device_params.thermostatTime2Temp = properties[prop]
continue
}
}
this.lastTimestamp = response.ts
} catch(e) {
this.plugin.log.error("Error in thermostat: " + e)
} finally {
this.updating = false
return response
}
}
async setPreset(value) {
const response = await this.plugin.client.thermostatSetIotProp(this.mac, this.product_model, 'config_scenario', value)
return response
}
// auto, on, off / / ['auto', 'circ', 'on']
async setFanMode(value) {
const response = await this.plugin.client.thermostatSetIotProp(this.mac, this.product_model, 'fan_mode', value)
return response
}
// auto, heat, cool
async setHvacMode(value) {
const response =await this.plugin.client.thermostatSetIotProp(this.mac, this.product_model, 'mode_sys', value)
return response
}
// heat stop point
async setHeatPoint(value) {
const response = await this.plugin.client.thermostatSetIotProp(this.mac, this.product_model, 'heat_sp', value)
return response
}
// Cool stop point
async setCoolPoint(value) {
const response = await this.plugin.client.thermostatSetIotProp(this.mac, this.product_model, 'cool_sp', value)
return response
}
}