node-red-contrib-knx-ultimate 2.1.11 → 2.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  # CHANGELOG
8
8
 
9
+ <p>
10
+ <b>Version 2.1.12</b> - June 2023<br/>
11
+ - Hue Light node: added tunable white.<br/>
12
+ </p>
9
13
  <p>
10
14
  <b>Version 2.1.11</b> - June 2023<br/>
11
15
  - KNX Global Context node: added the optional datastore to choose from.<br/>
@@ -331,23 +331,30 @@
331
331
 
332
332
 
333
333
 
334
- // DPT dptLightTunableWhite and dptLightTunableWhiteState
334
+ // DPT dptLightTunableWhite
335
335
  // ########################
336
336
  $.getJSON('knxUltimateDpts', (data) => {
337
337
  data.forEach(dpt => {
338
- if (dpt.value === "3.007" || dpt.value === "5.001") {
338
+ if (dpt.value === "3.007") {
339
339
  $("#node-input-dptLightHSV").append($("<option></option>")
340
340
  .attr("value", dpt.value)
341
- .text(dpt.text))
341
+ .text(dpt.text))
342
+ }
343
+ });
344
+
345
+ $("#node-input-dptLightHSV").val(this.dptLightHSV)
346
+ })
347
+
348
+ // DPT dptLightTunableWhiteState
349
+ $.getJSON('knxUltimateDpts', (data) => {
350
+ data.forEach(dpt => {
351
+ if (dpt.value === "5.001") {
342
352
  $("#node-input-dptLightHSVState").append($("<option></option>")
343
353
  .attr("value", dpt.value)
344
354
  .text(dpt.text))
345
355
  }
346
356
  });
347
-
348
- $("#node-input-dptLightHSV").val(this.dptLightHSV)
349
357
  $("#node-input-dptLightHSVState").val(this.dptLightHSVState)
350
-
351
358
  })
352
359
 
353
360
  // Autocomplete suggestion with ETS csv File
@@ -356,7 +363,7 @@
356
363
  source: function (request, response) {
357
364
  $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
358
365
  response($.map(data, function (value, key) {
359
- if (value.dpt === "3.007" || value.dpt === "5.001") {
366
+ if (value.dpt === "3.007") {
360
367
  var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
361
368
  if (fullSearch(sSearch, request.term)) {
362
369
  return {
@@ -389,7 +396,7 @@
389
396
  source: function (request, response) {
390
397
  $.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
391
398
  response($.map(data, function (value, key) {
392
- if (value.dpt === "3.007" || value.dpt === "5.001") {
399
+ if (value.dpt === "5.001") {
393
400
  var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
394
401
  if (fullSearch(sSearch, request.term)) {
395
402
  return {
@@ -751,7 +758,30 @@
751
758
  </div>
752
759
 
753
760
 
754
-
761
+ <div class="form-row">
762
+ <label for="node-input-nameLightHSV" style="width:100px;"><i class="fa fa-play-circle-o"></i> Tunable white</label>
763
+
764
+ <label for="node-input-GALightHSV" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
765
+ <input type="text" id="node-input-GALightHSV" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
766
+
767
+ <label for="node-input-dptLightHSV" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
768
+ <select id="node-input-dptLightHSV" style="width:140px;"></select>
769
+
770
+ <label for="node-input-nameLightHSV" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
771
+ <input type="text" id="node-input-nameLightHSV" style="width:200px;margin-left: 5px; text-align: left;">
772
+ </div>
773
+ <div class="form-row">
774
+ <label for="node-input-nameLightHSVState" style="width:100px;"><i class="fa fa-play-circle-o"></i> Tunable white Status</label>
775
+
776
+ <label for="node-input-GALightHSVState" style="width:20px;"><span data-i18n="knxUltimateHueLight.node-input-GALightState"></span></label>
777
+ <input type="text" id="node-input-GALightHSVState" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
778
+
779
+ <label for="node-input-dptLightHSVState" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
780
+ <select id="node-input-dptLightHSVState" style="width:140px;"></select>
781
+
782
+ <label for="node-input-nameLightHSVState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
783
+ <input type="text" id="node-input-nameLightHSVState" style="width:200px;margin-left: 5px; text-align: left;">
784
+ </div>
755
785
 
756
786
 
757
787
  <div class="form-row">
@@ -836,8 +866,8 @@ Start typing in the GA field, the name or group address of your KNX device, the
836
866
  | Dimming | Relative DIM the HUE light |
837
867
  | Color | This command is used to change the HUE light's color. Accepted datapoint is RGB triplet (r,g,b). The node handles the gamut color correction. As soon as you send a color KNX telegran, the light turns on and sets color and brightness, derived from the brightness human perception. As soon as you send a KNX telegram with r,g,b set to zero, the light turns off |
838
868
  | Color Status | Link this to the light's color status group address. Accepted datapoint is RGB triplet (r,g,b)|
839
- | Tunable white | This command is used to change the HUE light's white temperature. There are a bunch of Datapoints you can choose from, to suit your needs. |
840
- | Tunable white Status | Link this to the light temperature status group address. |
869
+ | Tunable white | This command is used to change the HUE light's white temperature. Datapoint is 3.007 dimming. |
870
+ | Tunable white Status | Link this to the light temperature status group address. Datapoint is 5.001 absolute value|
841
871
  | Brightness | This command is used to change the absolute HUE light's brightness |
842
872
  | Brightness Status| Link this to the light's brightness status group address |
843
873
  | Blink| *true* Blink the light, *false* Stop blinking. Blinks the light on and off. Useful for signalling. Works with all HUE lights. |
@@ -2,7 +2,7 @@ module.exports = function (RED) {
2
2
  const dptlib = require('./../KNXEngine/src/dptlib')
3
3
  const hueColorConverter = require('./utils/hueColorConverter')
4
4
 
5
- function knxUltimateHueLight (config) {
5
+ function knxUltimateHueLight(config) {
6
6
  RED.nodes.createNode(this, config)
7
7
  const node = this
8
8
  node.server = RED.nodes.getNode(config.server)
@@ -60,6 +60,21 @@ module.exports = function (RED) {
60
60
  node.startDimStopper('stop')
61
61
  }
62
62
  break
63
+ case config.GALightHSV:
64
+ if (config.dptLightHSV === '3.007') {
65
+ // MDT smartbutton will dim the color temperature
66
+ // { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
67
+ // { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
68
+ msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightHSV))
69
+ if (msg.payload.data > 0) {
70
+ let dimDirectionTunableWhite = 'down'
71
+ dimDirectionTunableWhite = msg.payload.decr_incr === 1 ? 'up' : 'down'
72
+ node.startDimStopperTunableWhite(dimDirectionTunableWhite)
73
+ } else {
74
+ node.startDimStopperTunableWhite('stop')
75
+ }
76
+ }
77
+ break
63
78
  case config.GALightBrightness:
64
79
  msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
65
80
  state = { dimming: { brightness: msg.payload } }
@@ -97,7 +112,7 @@ module.exports = function (RED) {
97
112
  node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: true } }, 'setLight')
98
113
  node.timerColorCycle = setInterval(() => {
99
114
  try {
100
- function getRandomIntInclusive (min, max) {
115
+ function getRandomIntInclusive(min, max) {
101
116
  min = Math.ceil(min)
102
117
  max = Math.floor(max)
103
118
  return Math.floor(Math.random() * (max - min + 1) + min) // The maximum is inclusive and the minimum is inclusive
@@ -150,6 +165,29 @@ module.exports = function (RED) {
150
165
  }, 300)
151
166
  }
152
167
 
168
+ // Start dimming tunable white
169
+ // mirek: required(integer – minimum: 153 – maximum: 500)
170
+ node.timerDimTunableWhite = undefined
171
+ node.dimDirectionTunableWhite = {}
172
+ node.startDimStopperTunableWhite = function (_direction) {
173
+ if (node.timerDimTunableWhite !== undefined) clearInterval(node.timerDimTunableWhite)
174
+ if (_direction === 'stop') return
175
+ switch (_direction) {
176
+ case 'up':
177
+ node.dimDirectionTunableWhite = { color_temperature_delta: { action: 'up', mirek_delta: 10 } }
178
+ break
179
+ case 'down':
180
+ node.dimDirectionTunableWhite = { color_temperature_delta: { action: 'down', mirek_delta: 10 } }
181
+ break
182
+ default:
183
+ break
184
+ }
185
+ node.timerDimTunableWhite = setInterval(() => {
186
+ node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, node.dimDirectionTunableWhite, 'setLight')
187
+ }, 300)
188
+
189
+ }
190
+
153
191
  node.handleSendHUE = _event => {
154
192
  try {
155
193
  if (_event.id === config.hueDevice) {
@@ -187,6 +225,17 @@ module.exports = function (RED) {
187
225
  // Send to KNX bus
188
226
  if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
189
227
  }
228
+ if (_event.hasOwnProperty('color_temperature')) {
229
+ knxMsgPayload.topic = config.GALightHSVState
230
+ knxMsgPayload.dpt = config.dptLightHSVState
231
+ if (config.dptLightHSVState === '5.001') {
232
+ //NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
233
+ let NewValue = (((_event.color_temperature.mirek - 153) * (100 - 0)) / (500 - 153)) + 0
234
+ knxMsgPayload.payload = NewValue
235
+ }
236
+ // Send to KNX bus
237
+ if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
238
+ }
190
239
  node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX State ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
191
240
  }
192
241
  } catch (error) {
@@ -4,9 +4,10 @@
4
4
  const hueApiV2 = require('node-hue')
5
5
  const { EventEmitter } = require('events')
6
6
  const https = require('https')
7
+ const EventSource = require('eventsource');
7
8
 
8
9
  class classHUE extends EventEmitter {
9
- constructor (_hueBridgeIP, _username, _clientkey, _bridgeid) {
10
+ constructor(_hueBridgeIP, _username, _clientkey, _bridgeid) {
10
11
  super()
11
12
  this.setup(_hueBridgeIP, _username, _clientkey, _bridgeid)
12
13
  }
@@ -19,6 +20,8 @@ class classHUE extends EventEmitter {
19
20
  this.commandQueue = []
20
21
  this.closePushEventStream = false
21
22
  this.timerwriteQueueAdd = setTimeout(this.handleQueue, 3000) // First start
23
+ this.connect()
24
+
22
25
  // this.run()
23
26
  // start the SSE Stream Receiver
24
27
  // #############################################
@@ -80,28 +83,84 @@ class classHUE extends EventEmitter {
80
83
  // // Starts the connection for the first time
81
84
  // req();
82
85
 
86
+
83
87
  // Eventstream Reader using hueApiV2
84
- const runStreamReader = async () => {
88
+ // const runStreamReader = async () => {
89
+ // try {
90
+ // const listener = (event) => {
91
+ // // console.log(event)
92
+ // event.data.forEach(element => {
93
+ // if (event.type === 'update') this.emit('event', element)
94
+ // })
95
+ // }
96
+ // const hueEventStream = hueApiV2.connect({
97
+ // host: this.hueBridgeIP,
98
+ // key: this.username,
99
+ // eventListener: listener
100
+ // })
101
+ // } catch (error) {
102
+ // console.log('KNXUltimateHUEConfig: classHUE: const run = async: ' + error.message)
103
+ // }
104
+ // }
105
+ // runStreamReader()
106
+
107
+
108
+
109
+ // #############################################
110
+ }
111
+
112
+
113
+ connect() {
114
+ // #############################################
115
+ const options = {
116
+ headers: {
117
+ 'hue-application-key': this.username,
118
+ },
119
+ https: {
120
+ rejectUnauthorized: false,
121
+ },
122
+ };
123
+
124
+ this.es = new EventSource('https://' + this.hueBridgeIP + '/eventstream/clip/v2', options);
125
+
126
+ this.es.onmessage = (event) => {
85
127
  try {
86
- const listener = (event) => {
87
- // console.log(event)
88
- event.data.forEach(element => {
89
- if (event.type === 'update') this.emit('event', element)
128
+ if (event && event.type === 'message' && event.data) {
129
+ const data = JSON.parse(event.data);
130
+ data.forEach(element => {
131
+ if (element.type === 'update') {
132
+ element.data.forEach(ev => {
133
+ this.emit('event', ev)
134
+ })
135
+ }
90
136
  })
91
137
  }
92
- const hueEventStream = hueApiV2.connect({
93
- host: this.hueBridgeIP,
94
- key: this.username,
95
- eventListener: listener
96
- })
97
138
  } catch (error) {
98
- console.log('KNXUltimateHUEConfig: classHUE: const run = async: ' + error.message)
139
+ console.log('KNXUltimateHUEConfig: classHUE: this.es.onmessage: ' + error.message)
99
140
  }
141
+
142
+ };
143
+
144
+ this.es.onopen = () => {
145
+ //console.log('KNXUltimateHUEConfig: classHUE: SSE-Connected')
146
+ //this.emit('connected');
100
147
  }
101
- runStreamReader()
148
+ this.es.onerror = (error) => {
149
+ try {
150
+ this.es.close()
151
+ this.es = null
152
+ console.log('KNXUltimateHUEConfig: classHUE: request.on(error): ' + error.message)
153
+ setTimeout(() => {
154
+ this.connect()
155
+ }, 5000);
156
+ } catch (error) {
102
157
 
103
- // #############################################
158
+ }
159
+ //this.emit('error', err)
160
+ };
104
161
  }
162
+ // #############################################
163
+
105
164
 
106
165
  // Handle the send queue
107
166
  // ######################################
@@ -131,7 +190,7 @@ class classHUE extends EventEmitter {
131
190
  }
132
191
  }
133
192
  // The Hue bridge allows about 10 telegram per second, so i need to make a queue manager
134
- setTimeout(this.handleQueue, 100)
193
+ setTimeout(this.handleQueue, 150)
135
194
  }
136
195
 
137
196
  writeHueQueueAdd = async (_lightID, _state, _operation, _callback) => {
@@ -185,10 +244,22 @@ class classHUE extends EventEmitter {
185
244
  }
186
245
  }
187
246
 
247
+ // Get the light details
248
+ getLightStatus = async (_rid) => {
249
+ try {
250
+ const hue = hueApiV2.connect({ host: this.hueBridgeIP, key: this.username })
251
+ const oLight = await hue.getLight(_rid)
252
+ return oLight[0]
253
+ } catch (error) {
254
+ }
255
+ }
256
+
188
257
  close = async () => {
189
258
  return new Promise((resolve, reject) => {
190
259
  try {
191
260
  this.closePushEventStream = true
261
+ this.es.close();
262
+ this.es = null;
192
263
  setTimeout(() => {
193
264
  resolve(true)
194
265
  }, 1000)
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=16.0.0"
5
5
  },
6
- "version": "2.1.11",
6
+ "version": "2.1.12",
7
7
  "description": "Control your KNX intallation via Node-Red! Single Node KNX IN/OUT with optional ETS group address importer. Easy to use and highly configurable. With integrated Philips HUE devices handling.",
8
8
  "dependencies": {
9
9
  "mkdirp": "1.0.4",
@@ -18,7 +18,8 @@
18
18
  "dns-sync": "0.2.1",
19
19
  "node-hue-api": "5.0.0-beta.16",
20
20
  "node-hue": "1.0.4",
21
- "color-convert": "2.0.1"
21
+ "color-convert": "2.0.1",
22
+ "eventsource": "2.0.2"
22
23
  },
23
24
  "node-red": {
24
25
  "version": ">=2.0.0",