redmatic-homekit
Version:
HAP-Nodejs based Node-RED nodes to create HomeKit Accessories
174 lines (148 loc) • 6.14 kB
JavaScript
const fs = require('fs');
const path = require('path');
module.exports = function (RED) {
const homematicValidDevices = [];
const devPath = path.join(__dirname, '..', 'homematic-devices');
fs.readdir(devPath, (error, files) => {
if (!error && files) {
files.forEach(file => {
if (file.endsWith('.js')) {
homematicValidDevices.push(file.replace('.js', ''));
}
});
}
});
RED.httpAdmin.get('/redmatic-homekit/homematic-devices', (req, res) => {
res.status(200).send(JSON.stringify(homematicValidDevices));
});
class RedMaticHomeKitHomematicDevices {
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);
this.devices = config.devices;
if (!this.ccu) {
return;
}
this.bridgeConfig.waitForHomematic = true;
this.ccu.register(this);
this.homematicDevices = {};
}
publishDevices(callback) {
if (!this.ccu.channelNames) {
this.error('ccu.channelNames missing');
return;
}
if (Object.keys(this.ccu.channelNames).length === 0) {
this.error('ccu.channelNames empty');
return;
}
if (!this.ccu.metadata.devices) {
this.error('ccu.metadata.devices missing');
return;
}
if (!this.devices) {
this.devices = {};
}
const queue = [];
Object.keys(this.ccu.channelNames).forEach(address => {
if (this.devices[address] && this.devices[address].disabled) {
return;
}
if (!address.match(/:\d+$/)) {
const iface = this.ccu.findIface(address);
if (iface && this.ccu.enabledIfaces.includes(iface) && this.ccu.metadata.devices[iface]) {
const options = {};
Object.keys(this.devices).forEach(addr => {
if (addr === address || addr.startsWith(address + ':')) {
options[addr] = this.devices[addr];
}
});
queue.push(() => {
return new Promise(resolve => {
this.createHomematicDevice({
name: this.ccu.channelNames[address],
iface,
deviceAddress: iface + '.' + address,
description: this.ccu.metadata.devices[iface][address],
options
});
setTimeout(() => {
resolve();
}, 50);
});
});
}
}
});
this.log('publish ' + queue.length + ' devices');
queue.reduce((p, task) => p.then(task), Promise.resolve()).then(() => {
callback();
});
}
createHomematicDevice(dev) {
let type = dev && dev.description && dev.description.TYPE;
if (!type) {
this.error('invalid homematic device type ' + type);
return;
}
type = type.toLowerCase().replace(/ /g, '_');
if (!homematicValidDevices.includes(type)) {
return;
}
if (!this.homematicDevices[type]) {
try {
this.homematicDevices[type] = require('../homematic-devices/' + type);
this.debug('loaded homematic-devices/' + type);
} catch (error) {
this.warn('missing homematic-devices/' + type);
return;
}
}
if (this.homematicDevices[type] && typeof this.homematicDevices[type] === 'function') {
try {
return new this.homematicDevices[type](dev, this);
} catch (error) {
this.error('createHomematicDevice Exception ' + dev.name + ' ' + type);
this.error(error.stack);
return;
}
}
this.error('invalid homematic-devices/' + type);
}
setStatus(data) {
this.ccuStatus = data;
let status = 0;
Object.keys(data.ifaceStatus).forEach(s => {
if (data.ifaceStatus[s]) {
status += 1;
}
});
this.debug(JSON.stringify(data));
if (status < 1) {
this.status({fill: 'red', shape: 'dot', text: 'disconnected'});
} else if (status === this.ccu.enabledIfaces.length) {
this.status({fill: 'green', shape: 'dot', text: 'connected'});
if (!this.ccuConnected) {
this.ccuConnected = true;
this.publishDevices(() => {
this.log('publish done');
this.bridgeConfig.waitForHomematic = false;
// this.bridgeConfig.emit('homematic-ready');
});
}
} else {
this.status({fill: 'yellow', shape: 'dot', text: 'partly connected'});
}
}
_destructor(done) {
this.ccu.deregister(this);
this.ccu.unsubscribe(this.idSubscription);
done();
}
}
RED.nodes.registerType('redmatic-homekit-homematic-devices', RedMaticHomeKitHomematicDevices);
};