UNPKG

redmatic-homekit

Version:

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

769 lines (650 loc) 33.4 kB
<script type="text/javascript"> RED.nodes.registerType('redmatic-homekit-homematic-devices', { category: 'redmatic homekit', defaults: { ccuConfig: {value: 'localhost', type: 'ccu-connection', required: true}, bridgeConfig: {value: 'CC:22:3D:E3:CE:C7:51826', type: 'redmatic-homekit-bridge', required: true}, devices: {value: {}} }, inputs: 0, outputs: 0, icon: 'homekit2.png', color: '#E2D96E', paletteLabel: 'homematic', align: 'left', label() { return this.name || 'homematic'; }, labelStyle() { return this.name ? 'node_label_italic' : ''; }, oneditprepare() { const that = this; let data; let homematicValidDevices; function addVirtualChannels(addr, first, last, step, dropdowns) { if (typeof dropdowns === 'undefined' && typeof step !== 'number') { dropdowns = step; step = 1; } for (let i = first; i <= last; i += step) { const ch = addr + ':' + i; const name = data.channelNames[ch]; const vch1 = addr + ':' + (i + 1); const vname1 = data.channelNames[vch1]; const vch2 = addr + ':' + (i + 2); const vname2 = data.channelNames[vch2]; if (!that.devices[ch]) { that.devices[ch] = {}; } if (!that.devices[vch1]) { that.devices[vch1] = {}; } if (!that.devices[vch2]) { that.devices[vch2] = {}; } let html = ''; let vhtml1 = ''; let vhtml2 = ''; if (dropdowns) { Object.keys(dropdowns).forEach(option => { html += addDropdown(ch, option, dropdowns[option]); vhtml1 += addDropdown(vch1, option, dropdowns[option]); vhtml2 += addDropdown(vch2, option, dropdowns[option]); }); } $('#devices').append('<tr class="device-options" data-addr="' + addr + '"><td></td><td class="text small"><input class="channel-enabled device-enabled" data-addr="' + ch + '" type="checkbox"' + (that.devices[ch].disabled ? '' : ' checked') + '>' + name + '</td><td class="text small">' + ch + '</td><td class="text small">' + html + '</td></tr>'); $('#devices').append('<tr class="device-options" data-addr="' + addr + '"><td></td><td class="text small"><input class="vchannel-enabled device-enabled" data-addr="' + vch1 + '" type="checkbox"' + (that.devices[vch1].enabled ? ' checked' : '') + '>' + vname1 + '</td><td class="text small">' + vch1 + '</td><td class="text small">' + vhtml1 + '</td></tr>'); $('#devices').append('<tr class="device-options" data-addr="' + addr + '"><td></td><td class="text small"><input class="vchannel-enabled device-enabled" data-addr="' + vch2 + '" type="checkbox"' + (that.devices[vch2].enabled ? ' checked' : '') + '>' + vname2 + '</td><td class="text small">' + vch2 + '</td><td class="text small">' + vhtml2 + '</td></tr>'); } } function addChannels(addr, first, last, step, dropdowns) { if (typeof dropdowns === 'undefined' && typeof step !== 'number') { dropdowns = step; step = 1; } for (let i = first; i <= last; i += step) { const ch = addr + ':' + i; const name = data.channelNames[ch]; let checked = true; if (!that.devices[ch]) { that.devices[ch] = {}; } if (that.devices[ch].disabled) { checked = false; } let html = ''; if (dropdowns) { Object.keys(dropdowns).forEach(option => { html += addDropdown(ch, option, dropdowns[option]); }); } const disabled = first === last ? ' disabled' : ''; $('#devices').append('<tr class="device-options" data-addr="' + addr + '"><td></td><td class="text small"><input class="channel-enabled device-enabled" data-addr="' + ch + '" type="checkbox"' + (checked ? ' checked' : '') + disabled + '>' + name + '</td><td class="text small">' + ch + '</td><td class="text small">' + html + '</td></tr>'); } } function addOption(addr, option) { const ch = addr + ':' + option; let checked = true; if (!that.devices[ch]) { that.devices[ch] = {}; } if (that.devices[ch].disabled) { checked = false; } $('#devices').append('<tr class="device-options" data-addr="' + addr + '"><td></td><td class="text small"><input class="channel-enabled device-enabled" data-addr="' + ch + '" type="checkbox"' + (checked ? ' checked' : '') + '>' + option + '</td><td class="text small"></td><td class="text small"></td></tr>'); } function addDropdown(ch, option, options) { if (!that.devices[ch]) { that.devices[ch] = {}; } const selected = that.devices[ch][option]; let html = '<select class="small channel-option" data-addr="' + ch + '" data-option="' + option + '">'; options.forEach(val => { html += '<option' + (selected === val ? ' selected' : '') + '>' + val + '</option>'; }); html += '</select>'; return html; } function addDevice(addr, type, name, channelCount) { if (!that.devices) { that.devices = {}; } const blindTypes = ['VerticalTilt Enabled', 'VerticalTilt Disabled']; const switchTypes = ['Switch', 'Outlet', 'Lightbulb', 'Fan', 'Valve', 'ValveIrrigation']; const outletTypes = ['Outlet', 'Switch', 'Lightbulb', 'Fan', 'Valve', 'ValveIrrigation']; if (!addr.match(/:\d+$/)) { if (!that.devices[addr]) { that.devices[addr] = {}; } let checked = true; if (that.devices[addr].disabled) { checked = false; } $('#devices').append('<tr><td class="enabled"><input class="device-enabled option-hide" data-addr="' + addr + '" type="checkbox"' + (checked ? ' checked' : '') + '></td><td class="text">' + name + '</td><td class="text small">' + addr + '</td><td class="text small">' + type + '</td></tr>') switch (type.toLowerCase()) { case 'hb-lc-bl1pbu-fm': break; case 'hb-lc-sw1pbu-fm': case 'hb-lc-sw2-fm': case 'hb-lc-sw2pbu-fm': if (channelCount > 2) { addOption(addr, 'SingleAccessory'); } addChannels(addr, 1, channelCount - 1, {type: switchTypes}); break; case 'hb-uni-dmx-master': break; case 'hb-uni-rgb-led-ctrl': break; case 'hb-uni-sen-press-sc': break; case 'hb-uni-sen-temp-ds18b20': break; case 'hb-uni-sen-temp-ir': addChannels(addr, 1, 5); break; case 'hb-uni-sen-wea': addOption(addr, 'HumiditySensor'); addOption(addr, 'LightSensor'); break; case 'hb-uni-senact-4-4-rc-bat': case 'hb-uni-senact-4-4-rc': addChannels(addr, 1, 4); break; case 'hb-uni-senact-4-4-sc-bat': case 'hb-uni-senact-4-4-sc': addChannels(addr, 1, 8); break; case 'hb-uni-senact-8-8-rc-bat': case 'hb-uni-senact-8-8-rc': addChannels(addr, 1, 8); break; case 'hb-uni-senact-8-8-sc-bat': case 'hb-uni-senact-8-8-sc': addChannels(addr, 1, 16); break; case 'hm-cc-rt-dn-bom': case 'hm-cc-rt-dn': addOption(addr, 'BoostSwitch'); break; case 'hm-cc-scd': break; case 'hm-cc-tc': addOption(addr, 'HumiditySensor'); break; case 'hm-dw-wm': break; case 'hm-es-pmsw1-dr': case 'hm-es-pmsw1-pl-dn-r1': case 'hm-es-pmsw1-pl': addChannels(addr, 1, 1, {type: switchTypes}); break; case 'hm-lc-ao-sm': break; case 'hm-lc-bl1-fm-2': break; case 'hm-lc-bl1-fm': case 'zel stg rm fep 230v': break; case 'hm-lc-bl1-pb-fm': break; case 'hm-lc-bl1-sm-2': break; case 'hm-lc-bl1-sm': break; case 'hm-lc-bl1pbu-fm': break; case 'hm-lc-dim1l-cv-2': case 'hm-lc-dim1l-cv': case 'hm-lc-dim1l-pl-2': case 'hm-lc-dim1l-pl-3': case 'hm-lc-dim1l-pl': case 'hm-lc-dim1pwm-cv-2': case 'hm-lc-dim1pwm-cv': case 'hm-lc-dim1t-cv-2': case 'hm-lc-dim1t-cv': case 'hm-lc-dim1t-dr': case 'hm-lc-dim1t-fm-2': case 'hm-lc-dim1t-fm-lf': case 'hm-lc-dim1t-fm': case 'hm-lc-dim1t-pl-2': case 'hm-lc-dim1t-pl-3': case 'hm-lc-dim1t-pl': case 'hm-lc-dim1tpbu-fm-2': case 'hm-lc-dim1tpbu-fm': break; case 'hm-lc-dim2l-sm-2': case 'hm-lc-dim2l-sm': case 'hm-lc-dim2t-sm-2': case 'hm-lc-dim2t-sm': break; case 'hm-lc-ja1pbu-fm': break; case 'hm-lc-rgbw-wm': break; case 'hm-lc-sw1-ba-pcb': case 'hm-lc-sw1-dr': case 'hm-lc-sw1-fm-2': case 'hm-lc-sw1-fm': case 'hm-lc-sw1-pb-fm': case 'hm-lc-sw1-pcb': case 'hm-lc-sw1-pl-2': case 'hm-lc-sw1-pl-3': case 'hm-lc-sw1-pl-ct-r1': case 'hm-lc-sw1-pl-ct-r2': case 'hm-lc-sw1-pl-ct-r3': case 'hm-lc-sw1-pl-ct-r4': case 'hm-lc-sw1-pl-ct-r5': case 'hm-lc-sw1-pl-dn-r1': case 'hm-lc-sw1-pl-dn-r2': case 'hm-lc-sw1-pl-dn-r3': case 'hm-lc-sw1-pl-dn-r4': case 'hm-lc-sw1-pl-dn-r5': case 'hm-lc-sw1-pl-om54': case 'hm-lc-sw1-pl': case 'hm-lc-sw1-sm-2': case 'hm-lc-sw1-sm': case 'hm-lc-sw1pbu-fm': case 'hm-lc-sw2-dr-2': case 'hm-lc-sw2-dr': case 'hm-lc-sw2-fm-2': case 'hm-lc-sw2-fm': case 'hm-lc-sw2-pb-fm': case 'hm-lc-sw2pbu-fm': case 'hm-lc-sw4-ba-pcb': case 'hm-lc-sw4-dr-2': case 'hm-lc-sw4-dr': case 'hm-lc-sw4-pcb-2': case 'hm-lc-sw4-pcb': case 'hm-lc-sw4-sm-2': case 'hm-lc-sw4-sm': case 'hm-lc-sw4-wm-2': case 'hm-lc-sw4-wm': case 'hm-mod-re-8': if (channelCount > 2) { addOption(addr, 'SingleAccessory'); } addChannels(addr, 1, channelCount - 1, {type: switchTypes}); break; case 'hm-sci-3-fm': addChannels(addr, 1, 3, {type: ['ContactSensor', 'Door', 'Window']}); break; case 'hm-sec-key-o': case 'hm-sec-key-s': case 'hm-sec-key': addOption(addr, 'OpenOnUnlock'); break; case 'hm-sec-mdir-2': case 'hm-sec-mdir-3': case 'hm-sec-mdir': addOption(addr, 'LightSensor'); break; case 'hm-sec-rhs-2': case 'hm-sec-rhs': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window']}); break; case 'hm-sec-sc-2': case 'hm-sec-sc': case 'hm-sec-sco': case 'zel stg rm ffk': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window', 'GarageDoorOpener']}); break; case 'hm-sec-sd-2': case 'hm-sec-sd': break; case 'hm-sec-tis': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window', 'GarageDoorOpener']}); break; case 'hm-sec-wds-2': case 'hm-sec-wds': break; case 'hm-sec-win': addOption(addr, 'LockOnClose'); break; case 'hm-sen-db-pcb': break; case 'hm-sen-li-o': break; case 'hm-sen-mdir-o-2': case 'hm-sen-mdir-o-3': case 'hm-sen-mdir-o': case 'hm-sen-mdir-sm': case 'hm-sen-mdir-wm55': addOption(addr, 'LightSensor'); break; case 'hm-tc-it-wm-w-eu': case 'hm-cc-vg-1': addOption(addr, 'BoostSwitch'); addOption(addr, 'HumiditySensor'); break; case 'hm-wds10-th-o': addOption(addr, 'HumiditySensor'); break; case 'hm-wds100-c6-o-2': case 'hm-wds100-c6-o': addOption(addr, 'HumiditySensor'); addOption(addr, 'LightSensor'); break; case 'hm-wds20-th-o': addOption(addr, 'HumiditySensor'); break; case 'hm-wds30-ot2-sm-2': case 'hm-wds30-ot2-sm': addChannels(addr, 1, 5); break; case 'hm-wds30-t-o': break; case 'hm-wds40-th-i-2': case 'hm-wds40-th-i': addOption(addr, 'HumiditySensor'); break; case 'hm-ws550st-io': break; case 'hm-ws550sth-i': case 'hm-ws550sth-o': addOption(addr, 'HumiditySensor'); break; case 'hmip-bdt': addVirtualChannels(addr, 4, 4); break; break; case 'hmip-bsl': break; case 'hmip-bsm': addChannels(addr, 4, 4, {type: switchTypes}); break; case 'hmip-bwth': case 'hmip-bwth24': case 'hmip-heating': addOption(addr, 'BoostSwitch'); addOption(addr, 'HumiditySensor'); break; case 'hmip-dbb': break; case 'hmip-etrv-2': case 'hmip-etrv-b': case 'hmip-etrv-b1': case 'hmip-etrv-c': case 'hmip-etrv': addOption(addr, 'BoostSwitch'); break; case 'hmip-bbl': case 'hmip-fbl': addVirtualChannels(addr, 4, 4, {type: blindTypes}); break; case 'hmip-froll': case 'hmip-broll': addVirtualChannels(addr, 4, 4); break; case 'hmip-fci1': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window']}); break; case 'hmip-fdt': addVirtualChannels(addr, 2, 2); break; case 'hmip-fsm': case 'hmip-fsm16': addChannels(addr, 2, 2, {type: switchTypes}); break; case 'hmip-mod-oc8': addOption(addr, 'SingleAccessory'); addVirtualChannels(addr, 10, channelCount - 2, 4, {type: switchTypes}); break; case 'hmip-pcbs-bat': case 'hmip-pcbs': addChannels(addr, 3, 3, {type: switchTypes}); break; case 'hmip-pcbs2': addChannels(addr, 4, 4, {type: switchTypes}); addChannels(addr, 8, 8, {type: switchTypes}); break; case 'hmip-pdt': addVirtualChannels(addr, 3, 3); break; case 'hmip-ps': case 'hmip-psm': addChannels(addr, 3, 3, {type: outletTypes}); break; case 'hmip-sam': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window', 'GarageDoorOpener']}); break; case 'hmip-slo': break; case 'hmip-smi': case 'hmip-smi55': addOption(addr, 'LightSensor'); break; case 'hmip-smo-a': break; case 'hmip-smo': break; case 'hmip-spdr': break; case 'hmip-spi': addOption(addr, 'LightSensor'); break; case 'hmip-srh': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window']}); break; case 'hmip-sth': case 'hmip-sthd': addOption(addr, 'Thermostat'); addOption(addr, 'BoostSwitch'); addOption(addr, 'HumiditySensor'); break; case 'hmip-stho-a': case 'hmip-stho': addOption(addr, 'HumiditySensor'); break; case 'hmip-swd': break; case 'hmip-swdm-b': case 'hmip-swdm-b1': case 'hmip-swdm-b2': case 'hmip-swdm': case 'hmip-swdo-i': case 'hmip-swdo': addChannels(addr, 1, 1, {type: ['ContactSensor', 'Door', 'Window', 'GarageDoorOpener']}); break; case 'hmip-swo-b': case 'hmip-swo-pl': case 'hmip-swo-pr': addOption(addr, 'HumiditySensor'); addOption(addr, 'LightSensor'); break; case 'hmip-swsd': break; case 'hmip-wth-2': case 'hmip-wth': addOption(addr, 'BoostSwitch'); addOption(addr, 'HumiditySensor'); break; case 'hmw-io-12-sw7-dr': addOption(addr, 'SingleAccessory'); addChannels(addr, 13, 19, {type: switchTypes}); break; case 'hmw-lc-bl1-dr-2': case 'hmw-lc-bl1-dr': break; case 'hmw-lc-dim1l-dr': break; case 'hmw-lc-sw2-dr': addOption(addr, 'SingleAccessory'); addChannels(addr, 3, 4, {type: switchTypes}); break; case 'hmw-sen-sc-12-dr': case 'hmw-sen-sc-12-fm': addOption(addr, 'SingleAccessory'); addChannels(addr, 1, 12, {type: ['ContactSensor', 'Door', 'Window']}); break; case 'hmipw-drd3': addVirtualChannels(addr, 2, 12, 4); break; case 'hmip-drsi4': case 'hmipw-drs4': case 'hmipw-drs8': addOption(addr, 'SingleAccessory'); addVirtualChannels(addr, 2, channelCount - 2, 4, {type: switchTypes}); break; case 'hmipw-fio6': addOption(addr, 'SingleAccessory'); addChannels(addr, 1, 6, {type: ['ContactSensor', 'Door', 'Window']}); addVirtualChannels(addr, 8, channelCount - 3, 4, {type: switchTypes}); break; case 'hmipw-dri16': case 'hmipw-dri32': addOption(addr, 'SingleAccessory'); addChannels(addr, 1, channelCount - 2, 1, {type: ['ContactSensor', 'Door', 'Window']}); break; case 'hmip-drbli4': case 'hmipw-drbl4': addVirtualChannels(addr, 2, channelCount - 2, 4, {type: blindTypes}); break; default: } } } if (this.ccuConfig) { $('#spinner').show(); $('.config').hide(); $.getJSON('redmatic-homekit/homematic-devices', res => { homematicValidDevices = res; const url = 'ccu?config=' + this.ccuConfig; $.getJSON(url, d => { data = d; const devs = []; Object.keys(d.metadata.devices).forEach(iface => { if (d.enabledIfaces.includes(iface)) { Object.keys(d.metadata.devices[iface]).forEach(addr => { const dev = d.metadata.devices[iface][addr]; if (homematicValidDevices.includes((dev.TYPE && dev.TYPE.toLowerCase())) || (dev.PARENT_TYPE && homematicValidDevices.includes(dev.PARENT_TYPE.toLowerCase()))) { devs.push({addr, type: dev.TYPE, name: d.channelNames[addr] || addr, channelCount: dev.CHILDREN && dev.CHILDREN.length}); } }); } }); devs.sort((a, b) => a.name.localeCompare(b.name)).forEach(dev => { addDevice(dev.addr, dev.type, dev.name, dev.channelCount); }); $('.option-hide').each(function () { $(this).change(function () { const addr = $(this).data('addr'); if ($(this).is(':checked')) { $('.device-options[data-addr="' + addr + '"]').show(); } else { $('.device-options[data-addr="' + addr + '"]').hide(); } }) }); $('.option-hide').each(function () { $(this).trigger('change'); }); $('#spinner').hide(); $('.config').show(); }); }); } $('#disable-all').click(() => { $('.option-hide').each(function () { $(this).removeAttr('checked').trigger('change'); }); }); $('#enable-all').click(() => { $('.option-hide').each(function () { console.log('enable!', this); $(this).prop('checked', true).trigger('change'); //$(this).trigger('change'); }); }); }, oneditsave() { const devices = {}; $('.channel-option').each(function () { const addr = $(this).data('addr'); const option = $(this).data('option'); if (!devices[addr]) { devices[addr] = {}; } devices[addr][option] = $(this).val(); }); $('.device-enabled:not(.vchannel-enabled)').each(function () { const addr = $(this).data('addr'); const enabled = $(this).is(':checked'); if (enabled && devices[addr]) { devices[addr].disabled = false; } else if (!enabled) { if (!devices[addr]) { devices[addr] = {}; } devices[addr].disabled = true; } }); $('.device-enabled.vchannel-enabled').each(function () { const addr = $(this).data('addr'); const enabled = $(this).is(':checked'); if (!enabled && devices[addr]) { devices[addr].enabled = false; } else if (enabled) { if (!devices[addr]) { devices[addr] = {}; } devices[addr].enabled = true; } }); this.devices = devices; } }); </script> <script type="text/x-red" data-template-name="redmatic-homekit-homematic-devices"> <div class="form-row"> <label for="node-input-ccuConfig"><i class="icon-globe"></i> CCU</label> <input type="text" id="node-input-ccuConfig"> </div> <div class="form-row"> <label for="node-input-bridgeConfig"><i class="icon-globe"></i> Bridge</label> <input type="text" id="node-input-bridgeConfig"> </div> <style> #devices { width: 100%; } #devices td.text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #devices td.small { max-width: 90px; font-size: 10px; } #devices td.enabled { width: 16px; display: block; margin-top: -2px; } .channel-enabled, .vchannel-enabled { width: 11px !important; margin-top: -1px !important; margin-right: 2px !important; } select.small { width: 100%; height: auto; padding: 0; font-size: 9px; } </style> <div class="form-row"> <div style="width: 100%; display: flex"> <img id="spinner" src="red/images/spin.svg" style="margin: auto; display: none;"> </div> <div class="config" style="width: 100%; display: none"> <button id="disable-all" style="margin-right: 12px;">disable all</button> <button id="enable-all">enable all</button> </div> <table class="config" id="devices"></table> </div> </script> <script type="text/x-red" data-help-name="redmatic-homekit-homematic-devices"> </script>