node-red-contrib-knx-ultimate 2.0.0 → 2.0.2
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 +9 -0
- package/KNXEngine/dptlib/index.js +1 -1
- package/KNXUltimate.code-workspace +11 -1
- package/README.md +1 -1
- package/img/hue.png +0 -0
- package/img/hueButton.png +0 -0
- package/img/hueLight.png +0 -0
- package/img/knx.png +0 -0
- package/nodes/hue-config.js +20 -6
- package/nodes/knxUltimateHueButton.html +586 -0
- package/nodes/knxUltimateHueButton.js +159 -0
- package/nodes/knxUltimateHueLight.html +363 -55
- package/nodes/knxUltimateHueLight.js +70 -13
- package/nodes/locales/en-US/knxUltimateHueLight.json +3 -3
- package/nodes/utils/hueColorConverter.js +237 -0
- package/nodes/utils/hueUtils.js +37 -21
- package/package.json +6 -4
- package/nodes/locales/en-US/knxUltimateHueLight.html +0 -10
- /package/nodes/locales/{de → de-disabled}/hue-config.html +0 -0
- /package/nodes/locales/{de → de-disabled}/hue-config.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate-config.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate-config.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimate.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateAlerter.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateAlerter.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateGlobalContext.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateGlobalContext.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateHueLight.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateHueLight.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLoadControl.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLoadControl.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLogger.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateLogger.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateSceneController.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateSceneController.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateViewer.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateViewer.json +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateWatchDog.html +0 -0
- /package/nodes/locales/{de → de-disabled}/knxUltimateWatchDog.json +0 -0
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
const dptlib = require('./../KNXEngine/dptlib')
|
|
3
|
+
const hueColorConverter = require('./utils/hueColorConverter')
|
|
3
4
|
|
|
4
5
|
|
|
6
|
+
async function getLightState(node, _lightID) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
try {
|
|
9
|
+
if (node !== null && node.serverHue !== null && node.serverHue.hueManager !== null) {
|
|
10
|
+
node.serverHue.hueManager.getLight(_lightID).then(ret => {
|
|
11
|
+
node.currentHUEDevice = ret[0]
|
|
12
|
+
resolve(ret)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
reject(error)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
function knxUltimateHueLight(config) {
|
|
6
22
|
RED.nodes.createNode(this, config)
|
|
7
23
|
const node = this
|
|
@@ -27,6 +43,16 @@ module.exports = function (RED) {
|
|
|
27
43
|
node.formatnegativevalue = 'leave'
|
|
28
44
|
node.formatdecimalsvalue = 2
|
|
29
45
|
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// Read the state of the light and store it in the holding object
|
|
49
|
+
try {
|
|
50
|
+
if (config.hueDevice !== undefined && config.hueDevice !== '') getLightState(node, config.hueDevice)
|
|
51
|
+
} catch (error) {
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
30
56
|
// Used to call the status update from the config node.
|
|
31
57
|
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
32
58
|
|
|
@@ -34,38 +60,69 @@ module.exports = function (RED) {
|
|
|
34
60
|
|
|
35
61
|
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
36
62
|
node.handleSend = msg => {
|
|
37
|
-
let state
|
|
63
|
+
let state = {}
|
|
38
64
|
try {
|
|
39
65
|
switch (msg.knx.destination) {
|
|
40
66
|
case config.GALightSwitch:
|
|
41
67
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
|
|
42
68
|
state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
|
|
43
|
-
node.serverHue.setLightState(config.
|
|
69
|
+
node.serverHue.hueManager.setLightState(config.hueDevice, state)
|
|
44
70
|
break
|
|
45
71
|
case config.GALightDIM:
|
|
46
72
|
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
|
|
47
73
|
state = msg.payload.decr_incr === 1 ? { dimming_delta: { action: 'up', brightness_delta: 20 } } : { dimming_delta: { action: 'down', brightness_delta: 20 } }
|
|
48
|
-
node.serverHue.setLightState(config.
|
|
74
|
+
node.serverHue.hueManager.setLightState(config.hueDevice, state)
|
|
75
|
+
break
|
|
76
|
+
case config.GALightBrightness:
|
|
77
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
|
|
78
|
+
state = { dimming: { brightness: msg.payload } }
|
|
79
|
+
node.serverHue.hueManager.setLightState(config.hueDevice, state)
|
|
80
|
+
break
|
|
81
|
+
case config.GALightColor:
|
|
82
|
+
// Behavior like ISE HUE CONNECT, by setting the brightness and on/off as well
|
|
83
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColor))
|
|
84
|
+
const gamut = node.currentHUEDevice.color.gamut_type || null
|
|
85
|
+
const retXY = hueColorConverter.ColorConverter.rgbToXy(msg.payload.red, msg.payload.green, msg.payload.blue, gamut)
|
|
86
|
+
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue)
|
|
87
|
+
bright > 0 ? state = { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : state = { on: { on: false } }
|
|
88
|
+
node.serverHue.hueManager.setLightState(config.hueDevice, state)
|
|
49
89
|
break
|
|
50
90
|
default:
|
|
51
91
|
break
|
|
52
92
|
}
|
|
53
93
|
} catch (error) {
|
|
54
|
-
|
|
94
|
+
node.status({ fill: 'red', shape: 'dot', text: 'KNX->HUE error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
55
95
|
}
|
|
56
|
-
//node.exposedGAs.push({ address: msg.knx.destination, addressRAW: sAddressRAW, dpt: msg.knx.dpt, payload: msg.payload, devicename: sDeviceName, lastupdate: new Date(), rawPayload: 'HEX Raw: ' + msg.knx.rawValue.toString('hex') || '?', payloadmeasureunit: (msg.payloadmeasureunit !== 'unknown' ? ' ' + msg.payloadmeasureunit : '') })
|
|
96
|
+
// node.exposedGAs.push({ address: msg.knx.destination, addressRAW: sAddressRAW, dpt: msg.knx.dpt, payload: msg.payload, devicename: sDeviceName, lastupdate: new Date(), rawPayload: 'HEX Raw: ' + msg.knx.rawValue.toString('hex') || '?', payloadmeasureunit: (msg.payloadmeasureunit !== 'unknown' ? ' ' + msg.payloadmeasureunit : '') })
|
|
57
97
|
}
|
|
58
98
|
|
|
59
|
-
|
|
60
99
|
node.handleSendHUE = _event => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
100
|
+
try {
|
|
101
|
+
if (_event.id === config.hueDevice) {
|
|
102
|
+
let knxMsgPayload = {}
|
|
103
|
+
if (_event.hasOwnProperty('on')) {
|
|
104
|
+
knxMsgPayload.ga = config.GALightState
|
|
105
|
+
knxMsgPayload.dpt = config.dptLightState
|
|
106
|
+
knxMsgPayload.payload = _event.on.on
|
|
107
|
+
}
|
|
108
|
+
if (_event.hasOwnProperty('color')) {
|
|
109
|
+
knxMsgPayload.ga = config.GALightColorState
|
|
110
|
+
knxMsgPayload.dpt = config.dptLightColorState
|
|
111
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(_event.color.xy.x, _event.color.xy.y, node.currentHUEDevice.dimming.brightness)
|
|
112
|
+
}
|
|
113
|
+
if (_event.hasOwnProperty('dimming')) {
|
|
114
|
+
knxMsgPayload.ga = config.GALightBrightnessState
|
|
115
|
+
knxMsgPayload.dpt = config.dptLightBrightnessState
|
|
116
|
+
knxMsgPayload.payload = _event.dimming.brightness
|
|
117
|
+
}
|
|
118
|
+
// Send to KNX bus
|
|
119
|
+
if (knxMsgPayload.ga !== undefined) {
|
|
120
|
+
node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX State ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
121
|
+
if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
122
|
+
}
|
|
68
123
|
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
69
126
|
}
|
|
70
127
|
}
|
|
71
128
|
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"helplink": " <i class=\"fa fa-question-circle\"></i> <a target=\"_blank\" href=\"https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration\"><u>Configuration</u></a>",
|
|
4
4
|
"title": "HUE node",
|
|
5
5
|
"node-input-name": "Name",
|
|
6
|
-
"node-input-nameLightSwitch": "Switch
|
|
6
|
+
"node-input-nameLightSwitch": "Switch",
|
|
7
7
|
"node-input-GALightSwitch": "GA",
|
|
8
8
|
"node-input-dptLightSwitch": "dpt",
|
|
9
|
-
"node-input-nameLightState": "State
|
|
9
|
+
"node-input-nameLightState": "Switch State",
|
|
10
10
|
"node-input-GALightState": "GA",
|
|
11
11
|
"node-input-dptLightState": "dpt",
|
|
12
|
-
"node-input-hueLight": "HUE
|
|
12
|
+
"node-input-hueLight": "HUE Light"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const convert = require('color-convert')
|
|
2
|
+
class ColorConverter {
|
|
3
|
+
static getGamutRanges() {
|
|
4
|
+
const gamutA = {
|
|
5
|
+
red: [0.704, 0.296],
|
|
6
|
+
green: [0.2151, 0.7106],
|
|
7
|
+
blue: [0.138, 0.08]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const gamutB = {
|
|
11
|
+
red: [0.675, 0.322],
|
|
12
|
+
green: [0.409, 0.518],
|
|
13
|
+
blue: [0.167, 0.04]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const gamutC = {
|
|
17
|
+
red: [0.692, 0.308],
|
|
18
|
+
green: [0.17, 0.7],
|
|
19
|
+
blue: [0.153, 0.048]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultGamut = {
|
|
23
|
+
red: [1.0, 0],
|
|
24
|
+
green: [0.0, 1.0],
|
|
25
|
+
blue: [0.0, 0.0]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { gamutA, gamutB, gamutC, default: defaultGamut }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static getLightColorGamutRange(gamutTypeABC = null) {
|
|
32
|
+
const ranges = ColorConverter.getGamutRanges()
|
|
33
|
+
const gamutA = ranges.gamutA
|
|
34
|
+
const gamutB = ranges.gamutB
|
|
35
|
+
const gamutC = ranges.gamutC
|
|
36
|
+
|
|
37
|
+
const philipsModels = {
|
|
38
|
+
A: gamutA,
|
|
39
|
+
B: gamutB,
|
|
40
|
+
C: gamutC
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (philipsModels[gamutTypeABC]) {
|
|
44
|
+
return philipsModels[gamutTypeABC]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return ranges.default
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static rgbToXy(red, green, blue, gamutTypeABC = null) {
|
|
51
|
+
function getGammaCorrectedValue(value) {
|
|
52
|
+
return (value > 0.04045) ? Math.pow((value + 0.055) / (1.0 + 0.055), 2.4) : (value / 12.92)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const colorGamut = ColorConverter.getLightColorGamutRange(gamutTypeABC)
|
|
56
|
+
|
|
57
|
+
red = parseFloat(red / 255)
|
|
58
|
+
green = parseFloat(green / 255)
|
|
59
|
+
blue = parseFloat(blue / 255)
|
|
60
|
+
|
|
61
|
+
red = getGammaCorrectedValue(red)
|
|
62
|
+
green = getGammaCorrectedValue(green)
|
|
63
|
+
blue = getGammaCorrectedValue(blue)
|
|
64
|
+
|
|
65
|
+
const x = red * 0.649926 + green * 0.103455 + blue * 0.197109
|
|
66
|
+
const y = red * 0.234327 + green * 0.743075 + blue * 0.022598
|
|
67
|
+
const z = red * 0.0000000 + green * 0.053077 + blue * 1.035763
|
|
68
|
+
|
|
69
|
+
let xy = {
|
|
70
|
+
x: x / (x + y + z),
|
|
71
|
+
y: y / (x + y + z)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!ColorConverter.xyIsInGamutRange(xy, colorGamut)) {
|
|
75
|
+
xy = ColorConverter.getClosestColor(xy, colorGamut)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return xy
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static getBrightnessFromRGB(red, green, blue) {
|
|
82
|
+
const hsv = convert.rgb.hsv(red, green, blue)
|
|
83
|
+
const brightness = hsv[2]
|
|
84
|
+
return brightness
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static convert_1_255_ToPercentage(number) {
|
|
88
|
+
const percentage = (number / 255) * 100
|
|
89
|
+
return percentage
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
static xyIsInGamutRange(xy, gamut) {
|
|
93
|
+
gamut = gamut || ColorConverter.getGamutRanges().gamutC
|
|
94
|
+
if (Array.isArray(xy)) {
|
|
95
|
+
xy = {
|
|
96
|
+
x: xy[0],
|
|
97
|
+
y: xy[1]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]]
|
|
102
|
+
const v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]]
|
|
103
|
+
const v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]]
|
|
104
|
+
|
|
105
|
+
const dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1])
|
|
106
|
+
const dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1])
|
|
107
|
+
const dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1])
|
|
108
|
+
const dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1])
|
|
109
|
+
const dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1])
|
|
110
|
+
|
|
111
|
+
const invDenom = 1 / (dot00 * dot11 - dot01 * dot01)
|
|
112
|
+
|
|
113
|
+
const u = (dot11 * dot02 - dot01 * dot12) * invDenom
|
|
114
|
+
const v = (dot00 * dot12 - dot01 * dot02) * invDenom
|
|
115
|
+
|
|
116
|
+
return ((u >= 0) && (v >= 0) && (u + v < 1))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static getClosestColor(xy, gamut) {
|
|
120
|
+
function getLineDistance(pointA, pointB) {
|
|
121
|
+
return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getClosestPoint(xy, pointA, pointB) {
|
|
125
|
+
const xy2a = [xy.x - pointA.x, xy.y - pointA.y]
|
|
126
|
+
const a2b = [pointB.x - pointA.x, pointB.y - pointA.y]
|
|
127
|
+
const a2bSqr = Math.pow(a2b[0], 2) + Math.pow(a2b[1], 2)
|
|
128
|
+
const xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1]
|
|
129
|
+
const t = xy2a_dot_a2b / a2bSqr
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
x: pointA.x + a2b[0] * t,
|
|
133
|
+
y: pointA.y + a2b[1] * t
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const greenBlue = {
|
|
138
|
+
a: {
|
|
139
|
+
x: gamut.green[0],
|
|
140
|
+
y: gamut.green[1]
|
|
141
|
+
},
|
|
142
|
+
b: {
|
|
143
|
+
x: gamut.blue[0],
|
|
144
|
+
y: gamut.blue[1]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const greenRed = {
|
|
149
|
+
a: {
|
|
150
|
+
x: gamut.green[0],
|
|
151
|
+
y: gamut.green[1]
|
|
152
|
+
},
|
|
153
|
+
b: {
|
|
154
|
+
x: gamut.red[0],
|
|
155
|
+
y: gamut.red[1]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const blueRed = {
|
|
160
|
+
a: {
|
|
161
|
+
x: gamut.red[0],
|
|
162
|
+
y: gamut.red[1]
|
|
163
|
+
},
|
|
164
|
+
b: {
|
|
165
|
+
x: gamut.blue[0],
|
|
166
|
+
y: gamut.blue[1]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const closestColorPoints = {
|
|
171
|
+
greenBlue: getClosestPoint(xy, greenBlue.a, greenBlue.b),
|
|
172
|
+
greenRed: getClosestPoint(xy, greenRed.a, greenRed.b),
|
|
173
|
+
blueRed: getClosestPoint(xy, blueRed.a, blueRed.b)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const distance = {
|
|
177
|
+
greenBlue: getLineDistance(xy, closestColorPoints.greenBlue),
|
|
178
|
+
greenRed: getLineDistance(xy, closestColorPoints.greenRed),
|
|
179
|
+
blueRed: getLineDistance(xy, closestColorPoints.blueRed)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let closestDistance
|
|
183
|
+
let closestColor
|
|
184
|
+
for (const i in distance) {
|
|
185
|
+
if (distance.hasOwnProperty(i)) {
|
|
186
|
+
if (!closestDistance) {
|
|
187
|
+
closestDistance = distance[i]
|
|
188
|
+
closestColor = i
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (closestDistance > distance[i]) {
|
|
192
|
+
closestDistance = distance[i]
|
|
193
|
+
closestColor = i
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return closestColorPoints[closestColor]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static xyBriToRgb(x, y, bri) {
|
|
201
|
+
function getReversedGammaCorrectedValue(value) {
|
|
202
|
+
return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * Math.pow(value, (1.0 / 2.4)) - 0.055
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const z = 1.0 - x - y
|
|
206
|
+
const Y = bri / 255
|
|
207
|
+
const X = (Y / y) * x
|
|
208
|
+
const Z = (Y / y) * z
|
|
209
|
+
let r = X * 1.612 - Y * 0.203 - Z * 0.302
|
|
210
|
+
let g = -X * 0.509 + Y * 1.412 + Z * 0.066
|
|
211
|
+
let b = X * 0.026 - Y * 0.072 + Z * 0.962
|
|
212
|
+
|
|
213
|
+
r = getReversedGammaCorrectedValue(r)
|
|
214
|
+
g = getReversedGammaCorrectedValue(g)
|
|
215
|
+
b = getReversedGammaCorrectedValue(b)
|
|
216
|
+
|
|
217
|
+
// Bring all negative components to zero
|
|
218
|
+
r = Math.max(r, 0)
|
|
219
|
+
g = Math.max(g, 0)
|
|
220
|
+
b = Math.max(b, 0)
|
|
221
|
+
|
|
222
|
+
// If one component is greater than 1, weight components by that value
|
|
223
|
+
const max = Math.max(r, g, b)
|
|
224
|
+
if (max > 1) {
|
|
225
|
+
r = r / max
|
|
226
|
+
g = g / max
|
|
227
|
+
b = b / max
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
red: Math.floor(r * 255),
|
|
232
|
+
geen: Math.floor(g * 255),
|
|
233
|
+
blue: Math.floor(b * 255)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.ColorConverter = ColorConverter
|
package/nodes/utils/hueUtils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
//const hueApi = require('node-hue-api')
|
|
3
|
+
// const hueApi = require('node-hue-api')
|
|
4
4
|
const hueApiV2 = require('node-hue')
|
|
5
5
|
const { EventEmitter } = require('events')
|
|
6
6
|
|
|
@@ -13,30 +13,37 @@ class classHUE extends EventEmitter {
|
|
|
13
13
|
this.bridgeid = _bridgeid
|
|
14
14
|
this.startPushEvents()
|
|
15
15
|
this.timerWatchDog = undefined
|
|
16
|
+
this.hue = undefined
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// Get all devices and join it with relative rooms, by adding the room name to the device name
|
|
20
|
+
getDevices = async (_rtype) => {
|
|
19
21
|
try {
|
|
20
22
|
// V2
|
|
21
23
|
const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
const retArray = []
|
|
25
|
+
const allResources = await hue.getResources()
|
|
26
|
+
const allRooms = await hue.getRooms()
|
|
27
|
+
const newArray = allResources.filter(x => x.type === _rtype)
|
|
28
|
+
// Add room name to the device name
|
|
29
|
+
newArray.forEach(device => {
|
|
30
|
+
// const Room = allRooms.find(room => {
|
|
31
|
+
// return room.children.find(child => child.rid === device.id)
|
|
32
|
+
// })
|
|
33
|
+
const Room = allRooms.find(room => room.children.find(child => child.rid === device.owner.rid))
|
|
34
|
+
const linkedDevName = allResources.find(dev => dev.type === 'device' && dev.services.find(serv => serv.rid === device.id)).metadata.name || ''
|
|
35
|
+
if (_rtype === 'button') {
|
|
36
|
+
const controlID = device.metadata !== undefined ? (device.metadata.control_id || '') : ''
|
|
37
|
+
retArray.push({ name: linkedDevName + (controlID !== '' ? ', button ' + controlID : '') + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
|
|
38
|
+
}
|
|
39
|
+
if (_rtype === 'light') {
|
|
40
|
+
retArray.push({ name: linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
return { devices: retArray }
|
|
37
44
|
} catch (error) {
|
|
38
|
-
console.log('KNXUltimateHue: classHUE: error ' + error.message)
|
|
39
|
-
return ({
|
|
45
|
+
console.log('KNXUltimateHue: HueUtils: classHUE: getDevices: error ' + error.message)
|
|
46
|
+
return ({ devices: error.message })
|
|
40
47
|
}
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -54,11 +61,21 @@ class classHUE extends EventEmitter {
|
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
64
|
+
// Get light state
|
|
65
|
+
getLight = async (_LightID) => {
|
|
66
|
+
try {
|
|
67
|
+
const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
|
|
68
|
+
return await hue.getLight(_LightID)
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return ({ error: error.message })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
57
74
|
// Check the bridge for disconnections
|
|
58
75
|
handleTheDog = async () => {
|
|
59
76
|
this.timerWatchDog = setInterval(async () => {
|
|
60
77
|
try {
|
|
61
|
-
//const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
|
|
78
|
+
// const hue = hueApiV2.connect({ host: this.HUEBridgeIP, key: this.username })
|
|
62
79
|
if (this.hue !== undefined) {
|
|
63
80
|
const sRet = await this.hue.getBridges()
|
|
64
81
|
if (sRet.filter(e => e.bridge_id.toString().toLowerCase() === this.bridgeid.toString().toLowerCase()).length === 0) {
|
|
@@ -96,4 +113,3 @@ class classHUE extends EventEmitter {
|
|
|
96
113
|
}
|
|
97
114
|
}
|
|
98
115
|
module.exports.classHUE = classHUE
|
|
99
|
-
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-knx-ultimate",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"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.",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"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.",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"mkdirp": "1.0.4",
|
|
7
7
|
"ping": "0.4.1",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"xml2js": "0.5.0",
|
|
15
15
|
"dns-sync": "0.2.1",
|
|
16
16
|
"node-hue-api": "5.0.0-beta.16",
|
|
17
|
-
"node-hue": "1.0.3"
|
|
17
|
+
"node-hue": "1.0.3",
|
|
18
|
+
"color-convert": "2.0.1"
|
|
18
19
|
},
|
|
19
20
|
"node-red": {
|
|
20
21
|
"nodes": {
|
|
@@ -28,7 +29,8 @@
|
|
|
28
29
|
"knxUltimateLoadControl": "/nodes/knxUltimateLoadControl.js",
|
|
29
30
|
"knxUltimateViewer": "/nodes/knxUltimateViewer.js",
|
|
30
31
|
"hueConfig": "/nodes/hue-config.js",
|
|
31
|
-
"knxUltimateHueLight": "/nodes/knxUltimateHueLight.js"
|
|
32
|
+
"knxUltimateHueLight": "/nodes/knxUltimateHueLight.js",
|
|
33
|
+
"knxUltimateHueButton": "/nodes/knxUltimateHueButton.js"
|
|
32
34
|
}
|
|
33
35
|
},
|
|
34
36
|
"repository": {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<script type="text/x-red" data-help-name="knxUltimateHueLight">
|
|
2
|
-
<h1>KNX Ultimate - Nodo HUE</h1>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<p>
|
|
7
|
-
<a href="https://www.paypal.me/techtoday" target="_blank"><img src='https://img.shields.io/badge/Donate-PayPal-blue.svg?style=flat-square' width='30%'></a>
|
|
8
|
-
|
|
9
|
-
</p>
|
|
10
|
-
</script>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|