UNPKG

node-red-contrib-knx-ultimate

Version:

Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.

212 lines (193 loc) 7.42 kB
module.exports = function (RED) { function knxUltimateHueCameraMotion(config) { RED.nodes.createNode(this, config); const node = this; node.serverKNX = RED.nodes.getNode(config.server) || undefined; node.serverHue = RED.nodes.getNode(config.serverHue) || undefined; node.topic = node.name; node.name = config.name === undefined ? 'Hue' : config.name; node.dpt = ''; node.notifyreadrequest = true; node.notifyreadrequestalsorespondtobus = 'false'; node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''; node.notifyresponse = false; node.notifywrite = true; node.initialread = true; node.listenallga = true; // Don't remove node.outputtype = 'write'; node.outputRBE = 'false'; node.inputRBE = 'false'; node.currentPayload = ''; node.passthrough = 'no'; node.formatmultiplyvalue = 1; node.formatnegativevalue = 'leave'; node.formatdecimalsvalue = 2; node.hueDevice = config.hueDevice; node.dptCameraMotion = config.dptCameraMotion; node.motionDPT = (config.dptCameraMotion && config.dptCameraMotion !== '') ? config.dptCameraMotion : '1.001'; node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes'); node.currentDeviceValue = false; node.enableNodePINS = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes'); node.outputs = node.enableNodePINS ? 1 : 0; if (!node.serverKNX && node.enableNodePINS !== 'yes') { node.enableNodePINS = 'yes'; node.outputs = 1; } const pushStatus = (status) => { if (!status) return; const provider = node.serverKNX; if (provider && typeof provider.applyStatusUpdate === 'function') { provider.applyStatusUpdate(node, status); } else { node.status(status); } }; const updateStatus = (status) => { if (!status) return; pushStatus(status); }; const safeSendToKNX = (telegram, context = 'write') => { try { if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') { const now = new Date(); updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${now.getDate()}, ${now.toLocaleTimeString()})` }); return; } node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id }); } catch (error) { updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` }); } }; node.setNodeStatus = ({ fill, shape, text, payload }) => { try { if (payload === undefined) payload = ''; const dDate = new Date(); payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString(); node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`; pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') }); } catch (error) { /* empty */ } }; node.setNodeStatusHue = ({ fill, shape, text, payload }) => { try { if (payload === undefined) payload = ''; const dDate = new Date(); payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString(); node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${dDate.getDate()}, ${dDate.toLocaleTimeString()})`; pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') }); } catch (error) { /* empty */ } }; const mapMotionValue = (_event) => { const motion = _event?.motion; if (!motion) return undefined; const readBoolean = (candidate) => { if (typeof candidate === 'boolean') return candidate; if (candidate && typeof candidate.value === 'boolean') return candidate.value; return undefined; }; let value = readBoolean(motion.motion); if (value === undefined && motion.motion_report) { value = readBoolean(motion.motion_report.motion); } if (value === undefined && motion.motion_report && typeof motion.motion_report.motion === 'boolean') { value = motion.motion_report.motion; } if (value === undefined && typeof motion.motion_valid === 'boolean') { value = motion.motion_valid; } if (value === undefined) { value = readBoolean(motion.motion_valid); } return value; }; node.handleSend = (msg) => { if (!msg || !msg.knx) return; if (msg.knx.event === 'GroupValue_Read') { switch (msg.knx.destination) { case config.GAcameraMotion: node.sendResponseToKNX(node.currentDeviceValue); break; default: break; } } }; node.handleSendHUE = (_event) => { try { if (_event.id !== config.hueDevice) return; const motionValue = mapMotionValue(_event); if (motionValue === undefined) return; const knxMsgPayload = { topic: config.GAcameraMotion, dpt: node.motionDPT, payload: motionValue === true || motionValue === 1 || motionValue === '1' ? true : Boolean(motionValue), name: node.name, event: 'motion', rawEvent: _event, }; if (knxMsgPayload.topic) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', }, 'write'); } node.currentDeviceValue = knxMsgPayload.payload; updateStatus({ fill: 'green', shape: 'dot', text: `HUE->KNX ${knxMsgPayload.payload} (${new Date().getDate()}, ${new Date().toLocaleTimeString()})`, }); if (node.enableNodePINS) { node.send(knxMsgPayload); } node.setNodeStatusHue({ fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload, }); } catch (error) { updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${new Date().getDate()}, ${new Date().toLocaleTimeString()})`, }); } }; node.sendResponseToKNX = (_level) => { if (!node.serverKNX) return; const knxMsgPayload = { topic: config.GAcameraMotion, dpt: node.motionDPT, payload: _level === true || _level === 1 || _level === '1' ? true : false, }; if (knxMsgPayload.topic) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response', }, 'response'); } }; if (node.serverKNX) { node.serverKNX.removeClient(node); node.serverKNX.addClient(node); } if (node.serverHue) { node.serverHue.removeClient(node); node.serverHue.addClient(node); } node.on('close', (done) => { if (node.serverKNX) { node.serverKNX.removeClient(node); } if (node.serverHue) { node.serverHue.removeClient(node); } done(); }); } RED.nodes.registerType('knxUltimateHueCameraMotion', knxUltimateHueCameraMotion); };