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.
191 lines (178 loc) • 8.16 kB
JavaScript
module.exports = function (RED) {
  // msg is:
  //  // Build final input message object
  //  return {
  //     topic: _outputtopic
  //     , payload: jsValue
  //     , devicename: (typeof _devicename !== 'undefined') ? _devicename : ""
  //     , payloadmeasureunit: sPayloadmeasureunit
  //     , payloadsubtypevalue: sPayloadsubtypevalue
  //     , knx:
  //     {
  //         event: _event
  //         , dpt: sInputDpt
  //         //, details: dpt
  //         , dptdesc: sDptdesc
  //         , source: _srcGA
  //         , destination: _destGA
  //         , rawValue: _Rawvalue
  //     }
  // };
  // The node.exposedGAs is and array of:
  // {
  //     address,
  //     dpt,
  //     payload
  //     lastupdate
  //     lastupdateLocale
  // }
  function knxUltimateGlobalContext(config) {
    RED.nodes.createNode(this, config)
    const node = this
    node.serverKNX = RED.nodes.getNode(config.server) || undefined
    node.topic = node.name
    node.name = config.name === undefined ? 'KNXGlobalContext' : config.name
    node.outputtopic = node.name
    node.dpt = ''
    node.notifyreadrequest = false
    node.notifyreadrequestalsorespondtobus = 'false'
    node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
    node.notifyresponse = true
    node.notifywrite = true
    node.initialread = false
    node.listenallga = true
    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 = 999
    node.writeExecutionInterval = config.writeExecutionInterval === undefined ? 1000 : config.writeExecutionInterval
    node.contextStorage = config.contextStorage !== undefined ? config.contextStorage : ''
    node.exposeAsVariable = config.exposeAsVariable !== undefined ? config.exposeAsVariable : 'exposeAsVariableREADONLY' // Should expose the Group Addresses to the Global Context?
    node.exposedGAs = []
    node.timerExposedGAs = null
    // Used to call the status update from the config node.
    node.setNodeStatus = ({ fill, shape, text, payload, GA, dpt, devicename }) => {
      try {
        if (node.serverKNX === null) { node.status({ fill: 'red', shape: 'dot', text: '[NO GATEWAY SELECTED]' }); return }
        GA = GA === undefined ? '' : GA
        payload = payload === undefined ? '' : payload
        payload = typeof payload === 'object' ? JSON.stringify(payload) : payload
        const dDate = new Date()
        node.status({ fill, shape, text: GA + ' ' + payload + ' ' + text + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ')' })
      } catch (error) {
      }
    }
    // 02/12/2022 Expose the complete ETS CSV as well
    if (node.exposeAsVariable !== 'exposeAsVariableNO') {
      try {
        node.serverKNX.csv.forEach(element => {
          node.exposedGAs.push({ address: element.ga, dpt: element.dpt, devicename: element.devicename, payload: undefined, lastupdate: undefined, lastupdateLocale: undefined })
        })
      } catch (error) {
      }
    }
    // exposeAsVariableREADWRITE
    // #region "WRITE TO BUS"
    node.goTimerGo = function () {
      if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs) // 21/03/2021
      node.timerExposedGAs = setTimeout(() => {
        let oContext = node.context().global.get(node.name + '_WRITE', node.contextStorage) || []
        node.context().global.set(node.name + '_WRITE', [], node.contextStorage) // Delete the var
        for (let index = 0; index < oContext.length; index++) {
          const element = oContext[index]
          if (element.hasOwnProperty('address') === false) {
            node.setNodeStatus({ fill: 'RED', shape: 'dot', text: 'NO Group Address set', payload: '', GA: '', dpt: '', devicename: '' })
            RED.log.error('knxUltimateGlobalContext: No group address set in node ' + node.id)
            oContext = null // 21/03/2022
            node.goTimerGo()
            return
          }
          if (element.hasOwnProperty('payload') === false) {
            node.setNodeStatus({ fill: 'RED', shape: 'dot', text: 'NO payload set', payload: '', GA: '', dpt: '', devicename: '' })
            RED.log.error('knxUltimateGlobalContext: No payload set for address ' + element.address + ' in node ' + node.id)
            oContext = null // 21/03/2022
            node.goTimerGo()
            return
          }
          // 13/09/2021 retrieve the datapoint if not specified
          if (element.hasOwnProperty('dpt') === false || element.dpt === undefined || element.dpt === '') {
            try {
              const sDPT = node.serverKNX.csv.find(item => item.ga === element.address).dpt
              element.dpt = sDPT
            } catch (error) {
              node.setNodeStatus({ fill: 'RED', shape: 'dot', text: 'Datapoint not found in CSV for ' + element.address, payload: '', GA: '', dpt: '', devicename: '' })
              RED.log.error('knxUltimateGlobalContext: Datapoint not found in CSV for address ' + element.address + ' in node ' + node.id)
              oContext = null // 21/03/2022
              node.goTimerGo()
              return
            }
          }
          node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Write', payload: element.payload, GA: element.address, dpt: element.dpt || '', devicename: '' })
          node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: element.address, payload: element.payload, dpt: element.dpt || '', outputtype: 'write', nodecallerid: node.id })
        }
        oContext = null // 21/03/2022
        node.goTimerGo()
      }, node.writeExecutionInterval)
    }
    // 21/02/2021 timer for write to BUS
    if (node.exposeAsVariable === 'exposeAsVariableREADWRITE') {
      node.goTimerGo()
      node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Start Writing', payload: '', GA: '', dpt: '', devicename: '' })
    } else {
      if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs)
      node.context().global.set(node.name + '_WRITE', [], node.contextStorage) // Delete the var
    }
    // #endregion
    // This function is called by the knx-ultimate config node, to output a msg.payload.
    node.handleSend = msg => {
      if (node.exposeAsVariable !== 'exposeAsVariableNO') {
        try {
          var oGa = node.exposedGAs.find(ga => ga.address === msg.knx.destination)
        } catch (error) {
          console.log(error)
        }
        const dNow = new Date()
        const lastupdate = dNow.toISOString()
        const lastupdateLocale = dNow.toLocaleString()
        if (oGa === undefined) {
          node.exposedGAs.push({ address: msg.knx.destination, devicename: undefined, dpt: msg.knx.dpt, payload: msg.payload, lastupdate, lastupdateLocale })
        } else {
          oGa.dpt = msg.knx.dpt
          oGa.payload = msg.payload
          oGa.lastupdate = lastupdate
          oGa.lastupdateLocale = lastupdateLocale
        }
        // Save into the global Context
        try {
          node.context().global.set(node.name + '_READ', node.exposedGAs, node.contextStorage)
        } catch (error) {
          console.log(error)
        }
        oGa = null // 21/03/2022
      } else {
        node.exposedGAs = []
        node.context().global.set(node.name + '_READ', node.exposedGAs, node.contextStorage)
      }
    }
    node.on('input', function (msg) {
    })
    node.on('close', function (done) {
      if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs)
      node.exposedGAs = []
      if (node.serverKNX) {
        node.serverKNX.removeClient(node)
      }
      done()
    })
    // On each deploy, unsubscribe+resubscribe
    if (node.serverKNX) {
      node.serverKNX.removeClient(node)
      node.serverKNX.addClient(node)
    }
  }
  RED.nodes.registerType('knxUltimateGlobalContext', knxUltimateGlobalContext)
}