UNPKG

node-red-contrib-homekit-abc

Version:

HAP-Nodejs based Node-RED nodes to create HomeKit Accessories

334 lines (290 loc) 16.5 kB
const mqtt = require("mqtt"); const path = require('path'); const net = require('net'); const hap = require('hap-nodejs'); const storage = require('node-persist'); const {uuid, Accessory, Service, Characteristic} = hap; const pkg = require('../package.json'); var ping = require("ping"); const accessories = {}; module.exports = function (RED) { //hap.init(path.join(RED.settings.userDir, 'homekit')); RED.httpAdmin.get('/hassbian-abc-homekit-mqttAC', (req, res) => { if (req.query.config) { const acc = accessories[req.query.config]; if (acc) { res.status(200).send(JSON.stringify({setupURI: acc.setupURI()})); } else { res.status(500).send(JSON.stringify({})); } } else { res.status(404).send(JSON.stringify({})); } }); class hassbianHomeKitirac { constructor(config) { RED.nodes.createNode(this, config); const that = this; var mqttHost = config.mqtthost; var mqttUsername = config.mqttname; var mqttPassword = config.mqttpassword; var mqttOptions = { clientId: 'mqttjs_' + Math.random().toString(16).substr(2, 8), username: mqttUsername, password: mqttPassword }; this.mode = Characteristic.CurrentHeaterCoolerState.INACTIVE; this.modestate = Characteristic.TargetHeaterCoolerState.AUTO; this.maxTemperature = 18; this.Temperature = 26; this.fanspeed = 100; this.mqtttemp = config.mqttTemp; var mqttPrefix = config.prefix; this.mqttTopic = { "power": mqttPrefix + "/ac/cmnd/power", "mode": mqttPrefix + "/ac/cmnd/mode", "temp": mqttPrefix + "/ac/cmnd/temp", "fanspeed": mqttPrefix + "/ac/cmnd/fanspeed", "swingv": mqttPrefix + "/ac/cmnd/swingv", "light": mqttPrefix + "/ac/cmnd/light", "powerstat": mqttPrefix + "/ac/stat/power", "modestat": mqttPrefix + "/ac/stat/mode", "tempstat": mqttPrefix + "/ac/stat/temp", "fanspeedstat": mqttPrefix + "/ac/stat/fanspeed", "swinghstat": mqttPrefix + "/ac/stat/swingv", "lightstat": mqttPrefix + "/ac/stat/light" }; try { this.mqttClient = mqtt.connect(mqttHost, mqttOptions); that.mqttClient.subscribe(that.mqttTopic['powerstat']); that.mqttClient.subscribe(that.mqttTopic['modestat']); that.mqttClient.subscribe(that.mqttTopic['tempstat']); that.mqttClient.subscribe(that.mqttTopic['fanspeedstat']); that.mqttClient.subscribe(that.mqttTopic['swinghstat']); that.mqttClient.subscribe(that.mqttTopic['lightstat']); that.mqttClient.subscribe(this.mqtttemp); } catch(e) { } function logger(...args) { let str = args.join(' '); if (str.match(/^(error: )/i)) { str = str.replace(/^error: (.*)/i, '$1'); that.error(str); } else if (config.debug) { that.debug(str); } } if (accessories[this.id]) { this.debug('mqttAC already configured ' + this.name + ' ' + this.id + ' ' + this.username); return; } const [firstLogger] = Object.keys(RED.settings.logging); config.debug = config.debug && (RED.settings.logging[firstLogger].level === 'debug' || RED.settings.logging[firstLogger].level === 'trace'); this.name = config.name || ('mqttAC ' + this.id); storage.initSync(); const acc = new Accessory(this.name, uuid.generate(config.id), Accessory.Categories.THERMOSTAT); accessories[this.id] = acc; this.debug('mqttAC created' + this.name + ' ' + this.id + ' ' + config.username); acc.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, 'hassbian-abc') .setCharacteristic(Characteristic.Model, 'mqttAC') .setCharacteristic(Characteristic.SerialNumber, config.username) .setCharacteristic(Characteristic.FirmwareRevision, pkg.version); acc.on('identify', (paired, callback) => { this.log('identify ' + this.id + ' ' + this.username + ' ' + paired); callback(); }); var heaterCoolerService = acc.addService(Service.HeaterCooler, this.name, "HeaterCooler"); var activeCharacteristic = heaterCoolerService.getCharacteristic(Characteristic.Active); var currentHeaterCoolerStateCharacteristic = heaterCoolerService.getCharacteristic(Characteristic.CurrentHeaterCoolerState); var targetHeaterCoolerStateCharacteristic = heaterCoolerService.getCharacteristic(Characteristic.TargetHeaterCoolerState); var currentTemperatureCharacteristic = heaterCoolerService.getCharacteristic(Characteristic.CurrentTemperature); var swingModeCharacteristic = heaterCoolerService.addCharacteristic(Characteristic.SwingMode); var coolingThresholdTemperatureCharacteristic = heaterCoolerService.getCharacteristic(Characteristic.CoolingThresholdTemperature); var heatingThresholdTemperatureCharacteristic = heaterCoolerService.addCharacteristic(Characteristic.HeatingThresholdTemperature); var rotationSpeedCharacteristic = heaterCoolerService.addCharacteristic(Characteristic.RotationSpeed); rotationSpeedCharacteristic.setProps({ minValue: 0, maxValue: 100, minStep: 25, }); coolingThresholdTemperatureCharacteristic.setProps({ minValue: 17, maxValue: 30, minStep: 1, }); heatingThresholdTemperatureCharacteristic.setProps({ minValue: 17, maxValue: 30, minStep: 1, }); activeCharacteristic .on('set', (value, callback) => { if (value == Characteristic.Active.INACTIVE) { that.mqttClient.publish(that.mqttTopic['power'], "off"); that.mqttClient.publish(that.mqttTopic['swingv'], "off"); currentHeaterCoolerStateCharacteristic.updateValue(Characteristic.CurrentHeaterCoolerState.INACTIVE); that.lastControlFlag = (new Date()).getTime(); callback(null); } else { that.mqttClient.publish(that.mqttTopic['power'], "on"); if (this.mode = Characteristic.CurrentHeaterCoolerState.INACTIVE) { targetHeaterCoolerStateCharacteristic.setValue(this.modestate); } if (targetHeaterCoolerStateCharacteristic.value === Characteristic.TargetHeaterCoolerState.AUTO) { heatingThresholdTemperatureCharacteristic.updateValue(this.maxTemperature); coolingThresholdTemperatureCharacteristic.updateValue(this.Temperature); } else if (targetHeaterCoolerStateCharacteristic.value !== Characteristic.TargetHeaterCoolerState.AUTO) { coolingThresholdTemperatureCharacteristic.updateValue(this.Temperature); heatingThresholdTemperatureCharacteristic.updateValue(this.Temperature); } that.lastControlFlag = (new Date()).getTime(); callback(null); } }); targetHeaterCoolerStateCharacteristic .on('set', (value, callback) => { if (value === Characteristic.TargetHeaterCoolerState.AUTO) { that.mqttClient.publish(that.mqttTopic['mode'], "auto"); } else if (value === Characteristic.TargetHeaterCoolerState.HEAT) { that.mqttClient.publish(that.mqttTopic['mode'], "heat"); currentHeaterCoolerStateCharacteristic.updateValue(Characteristic.CurrentHeaterCoolerState.HEATING); } else if (value === Characteristic.TargetHeaterCoolerState.COOL) { that.mqttClient.publish(that.mqttTopic['mode'], "cool"); currentHeaterCoolerStateCharacteristic.updateValue(Characteristic.CurrentHeaterCoolerState.COOLING); } that.lastControlFlag = (new Date()).getTime(); callback(null); }); swingModeCharacteristic .on('set', (value, callback) => { var valueStr = value ? "auto" : "off"; that.mqttClient.publish(that.mqttTopic['swingv'], valueStr); that.lastControlFlag = (new Date()).getTime(); callback(null); }); coolingThresholdTemperatureCharacteristic .on('set', (value, callback) => { this.Temperature = value; var valueStr = value.toString(); that.mqttClient.publish(that.mqttTopic['temp'], valueStr); that.lastControlFlag = (new Date()).getTime(); callback(null); }); heatingThresholdTemperatureCharacteristic .on('set', (value, callback) => { if (targetHeaterCoolerStateCharacteristic.value === Characteristic.TargetHeaterCoolerState.AUTO) { this.maxTemperature = value; } else { this.Temperature = value; var valueStr = value.toString(); that.mqttClient.publish(that.mqttTopic['temp'], valueStr); } that.lastControlFlag = (new Date()).getTime(); callback(null); }); rotationSpeedCharacteristic .on('set', (value, callback) => { this.fanspeed = value; if (value == 25) { that.mqttClient.publish(that.mqttTopic['fanspeed'], "min"); } else if (value == 50) { that.mqttClient.publish(that.mqttTopic['fanspeed'], "medium"); } else if (value == 75) { that.mqttClient.publish(that.mqttTopic['fanspeed'], "max"); } else { that.mqttClient.publish(that.mqttTopic['fanspeed'], "auto"); } that.lastControlFlag = (new Date()).getTime(); callback(null); }); that.mqttClient.on('message', (topic, message) => { if((new Date()).getTime() - that.lastControlFlag < 1000) { return; } if(topic == that.mqttTopic['powerstat']) { var value = (message == 'on' ? true : false); activeCharacteristic.updateValue(value); } else if (topic == that.mqttTopic['tempstat']) { var value = parseInt(message); this.Temperature = value; if (targetHeaterCoolerStateCharacteristic.value === Characteristic.TargetHeaterCoolerState.AUTO) { heatingThresholdTemperatureCharacteristic.updateValue(this.maxTemperature); coolingThresholdTemperatureCharacteristic.updateValue(this.Temperature); } else if (targetHeaterCoolerStateCharacteristic.value !== Characteristic.TargetHeaterCoolerState.AUTO) { coolingThresholdTemperatureCharacteristic.updateValue(this.Temperature); heatingThresholdTemperatureCharacteristic.updateValue(this.Temperature); } } else if (topic == that.mqttTopic['swinghstat']) { var value = (message == 'auto' ? true : false); swingModeCharacteristic.updateValue(value); } else if (topic == that.mqttTopic['fanspeedstat']) { var value = message; if (value == "auto") { this.fanspeed = 100; } else if (value == "min") { this.fanspeed = 25; } else if (value == "medium") { this.fanspeed = 50; } else if (value == "max") { this.fanspeed = 75; } rotationSpeedCharacteristic.updateValue(this.fanspeed); } else if (topic == that.mqttTopic['modestat']) { var value = message; if (value == "auto") { this.modestate = Characteristic.TargetHeaterCoolerState.AUTO; } else if (value == "heat") { this.mode = Characteristic.CurrentHeaterCoolerState.HEATING; this.modestate = Characteristic.TargetHeaterCoolerState.HEAT; } else if (value == "cool") { this.mode = Characteristic.CurrentHeaterCoolerState.COOLING; this.modestate = Characteristic.TargetHeaterCoolerState.COOL; } else if (value == "off") { this.mode = Characteristic.CurrentHeaterCoolerState.INACTIVE; } else { this.modestate = Characteristic.TargetHeaterCoolerState.AUTO; } targetHeaterCoolerStateCharacteristic.updateValue(this.modestate); currentHeaterCoolerStateCharacteristic.updateValue(this.mode); } else if (topic == this.mqtttemp) { var value = parseFloat(message); currentTemperatureCharacteristic.updateValue(value); } }); this.log('publishing mqttAC ' + this.name + ' ' + config.username); const testPort = net.createServer() .once('error', err => { this.error(err); this.status({fill: 'red', shape: 'dot', text: err.message}); }) .once('listening', () => { testPort.once('close', () => { acc.publish({ username: config.username, port: config.port, pincode: config.pincode, category: Accessory.Categories.THERMOSTAT }); acc._server.on('listening', () => { this.log('mqttAC ' + this.name + ' listening on port ' + config.port); this.status({fill: 'green', shape: 'ring', text: ' '}); }); acc._server.on('pair', username => { this.log('mqttAC ' + this.name + ' paired', username); }); acc._server.on('unpair', username => { this.log('mqttAC ' + this.name + ' unpaired', username); }); acc._server.on('verify', () => { this.log('mqttAC ' + this.name + ' verify'); }); }).close(); }) .listen(config.port); this.on('close', () => { }); } } RED.nodes.registerType('hassbian-abc-homekit-mqttAC', hassbianHomeKitirac); };