UNPKG

redmatic-homekit

Version:

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

281 lines (246 loc) 13.8 kB
module.exports = function (RED) { class RedMaticHomeKitHomematicIrrigation { constructor(config) { RED.nodes.createNode(this, config); this.bridgeConfig = RED.nodes.getNode(config.bridgeConfig); if (!this.bridgeConfig) { return; } this.ccu = RED.nodes.getNode(config.ccuConfig); if (!this.ccu) { return; } this.iface = config.iface; this.onTime = this.context().get('onTime') || parseFloat(config.onTime) || 5; this.remaining = this.onTime * 60; const channel = config.channel.split(' ')[0]; this.status({fill: 'grey', shape: 'ring', text: (this.onTime * 60) + 's'}); this.send([{topic: config.topic, payload: false}, {topic: config.topic, payload: this.onTime * 60}]); const stop = () => { clearInterval(this.interval); this.remaining = 0; this.debug('update Valve 0 RemainingDuration ' + this.remaining); service.updateCharacteristic(hap.Characteristic.RemainingDuration, this.remaining); return new Promise((resolve, reject) => { this.ccu.setValue(config.iface, channel, 'STATE', false) .then(() => { // this.state = false; this.status({fill: 'grey', shape: 'ring', text: (this.onTime * 60) + 's'}); this.send([{topic: config.topic, payload: false}, {topic: config.topic, payload: this.onTime}]); resolve(); }) .catch(() => { // try again this.ccu.setValue(config.iface, channel, 'STATE', false) .then(() => { this.state = false; this.send([{topic: config.topic, payload: false}, {topic: config.topic, payload: this.onTime}]); this.status({fill: 'grey', shape: 'ring', text: (this.onTime * 60) + 's'}); resolve(); }) .catch(error => { this.status({fill: 'red', shape: 'dot', text: (this.onTime * 60) + 's'}); reject(error); }); }); }); }; const startInterval = () => { clearInterval(this.interval); this.interval = setInterval(() => { this.remaining -= 1; if (this.remaining < 0) { clearInterval(this.interval); this.remaining = 0; this.debug('update Valve 0 RemainingDuration ' + 0); service.updateCharacteristic(hap.Characteristic.RemainingDuration, 0); this.send([{topic: config.topic, payload: false}, {topic: config.topic, payload: 0}]); this.status({fill: 'grey', shape: 'ring', text: (this.onTime * 60) + 's'}); } else { this.send([null, {topic: config.topic, payload: this.remaining}]); this.status({fill: 'green', shape: 'dot', text: this.remaining + 's'}); } }, 1000); }; const start = () => { return new Promise((resolve, reject) => { const dev = this.ccu.metadata.devices[config.iface] && this.ccu.metadata.devices[config.iface][channel]; if (dev) { const ps = this.ccu.getParamsetDescription(config.iface, dev, 'VALUES'); if (ps && ps.STATE) { if (ps.ON_TIME) { this.debug('starting with ON_TIME ' + (this.onTime * 60)); this.ccu.methodCall(config.iface, 'putParamset', [channel, 'VALUES', { ON_TIME: this.ccu.paramCast(config.iface, channel, 'VALUES', 'ON_TIME', this.onTime * 60), STATE: true }]).then(() => { // this.state = true; this.remaining = this.onTime * 60; this.debug('update Valve 0 RemainingDuration ' + this.remaining); service.updateCharacteristic(hap.Characteristic.RemainingDuration, this.remaining); this.debug('update Valve 0 Active 1'); service.updateCharacteristic(hap.Characteristic.InUse, 1); this.debug('update Valve 0 InUse true'); service.updateCharacteristic(hap.Characteristic.Active, true); resolve(); startInterval(); }).catch(reject); } else { this.debug('starting with timeout'); this.ccu.setValue(config.iface, channel, 'STATE', true) .then(() => { // this.state = true; this.remaining = this.onTime * 60; this.debug('update Valve 0 RemainingDuration ' + this.remaining); service.updateCharacteristic(hap.Characteristic.RemainingDuration, this.remaining); this.debug('update Valve 0 Active 1'); service.updateCharacteristic(hap.Characteristic.InUse, 1); this.debug('update Valve 0 InUse true'); service.updateCharacteristic(hap.Characteristic.Active, true); setTimeout(() => { stop(); }, (this.onTime * 60 * 1000) + 1000); resolve(); startInterval(); }) .catch(reject); } } else { reject(); } } else { reject(); } }); }; const {hap, version} = this.bridgeConfig; this.name = config.name || ('Irrigation ' + this.id); const acc = this.bridgeConfig.accessory({id: this.id, name: this.name}); const subtype = '0'; let service; if (acc.isConfigured) { service = acc.getService(subtype); } else { acc.getService(hap.Service.AccessoryInformation) .setCharacteristic(hap.Characteristic.Manufacturer, 'RedMatic') .setCharacteristic(hap.Characteristic.Model, 'Homematic Irrigation') .setCharacteristic(hap.Characteristic.SerialNumber, this.id) .setCharacteristic(hap.Characteristic.FirmwareRevision, version); service = acc.addService(hap.Service.Valve, this.name, subtype); acc.isConfigured = true; } this.debug('update Valve 0 ValveType 1'); service.updateCharacteristic(hap.Characteristic.ValveType, 1); this.debug('update Valve 0 RemainingDuration ' + (this.onTime * 60)); service.updateCharacteristic(hap.Characteristic.RemainingDuration, 0); this.debug('update Valve 0 SetDuration ' + (this.onTime * 60)); service.updateCharacteristic(hap.Characteristic.SetDuration, this.onTime * 60); let changeExpected; let changeTimer; this.debug('ccu subscribe ' + config.iface + ' ' + channel); this.ccu.subscribe({ iface: config.iface, channel, datapoint: 'STATE', cache: true, change: true, stable: true }, msg => { this.state = msg.value; if (!this.state) { stop(); } this.debug('this.state=' + this.state); this.debug('update Valve 0 InUse ' + msg.value); service.updateCharacteristic(hap.Characteristic.InUse, msg.value); this.debug('update Valve 0 Active ' + (msg.value ? 1 : 0)); service.updateCharacteristic(hap.Characteristic.Active, msg.value ? 1 : 0); if (!changeExpected) { this.status({fill: this.state ? 'green' : 'grey', shape: 'ring', text: '?'}); this.send([{topic: config.topic, payload: true}, {topic: config.topic, payload: 0}]); this.debug('update Valve 0 RemainingDuration 0'); service.updateCharacteristic(hap.Characteristic.RemainingDuration, 0); } }); const setActive = (value, callback) => { this.debug('set Valve 0 Active ' + value); changeExpected = true; clearTimeout(changeExpected); clearTimeout(changeTimer); changeTimer = setTimeout(() => { changeExpected = false; }, 5000); if (value) { start().then(() => { this.debug('promise resolved!'); callback(); }).catch(() => { this.debug('promise rejected!'); callback(new Error(hap.HAPServer.Status.SERVICE_COMMUNICATION_FAILURE)); }); } else { stop().then(() => { this.debug('promise resolved!'); callback(); }).catch(() => { this.debug('promise rejected!'); callback(new Error(hap.HAPServer.Status.SERVICE_COMMUNICATION_FAILURE)); }); } }; const getActive = callback => { this.debug('get Valve 0 Active ' + (this.state ? 1 : 0)); callback(this.state ? 1 : 0); }; const getInUse = callback => { this.debug('get Valve 0 InUse ' + this.state); callback(this.state); }; const setSetDuration = (value, callback) => { this.debug('set Valve 0 SetDuration ' + value); this.onTime = value / 60; this.context().set('onTime', this.onTime); this.status({fill: 'grey', shape: 'ring', text: value + 's'}); callback(); }; const getRemainingDuration = callback => { const res = this.remaining; // || (this.onTime * 60); this.debug('get Valve 0 RemainingDuration ' + res); callback(res); }; this.on('input', msg => { if (typeof msg.payload === 'boolean') { if (msg.payload) { start(); } else { stop(); } } else { const time = parseFloat(msg.payload) || 0; if (time) { this.onTime = time; this.context().set('onTime', this.onTime); if (this.remaining === 0 && !this.state) { this.status({fill: 'grey', shape: 'ring', text: (this.onTime * 60) + 's'}); } this.debug('update Valve 0 SetDuration ' + (this.onTime * 60)); service.updateCharacteristic(hap.Characteristic.SetDuration, (this.onTime * 60)); } } }); service.getCharacteristic(hap.Characteristic.Active).on('get', getActive); service.getCharacteristic(hap.Characteristic.Active).on('set', setActive); service.getCharacteristic(hap.Characteristic.InUse).on('get', getInUse); service.getCharacteristic(hap.Characteristic.SetDuration).on('set', setSetDuration); service.getCharacteristic(hap.Characteristic.RemainingDuration).on('get', getRemainingDuration); this.on('close', () => { service.getCharacteristic(hap.Characteristic.Active).removeListener('get', getActive); service.getCharacteristic(hap.Characteristic.Active).removeListener('set', setActive); service.getCharacteristic(hap.Characteristic.InUse).removeListener('get', getInUse); service.getCharacteristic(hap.Characteristic.SetDuration).removeListener('set', setSetDuration); service.getCharacteristic(hap.Characteristic.RemainingDuration).removeListener('get', getRemainingDuration); }); } } RED.nodes.registerType('redmatic-homekit-homematic-irrigation', RedMaticHomeKitHomematicIrrigation); };