UNPKG

homebridge-wyze-smart-home-justoy-forked

Version:

Wyze Smart Home plugin for Homebridge forked by justoy, which supports wyze battery cam pro

434 lines (370 loc) 13.9 kB
const { Service, Characteristic } = require("../types"); const WyzeAccessory = require("./WyzeAccessory"); // want to add a temperature/humidity sensor to display how long it will take for system to complete current job - prop time2temp_val // add temp humidity for real humidity as well // add switch maybe to control fan mode - switch on is fan auto and switch off is fan off // want to add temp sensors for all that are connected to thermostat as well const noResponse = new Error("No Response"); noResponse.toString = () => { return noResponse.message; }; module.exports = class WyzeThermostat extends WyzeAccessory { constructor(plugin, homeKitAccessory) { super(plugin, homeKitAccessory); // Set Defaults this.thermostatTemperature = 69.0; this.thermostatCoolSetpoint = 65.0; this.thermostatHeatSetpoint = 72.0; this.thermostatModeSys = "auto"; this.thermostatWorkingState = "idle"; this.thermostatTempUnit = "F"; this.service = this.getThermostatService(); // GET current heat/cool/off state this.service .getCharacteristic(Characteristic.CurrentHeatingCoolingState) .onGet(this.handleCurrentHeatingCoolingStateGet.bind(this)); // GET current temperature this.service .getCharacteristic(Characteristic.CurrentTemperature) .onGet(this.handleCurrentTemperatureGet.bind(this)); // Target heat/cool/off state this.service .getCharacteristic(Characteristic.TargetHeatingCoolingState) .onGet(this.handleTargetHeatingCoolingStateGet.bind(this)) .onSet(this.handleTargetHeatingCoolingStateSet.bind(this)); // Target temperature handlers - needed given that the system is not in auto this.service .getCharacteristic(Characteristic.TargetTemperature) .onGet(this.handleTargetTemperatureGet.bind(this)) .onSet(this.handleTargetTemperatureSet.bind(this)); // Cooling setpoint handlers - needed for system in Auto this.service .getCharacteristic(Characteristic.CoolingThresholdTemperature) .onGet(this.handleCoolingThresholdTemperatureGet.bind(this)) .onSet(this.handleCoolingThresholdTemperatureSet.bind(this)) // Heating setpoint handlers - needed for system in Auto // Default max value is 25, but we set it higher here for Wyze thermostats // Shoutout to @fennix for the idea here: // https://github.com/tomas-kulhanek/homebridge-fenix-v24-wifi/blob/30a016ea125a5cfd439106472c1afd11f0ad6a2f/src/platformAccessory.ts#L65 this.service .getCharacteristic(Characteristic.HeatingThresholdTemperature) .onGet(this.handleHeatingThresholdTemperatureGet.bind(this)) .onSet(this.handleHeatingThresholdTemperatureSet.bind(this)) .setProps({ maxValue: 35 }); // Target display unit handlers this.service .getCharacteristic(Characteristic.TemperatureDisplayUnits) .onGet(this.handleTemperatureDisplayUnitsGet.bind(this)) .onSet(this.handleTemperatureDisplayUnitsSet.bind(this)); this.updateCharacteristics(); } async handleCurrentTemperatureGet() { this.debugLog("handleCurrentTemperatureGet status of " + this.display_name + " to " + this.thermostatTemperature); return this.f2c(this.thermostatTemperature); } async handleCurrentHeatingCoolingStateGet() { return this.Wyze2HomekitWorkingStates[this.thermostatWorkingState]; } async handleTargetHeatingCoolingStateGet() { return this.Wyze2HomekitStates[this.thermostatModeSys]; } async handleTargetTemperatureGet() { this.debugLog("handleTargetTemperatureGet Target Temp: " + this.c2f(this.getTargetTemperatureForSystemState())); return this.getTargetTemperatureForSystemState(); } async handleCoolingThresholdTemperatureGet() { this.debugLog("handleCoolingThresholdTemperatureGet Cool Setpoint: " + this.thermostatCoolSetpoint); return this.f2c(this.thermostatCoolSetpoint); } async handleHeatingThresholdTemperatureGet() { this.debugLog("handleHeatingThresholdTemperatureGet Heat Setpoint: " + this.thermostatHeatSetpoint); return this.f2c(this.thermostatHeatSetpoint); } async handleTemperatureDisplayUnitsGet() { return this.Wyze2HomekitUnits[this.thermostatTempUnit]; } // SET Methods for taking data from homekit and applying it to Wyze thermostat async handleTargetHeatingCoolingStateSet(value) { let targetState = this.getKey(this.Wyze2HomekitStates, value); this.debugLog("handleTargetHeatingCoolingStateSet status of " + this.display_name + " to " + targetState); this.setHvacMode(targetState); this.service .getCharacteristic(Characteristic.TargetHeatingCoolingState) .updateValue(value); this.thermostatModeSys = targetState; this.service .getCharacteristic(Characteristic.TargetTemperature) .updateValue(this.getTargetTemperatureForSystemState()); } async handleTargetTemperatureSet(value) { let targetTemp = this.c2f(value); let currentStateNumber = this.Wyze2HomekitStates[this.thermostatModeSys]; this.debugLog("handleTargetTemperatureSet status of " + this.display_name + " to " + targetTemp + " for mode " + this.thermostatModeSys + " which is " + currentStateNumber); // switch on current heating cooling state since we are NOT in auto mode switch (currentStateNumber) { case this.Wyze2HomekitStates.auto: this.debugLog("handleTargetTemperatureSet cannot set value since system is in AUTO"); break; case this.Wyze2HomekitStates.cool: this.debugLog("handleTargetTemperatureSet for COOLING"); this.handleCoolingThresholdTemperatureSet(value); break; case this.Wyze2HomekitStates.heat: this.debugLog("handleTargetTemperatureSet for HEATING"); this.handleHeatingThresholdTemperatureSet(value); break; case this.Wyze2HomekitStates.off: this.debugLog("handleTargetTemperatureSet cannot set value since system is OFF"); break; default: this.debugLog("handleTargetTemperatureSet cannot set value since system mode is UNDEFINED"); break; } this.service .getCharacteristic(Characteristic.TargetTemperature) .updateValue(value); } async handleCoolingThresholdTemperatureSet(value) { let c = this.clamp(value, 10, 35); let val = Math.round(this.c2f(c)); this.debugLog("handleCoolingThresholdTemperatureSet status of " + this.display_name + " to " + val); this.setCoolPoint(val); this.thermostatCoolSetpoint = val; this.service .getCharacteristic(Characteristic.CoolingThresholdTemperature) .updateValue(c); } async handleHeatingThresholdTemperatureSet(value) { let c = this.clamp(value, 0, 25); let val = Math.round(this.c2f(c)); this.debugLog("handleHeatingThresholdTemperatureSet status of " + this.display_name + " to " + val); this.setHeatPoint(val); this.thermostatHeatSetpoint = val; this.service .getCharacteristic(Characteristic.HeatingThresholdTemperature) .updateValue(c); } async handleTemperatureDisplayUnitsSet(value) { this.debugLog("handleTemperatureDisplayUnitsSet status of " + this.display_name + " to " + value); this.service .getCharacteristic(Characteristic.TemperatureDisplayUnits) .updateValue(value); } // this is where we do the magic async updateCharacteristics(device) { this.debugLog("Updating status of " + this.display_name); // have to wait for this call to finish before updating or we might get null data this.thermostatGetIotProp().then(this.fillData.bind(this)); } fillData() { // This just prints new data from Wyze API this.debugLog("Temp: " + this.thermostatTemperature); this.debugLog("Target Temp: " + this.c2f(this.getTargetTemperatureForSystemState())); this.debugLog("Mode Sys: " + this.thermostatModeSys + " for " + this.Wyze2HomekitStates[this.thermostatModeSys]); this.debugLog("Working State: " + this.thermostatWorkingState + " for " + this.Wyze2HomekitWorkingStates[this.thermostatWorkingState]); this.debugLog("Cool Setpoint: " + this.thermostatCoolSetpoint); this.debugLog("Heat Setpoint: " + this.thermostatHeatSetpoint); this.debugLog("Temp Units: " + this.Wyze2HomekitUnits[this.thermostatTempUnit]); } getTargetTemperatureForSystemState() { // relies on temperature setpoints to tell Homekit what the system is currently doing // Wyze seems to not actually send the "working state" to user at the moment so we have to decide what the system is doing manually let s = this.Wyze2HomekitStates[this.thermostatModeSys]; if (s == this.Wyze2HomekitStates.cool) { return this.f2c(this.thermostatCoolSetpoint); } else if (s == this.Wyze2HomekitStates.heat) { return this.f2c(this.thermostatHeatSetpoint); } else if (s == this.Wyze2HomekitStates.auto) { if (this.thermostatCoolSetpoint < this.thermostatTemperature) { return this.f2c(this.thermostatCoolSetpoint); } else if (this.thermostatHeatSetpoint > this.thermostatTemperature) { return this.f2c(this.thermostatHeatSetpoint); } else { return this.f2c(this.thermostatTemperature); } } else { return this.f2c(this.thermostatTemperature); } } getThermostatService() { this.debugLog("Retrieving previous service for " + this.display_name); let service = this.homeKitAccessory.getService(Service.Thermostat); if (!service) { this.debugLog("Adding service for " + this.display_name); service = this.homeKitAccessory.addService(Service.Thermostat); } return service; } // Wyze API Calls to GET info // Thermostat Methods async thermostatGetIotProp() { let response; try { response = await this.plugin.client.thermostatGetIotProp(this.mac); 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.thermostatTemperature = Math.round(properties[prop]); continue; case "cool_sp": this.thermostatCoolSetpoint = Math.round(properties[prop]); continue; case "heat_sp": this.thermostatHeatSetpoint = Math.round(properties[prop]); continue; case "working_state": this.thermostatWorkingState = properties[prop]; continue; case "temp_unit": this.thermostatTempUnit = properties[prop]; continue; case "mode_sys": this.thermostatModeSys = properties[prop]; continue; // can check for "iot_state" and "time2temp_val" in future if needed } } this.lastTimestamp = response.ts; } catch (e) { this.plugin.log.error("Error in thermostat: " + e); } finally { return response; } } // IOT API Calls 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; } // Helper Functions debugLog(message) { if (this.plugin.config.logLevel == "debug" && this.plugin.config.pluginLoggingEnabled) this.plugin.log.info( this.colors.CYAN + "[Thermostat] " + message + this.colors.RESET ); } f2c(fahrenheit) { let out = (fahrenheit - 32.0) / 1.8; return out; } c2f(celsius) { return celsius * 1.8 + 32.0; } getKey(object, value) { return Object.keys(object)[value]; } Wyze2HomekitUnits = { C: 0, F: 1, }; Wyze2HomekitStates = { off: 0, heat: 1, cool: 2, auto: 3, }; Wyze2HomekitWorkingStates = { idle: 0, heating: 1, cooling: 2, }; HomekitTargetTempValues = { max: 38, min: 10, step: 0.1 } HomekitHeatingThresholdValues = { max: 25, min: 0, step: 0.1 } HomekitCoolingThresholdValues = { max: 35, min: 10, step: 0.1 } clamp(number, min, max) { if (number < min) { if (this.plugin.config.pluginLoggingEnabled) this.plugin.log( `[Thermostat] Clamping value: ${number} to min ${min}` ); } if (number > max) { if (this.plugin.config.pluginLoggingEnabled) this.plugin.log( `[Thermostat] Clamping value: ${number} to max ${max}` ); } return Math.max(min, Math.min(number, max)); } colors = { RESET: '\x1b[0m', BRIGHT: '\x1b[1m', DIM: '\x1b[2m', UNDERSCORE: '\x1b[4m', BLINK: '\x1b[5m', REVERSE: '\x1b[7m', HIDDEN: '\x1b[8m', BLACK: '\x1b[30m', RED: '\x1b[31m', GREEN: '\x1b[32m', YELLOW: '\x1b[33m', BLUE: '\x1b[34m', MAGENTA: '\x1b[35m', CYAN: '\x1b[36m', LIGHT_GREY: '\x1b[37m', GREY: '\x1b[90m', WHITE: '\x1b[97m' }; };