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.

262 lines (247 loc) 12 kB
/* eslint-disable max-len */ module.exports = function (RED) { function knxUltimateHueTapDial (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 = false 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' // Apply or not RBE to the output (Messages coming from flow) node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS) node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg node.passthrough = 'no' node.formatmultiplyvalue = 1 node.formatnegativevalue = 'leave' node.formatdecimalsvalue = 2 node.brightnessState = 0 node.isTimerDimStopRunning = false node.hueDevice = config.hueDevice const pinsSetting = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes' || config.enableNodePINS === true) node.enableNodePINS = pinsSetting ? 'yes' : 'no' node.outputs = pinsSetting ? 1 : 0 if (!node.serverKNX && node.outputs === 0) { node.enableNodePINS = 'yes' node.outputs = 1 } node.initializingAtStart = false 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}` }) } } // Used to call the status update from the config node. 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) { } } // Used to call the status update from the HUE config node. 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) { } } // This function is called by the knx-ultimate config node, to output a msg.payload. node.handleSend = (msg) => { } node.handleSendHUE = (_event) => { try { if (_event.id === config.hueDevice) { if (!_event.hasOwnProperty('relative_rotary') || !_event.relative_rotary.hasOwnProperty('last_event') || _event.relative_rotary.last_event === undefined || !_event.relative_rotary.last_event.hasOwnProperty('rotation') || !_event.relative_rotary.last_event.rotation.direction === undefined || _event.relative_rotary.last_event.action === undefined) return const knxMsgPayload = {} knxMsgPayload.topic = config.GArepeat knxMsgPayload.dpt = config.dptrepeat if (_event.relative_rotary.last_event.rotation.direction === 'clock_wise') { if (knxMsgPayload.dpt.startsWith('3.007')) { if (node.isTimerDimStopRunning === false) { // Set KNX Dim up/down start knxMsgPayload.payload = { decr_incr: 1, data: 5 } // Send to KNX bus if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) updateStatus({ fill: 'green', shape: 'dot', text: 'HUE->KNX start Dim' + ` (${new Date().getDate()}, ${new Date().toLocaleTimeString()})` }) } node.startDimStopper(knxMsgPayload) } else if (knxMsgPayload.dpt.startsWith('5.001')) { // 0 - maximum: 32767 node.brightnessState < 100 ? node.brightnessState += 5 : node.brightnessState = 100 knxMsgPayload.payload = node.brightnessState if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } } else if (knxMsgPayload.dpt.startsWith('232.600')) { if (_event.relative_rotary.last_event.action === 'start') { // Random color function getRandomIntInclusive (min, max) { min = Math.ceil(min) max = Math.floor(max) return Math.floor(Math.random() * (max - min + 1) + min) // The maximum is inclusive and the minimum is inclusive } knxMsgPayload.payload = { red: getRandomIntInclusive(0, 255), green: getRandomIntInclusive(0, 255), blue: getRandomIntInclusive(0, 255) } // Send to KNX bus if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) updateStatus({ fill: 'green', shape: 'dot', text: 'HUE->KNX Change color clockwise' + ` (${new Date().getDate()}, ${new Date().toLocaleTimeString()})` }) } } } else if (_event.relative_rotary.last_event.rotation.direction === 'counter_clock_wise') { if (knxMsgPayload.dpt.startsWith('3.007')) { if (node.isTimerDimStopRunning === false) { // Set KNX Dim up/down start knxMsgPayload.payload = { decr_incr: 0, data: 5 } // Send to KNX bus if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) updateStatus({ fill: 'green', shape: 'dot', text: 'HUE->KNX start Dim' + ` (${new Date().getDate()}, ${new Date().toLocaleTimeString()})` }) } node.startDimStopper(knxMsgPayload) } else if (knxMsgPayload.dpt.startsWith('5.001')) { node.brightnessState > 0 ? node.brightnessState -= 5 : node.brightnessState = 0 knxMsgPayload.payload = node.brightnessState if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } } else if (knxMsgPayload.dpt.startsWith('232.600')) { if (_event.relative_rotary.last_event.action === 'start') { // Set white color knxMsgPayload.payload = { red: 255, green: 255, blue: 255 } // Send to KNX bus if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) updateStatus({ fill: 'green', shape: 'dot', text: 'HUE->KNX Change color counterclockwise' + ` (${new Date().getDate()}, ${new Date().toLocaleTimeString()})` }) } } } // Setup the output msg knxMsgPayload.name = node.name knxMsgPayload.event = `rotation ${_event.relative_rotary.last_event.rotation.direction}` knxMsgPayload.payload = _event 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()})` }) } } // Timer to stop the dimming sequence node.startDimStopper = function (knxMsgPayload) { if (node.timerDimStop !== undefined) clearTimeout(node.timerDimStop) node.isTimerDimStopRunning = true node.timerDimStop = setTimeout(() => { // KNX Stop DIM knxMsgPayload.payload = { decr_incr: 0, data: 0 } // Payload for the output msg // Send to KNX bus if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) { safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write') } if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) updateStatus({ fill: 'green', shape: 'dot', text: 'HUE->KNX Stop DIM' + ` (${new Date().getDate()}, ${new Date().toLocaleTimeString()})` }) node.isTimerDimStopRunning = false }, 500) } // On each deploy, unsubscribe+resubscribe if (node.serverKNX) { node.serverKNX.removeClient(node) node.serverKNX.addClient(node) } if (node.serverHue) { node.serverHue.removeClient(node) node.serverHue.addClient(node) } node.on('input', (msg) => { }) node.on('close', (done) => { if (node.serverKNX) { node.serverKNX.removeClient(node) } if (node.serverHue) { node.serverHue.removeClient(node) } done() }) } RED.nodes.registerType('knxUltimateHueTapDial', knxUltimateHueTapDial) }