node-red-contrib-knx-ultimate 2.1.5 → 2.1.6
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 +5 -1
- package/nodes/knxUltimateHueLight.js +21 -29
- package/nodes/knxUltimateHueTapDial.html +1 -1
- package/nodes/knxUltimateHueTapDial.js +22 -41
- package/nodes/utils/hueUtils.js +24 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
# CHANGELOG
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
|
-
<b>Version 2.1.
|
|
10
|
+
<b>Version 2.1.6</b> - June 2023<br/>
|
|
11
|
+
- Several fixes for reading the correct GAMUT color.<br/>
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
<b>Version 2.1.4</b> - June 2023<br/>
|
|
11
15
|
- NEW: Hue light node: added random color cycle effect group address.<br/>
|
|
12
16
|
- Fixed destroying KNX nodes.<br/>
|
|
13
17
|
- Fixed destroying HUE nodes.<br/>
|
|
@@ -47,7 +47,7 @@ module.exports = function (RED) {
|
|
|
47
47
|
case config.GALightSwitch:
|
|
48
48
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
|
|
49
49
|
state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
|
|
50
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state)
|
|
50
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setLight')
|
|
51
51
|
break
|
|
52
52
|
case config.GALightDIM:
|
|
53
53
|
// { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
|
|
@@ -64,17 +64,16 @@ module.exports = function (RED) {
|
|
|
64
64
|
case config.GALightBrightness:
|
|
65
65
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
|
|
66
66
|
state = { dimming: { brightness: msg.payload } }
|
|
67
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state)
|
|
67
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setLight')
|
|
68
68
|
break
|
|
69
69
|
case config.GALightColor:
|
|
70
70
|
// Behavior like ISE HUE CONNECT, by setting the brightness and on/off as well
|
|
71
|
-
if (node.currentHUEDevice === undefined) return
|
|
72
71
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColor))
|
|
73
|
-
const gamut = node.currentHUEDevice.color.gamut_type
|
|
72
|
+
const gamut = node.currentHUEDevice !== undefined ? node.currentHUEDevice.color.gamut_type : null
|
|
74
73
|
const retXY = hueColorConverter.ColorConverter.rgbToXy(msg.payload.red, msg.payload.green, msg.payload.blue, gamut)
|
|
75
74
|
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue)
|
|
76
75
|
state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } }
|
|
77
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state)
|
|
76
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setLight')
|
|
78
77
|
break
|
|
79
78
|
case config.GALightBlink:
|
|
80
79
|
const gaVal = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
|
|
@@ -85,19 +84,19 @@ module.exports = function (RED) {
|
|
|
85
84
|
msg.payload = node.blinkValue
|
|
86
85
|
//state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
|
|
87
86
|
state = msg.payload === true ? { on: { on: true }, dimming: { brightness: 100 } } : { on: { on: false } }
|
|
88
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state)
|
|
89
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state) // It's ok twice, so the light turns off immeridaley
|
|
87
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setLight')
|
|
88
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setLight') // It's ok twice, so the light turns off immeridaley
|
|
90
89
|
}, 600);
|
|
91
90
|
} else {
|
|
92
91
|
if (node.timerBlink !== undefined) clearInterval(node.timerBlink)
|
|
93
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } })
|
|
92
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'setLight')
|
|
94
93
|
}
|
|
95
94
|
break
|
|
96
95
|
case config.GALightColorCycle:
|
|
97
96
|
const gaValColorCycle = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
|
|
98
97
|
if (gaValColorCycle) {
|
|
99
98
|
|
|
100
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: true } })
|
|
99
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: true } }, 'setLight')
|
|
101
100
|
node.timerColorCycle = setInterval(() => {
|
|
102
101
|
try {
|
|
103
102
|
function getRandomIntInclusive(min, max) {
|
|
@@ -108,27 +107,19 @@ module.exports = function (RED) {
|
|
|
108
107
|
const red = getRandomIntInclusive(0, 255)
|
|
109
108
|
const green = getRandomIntInclusive(0, 255)
|
|
110
109
|
const blue = getRandomIntInclusive(0, 255)
|
|
111
|
-
const gamut = node.currentHUEDevice.color.gamut_type
|
|
110
|
+
const gamut = node.currentHUEDevice !== undefined ? node.currentHUEDevice.color.gamut_type : null
|
|
112
111
|
const retXY = hueColorConverter.ColorConverter.rgbToXy(red, green, blue, gamut)
|
|
113
112
|
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGB(red, green, blue)
|
|
114
113
|
state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } }
|
|
115
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state)
|
|
114
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setLight')
|
|
116
115
|
} catch (error) {
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const retLight = await node.serverHue.hueManager.getLight(config.hueDevice)
|
|
120
|
-
node.currentHUEDevice = retLight[0]
|
|
121
|
-
RED.log.error('Errore knxUltimateHueLight node.currentHUEDevice Samba ' + error.message)
|
|
122
|
-
} catch (err) {
|
|
123
|
-
RED.log.error('Errore knxUltimateHueLight node.currentHUEDevice Banana ' + err.message)
|
|
124
|
-
}
|
|
125
|
-
})()
|
|
116
|
+
|
|
126
117
|
}
|
|
127
118
|
}, 10000);
|
|
128
119
|
|
|
129
|
-
} else {
|
|
120
|
+
} else {
|
|
130
121
|
if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
|
|
131
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } })
|
|
122
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'setLight')
|
|
132
123
|
}
|
|
133
124
|
break
|
|
134
125
|
default:
|
|
@@ -158,7 +149,7 @@ module.exports = function (RED) {
|
|
|
158
149
|
node.timerDim = setInterval(() => {
|
|
159
150
|
node.timeoutDim += 1
|
|
160
151
|
if (node.timeoutDim > 100) { node.timeoutDim = 0; clearInterval(node.timerDim) }
|
|
161
|
-
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, node.dimDirection)
|
|
152
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, node.dimDirection, 'setLight')
|
|
162
153
|
}, 300);
|
|
163
154
|
}
|
|
164
155
|
|
|
@@ -182,10 +173,9 @@ module.exports = function (RED) {
|
|
|
182
173
|
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
183
174
|
}
|
|
184
175
|
if (_event.hasOwnProperty('color')) {
|
|
185
|
-
if (node.currentHUEDevice === undefined) return
|
|
186
176
|
knxMsgPayload.topic = config.GALightColorState
|
|
187
177
|
knxMsgPayload.dpt = config.dptLightColorState
|
|
188
|
-
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(_event.color.xy.x, _event.color.xy.y, node.currentHUEDevice.dimming.brightness)
|
|
178
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(_event.color.xy.x, _event.color.xy.y, (node.currentHUEDevice !== undefined ? node.currentHUEDevice.dimming.brightness : 100))
|
|
189
179
|
// Send to KNX bus
|
|
190
180
|
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
191
181
|
}
|
|
@@ -217,12 +207,15 @@ module.exports = function (RED) {
|
|
|
217
207
|
}
|
|
218
208
|
if (node.serverHue) {
|
|
219
209
|
node.serverHue.removeClient(node)
|
|
210
|
+
// I must get the light object, to store it in the node.currentHUEDevice variable
|
|
211
|
+
// I queue the state request, by passing the callback to call whenever the HUE bridge send me the light status async
|
|
220
212
|
if (node !== null && node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
221
213
|
(async () => {
|
|
222
214
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
215
|
+
await node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, null, 'getLight', (jLight) => {
|
|
216
|
+
node.currentHUEDevice = jLight
|
|
217
|
+
node.serverHue.addClient(node)
|
|
218
|
+
})
|
|
226
219
|
} catch (err) {
|
|
227
220
|
RED.log.error('Errore knxUltimateHueLight node.currentHUEDevice ' + err.message)
|
|
228
221
|
}
|
|
@@ -230,7 +223,6 @@ module.exports = function (RED) {
|
|
|
230
223
|
}
|
|
231
224
|
}
|
|
232
225
|
|
|
233
|
-
|
|
234
226
|
node.on('input', function (msg) {
|
|
235
227
|
|
|
236
228
|
})
|
|
@@ -249,7 +249,7 @@ Start typing in the GA field, the name or group address of your KNX device, the
|
|
|
249
249
|
|
|
250
250
|
|Property|Description|
|
|
251
251
|
|--|--|
|
|
252
|
-
| Rotate | This command is used either to send DIM (increase/decrease), aboslute brightness, or a random color, depending on the selected datapoint. |
|
|
252
|
+
| Rotate | This command is used either to send DIM (increase/decrease), aboslute brightness, or a random color, depending on the selected datapoint. If the random color (datapoint 232.600) is selected, **clockwise rotation** changes random colors and **counterclockwise rotation** set the light to **white** |
|
|
253
253
|
|
|
254
254
|
### Outputs
|
|
255
255
|
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
|
|
3
|
-
async function getLightState(node, _lightID) {
|
|
4
|
-
return new Promise((resolve, reject) => {
|
|
5
|
-
try {
|
|
6
|
-
if (node !== null && node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
7
|
-
node.serverHue.hueManager.getLight(_lightID).then(ret => {
|
|
8
|
-
node.currentHUEDevice = ret[0]
|
|
9
|
-
resolve(ret)
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
} catch (error) {
|
|
13
|
-
reject(error)
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
3
|
|
|
18
4
|
function knxUltimateHueTapDial(config) {
|
|
19
5
|
RED.nodes.createNode(this, config)
|
|
@@ -41,14 +27,6 @@ module.exports = function (RED) {
|
|
|
41
27
|
node.brightnessState = 0
|
|
42
28
|
node.isTimerDimStopRunning = false
|
|
43
29
|
|
|
44
|
-
// Read the state of the light and store it in the holding object
|
|
45
|
-
try {
|
|
46
|
-
if (config.hueLight !== undefined && config.hueLight !== '') getLightState(node, config.hueLight)
|
|
47
|
-
} catch (error) {
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
30
|
// Used to call the status update from the config node.
|
|
53
31
|
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
54
32
|
|
|
@@ -58,7 +36,7 @@ module.exports = function (RED) {
|
|
|
58
36
|
const dDate = new Date()
|
|
59
37
|
node.status({ fill: fill, shape: shape, text: text + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ')' })
|
|
60
38
|
}
|
|
61
|
-
|
|
39
|
+
|
|
62
40
|
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
63
41
|
node.handleSend = msg => {
|
|
64
42
|
}
|
|
@@ -84,16 +62,20 @@ module.exports = function (RED) {
|
|
|
84
62
|
node.brightnessState < 100 ? node.brightnessState += 20 : node.brightnessState = 100
|
|
85
63
|
knxMsgPayload.payload = node.brightnessState
|
|
86
64
|
} else if (knxMsgPayload.dpt.startsWith('232.600')) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
65
|
+
|
|
66
|
+
if (_event.relative_rotary.last_event.action === 'start') {
|
|
67
|
+
// Random color
|
|
68
|
+
knxMsgPayload.payload = { red: getRandomIntInclusive(0, 255), green: getRandomIntInclusive(0, 255), blue: getRandomIntInclusive(0, 255) }
|
|
69
|
+
function getRandomIntInclusive(min, max) {
|
|
70
|
+
min = Math.ceil(min);
|
|
71
|
+
max = Math.floor(max);
|
|
72
|
+
return Math.floor(Math.random() * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive
|
|
73
|
+
}
|
|
74
|
+
// Send to KNX bus
|
|
75
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
76
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX Change color clockwise' + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
93
77
|
}
|
|
94
|
-
|
|
95
|
-
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
96
|
-
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX Change color clockwise' + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
78
|
+
|
|
97
79
|
}
|
|
98
80
|
} else if (_event.relative_rotary.last_event.rotation.direction === 'counter_clock_wise') {
|
|
99
81
|
if (knxMsgPayload.dpt.startsWith('3.007')) {
|
|
@@ -108,16 +90,15 @@ module.exports = function (RED) {
|
|
|
108
90
|
node.brightnessState > 0 ? node.brightnessState -= 20 : node.brightnessState = 0
|
|
109
91
|
knxMsgPayload.payload = node.brightnessState
|
|
110
92
|
} else if (knxMsgPayload.dpt.startsWith('232.600')) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
93
|
+
|
|
94
|
+
if (_event.relative_rotary.last_event.action === 'start') {
|
|
95
|
+
// Set white color
|
|
96
|
+
knxMsgPayload.payload = { red: 255, green: 255, blue: 255 }
|
|
97
|
+
// Send to KNX bus
|
|
98
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
99
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX Change color counterclockwise' + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
117
100
|
}
|
|
118
|
-
|
|
119
|
-
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
120
|
-
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX Change color counterclockwise' + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
101
|
+
|
|
121
102
|
}
|
|
122
103
|
}
|
|
123
104
|
|
package/nodes/utils/hueUtils.js
CHANGED
|
@@ -75,6 +75,7 @@ class classHUE extends EventEmitter {
|
|
|
75
75
|
console.log('KNXUltimateHUEConfig: classHUE: request.on(error): ' + error.message)
|
|
76
76
|
// Restart the connection
|
|
77
77
|
setTimeout(() => {
|
|
78
|
+
this.commandQueue = []
|
|
78
79
|
req();
|
|
79
80
|
}, 2000);
|
|
80
81
|
});
|
|
@@ -89,19 +90,33 @@ class classHUE extends EventEmitter {
|
|
|
89
90
|
handleQueue = async () => {
|
|
90
91
|
if (this.commandQueue.length > 0) {
|
|
91
92
|
const jRet = this.commandQueue.shift()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
switch (jRet._operation) {
|
|
94
|
+
case 'setLight':
|
|
95
|
+
try {
|
|
96
|
+
const hue = hueApiV2.connect({ host: this.hueBridgeIP, key: this.username })
|
|
97
|
+
const ok = await hue.setLight(jRet._lightID, jRet._state)
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.log('KNXUltimateHUEConfig: classHUE: handleQueue: setLight: ' + error.message)
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case 'getLight':
|
|
103
|
+
try {
|
|
104
|
+
const hue = hueApiV2.connect({ host: this.hueBridgeIP, key: this.username })
|
|
105
|
+
const jReturn = await hue.getLight(jRet._lightID)
|
|
106
|
+
jRet._callback(jReturn[0]) // Need to call the callback, because the event is absolutely async
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.log('KNXUltimateHUEConfig: classHUE: handleQueue: getLight: ' + error.message)
|
|
109
|
+
}
|
|
110
|
+
break
|
|
111
|
+
default:
|
|
112
|
+
break;
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
// The Hue bridge allows about 10 telegram per second, so i need to make a queue manager
|
|
101
116
|
setTimeout(this.handleQueue, 100)
|
|
102
117
|
}
|
|
103
|
-
writeHueQueueAdd = async (_lightID, _state) => {
|
|
104
|
-
this.commandQueue.push({ _lightID, _state })
|
|
118
|
+
writeHueQueueAdd = async (_lightID, _state, _operation = 'setLight', _callback) => {
|
|
119
|
+
this.commandQueue.push({ _lightID, _state, _operation, _callback })
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
|
|
@@ -153,15 +168,7 @@ class classHUE extends EventEmitter {
|
|
|
153
168
|
}
|
|
154
169
|
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
getLight = async (_LightID) => {
|
|
158
|
-
try {
|
|
159
|
-
const hue = hueApiV2.connect({ host: this.hueBridgeIP, key: this.username })
|
|
160
|
-
return await hue.getLight(_LightID)
|
|
161
|
-
} catch (error) {
|
|
162
|
-
console.log('KNXUltimateHUEConfig: classHUE: getLight: ' + error.message)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
171
|
+
|
|
165
172
|
|
|
166
173
|
close = async () => {
|
|
167
174
|
return new Promise((resolve, reject) => {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=14.0.0"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.1.
|
|
6
|
+
"version": "2.1.6",
|
|
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",
|