UNPKG

homebridge-tasmota-control

Version:
550 lines (500 loc) 31.6 kB
import { promises as fsPromises } from 'fs'; import axios from 'axios'; import EventEmitter from 'events'; import ImpulseGenerator from './impulsegenerator.js'; import { ApiCommands } from './constants.js'; let Accessory, Characteristic, Service, Categories, AccessoryUUID; class Fans extends EventEmitter { constructor(api, config, info, serialNumber, refreshInterval) { super(); Accessory = api.platformAccessory; Characteristic = api.hap.Characteristic; Service = api.hap.Service; Categories = api.hap.Categories; AccessoryUUID = api.hap.uuid; //info this.info = info; this.serialNumber = serialNumber; this.relaysCount = info.friendlyNames.length; //other config this.lightsNamePrefix = config.lightsNamePrefix || false; this.fansNamePrefix = config.fansNamePrefix || false; this.enableDebugMode = config.enableDebugMode || false; this.disableLogInfo = config.disableLogInfo || false; this.disableLogDeviceInfo = config.disableLogDeviceInfo || false; this.refreshInterval = refreshInterval; //variable this.startPrepareAccessory = true; //axios instance const url = `http://${config.host}/cm?cmnd=`; this.axiosInstance = axios.create({ method: 'GET', baseURL: url, timeout: refreshInterval > 10000 ? 10000 : refreshInterval, withCredentials: config.auth, auth: { username: config.user, password: config.passwd } }); //impulse generator this.impulseGenerator = new ImpulseGenerator(); this.impulseGenerator.on('checkDeviceState', async () => { try { await this.checkDeviceState(); } catch (error) { this.emit('error', `Impulse generator error: ${error}`); } }).on('state', (state) => { const emitState = state ? this.emit('success', `Impulse generator started`) : this.emit('warn', `Impulse generator stopped`); }); } async checkDeviceState() { const debug = this.enableDebugMode ? this.emit('debug', `Requesting status`) : false; try { //power status const powerStatusData = await this.axiosInstance(ApiCommands.PowerStatus); const powerStatus = powerStatusData.data ?? {}; const powerStatusKeys = Object.keys(powerStatus); const debug = this.enableDebugMode ? this.emit('debug', `Power status: ${JSON.stringify(powerStatus, null, 2)}`) : false; //sensor status const sensorStatusData = await this.axiosInstance(ApiCommands.Status); const sensorStatus = sensorStatusData.data ?? {}; const debug1 = this.enableDebugMode ? this.emit('debug', `Sensors status: ${JSON.stringify(sensorStatus, null, 2)}`) : false; //sensor status keys const sensorStatusKeys = Object.keys(sensorStatus); //status STS const statusStsSupported = sensorStatusKeys.includes('StatusSTS'); const statusSts = statusStsSupported ? sensorStatus.StatusSTS : {}; //relays const relaysCount = this.relaysCount; if (relaysCount > 0) { this.lights = []; this.fans = []; //iFan02/iFan03 only - 0 = turn fan OFF, 1..3 = set fan speed, + = increase fan speed, - = decrease fan speed for (let i = 0; i < relaysCount; i++) { const friendlyName = this.info.friendlyNames[i]; const powerNr = i + 1; const power1 = powerStatusKeys.includes('POWER1'); const powerKey = relaysCount === 1 ? (power1 ? 'POWER1' : 'POWER') : `POWER${powerNr}`; const power = powerStatus[powerKey] === 'ON'; //light const light = { friendlyName: friendlyName, power: power, power1: power1 }; this.lights.push(light); //update characteristics const lightService = this.lightServices?.[i]; if (lightService) { const serviceName = this.lightsNamePrefix ? `${this.info.deviceName} ${friendlyName}` : friendlyName; lightService.updateCharacteristic(Characteristic.ConfiguredName, serviceName) .updateCharacteristic(Characteristic.On, power); } //fan const powerFan = statusSts.FanSpeed > 0; const direction = statusSts.FanDirection ?? 0; const speed = statusSts.FanSpeed; const fan = { friendlyName: friendlyName, power: powerFan, direction: direction, speed: speed, power1: power1 }; this.fans.push(fan); //update characteristics const fanService = this.fanServices?.[i]; if (fanService) { const serviceName = this.fansNamePrefix ? `${this.info.deviceName} ${friendlyName}` : friendlyName; fanService.updateCharacteristic(Characteristic.ConfiguredName, serviceName) .updateCharacteristic(Characteristic.On, powerFan) // .updateCharacteristic(Characteristic.Direction, direction) .updateCharacteristic(Characteristic.RotationSpeed, speed); } //log info if (!this.disableLogInfo) { this.emit('info', `${friendlyName}, light: ${power ? 'ON' : 'OFF'}`); this.emit('info', `${friendlyName}, fan: ${powerFan ? 'ON' : 'OFF'}`); //this.emit('info', `${friendlyName}, direction: ${direction}`); this.emit('info', `${friendlyName}, fan speed: ${speed}`); } } } return true; } catch (error) { throw new Error(`Check state error: ${error}`); } } async saveData(path, data) { try { data = JSON.stringify(data, null, 2); await fsPromises.writeFile(path, data); const debug = !this.enableDebugMode ? false : this.emit('debug', `Saved data: ${data}`); return true; } catch (error) { throw new Error(`Save data error: ${error}`); } } async readData(path) { try { const data = await fsPromises.readFile(path); return data; } catch (error) { throw new Error(`Read data error: ${error}`); } } async startImpulseGenerator() { try { //start impulse generator const timers = [{ name: 'checkDeviceState', sampling: this.refreshInterval }]; await this.impulseGenerator.start(timers); return true; } catch (error) { throw new Error(`Impulse generator start error: ${error}`); } } async deviceInfo() { this.emit('devInfo', `----- ${this.info.deviceName} -----`); this.emit('devInfo', `Manufacturer: Tasmota`); this.emit('devInfo', `Hardware: ${this.info.modelName}`); this.emit('devInfo', `Serialnr: ${this.serialNumber}`) this.emit('devInfo', `Firmware: ${this.info.firmwareRevision}`); this.emit('devInfo', `Relays: ${this.relaysCount}`); this.emit('devInfo', `----------------------------------`); return; } //prepare accessory async prepareAccessory() { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Accessory`) : false; try { //accessory const accessoryName = this.info.deviceName; const accessoryUUID = AccessoryUUID.generate(this.serialNumber); const accessoryCategory = Categories.FAN; const accessory = new Accessory(accessoryName, accessoryUUID, accessoryCategory); //Prepare information service const debug1 = this.enableDebugMode ? this.emit('debug', `Prepare Information Service`) : false; accessory.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, 'Tasmota') .setCharacteristic(Characteristic.Model, this.info.modelName ?? 'Model Name') .setCharacteristic(Characteristic.SerialNumber, this.serialNumber ?? 'Serial Number') .setCharacteristic(Characteristic.FirmwareRevision, this.info.firmwareRevision.replace(/[a-zA-Z]/g, '') ?? '0') .setCharacteristic(Characteristic.ConfiguredName, accessoryName); //Prepare services const debug2 = this.enableDebugMode ? this.emit('debug', `Prepare Services`) : false; if (this.fans.length > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Fan Services`) : false; this.fanServices = []; for (let i = 0; i < this.fans.length; i++) { const friendlyName = this.fans[i].friendlyName; const serviceName = this.fansNamePrefix ? `${accessoryName} ${friendlyName}` : friendlyName; const fanService = accessory.addService(Service.Fan, serviceName, `Fan ${i}`) fanService.setPrimaryService(true); fanService.addOptionalCharacteristic(Characteristic.ConfiguredName); fanService.setCharacteristic(Characteristic.ConfiguredName, serviceName); fanService.getCharacteristic(Characteristic.On) .onGet(async () => { const state = this.fans[i].power; return state; }) .onSet(async (state) => { try { state = state ? 1 : 0; const speed = `${ApiCommands.FanSpeed}${state}`; await this.axiosInstance(speed); const logInfo = this.disableLogInfo ? false : this.emit('info', `${friendlyName}, set state: ${state ? 'ON' : 'OFF'}`); } catch (error) { this.emit('warn', `${friendlyName}, set state error: ${error}`); } }); //fanService.getCharacteristic(Characteristic.RotationDirection) // .onGet(async () => { // const value = this.fans[i].direction; // return value; // }) // .onSet(async (value) => { // try { // const direction = `${ApiCommands.FanDirection}${value}`; // await this.axiosInstance(direction); // const logInfo = this.disableLogInfo ? false : this.emit('info', `${friendlyName}, set direction: ${value}`); // } catch (error) { // this.emit('warn', `${friendlyName}, set direction error: ${error}`); // } // }); fanService.getCharacteristic(Characteristic.RotationSpeed) .setProps({ minValue: 0, maxValue: 3, minStep: 1 }) .onGet(async () => { const value = this.fans[i].speed; return value; }) .onSet(async (value) => { try { const speed = `${ApiCommands.FanSpeed}${value}`; await this.axiosInstance(speed); const logInfo = this.disableLogInfo ? false : this.emit('info', `${friendlyName}, set speed: ${value}`); } catch (error) { this.emit('warn', `${friendlyName}, set rotation speed error: ${error}`); } }); this.fanServices.push(fanService); } } if (this.lights.length > 0) { this.lightServices = []; for (let i = 0; i < this.lights.length; i++) { const friendlyName = this.lights[i].friendlyName; const serviceName = this.lightsNamePrefix ? `${accessoryName} ${friendlyName}` : friendlyName; const lightService = accessory.addService(Service.Lightbulb, serviceName, `Light ${i}`); lightService.addOptionalCharacteristic(Characteristic.ConfiguredName); lightService.setCharacteristic(Characteristic.ConfiguredName, serviceName); lightService.getCharacteristic(Characteristic.On) .onGet(async () => { const state = this.lights[i].power; return state; }) .onSet(async (state) => { try { const relayNr = i + 1; const powerOn = this.lights.length === 1 ? (this.lights[i].power1 ? `${ApiCommands.Power}${relayNr}${ApiCommands.On}` : ApiCommands.PowerOn) : `${ApiCommands.Power}${relayNr}${ApiCommands.On}`; const powerOff = this.lights.length === 1 ? (this.lights[i].power1 ? `${ApiCommands.Power}${relayNr}${ApiCommands.Off}` : ApiCommands.PowerOff) : `${ApiCommands.Power}${relayNr}${ApiCommands.Off}`; state = state ? powerOn : powerOff; await this.axiosInstance(state); const logInfo = this.disableLogInfo ? false : this.emit('info', `${friendlyName}, set state: ${state ? 'ON' : 'OFF'}`); } catch (error) { this.emit('warn', `${friendlyName}, set state error: ${error}`); } }); this.lightServices.push(lightService); } } //sensors if (this.sensorsCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Sensor Services`) : false; //temperature const sensorsTemperatureCount = this.sensorsTemperatureCount; if (sensorsTemperatureCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Temperature Sensor Services`) : false; this.sensorTemperatureServices = []; for (let i = 0; i < sensorsTemperatureCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Temperature` : `${sensorName} Temperature`; const sensorTemperatureService = accessory.addService(Service.TemperatureSensor, serviceName, `Temperature Sensor ${i}`); sensorTemperatureService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorTemperatureService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorTemperatureService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.sensorsTemperature[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} temperature: ${value} °${this.tempUnit}`); return value; }); this.sensorTemperatureServices.push(sensorTemperatureService); } } //reference temperature const sensorsReferenceTemperatureCount = this.sensorsReferenceTemperatureCount; if (sensorsReferenceTemperatureCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Reference Temperature Sensor Services`) : false; this.sensorReferenceTemperatureServices = []; for (let i = 0; i < sensorsReferenceTemperatureCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Reference Temperature` : `${sensorName} Reference Temperature`; const sensorReferenceTemperatureService = accessory.addService(Service.TemperatureSensor, serviceName, `Reference Temperature Sensor ${i}`); sensorReferenceTemperatureService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorReferenceTemperatureService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorReferenceTemperatureService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.sensorsReferenceTemperature[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} reference temperature: ${value} °${this.tempUnit}`); return value; }); this.sensorReferenceTemperatureServices.push(sensorReferenceTemperatureService); } } //object temperature const sensorsObjTemperatureCount = this.sensorsObjTemperatureCount; if (sensorsObjTemperatureCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Obj Temperature Sensor Services`) : false; this.sensorObjTemperatureServices = []; for (let i = 0; i < sensorsObjTemperatureCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Obj Temperature` : `${sensorName} Obj Temperature`; const sensorObjTemperatureService = accessory.addService(Service.TemperatureSensor, serviceName, `Obj Temperature Sensor ${i}`); sensorObjTemperatureService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorObjTemperatureService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorObjTemperatureService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.sensorsObjTemperature[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} obj temperature: ${value} °${this.tempUnit}`); return value; }); this.sensorObjTemperatureServices.push(sensorObjTemperatureService); } } //ambient temperature const sensorsAmbTemperatureCount = this.sensorsAmbTemperatureCount; if (sensorsAmbTemperatureCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Amb Temperature Sensor Services`) : false; this.sensorAmbTemperatureServices = []; for (let i = 0; i < sensorsAmbTemperatureCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Amb Temperature` : `${sensorName} Amb Temperature`; const sensorAmbTemperatureService = accessory.addService(Service.TemperatureSensor, serviceName, `Amb Temperature Sensor ${i}`); sensorAmbTemperatureService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorAmbTemperatureService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorAmbTemperatureService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.sensorsAmbTemperature[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} amb temperature: ${value} °${this.tempUnit}`); return value; }); this.sensorAmbTemperatureServices.push(sensorAmbTemperatureService); } } //dew point temperature const sensorsDewPointTemperatureCount = this.sensorsDewPointTemperatureCount; if (sensorsDewPointTemperatureCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Dew Point Temperature Sensor Services`) : false; this.sensorDewPointTemperatureServices = []; for (let i = 0; i < sensorsDewPointTemperatureCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Dew Point` : `${sensorName} Dew Point`; const sensorDewPointTemperatureService = accessory.addService(Service.TemperatureSensor, serviceName, `Dew Point Temperature Sensor ${i}`); sensorDewPointTemperatureService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorDewPointTemperatureService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorDewPointTemperatureService.getCharacteristic(Characteristic.CurrentTemperature) .onGet(async () => { const value = this.sensorsDewPointTemperature[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} dew point: ${value} °${this.tempUnit}`); return value; }); this.sensorDewPointTemperatureServices.push(sensorDewPointTemperatureService); } } //humidity const sensorsHumidityCount = this.sensorsHumidityCount; if (sensorsHumidityCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Humidity Sensor Services`) : false; this.sensorHumidityServices = []; for (let i = 0; i < sensorsHumidityCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Humidity` : `${sensorName} Humidity`; const sensorHumidityService = accessory.addService(Service.HumiditySensor, serviceName, `Humidity Sensor ${i}`); sensorHumidityService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorHumidityService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorHumidityService.getCharacteristic(Characteristic.CurrentRelativeHumidity) .onGet(async () => { const value = this.sensorsHumidity[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} humidity: ${value} %`); return value; }); this.sensorHumidityServices.push(sensorHumidityService); } } //pressure //gas //carbon dioxyde const sensorsCarbonDioxydeCount = this.sensorsCarbonDioxydeCount; if (sensorsCarbonDioxydeCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Carbon Dioxyde Sensor Services`) : false; this.sensorCarbonDioxydeServices = []; for (let i = 0; i < sensorsCarbonDioxydeCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Carbon Dioxyde` : `${sensorName} Carbon Dioxyde`; const sensorCarbonDioxydeService = accessory.addService(Service.CarbonDioxideSensor, serviceName, `Carbon Dioxyde Sensor ${i}`); sensorCarbonDioxydeService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorCarbonDioxydeService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorCarbonDioxydeService.getCharacteristic(Characteristic.CarbonDioxideDetected) .onGet(async () => { const state = this.sensorsCarbonDioxyde[i] > 1000; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} carbon dioxyde detected: ${state ? 'Yes' : 'No'}`); return state; }); sensorCarbonDioxydeService.getCharacteristic(Characteristic.CarbonDioxideLevel) .onGet(async () => { const value = this.sensorsCarbonDioxyde[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} carbon dioxyde level: ${value} ppm`); return value; }); sensorCarbonDioxydeService.getCharacteristic(Characteristic.CarbonDioxidePeakLevel) .onGet(async () => { const value = this.sensorsCarbonDioxyde[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} carbon dioxyde peak level: ${value} ppm`); return value; }); this.sensorCarbonDioxydeServices.push(sensorCarbonDioxydeService); } } //ambient light const sensorsAmbientLightCount = this.sensorsAmbientLightCount; if (sensorsAmbientLightCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Ambient Light Sensor Services`) : false; this.sensorAmbientLightServices = []; for (let i = 0; i < sensorsAmbientLightCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Ambient Light` : `${sensorName} Ambient Light`; const sensorAmbientLightService = accessory.addService(Service.LightSensor, serviceName, `Ambient Light Sensor ${i}`); sensorAmbientLightService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorAmbientLightService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorAmbientLightService.getCharacteristic(Characteristic.CurrentAmbientLightLevel) .onGet(async () => { const value = this.sensorsAmbientLight[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} ambient light: ${value} lx`); return value; }); this.sensorAmbientLightServices.push(sensorAmbientLightService); } } //motion const sensorsMotionCount = this.sensorsMotionCount; if (sensorsMotionCount > 0) { const debug = this.enableDebugMode ? this.emit('debug', `Prepare Motion Sensor Services`) : false; this.sensorMotionServices = []; for (let i = 0; i < sensorsMotionCount; i++) { const sensorName = this.sensorsName[i]; const serviceName = this.sensorsNamePrefix ? `${accessoryName} ${sensorName} Motion` : `${sensorName} Motion`; const sensorMotionService = accessory.addService(Service.MotionSensor, serviceName, `Motion Sensor ${i}`); sensorMotionService.addOptionalCharacteristic(Characteristic.ConfiguredName); sensorMotionService.setCharacteristic(Characteristic.ConfiguredName, serviceName); sensorMotionService.getCharacteristic(Characteristic.MotionDetected) .onGet(async () => { const state = this.sensorsMotion[i]; const logInfo = this.disableLogInfo ? false : this.emit('info', `sensor: ${sensorName} motion: ${state ? 'ON' : 'OFF'}`); return state; }); this.sensorMotionServices.push(sensorMotionService); } } } return accessory; } catch (error) { throw new Error(`Prepare accessory error: ${error}`) } } //start async start() { try { //check device state await this.checkDeviceState(); //connect to deice success this.emit('success', `Connect Success`) //check device info const devInfo = !this.disableLogDeviceInfo ? await this.deviceInfo() : false; //start prepare accessory if (this.startPrepareAccessory) { const accessory = await this.prepareAccessory(); const publishAccessory = this.emit('publishAccessory', accessory); this.startPrepareAccessory = false; } return true; } catch (error) { throw new Error(`Start error: ${error}`); } } } export default Fans;