node-red-contrib-knx-ultimate 2.1.37 → 2.1.39
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 +12 -0
- package/nodes/hue-config.js +1 -1
- package/nodes/knxUltimate-config.js +1 -1
- package/nodes/knxUltimate.js +1 -1
- package/nodes/knxUltimateHueBattery.js +2 -2
- package/nodes/knxUltimateHueLight.html +75 -50
- package/nodes/knxUltimateHueLight.js +12 -2
- package/nodes/knxUltimateHueLightSensor.js +5 -5
- package/nodes/knxUltimateHueTemperatureSensor.js +1 -1
- package/nodes/knxUltimateLoadControl.js +1 -1
- package/nodes/utils/hueEngine.js +135 -125
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
|
+
<p>
|
|
10
|
+
<b>Version 2.1.39</b> - August 2023<br/>
|
|
11
|
+
- KNX-Ultimate Node: fidex an issue with the topic, in case of update the value without writing on the KNX bus.<br/>
|
|
12
|
+
- NEW: HUE Light: now you can set the color temperature, using datapoint 5.001 as well.<br/>
|
|
13
|
+
- NEW: HUE Light: the node will now disable parts of the UI, based on the capabilities of the HUE lamp.<br/>
|
|
14
|
+
- HUE nodes now wait 15 seconds before getting status and updating KNX devices, after node-red restart. <br/>
|
|
15
|
+
</p>
|
|
16
|
+
<p>
|
|
17
|
+
<b>Version 2.1.38</b> - August 2023<br/>
|
|
18
|
+
- Strenghten HUE eventsource resiliency.<br/>
|
|
19
|
+
- Implemented standard logging on all HUE nodes (there was temporary console.log statements).<br/>
|
|
20
|
+
</p>
|
|
9
21
|
<p>
|
|
10
22
|
<b>Version 2.1.37</b> - July 2023<br/>
|
|
11
23
|
- Load control: added msg.shedding to force shed/unshed.<br/>
|
package/nodes/hue-config.js
CHANGED
|
@@ -64,7 +64,7 @@ module.exports = (RED) => {
|
|
|
64
64
|
node.name = (config.name === undefined || config.name === '') ? node.host : config.name // 12/08/2021
|
|
65
65
|
|
|
66
66
|
// Init HUE Utility
|
|
67
|
-
node.hueManager = new hueClass(node.host, node.credentials.username, node.credentials.clientkey, config.bridgeid)
|
|
67
|
+
node.hueManager = new hueClass(node.host, node.credentials.username, node.credentials.clientkey, config.bridgeid, node.sysLogger)
|
|
68
68
|
|
|
69
69
|
// Event clip V2
|
|
70
70
|
node.hueManager.on('event', _event => {
|
package/nodes/knxUltimate.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = function (RED) {
|
|
|
13
13
|
return
|
|
14
14
|
}
|
|
15
15
|
node.topic = config.topic
|
|
16
|
-
node.outputtopic = (
|
|
16
|
+
node.outputtopic = (config.outputtopic === undefined || config.outputtopic === '') ? config.topic : config.outputtopic // 07/02/2020 Importante, per retrocompatibilità
|
|
17
17
|
node.dpt = config.dpt || '1.001'
|
|
18
18
|
node.notifyreadrequest = config.notifyreadrequest || false
|
|
19
19
|
node.notifyreadrequestalsorespondtobus = config.notifyreadrequestalsorespondtobus || 'false' // Auto respond if notifireadrequest is true
|
|
@@ -78,12 +78,12 @@ module.exports = function (RED) {
|
|
|
78
78
|
if (node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
79
79
|
(async () => {
|
|
80
80
|
try {
|
|
81
|
+
node.serverHue.addClient(node)
|
|
81
82
|
await node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, null, 'getBattery', (jLight) => {
|
|
82
|
-
node.serverHue.addClient(node)
|
|
83
83
|
node.handleSendHUE(jLight)
|
|
84
84
|
})
|
|
85
85
|
} catch (err) {
|
|
86
|
-
RED.log.error('Errore
|
|
86
|
+
RED.log.error('Errore knxUltimateHueBattery subscribe: ' + err.message)
|
|
87
87
|
}
|
|
88
88
|
})()
|
|
89
89
|
}
|
|
@@ -348,7 +348,7 @@
|
|
|
348
348
|
// ########################
|
|
349
349
|
$.getJSON('knxUltimateDpts', (data) => {
|
|
350
350
|
data.forEach(dpt => {
|
|
351
|
-
if (dpt.value === "3.007") {
|
|
351
|
+
if (dpt.value === "3.007" || dpt.value === "5.001") {
|
|
352
352
|
$("#node-input-dptLightHSV").append($("<option></option>")
|
|
353
353
|
.attr("value", dpt.value)
|
|
354
354
|
.text(dpt.text))
|
|
@@ -376,7 +376,7 @@
|
|
|
376
376
|
source: function (request, response) {
|
|
377
377
|
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
378
378
|
response($.map(data, function (value, key) {
|
|
379
|
-
if (value.dpt === "3.007") {
|
|
379
|
+
if (value.dpt === "3.007" || value.dpt === "5.001") {
|
|
380
380
|
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
381
381
|
if (fullSearch(sSearch, request.term)) {
|
|
382
382
|
return {
|
|
@@ -688,8 +688,8 @@
|
|
|
688
688
|
var sSearch = (value.name);
|
|
689
689
|
if (fullSearch(sSearch, request.term)) {
|
|
690
690
|
return {
|
|
691
|
-
hueDevice: value.id,
|
|
692
|
-
value: value.name
|
|
691
|
+
hueDevice: value.id,
|
|
692
|
+
value: value.name
|
|
693
693
|
}
|
|
694
694
|
} else {
|
|
695
695
|
return null;
|
|
@@ -706,6 +706,27 @@
|
|
|
706
706
|
}
|
|
707
707
|
});
|
|
708
708
|
|
|
709
|
+
// Get the HUE capabilities to enable/disable UI parts
|
|
710
|
+
$.getJSON("KNXUltimateGetResourcesHUE?rtype=light&nodeID=" + oNodeServerHue.id, (data) => {
|
|
711
|
+
data.devices.forEach(element => {
|
|
712
|
+
if (element.id === this.hueDevice.split('#')[0] && element.deviceObject !== undefined) {
|
|
713
|
+
// Check dimming
|
|
714
|
+
if (element.deviceObject.dimming === undefined){
|
|
715
|
+
$('#tabs').tabs("disable", "#tabs-2");
|
|
716
|
+
$('#divColorsAtSwitchOn').hide()
|
|
717
|
+
$('#divColorCycle').hide()
|
|
718
|
+
}
|
|
719
|
+
// Check color
|
|
720
|
+
if (element.deviceObject.color_temperature === undefined) $('#tabs').tabs("disable", "#tabs-3");
|
|
721
|
+
if (element.deviceObject.color === undefined){
|
|
722
|
+
$('#tabs').tabs("disable", "#tabs-4");
|
|
723
|
+
$('#divColorsAtSwitchOn').hide()
|
|
724
|
+
$('#divColorCycle').hide()
|
|
725
|
+
}
|
|
726
|
+
return
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
});
|
|
709
730
|
|
|
710
731
|
// ########################
|
|
711
732
|
|
|
@@ -802,45 +823,47 @@
|
|
|
802
823
|
<label for="node-input-nameLightState" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
803
824
|
<input type="text" id="node-input-nameLightState" style="width:190px;margin-left: 5px; text-align: left;">
|
|
804
825
|
</div>
|
|
805
|
-
<div
|
|
806
|
-
<label for="node-input-colorAtSwitchOnDayTime" style="width:260px">
|
|
807
|
-
<i class="fa fa-sun-o"></i> Switch On - color/brightness
|
|
808
|
-
</label>
|
|
809
|
-
<input type="text" id="node-input-colorAtSwitchOnDayTime" placeholder='Leave blank or, for example, {"red":255, "green":255, "blue":255}'
|
|
810
|
-
style="width:260px">
|
|
811
|
-
</div>
|
|
812
|
-
<div class="form-row">
|
|
813
|
-
<label style="width:170px" for="node-input-enableDayNightLighting">
|
|
814
|
-
<i class="fa fa-clone"></i> Night Lighting
|
|
815
|
-
</label>
|
|
816
|
-
<input type="checkbox" id="node-input-enableDayNightLighting" style="display:inline-block; width:auto; vertical-align:top;" />
|
|
817
|
-
</div>
|
|
818
|
-
<div id="DivEnableDayNightLighting">
|
|
826
|
+
<div id="divColorsAtSwitchOn">
|
|
819
827
|
<div class="form-row">
|
|
820
|
-
<label for="node-input-
|
|
821
|
-
<i class="fa fa-
|
|
828
|
+
<label for="node-input-colorAtSwitchOnDayTime" style="width:260px">
|
|
829
|
+
<i class="fa fa-sun-o"></i> Switch On - color/brightness
|
|
822
830
|
</label>
|
|
823
|
-
<input type="text" id="node-input-
|
|
831
|
+
<input type="text" id="node-input-colorAtSwitchOnDayTime" placeholder='Leave blank or, for example, {"red":255, "green":255, "blue":255}'
|
|
824
832
|
style="width:260px">
|
|
825
833
|
</div>
|
|
826
834
|
<div class="form-row">
|
|
827
|
-
<label
|
|
828
|
-
|
|
829
|
-
<label for="node-input-GADaylightSensor" style="width:20px;">GA</label>
|
|
830
|
-
<input type="text" id="node-input-GADaylightSensor" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
831
|
-
|
|
832
|
-
<label for="node-input-dptDaylightSensor" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
|
|
833
|
-
<select id="node-input-dptDaylightSensor" style="width:140px;"></select>
|
|
834
|
-
|
|
835
|
-
<label for="node-input-nameDaylightSensor" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
836
|
-
<input type="text" id="node-input-nameDaylightSensor" style="width:190px;margin-left: 5px; text-align: left;">
|
|
837
|
-
</div>
|
|
838
|
-
<div class="form-row">
|
|
839
|
-
<label style="width:170px" for="node-input-invertDayNight">
|
|
840
|
-
<i class="fa fa-shuffle"></i> Invert day/night values
|
|
835
|
+
<label style="width:170px" for="node-input-enableDayNightLighting">
|
|
836
|
+
<i class="fa fa-clone"></i> Night Lighting
|
|
841
837
|
</label>
|
|
842
|
-
<input type="checkbox" id="node-input-
|
|
843
|
-
|
|
838
|
+
<input type="checkbox" id="node-input-enableDayNightLighting" style="display:inline-block; width:auto; vertical-align:top;" />
|
|
839
|
+
</div>
|
|
840
|
+
<div id="DivEnableDayNightLighting">
|
|
841
|
+
<div class="form-row">
|
|
842
|
+
<label for="node-input-colorAtSwitchOnNightTime" style="width:260px">
|
|
843
|
+
<i class="fa fa-moon-o"></i> Switch On - color/brightness at Night
|
|
844
|
+
</label>
|
|
845
|
+
<input type="text" id="node-input-colorAtSwitchOnNightTime" placeholder='Example {"red":100, "green":0, "blue":50}'
|
|
846
|
+
style="width:260px">
|
|
847
|
+
</div>
|
|
848
|
+
<div class="form-row">
|
|
849
|
+
<label for="node-input-nameDaylightSensor" style="width:110px;"><i class="fa fa-clock-o"></i> Day/Night</label>
|
|
850
|
+
|
|
851
|
+
<label for="node-input-GADaylightSensor" style="width:20px;">GA</label>
|
|
852
|
+
<input type="text" id="node-input-GADaylightSensor" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
853
|
+
|
|
854
|
+
<label for="node-input-dptDaylightSensor" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
|
|
855
|
+
<select id="node-input-dptDaylightSensor" style="width:140px;"></select>
|
|
856
|
+
|
|
857
|
+
<label for="node-input-nameDaylightSensor" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
858
|
+
<input type="text" id="node-input-nameDaylightSensor" style="width:190px;margin-left: 5px; text-align: left;">
|
|
859
|
+
</div>
|
|
860
|
+
<div class="form-row">
|
|
861
|
+
<label style="width:170px" for="node-input-invertDayNight">
|
|
862
|
+
<i class="fa fa-shuffle"></i> Invert day/night values
|
|
863
|
+
</label>
|
|
864
|
+
<input type="checkbox" id="node-input-invertDayNight" style="display:inline-block; width:auto; vertical-align:top;" />
|
|
865
|
+
|
|
866
|
+
</div>
|
|
844
867
|
</div>
|
|
845
868
|
</div>
|
|
846
869
|
</p>
|
|
@@ -958,17 +981,19 @@
|
|
|
958
981
|
<input type="text" id="node-input-nameLightBlink" style="width:190px;margin-left: 5px; text-align: left;">
|
|
959
982
|
</div>
|
|
960
983
|
|
|
961
|
-
<div
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
984
|
+
<div id="divColorCycle">
|
|
985
|
+
<div class="form-row">
|
|
986
|
+
<label for="node-input-nameLightColorCycle" style="width:110px;"><i class="fa fa-play-circle-o"></i> Color Cycle</label>
|
|
987
|
+
|
|
988
|
+
<label for="node-input-GALightColorCycle" style="width:20px;">GA</label>
|
|
989
|
+
<input type="text" id="node-input-GALightColorCycle" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
990
|
+
|
|
991
|
+
<label for="node-input-dptLightColorCycle" style="width:40px; margin-left: 0px; text-align: right;">DPT</label>
|
|
992
|
+
<select id="node-input-dptLightColorCycle" style="width:140px;"></select>
|
|
993
|
+
|
|
994
|
+
<label for="node-input-nameLightColorCycle" style="width:50px; margin-left: 0px; text-align: right;"><span data-i18n="knxUltimateHueLight.node-input-name"></span></label>
|
|
995
|
+
<input type="text" id="node-input-nameLightColorCycle" style="width:190px;margin-left: 5px; text-align: left;">
|
|
996
|
+
</div>
|
|
972
997
|
</div>
|
|
973
998
|
</p>
|
|
974
999
|
</div>
|
|
@@ -1030,8 +1055,8 @@ Start typing in the GA field, the name or group address of your KNX device, the
|
|
|
1030
1055
|
**Tunable white**
|
|
1031
1056
|
|Property|Description|
|
|
1032
1057
|
|--|--|
|
|
1033
|
-
| Tunable white | This command is used to change the HUE light's white temperature. Datapoint is 3.007 dimming.
|
|
1034
|
-
| Tunable white Status | Link this to the light temperature status group address. Datapoint is 5.001 absolute value
|
|
1058
|
+
| Tunable white | This command is used to change the HUE light's white temperature. Datapoint is 3.007 dimming or 5.001 percentage. When set to 5.001, a value of 0 is full warm, a value of 100 is full cold .|
|
|
1059
|
+
| Tunable white Status | Link this to the light temperature status group address. Datapoint is 5.001 absolute value. 0 is full warm, 100 is full cold.|
|
|
1035
1060
|
|
|
1036
1061
|
<br/>
|
|
1037
1062
|
|
|
@@ -90,7 +90,7 @@ module.exports = function (RED) {
|
|
|
90
90
|
if (config.linkBrightnessToSwitchStatus === undefined || config.linkBrightnessToSwitchStatus === 'yes') {
|
|
91
91
|
state = { on: { on: false }, dimming: { brightness: 0 }, dynamics: { duration: 2000 } }
|
|
92
92
|
} else {
|
|
93
|
-
state = { on: { on: false } }
|
|
93
|
+
state = { on: { on: false }, dynamics: { duration: 2000 } }
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, (node.isGrouped_light === false ? 'setLight' : 'setGroupedLight'))
|
|
@@ -141,6 +141,15 @@ module.exports = function (RED) {
|
|
|
141
141
|
node.startDimStopperTunableWhite('stop')
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
if (config.dptLightHSV === '5.001') {
|
|
145
|
+
// 0-100% tunable white
|
|
146
|
+
msg.payload = 100 - dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightHSV))
|
|
147
|
+
const retMirek = hueColorConverter.ColorConverter.scale(msg.payload, [0, 100], [153, 500])
|
|
148
|
+
msg.payload = retMirek
|
|
149
|
+
state = { color_temperature: { mirek: msg.payload } }
|
|
150
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, (node.isGrouped_light === false ? 'setLight' : 'setGroupedLight'))
|
|
151
|
+
node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: state })
|
|
152
|
+
}
|
|
144
153
|
node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload })
|
|
145
154
|
break
|
|
146
155
|
case config.GALightBrightness:
|
|
@@ -401,9 +410,10 @@ module.exports = function (RED) {
|
|
|
401
410
|
if (node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
402
411
|
(async () => {
|
|
403
412
|
try {
|
|
413
|
+
node.serverHue.addClient(node)
|
|
404
414
|
await node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, null, (node.isGrouped_light === false ? 'getLight' : 'getGroupedLight'), (jLight) => {
|
|
405
415
|
node.currentHUEDevice = jLight
|
|
406
|
-
node.
|
|
416
|
+
node.setNodeStatusHue({ fill: 'blue', shape: 'ring', text: 'Connected. Is on: ', payload: node.currentHUEDevice.on.on === true })
|
|
407
417
|
})
|
|
408
418
|
} catch (err) {
|
|
409
419
|
RED.log.error('Errore knxUltimateHueLight node.currentHUEDevice ' + err.message)
|
|
@@ -74,17 +74,17 @@ module.exports = function (RED) {
|
|
|
74
74
|
}
|
|
75
75
|
if (node.serverHue) {
|
|
76
76
|
node.serverHue.removeClient(node)
|
|
77
|
-
// I must get the object, to store read the
|
|
77
|
+
// I must get the object, to store read the status
|
|
78
78
|
// I queue the state request, by passing the callback to call whenever the HUE bridge send me the light status async
|
|
79
79
|
if (node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
80
80
|
(async () => {
|
|
81
|
+
node.serverHue.addClient(node)
|
|
81
82
|
try {
|
|
82
|
-
|
|
83
|
-
node.serverHue.addClient(node)
|
|
83
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, null, 'getLightLevel', (jLight) => {
|
|
84
84
|
node.handleSendHUE(jLight)
|
|
85
85
|
})
|
|
86
|
-
} catch (
|
|
87
|
-
RED.log.error('Errore
|
|
86
|
+
} catch (error) {
|
|
87
|
+
RED.log.error('Errore knxUltimateHueLightSensor subscribing: ' + error.message)
|
|
88
88
|
}
|
|
89
89
|
})()
|
|
90
90
|
}
|
|
@@ -79,8 +79,8 @@ module.exports = function (RED) {
|
|
|
79
79
|
if (node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
80
80
|
(async () => {
|
|
81
81
|
try {
|
|
82
|
+
node.serverHue.addClient(node)
|
|
82
83
|
await node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, null, 'getTemperature', (jLight) => {
|
|
83
|
-
node.serverHue.addClient(node)
|
|
84
84
|
node.handleSendHUE(jLight)
|
|
85
85
|
})
|
|
86
86
|
} catch (err) {
|
package/nodes/utils/hueEngine.js
CHANGED
|
@@ -5,19 +5,20 @@ const http = require('./http.js')
|
|
|
5
5
|
const EventSource = require('eventsource');
|
|
6
6
|
|
|
7
7
|
class classHUE extends EventEmitter {
|
|
8
|
-
constructor(_hueBridgeIP, _username, _clientkey, _bridgeid) {
|
|
8
|
+
constructor(_hueBridgeIP, _username, _clientkey, _bridgeid, _sysLogger) {
|
|
9
9
|
super()
|
|
10
|
-
this.setup(_hueBridgeIP, _username, _clientkey, _bridgeid)
|
|
10
|
+
this.setup(_hueBridgeIP, _username, _clientkey, _bridgeid, _sysLogger)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
setup = async (_hueBridgeIP, _username, _clientkey, _bridgeid) => {
|
|
13
|
+
setup = async (_hueBridgeIP, _username, _clientkey, _bridgeid, _sysLogger) => {
|
|
14
14
|
this.hueBridgeIP = _hueBridgeIP
|
|
15
15
|
this.username = _username
|
|
16
16
|
this.clientkey = _clientkey
|
|
17
17
|
this.bridgeid = _bridgeid
|
|
18
18
|
this.commandQueue = []
|
|
19
19
|
this.closePushEventStream = false
|
|
20
|
-
this.timerwriteQueueAdd = setTimeout(this.handleQueue,
|
|
20
|
+
this.timerwriteQueueAdd = setTimeout(this.handleQueue, 15000) // First start. Allow the KNX to connect
|
|
21
|
+
this.sysLogger = _sysLogger
|
|
21
22
|
|
|
22
23
|
// #############################################
|
|
23
24
|
const options = {
|
|
@@ -25,8 +26,8 @@ class classHUE extends EventEmitter {
|
|
|
25
26
|
'hue-application-key': this.username,
|
|
26
27
|
},
|
|
27
28
|
https: {
|
|
28
|
-
rejectUnauthorized: false
|
|
29
|
-
}
|
|
29
|
+
rejectUnauthorized: false
|
|
30
|
+
}
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
// Connect
|
|
@@ -47,25 +48,21 @@ class classHUE extends EventEmitter {
|
|
|
47
48
|
})
|
|
48
49
|
}
|
|
49
50
|
} catch (error) {
|
|
50
|
-
|
|
51
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: this.es.onmessage: ' + error.message)
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
this.es.onopen = () => {
|
|
56
|
-
//
|
|
57
|
+
//if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: SSE-Connected')
|
|
57
58
|
//this.emit('connected');
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
this.es.onerror = (error) => {
|
|
61
62
|
try {
|
|
62
|
-
if (this.timerReconnect !== undefined) clearTimeout(this.timerReconnect)
|
|
63
|
-
this.timerReconnect = setTimeout(() => {
|
|
64
|
-
this.connect()
|
|
65
|
-
}, 5000)
|
|
66
63
|
this.es.close()
|
|
67
64
|
this.es = null
|
|
68
|
-
|
|
65
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: request.on(error): ' + error.message)
|
|
69
66
|
} catch (error) {
|
|
70
67
|
}
|
|
71
68
|
//this.emit('error', err)
|
|
@@ -83,12 +80,20 @@ class classHUE extends EventEmitter {
|
|
|
83
80
|
this.hueAllRooms = await this.hueApiV2.get('/resource/room')
|
|
84
81
|
this.hueAllDevices = await this.hueApiV2.get('/resource/device')
|
|
85
82
|
} catch (error) {
|
|
86
|
-
if (this.
|
|
87
|
-
this.timerReconnect = setTimeout(() => {
|
|
88
|
-
this.connect()
|
|
89
|
-
}, 5000)
|
|
90
|
-
console.log('KNXUltimatehueEngine: classHUE: this.hueApiV2 = await http.use: ' + error.message)
|
|
83
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: this.hueApiV2 = await http.use: ' + error.message)
|
|
91
84
|
}
|
|
85
|
+
|
|
86
|
+
// 31/07/2023 Every now and then, restart the connection to the eventsource, because it can goes down without knowing that
|
|
87
|
+
if (this.timerReconnect !== undefined) clearInterval(this.timerReconnect)
|
|
88
|
+
this.timerReconnect = setInterval(() => {
|
|
89
|
+
try {
|
|
90
|
+
this.es.close()
|
|
91
|
+
this.es = null
|
|
92
|
+
} catch (error) {
|
|
93
|
+
}
|
|
94
|
+
this.connect()
|
|
95
|
+
}, 300000)
|
|
96
|
+
|
|
92
97
|
}
|
|
93
98
|
// First connection
|
|
94
99
|
this.connect()
|
|
@@ -101,6 +106,7 @@ class classHUE extends EventEmitter {
|
|
|
101
106
|
// ######################################
|
|
102
107
|
handleQueue = async () => {
|
|
103
108
|
if (this.commandQueue.length > 0) {
|
|
109
|
+
//const jRet = { ...this.commandQueue.shift() } //Clone the object by value
|
|
104
110
|
const jRet = this.commandQueue.shift()
|
|
105
111
|
switch (jRet._operation) {
|
|
106
112
|
case 'setLight':
|
|
@@ -108,14 +114,14 @@ class classHUE extends EventEmitter {
|
|
|
108
114
|
try {
|
|
109
115
|
const ok = await this.hueApiV2.put('/resource/light/' + jRet._lightID, jRet._state)
|
|
110
116
|
} catch (error) {
|
|
111
|
-
|
|
117
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.info('KNXUltimatehueEngine: classHUE: handleQueue: setLight light: ' + error.message)
|
|
112
118
|
}
|
|
113
119
|
break
|
|
114
120
|
case 'setGroupedLight':
|
|
115
121
|
try {
|
|
116
122
|
const ok = await this.hueApiV2.put('/resource/grouped_light/' + jRet._lightID, jRet._state)
|
|
117
123
|
} catch (error) {
|
|
118
|
-
|
|
124
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.info('KNXUltimatehueEngine: classHUE: handleQueue: setLight grouped_light: ' + error.message)
|
|
119
125
|
}
|
|
120
126
|
break
|
|
121
127
|
case 'getLight':
|
|
@@ -123,7 +129,7 @@ class classHUE extends EventEmitter {
|
|
|
123
129
|
const jReturn = await this.hueApiV2.get('/resource/light/' + jRet._lightID)
|
|
124
130
|
jRet._callback(jReturn[0]) // Need to call the callback, because the event is absolutely async
|
|
125
131
|
} catch (error) {
|
|
126
|
-
|
|
132
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.info('KNXUltimatehueEngine: classHUE: handleQueue: getLight light: ' + error.message)
|
|
127
133
|
}
|
|
128
134
|
break
|
|
129
135
|
case 'getGroupedLight':
|
|
@@ -131,7 +137,7 @@ class classHUE extends EventEmitter {
|
|
|
131
137
|
const jReturn = await this.hueApiV2.get('/resource/grouped_light/' + jRet._lightID)
|
|
132
138
|
jRet._callback(jReturn[0]) // Need to call the callback, because the event is absolutely async
|
|
133
139
|
} catch (error) {
|
|
134
|
-
|
|
140
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.info('KNXUltimatehueEngine: classHUE: handleQueue: getLight grouped_light: ' + error.message)
|
|
135
141
|
}
|
|
136
142
|
break
|
|
137
143
|
case 'setScene':
|
|
@@ -139,7 +145,7 @@ class classHUE extends EventEmitter {
|
|
|
139
145
|
const sceneID = jRet._lightID
|
|
140
146
|
const ok = await this.hueApiV2.put('/resource/scene/' + sceneID, jRet._state)
|
|
141
147
|
} catch (error) {
|
|
142
|
-
|
|
148
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.info('KNXUltimatehueEngine: classHUE: handleQueue: setScene: ' + error.message)
|
|
143
149
|
}
|
|
144
150
|
break
|
|
145
151
|
case 'stopScene':
|
|
@@ -152,7 +158,7 @@ class classHUE extends EventEmitter {
|
|
|
152
158
|
this.writeHueQueueAdd(light.rid, jRet._state, 'setLight')
|
|
153
159
|
});
|
|
154
160
|
} catch (error) {
|
|
155
|
-
|
|
161
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: handleQueue: stopScene: ' + error.message)
|
|
156
162
|
}
|
|
157
163
|
break
|
|
158
164
|
case 'getBattery':
|
|
@@ -160,14 +166,14 @@ class classHUE extends EventEmitter {
|
|
|
160
166
|
const jReturn = await this.hueApiV2.get('/resource/device_power/' + jRet._lightID)
|
|
161
167
|
jRet._callback(jReturn[0]) // Need to call the callback, because the event is absolutely async
|
|
162
168
|
} catch (error) {
|
|
163
|
-
|
|
169
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: handleQueue: getBattery: ' + error.message)
|
|
164
170
|
}
|
|
165
171
|
case 'getLightLevel':
|
|
166
172
|
try {
|
|
167
173
|
const jReturn = await this.hueApiV2.get('/resource/light_level/' + jRet._lightID)
|
|
168
174
|
jRet._callback(jReturn[0]) // Need to call the callback, because the event is absolutely async
|
|
169
175
|
} catch (error) {
|
|
170
|
-
|
|
176
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: handleQueue: getLightLevel: ' + error.message)
|
|
171
177
|
}
|
|
172
178
|
break
|
|
173
179
|
case 'getTemperature':
|
|
@@ -175,123 +181,127 @@ class classHUE extends EventEmitter {
|
|
|
175
181
|
const jReturn = await this.hueApiV2.get('/resource/temperature/' + jRet._lightID)
|
|
176
182
|
jRet._callback(jReturn[0]) // Need to call the callback, because the event is absolutely async
|
|
177
183
|
} catch (error) {
|
|
178
|
-
|
|
184
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimatehueEngine: classHUE: handleQueue: getTemperature: ' + error.message)
|
|
179
185
|
}
|
|
180
186
|
break
|
|
181
187
|
default:
|
|
182
188
|
break
|
|
183
189
|
}
|
|
184
190
|
}
|
|
185
|
-
|
|
186
|
-
setTimeout(this.handleQueue, 100)
|
|
191
|
+
// The Hue bridge allows about 10 telegram per second, so i need to make a queue manager
|
|
192
|
+
this.timerwriteQueueAdd = setTimeout(this.handleQueue, 100)
|
|
187
193
|
}
|
|
188
194
|
|
|
189
|
-
writeHueQueueAdd = async (_lightID, _state, _operation, _callback) => {
|
|
190
|
-
// Add the new item
|
|
191
|
-
this.commandQueue.push({ _lightID, _state, _operation, _callback })
|
|
192
|
-
}
|
|
193
|
-
// ######################################
|
|
194
195
|
|
|
195
196
|
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
writeHueQueueAdd = async (_lightID, _state, _operation, _callback) => {
|
|
199
|
+
// Add the new item
|
|
200
|
+
this.commandQueue.push({ _lightID, _state, _operation, _callback })
|
|
201
|
+
}
|
|
202
|
+
// ######################################
|
|
201
203
|
|
|
202
|
-
// Returns capitalized string
|
|
203
|
-
function capStr(s) {
|
|
204
|
-
if (typeof s !== 'string') return ''
|
|
205
|
-
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
206
|
-
}
|
|
207
204
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
let resourceName = ''
|
|
220
|
-
let sRoom = ''
|
|
221
|
-
if (_rtype === 'light' || _rtype === 'grouped_light') {
|
|
222
|
-
// It's a service, having a owner
|
|
223
|
-
const owners = await this.hueAllResources.filter(a => a.id === resource.owner.rid)
|
|
224
|
-
for (let index = 0; index < owners.length; index++) {
|
|
225
|
-
const owner = owners[index];
|
|
226
|
-
//resourceName += (owner.metadata !== undefined && owner.metadata.name !== undefined) ? owner.metadata.name + ' and ' : ' and '
|
|
227
|
-
resourceName += owner.metadata.name + ' and '
|
|
228
|
-
const room = await this.hueAllRooms.find(child => child.children.find(a => a.rid === owner.id))
|
|
229
|
-
sRoom += room !== undefined ? room.metadata.name + ' + ' : ' + '
|
|
230
|
-
}
|
|
231
|
-
sRoom = sRoom.slice(0, -(' + '.length))
|
|
232
|
-
resourceName = resourceName.slice(0, -(' and '.length))
|
|
233
|
-
resourceName += sRoom !== '' ? ' - Room: ' + sRoom : ''
|
|
234
|
-
retArray.push({ name: capStr(resource.type) + ': ' + resourceName, id: resource.id })
|
|
235
|
-
}
|
|
236
|
-
if (_rtype === 'scene') {
|
|
237
|
-
resourceName = resource.metadata.name || '**Name Not Found**'
|
|
238
|
-
// Get the linked zone
|
|
239
|
-
const zone = await this.hueAllResources.find(res => res.id === resource.group.rid)
|
|
240
|
-
resourceName += ' - ' + capStr(resource.group.rtype) + ': ' + zone.metadata.name
|
|
241
|
-
retArray.push({ name: capStr(_rtype) + ': ' + resourceName, id: resource.id })
|
|
242
|
-
}
|
|
243
|
-
if (_rtype === 'button') {
|
|
244
|
-
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
245
|
-
const controlID = resource.metadata !== undefined ? (resource.metadata.control_id || '') : ''
|
|
246
|
-
retArray.push({ name: capStr(_rtype) + ': ' + linkedDevName + ', button ' + controlID, id: resource.id })
|
|
247
|
-
}
|
|
248
|
-
if (_rtype === 'motion') {
|
|
249
|
-
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
250
|
-
retArray.push({ name: capStr(_rtype) + ': ' + linkedDevName, id: resource.id })
|
|
251
|
-
}
|
|
252
|
-
if (_rtype === 'relative_rotary') {
|
|
253
|
-
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
254
|
-
retArray.push({ name: 'Rotary: ' + linkedDevName, id: resource.id })
|
|
255
|
-
}
|
|
256
|
-
if (_rtype === 'light_level') {
|
|
257
|
-
let Room = await this.hueAllRooms.find(room => room.children.find(child => child.rid === resource.owner.rid))
|
|
258
|
-
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
259
|
-
retArray.push({ name: 'Light Level: ' + linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: resource.id })
|
|
260
|
-
}
|
|
261
|
-
if (_rtype === 'temperature') {
|
|
262
|
-
let Room = await this.hueAllRooms.find(room => room.children.find(child => child.rid === resource.owner.rid))
|
|
263
|
-
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
264
|
-
retArray.push({ name: 'Temperature: ' + linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: resource.id })
|
|
265
|
-
}
|
|
266
|
-
if (_rtype === 'device_power') {
|
|
267
|
-
let Room = await this.hueAllRooms.find(room => room.children.find(child => child.rid === resource.owner.rid))
|
|
268
|
-
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
269
|
-
retArray.push({ name: 'Battery: ' + linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: resource.id })
|
|
270
|
-
}
|
|
271
|
-
} catch (error) {
|
|
272
|
-
//retArray.push({ name: _rtype + ': ERROR ' + error.message, id: resource.id })
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return { devices: retArray }
|
|
276
|
-
} catch (error) {
|
|
277
|
-
console.log('KNXUltimateHue: hueEngine: classHUE: getDevices: error ' + error.message)
|
|
278
|
-
return ({ devices: error.message })
|
|
205
|
+
|
|
206
|
+
// Get all devices and join it with relative rooms, by adding the room name to the device name
|
|
207
|
+
getResources = async (_rtype, _host, _username) => {
|
|
208
|
+
try {
|
|
209
|
+
// Api V2
|
|
210
|
+
|
|
211
|
+
// Returns capitalized string
|
|
212
|
+
function capStr(s) {
|
|
213
|
+
if (typeof s !== 'string') return ''
|
|
214
|
+
return s.charAt(0).toUpperCase() + s.slice(1)
|
|
279
215
|
}
|
|
280
|
-
}
|
|
281
216
|
|
|
282
|
-
|
|
283
|
-
|
|
217
|
+
const retArray = []
|
|
218
|
+
let allResources = undefined
|
|
219
|
+
if (_rtype === 'light' || _rtype === 'grouped_light')
|
|
220
|
+
allResources = await this.hueAllResources.filter(a => a.type === 'light' || a.type === 'grouped_light')
|
|
221
|
+
else {
|
|
222
|
+
allResources = await this.hueAllResources.filter(a => a.type === _rtype)
|
|
223
|
+
}
|
|
224
|
+
for (let index = 0; index < allResources.length; index++) {
|
|
225
|
+
const resource = allResources[index];
|
|
226
|
+
// Get the owner
|
|
284
227
|
try {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
228
|
+
let resourceName = ''
|
|
229
|
+
let sRoom = ''
|
|
230
|
+
if (_rtype === 'light' || _rtype === 'grouped_light') {
|
|
231
|
+
// It's a service, having a owner
|
|
232
|
+
const owners = await this.hueAllResources.filter(a => a.id === resource.owner.rid)
|
|
233
|
+
for (let index = 0; index < owners.length; index++) {
|
|
234
|
+
const owner = owners[index];
|
|
235
|
+
//resourceName += (owner.metadata !== undefined && owner.metadata.name !== undefined) ? owner.metadata.name + ' and ' : ' and '
|
|
236
|
+
resourceName += owner.metadata.name + ' and '
|
|
237
|
+
const room = await this.hueAllRooms.find(child => child.children.find(a => a.rid === owner.id))
|
|
238
|
+
sRoom += room !== undefined ? room.metadata.name + ' + ' : ' + '
|
|
239
|
+
}
|
|
240
|
+
sRoom = sRoom.slice(0, -(' + '.length))
|
|
241
|
+
resourceName = resourceName.slice(0, -(' and '.length))
|
|
242
|
+
resourceName += sRoom !== '' ? ' - Room: ' + sRoom : ''
|
|
243
|
+
retArray.push({ name: capStr(resource.type) + ': ' + resourceName, id: resource.id, deviceObject:resource })
|
|
244
|
+
}
|
|
245
|
+
if (_rtype === 'scene') {
|
|
246
|
+
resourceName = resource.metadata.name || '**Name Not Found**'
|
|
247
|
+
// Get the linked zone
|
|
248
|
+
const zone = await this.hueAllResources.find(res => res.id === resource.group.rid)
|
|
249
|
+
resourceName += ' - ' + capStr(resource.group.rtype) + ': ' + zone.metadata.name
|
|
250
|
+
retArray.push({ name: capStr(_rtype) + ': ' + resourceName, id: resource.id })
|
|
251
|
+
}
|
|
252
|
+
if (_rtype === 'button') {
|
|
253
|
+
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
254
|
+
const controlID = resource.metadata !== undefined ? (resource.metadata.control_id || '') : ''
|
|
255
|
+
retArray.push({ name: capStr(_rtype) + ': ' + linkedDevName + ', button ' + controlID, id: resource.id })
|
|
256
|
+
}
|
|
257
|
+
if (_rtype === 'motion') {
|
|
258
|
+
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
259
|
+
retArray.push({ name: capStr(_rtype) + ': ' + linkedDevName, id: resource.id })
|
|
260
|
+
}
|
|
261
|
+
if (_rtype === 'relative_rotary') {
|
|
262
|
+
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
263
|
+
retArray.push({ name: 'Rotary: ' + linkedDevName, id: resource.id })
|
|
264
|
+
}
|
|
265
|
+
if (_rtype === 'light_level') {
|
|
266
|
+
let Room = await this.hueAllRooms.find(room => room.children.find(child => child.rid === resource.owner.rid))
|
|
267
|
+
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
268
|
+
retArray.push({ name: 'Light Level: ' + linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: resource.id })
|
|
269
|
+
}
|
|
270
|
+
if (_rtype === 'temperature') {
|
|
271
|
+
let Room = await this.hueAllRooms.find(room => room.children.find(child => child.rid === resource.owner.rid))
|
|
272
|
+
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
273
|
+
retArray.push({ name: 'Temperature: ' + linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: resource.id })
|
|
274
|
+
}
|
|
275
|
+
if (_rtype === 'device_power') {
|
|
276
|
+
let Room = await this.hueAllRooms.find(room => room.children.find(child => child.rid === resource.owner.rid))
|
|
277
|
+
let linkedDevName = await this.hueAllResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === resource.id)).metadata.name || ''
|
|
278
|
+
retArray.push({ name: 'Battery: ' + linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: resource.id })
|
|
279
|
+
}
|
|
291
280
|
} catch (error) {
|
|
292
|
-
|
|
281
|
+
//retArray.push({ name: _rtype + ': ERROR ' + error.message, id: resource.id })
|
|
293
282
|
}
|
|
294
|
-
}
|
|
283
|
+
}
|
|
284
|
+
return { devices: retArray }
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (this.sysLogger !== undefined && this.sysLogger !== null) this.sysLogger.error('KNXUltimateHue: hueEngine: classHUE: getDevices: error ' + error.message)
|
|
287
|
+
return ({ devices: error.message })
|
|
295
288
|
}
|
|
296
289
|
}
|
|
290
|
+
|
|
291
|
+
close = async () => {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
try {
|
|
294
|
+
if (this.timerReconnect !== undefined) clearInterval(this.timerReconnect)
|
|
295
|
+
this.closePushEventStream = true
|
|
296
|
+
if (this.es !== null) this.es.close();
|
|
297
|
+
this.es = null;
|
|
298
|
+
setTimeout(() => {
|
|
299
|
+
resolve(true)
|
|
300
|
+
}, 500)
|
|
301
|
+
} catch (error) {
|
|
302
|
+
reject(error)
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
}
|
|
297
307
|
module.exports.classHUE = classHUE
|
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.39",
|
|
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": "3.0.1",
|