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 +4 -0
- package/nodes/knxUltimateHueLight.html +41 -11
- package/nodes/knxUltimateHueLight.js +51 -2
- package/nodes/utils/hueUtils.js +86 -15
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -331,23 +331,30 @@
|
|
|
331
331
|
|
|
332
332
|
|
|
333
333
|
|
|
334
|
-
// DPT dptLightTunableWhite
|
|
334
|
+
// DPT dptLightTunableWhite
|
|
335
335
|
// ########################
|
|
336
336
|
$.getJSON('knxUltimateDpts', (data) => {
|
|
337
337
|
data.forEach(dpt => {
|
|
338
|
-
if (dpt.value === "3.007"
|
|
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"
|
|
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 === "
|
|
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.
|
|
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
|
|
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
|
|
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) {
|
package/nodes/utils/hueUtils.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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",
|