node-red-contrib-knx-ultimate 2.0.7 → 2.0.9
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/img/hueButton.png +0 -0
- package/img/hueMotion.png +0 -0
- package/img/hueTapDial.png +0 -0
- package/nodes/hue-config.html +14 -5
- package/nodes/hue-config.js +2 -21
- package/nodes/knxUltimateHueButton.html +23 -17
- package/nodes/knxUltimateHueButton.js +7 -43
- package/nodes/knxUltimateHueLight.html +6 -7
- package/nodes/knxUltimateHueLight.js +3 -10
- package/nodes/knxUltimateHueMotion.html +251 -0
- package/nodes/knxUltimateHueMotion.js +90 -0
- package/nodes/knxUltimateHueTapDial.html +269 -0
- package/nodes/knxUltimateHueTapDial.js +122 -0
- package/nodes/utils/hueUtils.js +13 -0
- package/package.json +5 -3
- package/nodes/locales/en-US/hue-config.html +0 -11
package/CHANGELOG.md
CHANGED
package/img/hueButton.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/nodes/hue-config.html
CHANGED
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
this.disabled = true;
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// Expected { bridge: bridgeConfig, user: createdUser }
|
|
43
43
|
$("#node-config-input-name").val(data.bridge.data.name);
|
|
44
44
|
$("#node-config-input-host").val(data.bridge.data.ipaddress);
|
|
45
45
|
$("#node-config-input-username").val(data.user.username);
|
|
46
46
|
$("#node-config-input-clientkey").val(data.user.clientkey);
|
|
47
47
|
$("#node-config-input-bridgeid").val(data.bridge.data.bridgeid);
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
}).error(function (jqXHR, textStatus, errorThrown) {
|
|
50
50
|
RED.notify("Something went wrong. Please create at least a KNX Gateway node first.",
|
|
51
51
|
{
|
|
@@ -88,10 +88,9 @@
|
|
|
88
88
|
|
|
89
89
|
<p align='center'> <img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/huehub.jpg' width='40%'></p>
|
|
90
90
|
|
|
91
|
-
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
<label><i class="fa fa-sign-in"></i>&
|
|
92
|
+
<div class="form-row">
|
|
93
|
+
<label><i class="fa fa-sign-in"></i> Register</label>
|
|
95
94
|
<input type="button" id="getinfocam" class="ui-button ui-corner-all ui-widget" style="background-color:#AEE1FF;width:150px" value="CONNECT">
|
|
96
95
|
</div>
|
|
97
96
|
|
|
@@ -129,4 +128,14 @@
|
|
|
129
128
|
|
|
130
129
|
|
|
131
130
|
|
|
131
|
+
</script>
|
|
132
|
+
<script type="text/x-red" data-help-name="hue-config">
|
|
133
|
+
<p> This node registes to the Hue Bridge.<br/>
|
|
134
|
+
|
|
135
|
+
Just click **Connect** button.
|
|
136
|
+
|
|
137
|
+
[Find it useful?](https://www.paypal.me/techtoday)
|
|
138
|
+
|
|
139
|
+
<br/>
|
|
140
|
+
|
|
132
141
|
</script>
|
package/nodes/hue-config.js
CHANGED
|
@@ -90,25 +90,6 @@ module.exports = (RED) => {
|
|
|
90
90
|
})
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
// Endpoint frontend
|
|
94
|
-
RED.httpAdmin.get('/KNXUltimateGetAllLightsHUE', RED.auth.needsPermission('hue-config.read'), function (req, res) {
|
|
95
|
-
try {
|
|
96
|
-
(async () => {
|
|
97
|
-
try {
|
|
98
|
-
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
99
|
-
const jRet = await node.hueManager.getAllLights()
|
|
100
|
-
res.json(jRet)
|
|
101
|
-
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
102
|
-
} catch (err) {
|
|
103
|
-
RED.log.error('Errore KNXUltimateGetAllLightsHUE non gestito ' + err.message)
|
|
104
|
-
res.json({ error: err.message })
|
|
105
|
-
}
|
|
106
|
-
})()
|
|
107
|
-
} catch (err) {
|
|
108
|
-
RED.log.error('Errore KNXUltimateGetAllLightsHUE bsonto ' + err.message)
|
|
109
|
-
res.json({ error: err.message })
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
93
|
|
|
113
94
|
RED.httpAdmin.get('/KNXUltimateGetDevicesHUE', RED.auth.needsPermission('hue-config.read'), function (req, res) {
|
|
114
95
|
try {
|
|
@@ -120,12 +101,12 @@ module.exports = (RED) => {
|
|
|
120
101
|
res.json(jRet)
|
|
121
102
|
// °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
|
|
122
103
|
} catch (err) {
|
|
123
|
-
RED.log.error('Errore
|
|
104
|
+
RED.log.error('Errore KNXUltimateGetDevicesHUE non gestito ' + err.message)
|
|
124
105
|
res.json({ error: err.message })
|
|
125
106
|
}
|
|
126
107
|
})()
|
|
127
108
|
} catch (err) {
|
|
128
|
-
RED.log.error('Errore
|
|
109
|
+
RED.log.error('Errore KNXUltimateGetDevicesHUE bsonto ' + err.message)
|
|
129
110
|
res.json({ error: err.message })
|
|
130
111
|
}
|
|
131
112
|
})
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
color: '#C7E9C0',
|
|
5
5
|
defaults: {
|
|
6
6
|
//buttonState: {value: true},
|
|
7
|
-
server: { type: "knxUltimate-config", required:
|
|
7
|
+
server: { type: "knxUltimate-config", required: false },
|
|
8
8
|
serverHue: { type: "hue-config", required: true },
|
|
9
|
-
name: { value: "
|
|
9
|
+
name: { value: "" },
|
|
10
10
|
|
|
11
11
|
nameinitial_press: { value: "" },
|
|
12
12
|
GAinitial_press: { value: "" },
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
GAlong_press: { value: "" },
|
|
33
33
|
dptlong_press: { value: "" },
|
|
34
34
|
|
|
35
|
-
outputSimpleMode: { value: true },
|
|
36
35
|
toggleValues: { value: true },
|
|
37
36
|
hueDevice: { value: "" }
|
|
38
37
|
},
|
|
@@ -363,7 +362,7 @@
|
|
|
363
362
|
|
|
364
363
|
|
|
365
364
|
|
|
366
|
-
// Autocomplete suggestion with HUE
|
|
365
|
+
// Autocomplete suggestion with HUE
|
|
367
366
|
$("#node-input-name").autocomplete({
|
|
368
367
|
minLength: 1,
|
|
369
368
|
source: function (request, response) {
|
|
@@ -534,11 +533,7 @@
|
|
|
534
533
|
Toggle values
|
|
535
534
|
</label>
|
|
536
535
|
</div>
|
|
537
|
-
|
|
538
|
-
<input type="checkbox" id="node-input-outputSimpleMode" style="display:inline-block; width:auto; vertical-align:top;" />
|
|
539
|
-
<label style="width:auto" for="node-input-outputSimpleMode"><i class="fa fa-filter">Simple output mode</i></label>
|
|
540
|
-
</div>
|
|
541
|
-
|
|
536
|
+
|
|
542
537
|
</div>
|
|
543
538
|
|
|
544
539
|
|
|
@@ -555,14 +550,14 @@
|
|
|
555
550
|
</script>
|
|
556
551
|
|
|
557
552
|
<script type="text/markdown" data-help-name="knxUltimateHueButton">
|
|
553
|
+
<p> This node lets you get the events from your HUE button.<br/>
|
|
558
554
|
|
|
559
|
-
|
|
555
|
+
There are many event you can choose from. The relevants one are *Initial press* and *repeat*.
|
|
556
|
+
In *Initial press* you can send true/false to your KNX group address to, for example, toggle a light.
|
|
557
|
+
In *repeat* event, you can DIM a KNX light.
|
|
558
|
+
The **toggle values** option is enabled by default. This option toggles the value of each KNX group address (*true/false, increase/decrease dim*)
|
|
559
|
+
If you want to simply switch on and off a KNX load or light, use the *Initial press* group address and be sure that the *Toggle values* is enabled.
|
|
560
560
|
|
|
561
|
-
This node lets you get the events of your HUE button.<br/>
|
|
562
|
-
There are many event you can choose from. The relevants one are *short release* and *repeat*.<br/>
|
|
563
|
-
In *short release* you can send true/false to your KNX group address to, for example, toggle a light.<br/>
|
|
564
|
-
In *repeat* event, you can DIM a KNX light.<br/>
|
|
565
|
-
The **toggle values** option is enabled by default. This option toggles the value of each KNX group address (*true/false, increase/decrease dim*))
|
|
566
561
|
|
|
567
562
|
**General**
|
|
568
563
|
|Property|Description|
|
|
@@ -580,13 +575,24 @@ Start typing in the GA field, the name or group address of your KNX device, the
|
|
|
580
575
|
|Property|Description|
|
|
581
576
|
|--|--|
|
|
582
577
|
| Initial press | As soon as you press your HUE button, this event fires|
|
|
583
|
-
| Repeat | This
|
|
578
|
+
| Repeat | This event is used either to send DIM (increase/decrease) or true/false commands to the KNX group address |
|
|
584
579
|
| Short release | Rapid push of the button, fired as soon as you release your finger from the button. Usually used to toggle a KNX light true/false |
|
|
585
580
|
| Long release | Long press of the button, fired as soon as you release your finger from the button |
|
|
586
581
|
| Long press | Long press of the button, fired as soon as you long press the button |
|
|
587
582
|
| Toggle values | Enable or disable toggling values. If enabled, all values toggles, otherwise, all values are sent as *true* or *increase dim*, to the selected KNX group address |
|
|
588
|
-
| Simple output mode | Enables an RBE output on the payload *true/false*, independently from the HUE Button events type. *Repeat* messages are always repeated. When disabled, the node outputs a msg for every different HUE Button events |
|
|
589
583
|
|
|
584
|
+
### Outputs
|
|
585
|
+
|
|
586
|
+
1. Standard output
|
|
587
|
+
: payload (string|object) : the standard output of the command.
|
|
588
|
+
|
|
589
|
+
### Details
|
|
590
|
+
|
|
591
|
+
`msg.payload` is used as the payload of the published message.
|
|
592
|
+
If it contains an Object it will be converted to a JSON string before being sent.
|
|
593
|
+
|
|
594
|
+
[Find it useful?](https://www.paypal.me/techtoday)
|
|
590
595
|
|
|
591
596
|
<br/>
|
|
597
|
+
|
|
592
598
|
</script>
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
|
-
const dptlib = require('./../KNXEngine/dptlib')
|
|
3
|
-
const hueColorConverter = require('./utils/hueColorConverter')
|
|
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
2
|
|
|
21
3
|
function knxUltimateHueButton(config) {
|
|
22
4
|
RED.nodes.createNode(this, config)
|
|
@@ -48,15 +30,7 @@ module.exports = function (RED) {
|
|
|
48
30
|
node.toggle4 = false
|
|
49
31
|
node.toggle5 = false
|
|
50
32
|
node.toggle5 = false
|
|
51
|
-
node.
|
|
52
|
-
|
|
53
|
-
// Read the state of the light and store it in the holding object
|
|
54
|
-
try {
|
|
55
|
-
if (config.hueLight !== undefined && config.hueLight !== '') getLightState(node, config.hueLight)
|
|
56
|
-
} catch (error) {
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
33
|
+
node.toggle6 = false
|
|
60
34
|
|
|
61
35
|
// Used to call the status update from the config node.
|
|
62
36
|
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
@@ -117,22 +91,18 @@ module.exports = function (RED) {
|
|
|
117
91
|
knxMsgPayload.payload = node.toggle6
|
|
118
92
|
}
|
|
119
93
|
// Send to KNX bus
|
|
120
|
-
if (knxMsgPayload.ga !== undefined) {
|
|
121
|
-
node.
|
|
122
|
-
if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
94
|
+
if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) {
|
|
95
|
+
node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
123
96
|
}
|
|
97
|
+
node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX ' + _event.button.last_event + ' ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
98
|
+
|
|
124
99
|
// Setup the output msg
|
|
125
100
|
knxMsgPayload.topic = knxMsgPayload.ga
|
|
126
101
|
delete knxMsgPayload.ga
|
|
127
102
|
knxMsgPayload.name = node.name
|
|
128
103
|
knxMsgPayload.event = _event.button.last_event
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (config.outputSimpleMode && knxMsgPayload.payload !== node.rbeOutputPayload) {
|
|
132
|
-
node.rbeOutputPayload = knxMsgPayload.payload
|
|
133
|
-
} else {
|
|
134
|
-
node.send(knxMsgPayload)
|
|
135
|
-
}
|
|
104
|
+
knxMsgPayload.rawEvent = _event
|
|
105
|
+
node.send(knxMsgPayload)
|
|
136
106
|
}
|
|
137
107
|
} catch (error) {
|
|
138
108
|
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
@@ -159,12 +129,6 @@ module.exports = function (RED) {
|
|
|
159
129
|
}
|
|
160
130
|
done()
|
|
161
131
|
})
|
|
162
|
-
|
|
163
|
-
// On each deploy, unsubscribe+resubscribe
|
|
164
|
-
if (node.server) {
|
|
165
|
-
node.server.removeClient(node)
|
|
166
|
-
node.server.addClient(node)
|
|
167
|
-
}
|
|
168
132
|
}
|
|
169
133
|
RED.nodes.registerType('knxUltimateHueButton', knxUltimateHueButton)
|
|
170
134
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//buttonState: {value: true},
|
|
7
7
|
server: { type: "knxUltimate-config", required: true },
|
|
8
8
|
serverHue: { type: "hue-config", required: true },
|
|
9
|
-
name: { value: "
|
|
9
|
+
name: { value: "" },
|
|
10
10
|
|
|
11
11
|
nameLightSwitch: { value: "" },
|
|
12
12
|
GALightSwitch: { value: "" },
|
|
@@ -442,8 +442,8 @@
|
|
|
442
442
|
|
|
443
443
|
<div class="form-row">
|
|
444
444
|
<b>HUE Light</b>  <span style="color:red"
|
|
445
|
-
 
|
|
446
|
-
 <a target="_blank" href="https://
|
|
445
|
+
 
|
|
446
|
+
 <i class="fa fa-youtube"></i></span> <a target="_blank" href="https://youtu.be/3M02Du2gero"><u>Youtube sample</u></a>
|
|
447
447
|
<br />
|
|
448
448
|
<p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hueLight.png'></p>
|
|
449
449
|
<br />
|
|
@@ -599,10 +599,7 @@
|
|
|
599
599
|
|
|
600
600
|
|
|
601
601
|
<script type="text/markdown" data-help-name="knxUltimateHueLight">
|
|
602
|
-
|
|
603
|
-
[Find it useful?](https://www.paypal.me/techtoday)
|
|
604
|
-
|
|
605
|
-
This node lets you control your Philips HUE light and also get the states of this lights.
|
|
602
|
+
<p>This node lets you control your Philips HUE light and also get the states of this lights.
|
|
606
603
|
|
|
607
604
|
**General**
|
|
608
605
|
|Property|Description|
|
|
@@ -636,5 +633,7 @@ Start typing in the GA field, the name or group address of your KNX device, the
|
|
|
636
633
|
| Color | This state is used to send changes of the HUE light's color. Accepted datapoint is RGB triplet (r,g,b)|
|
|
637
634
|
| Brightness | This state is used to send changes of your absolute HUE light's brightness, to a KNX group address |
|
|
638
635
|
|
|
636
|
+
[Find it useful?](https://www.paypal.me/techtoday)
|
|
637
|
+
|
|
639
638
|
<br/>
|
|
640
639
|
</script>
|
|
@@ -116,10 +116,9 @@ module.exports = function (RED) {
|
|
|
116
116
|
knxMsgPayload.payload = _event.dimming.brightness
|
|
117
117
|
}
|
|
118
118
|
// Send to KNX bus
|
|
119
|
-
if (knxMsgPayload.ga !== undefined) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
119
|
+
if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
120
|
+
node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX State ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
121
|
+
|
|
123
122
|
}
|
|
124
123
|
} catch (error) {
|
|
125
124
|
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
@@ -146,12 +145,6 @@ module.exports = function (RED) {
|
|
|
146
145
|
}
|
|
147
146
|
done()
|
|
148
147
|
})
|
|
149
|
-
|
|
150
|
-
// On each deploy, unsubscribe+resubscribe
|
|
151
|
-
if (node.server) {
|
|
152
|
-
node.server.removeClient(node)
|
|
153
|
-
node.server.addClient(node)
|
|
154
|
-
}
|
|
155
148
|
}
|
|
156
149
|
RED.nodes.registerType('knxUltimateHueLight', knxUltimateHueLight)
|
|
157
150
|
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('knxUltimateHueMotion', {
|
|
3
|
+
category: "KNX Ultimate",
|
|
4
|
+
color: '#C7E9C0',
|
|
5
|
+
defaults: {
|
|
6
|
+
//buttonState: {value: true},
|
|
7
|
+
server: { type: "knxUltimate-config", required: false },
|
|
8
|
+
serverHue: { type: "hue-config", required: true },
|
|
9
|
+
name: { value: "" },
|
|
10
|
+
|
|
11
|
+
namemotion: { value: "" },
|
|
12
|
+
GAmotion: { value: "" },
|
|
13
|
+
dptmotion: { value: "" },
|
|
14
|
+
|
|
15
|
+
hueDevice: { value: "" }
|
|
16
|
+
},
|
|
17
|
+
inputs: 0,
|
|
18
|
+
outputs: 1,
|
|
19
|
+
icon: "node-hue-icon.svg",
|
|
20
|
+
label: function () {
|
|
21
|
+
return (this.name);
|
|
22
|
+
},
|
|
23
|
+
paletteLabel: "Hue Motion (beta)",
|
|
24
|
+
// button: {
|
|
25
|
+
// enabled: function() {
|
|
26
|
+
// // return whether or not the button is enabled, based on the current
|
|
27
|
+
// // configuration of the node
|
|
28
|
+
// return !this.changed
|
|
29
|
+
// },
|
|
30
|
+
// visible: function() {
|
|
31
|
+
// // return whether or not the button is visible, based on the current
|
|
32
|
+
// // configuration of the node
|
|
33
|
+
// return this.hasButton
|
|
34
|
+
// },
|
|
35
|
+
// //toggle: "buttonState",
|
|
36
|
+
// onclick: function() {}
|
|
37
|
+
// },
|
|
38
|
+
oneditprepare: function () {
|
|
39
|
+
var node = this;
|
|
40
|
+
var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
|
|
41
|
+
var oNodeServerHue = RED.nodes.node($("#node-input-serverHue").val()); // Store the config-node
|
|
42
|
+
|
|
43
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
44
|
+
$("#node-input-server").change(function () {
|
|
45
|
+
try {
|
|
46
|
+
oNodeServer = RED.nodes.node($(this).val());
|
|
47
|
+
} catch (error) { }
|
|
48
|
+
});
|
|
49
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
50
|
+
$("#node-input-serverHue").change(function () {
|
|
51
|
+
try {
|
|
52
|
+
oNodeServerHue = RED.nodes.node($(this).val());
|
|
53
|
+
} catch (error) { }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 31/03/2020 Search Helper
|
|
57
|
+
function fullSearch(sourceText, searchString) {
|
|
58
|
+
// This searches for all words in a string
|
|
59
|
+
var aSearchWords = searchString.toLowerCase().split(" ");
|
|
60
|
+
var i = 0;
|
|
61
|
+
for (let index = 0; index < aSearchWords.length; index++) {
|
|
62
|
+
if (sourceText.toLowerCase().indexOf(aSearchWords[index]) > -1) i += 1;
|
|
63
|
+
}
|
|
64
|
+
return i == aSearchWords.length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// DPT
|
|
68
|
+
// ########################
|
|
69
|
+
$.getJSON('knxUltimateDpts', (data) => {
|
|
70
|
+
data.forEach(dpt => {
|
|
71
|
+
if (dpt.value.startsWith("1.")) {
|
|
72
|
+
$("#node-input-dptmotion").append($("<option></option>")
|
|
73
|
+
.attr("value", dpt.value)
|
|
74
|
+
.text(dpt.text))
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
$("#node-input-dptmotion").val(this.dptmotion)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Autocomplete suggestion with ETS csv File
|
|
81
|
+
$("#node-input-GAmotion").autocomplete({
|
|
82
|
+
minLength: 1,
|
|
83
|
+
source: function (request, response) {
|
|
84
|
+
//$.getJSON("csv", request, function( data, status, xhr ) {
|
|
85
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
86
|
+
response($.map(data, function (value, key) {
|
|
87
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
88
|
+
if (fullSearch(sSearch, request.term + " 1.")) {
|
|
89
|
+
return {
|
|
90
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
91
|
+
value: value.ga // Value
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
98
|
+
}, select: function (event, ui) {
|
|
99
|
+
// Sets Datapoint and device name automatically
|
|
100
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
101
|
+
try {
|
|
102
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
}
|
|
105
|
+
$('#node-input-namemotion').val(sDevName);
|
|
106
|
+
var optVal = $("#node-input-dptmotion option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
107
|
+
// Select the option value
|
|
108
|
+
$("#node-input-dptmotion").val(optVal);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// ########################
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// Autocomplete suggestion with HUE
|
|
117
|
+
$("#node-input-name").autocomplete({
|
|
118
|
+
minLength: 1,
|
|
119
|
+
source: function (request, response) {
|
|
120
|
+
$.getJSON("KNXUltimateGetDevicesHUE?rtype=motion&nodeID=" + oNodeServerHue.id, (data) => {
|
|
121
|
+
response($.map(data.devices, function (value, key) {
|
|
122
|
+
//alert(JSON.stringify(value) + " "+ key)
|
|
123
|
+
var sSearch = (value.name);
|
|
124
|
+
if (fullSearch(sSearch, request.term)) {
|
|
125
|
+
return {
|
|
126
|
+
hueDevice: value.id, // Label for Display
|
|
127
|
+
value: value.name // Value
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}));
|
|
133
|
+
});
|
|
134
|
+
}, select: function (event, ui) {
|
|
135
|
+
// Sets the fields
|
|
136
|
+
$('#node-input-hueDevice').val(ui.item.hueDevice);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
// ########################
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
},
|
|
145
|
+
oneditsave: function () {
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
},
|
|
149
|
+
oneditcancel: function () {
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<script type="text/x-red" data-template-name="knxUltimateHueMotion">
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
<div class="form-row">
|
|
160
|
+
<b>HUE Light</b>  <span style="color:red"
|
|
161
|
+
 <i class="fa fa-question-circle"></i></span>
|
|
162
|
+
 <a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration"><u>Configuration</u></a>
|
|
163
|
+
<br />
|
|
164
|
+
<p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hueMotion.png'></p>
|
|
165
|
+
<br />
|
|
166
|
+
<label for="node-input-server" >
|
|
167
|
+
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img>
|
|
168
|
+
KNX GW
|
|
169
|
+
</label>
|
|
170
|
+
<input type="text" id="node-input-server" />
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<div class="form-row">
|
|
174
|
+
<label for="node-input-serverHue">
|
|
175
|
+
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAABFUlEQVQ4EZWSsWoCQRCG1yiENEFEi6QSkjqWWoqFoBYJ+Br6JHkMn8Iibd4ihQpaJIhWNkry/ZtdGZY78Qa+m39nZ+dm9s4550awglNBluS/gVtAX6KgDclf68w2OThgfR9iT/jnoEv4TtByDThWTCDKW4SSZTf/zj9/eZbN+izTDuKGimu0vPF8B/YN8aC8LmcOj/AAn9CFTEs70Js/oGqy79C69bqJ5XbQI2kGO5N8QL9D08S8zBtBF5ZaVsznpCMoqJnVdjTpb1Db0fwIWmQV6BLXzFOYgA6/gDVfQN9bBWp2J2hdWDPoBV5FrKnAJutHikk/CHHR8i7x4iG7qQ720IYvu3GFbpHjx3pFrOFYkA354z/5bkK826phyAAAAABJRU5ErkJggg=="/>
|
|
176
|
+
HUE Bridge
|
|
177
|
+
</label>
|
|
178
|
+
<input type="text" id="node-input-serverHue" />
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<br/>
|
|
182
|
+
<p>
|
|
183
|
+
<b>Philips HUE</b>
|
|
184
|
+
</p>
|
|
185
|
+
|
|
186
|
+
<div class="form-row">
|
|
187
|
+
<label for="node-input-hueDevice" >
|
|
188
|
+
<i class="fa fa-play-circle"></i> Hue Button</label>
|
|
189
|
+
<input type="text" id="node-input-name" placeholder="Enter your hue device name" />
|
|
190
|
+
<input type="hidden" id="node-input-hueDevice" />
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<br/>
|
|
194
|
+
|
|
195
|
+
<p>
|
|
196
|
+
<b>PHILIPS HUE MOTION EVENTS -> TO KNX</b>
|
|
197
|
+
</p>
|
|
198
|
+
|
|
199
|
+
<div class="form-row">
|
|
200
|
+
<label for="node-input-namemotion" style="width:100px;"><i class="fa fa-play-circle-o"></i> Motion</span></label>
|
|
201
|
+
|
|
202
|
+
<label for="node-input-GAmotion" style="width:20px;">GA</label>
|
|
203
|
+
<input type="text" id="node-input-GAmotion" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
204
|
+
|
|
205
|
+
<label for="node-input-dptmotion" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
|
|
206
|
+
<select id="node-input-dptmotion" style="width:140px;"></select>
|
|
207
|
+
|
|
208
|
+
<label for="node-input-namemotion" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
|
|
209
|
+
<input type="text" id="node-input-namemotion" style="width:200px;margin-left: 5px; text-align: left;">
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
<br/>
|
|
216
|
+
<br/>
|
|
217
|
+
<br/>
|
|
218
|
+
<br/>
|
|
219
|
+
<br/>
|
|
220
|
+
<br/>
|
|
221
|
+
<br/>
|
|
222
|
+
<br/>
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<script type="text/markdown" data-help-name="knxUltimateHueMotion">
|
|
228
|
+
<p> This node lets you get the events from your HUE motion device.<br/>
|
|
229
|
+
|
|
230
|
+
**PHILIPS HUE MOTION EVENTS -> TO KNX**
|
|
231
|
+
Here you can get the HUE motion events.<br/>
|
|
232
|
+
Start typing in the GA field, the name or group address of your KNX device, the avaiable devices start showing up while you're typing.
|
|
233
|
+
|
|
234
|
+
|Property|Description|
|
|
235
|
+
|--|--|
|
|
236
|
+
| Motion | As soon as someone moves in the motion device's range |
|
|
237
|
+
|
|
238
|
+
### Outputs
|
|
239
|
+
|
|
240
|
+
1. Standard output
|
|
241
|
+
: payload (boolean) : the standard output of the command. **true** for motion | **false** for motion end.
|
|
242
|
+
|
|
243
|
+
### Details
|
|
244
|
+
|
|
245
|
+
`msg.payload` is used as the payload of the published message.
|
|
246
|
+
|
|
247
|
+
[Find it useful?](https://www.paypal.me/techtoday)
|
|
248
|
+
|
|
249
|
+
<br/>
|
|
250
|
+
|
|
251
|
+
</script>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function knxUltimateHueMotion(config) {
|
|
5
|
+
RED.nodes.createNode(this, config)
|
|
6
|
+
const node = this
|
|
7
|
+
node.server = RED.nodes.getNode(config.server)
|
|
8
|
+
node.serverHue = RED.nodes.getNode(config.serverHue)
|
|
9
|
+
node.topic = node.name
|
|
10
|
+
node.name = config.name === undefined ? 'Hue' : config.name
|
|
11
|
+
node.dpt = ''
|
|
12
|
+
node.notifyreadrequest = false
|
|
13
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
14
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
15
|
+
node.notifyresponse = false
|
|
16
|
+
node.notifywrite = true
|
|
17
|
+
node.initialread = true
|
|
18
|
+
node.listenallga = true // Don't remove
|
|
19
|
+
node.outputtype = 'write'
|
|
20
|
+
node.outputRBE = false // Apply or not RBE to the output (Messages coming from flow)
|
|
21
|
+
node.inputRBE = false // Apply or not RBE to the input (Messages coming from BUS)
|
|
22
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
23
|
+
node.passthrough = 'no'
|
|
24
|
+
node.formatmultiplyvalue = 1
|
|
25
|
+
node.formatnegativevalue = 'leave'
|
|
26
|
+
node.formatdecimalsvalue = 2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// Used to call the status update from the config node.
|
|
31
|
+
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
36
|
+
node.handleSend = msg => {
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
node.handleSendHUE = _event => {
|
|
40
|
+
try {
|
|
41
|
+
if (_event.id === config.hueDevice) {
|
|
42
|
+
const knxMsgPayload = {}
|
|
43
|
+
knxMsgPayload.ga = config.GAmotion
|
|
44
|
+
knxMsgPayload.dpt = config.dptmotion
|
|
45
|
+
knxMsgPayload.payload = _event.motion.motion
|
|
46
|
+
|
|
47
|
+
if (_event.hasOwnProperty('motion') && _event.motion.hasOwnProperty('motion')) {
|
|
48
|
+
// Send to KNX bus
|
|
49
|
+
if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
50
|
+
node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
51
|
+
|
|
52
|
+
// Setup the output msg
|
|
53
|
+
knxMsgPayload.topic = knxMsgPayload.ga
|
|
54
|
+
delete knxMsgPayload.ga
|
|
55
|
+
knxMsgPayload.name = node.name
|
|
56
|
+
knxMsgPayload.event = 'motion'
|
|
57
|
+
|
|
58
|
+
// Send payload
|
|
59
|
+
knxMsgPayload.rawEvent = _event
|
|
60
|
+
node.send(knxMsgPayload)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// On each deploy, unsubscribe+resubscribe
|
|
69
|
+
if (node.server) {
|
|
70
|
+
node.server.removeClient(node)
|
|
71
|
+
node.server.addClient(node)
|
|
72
|
+
}
|
|
73
|
+
if (node.serverHue) {
|
|
74
|
+
node.serverHue.removeClient(node)
|
|
75
|
+
node.serverHue.addClient(node)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
node.on('input', function (msg) {
|
|
79
|
+
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
node.on('close', function (done) {
|
|
83
|
+
if (node.server) {
|
|
84
|
+
node.server.removeClient(node)
|
|
85
|
+
}
|
|
86
|
+
done()
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
RED.nodes.registerType('knxUltimateHueMotion', knxUltimateHueMotion)
|
|
90
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('knxUltimateHueTapDial', {
|
|
3
|
+
category: "KNX Ultimate",
|
|
4
|
+
color: '#C7E9C0',
|
|
5
|
+
defaults: {
|
|
6
|
+
//buttonState: {value: true},
|
|
7
|
+
server: { type: "knxUltimate-config", required: false },
|
|
8
|
+
serverHue: { type: "hue-config", required: true },
|
|
9
|
+
name: { value: "" },
|
|
10
|
+
|
|
11
|
+
namerepeat: { value: "" },
|
|
12
|
+
GArepeat: { value: "" },
|
|
13
|
+
dptrepeat: { value: "" },
|
|
14
|
+
|
|
15
|
+
hueDevice: { value: "" }
|
|
16
|
+
},
|
|
17
|
+
inputs: 0,
|
|
18
|
+
outputs: 1,
|
|
19
|
+
icon: "node-hue-icon.svg",
|
|
20
|
+
label: function () {
|
|
21
|
+
return (this.name);
|
|
22
|
+
},
|
|
23
|
+
paletteLabel: "Hue Tap Dial (beta)",
|
|
24
|
+
// button: {
|
|
25
|
+
// enabled: function() {
|
|
26
|
+
// // return whether or not the button is enabled, based on the current
|
|
27
|
+
// // configuration of the node
|
|
28
|
+
// return !this.changed
|
|
29
|
+
// },
|
|
30
|
+
// visible: function() {
|
|
31
|
+
// // return whether or not the button is visible, based on the current
|
|
32
|
+
// // configuration of the node
|
|
33
|
+
// return this.hasButton
|
|
34
|
+
// },
|
|
35
|
+
// //toggle: "buttonState",
|
|
36
|
+
// onclick: function() {}
|
|
37
|
+
// },
|
|
38
|
+
oneditprepare: function () {
|
|
39
|
+
var node = this;
|
|
40
|
+
var oNodeServer = RED.nodes.node($("#node-input-server").val()); // Store the config-node
|
|
41
|
+
var oNodeServerHue = RED.nodes.node($("#node-input-serverHue").val()); // Store the config-node
|
|
42
|
+
|
|
43
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
44
|
+
$("#node-input-server").change(function () {
|
|
45
|
+
try {
|
|
46
|
+
oNodeServer = RED.nodes.node($(this).val());
|
|
47
|
+
} catch (error) { }
|
|
48
|
+
});
|
|
49
|
+
// 19/02/2020 Used to get the server sooner als deploy.
|
|
50
|
+
$("#node-input-serverHue").change(function () {
|
|
51
|
+
try {
|
|
52
|
+
oNodeServerHue = RED.nodes.node($(this).val());
|
|
53
|
+
} catch (error) { }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 31/03/2020 Search Helper
|
|
57
|
+
function fullSearch(sourceText, searchString) {
|
|
58
|
+
// This searches for all words in a string
|
|
59
|
+
var aSearchWords = searchString.toLowerCase().split(" ");
|
|
60
|
+
var i = 0;
|
|
61
|
+
for (let index = 0; index < aSearchWords.length; index++) {
|
|
62
|
+
if (sourceText.toLowerCase().indexOf(aSearchWords[index]) > -1) i += 1;
|
|
63
|
+
}
|
|
64
|
+
return i == aSearchWords.length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// DPT repeat
|
|
69
|
+
// ########################
|
|
70
|
+
$.getJSON('knxUltimateDpts', (data) => {
|
|
71
|
+
data.forEach(dpt => {
|
|
72
|
+
if (dpt.value.startsWith("3.007") || dpt.value.startsWith("5.00")) {
|
|
73
|
+
$("#node-input-dptrepeat").append($("<option></option>")
|
|
74
|
+
.attr("value", dpt.value)
|
|
75
|
+
.text(dpt.text))
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
$("#node-input-dptrepeat").val(this.dptrepeat)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Autocomplete suggestion with ETS csv File
|
|
83
|
+
$("#node-input-GArepeat").autocomplete({
|
|
84
|
+
minLength: 1,
|
|
85
|
+
source: function (request, response) {
|
|
86
|
+
$.getJSON("knxUltimatecsv?nodeID=" + oNodeServer.id, (data) => {
|
|
87
|
+
response($.map(data, function (value, key) {
|
|
88
|
+
var sSearch = (value.ga + " (" + value.devicename + ") DPT" + value.dpt);
|
|
89
|
+
if (fullSearch(sSearch, request.term)) {
|
|
90
|
+
if (value.dpt.startsWith('5.001') || value.dpt.startsWith('3.007')) {
|
|
91
|
+
return {
|
|
92
|
+
label: value.ga + " # " + value.devicename + " # " + value.dpt, // Label for Display
|
|
93
|
+
value: value.ga // Value
|
|
94
|
+
}
|
|
95
|
+
} else { return null; }
|
|
96
|
+
} else {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
101
|
+
}, select: function (event, ui) {
|
|
102
|
+
// Sets Datapoint and device name automatically
|
|
103
|
+
var sDevName = ui.item.label.split("#")[1].trim();
|
|
104
|
+
try {
|
|
105
|
+
sDevName = sDevName.substr(sDevName.indexOf(")") + 1).trim();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
}
|
|
108
|
+
$('#node-input-namerepeat').val(sDevName);
|
|
109
|
+
var optVal = $("#node-input-dptrepeat option:contains('" + ui.item.label.split("#")[2].trim() + "')").attr('value');
|
|
110
|
+
// Select the option value
|
|
111
|
+
$("#node-input-dptrepeat").val(optVal);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
// Autocomplete suggestion with HUE
|
|
119
|
+
$("#node-input-name").autocomplete({
|
|
120
|
+
minLength: 1,
|
|
121
|
+
source: function (request, response) {
|
|
122
|
+
$.getJSON("KNXUltimateGetDevicesHUE?rtype=relative_rotary&nodeID=" + oNodeServerHue.id, (data) => {
|
|
123
|
+
response($.map(data.devices, function (value, key) {
|
|
124
|
+
//alert(JSON.stringify(value) + " "+ key)
|
|
125
|
+
var sSearch = (value.name);
|
|
126
|
+
if (fullSearch(sSearch, request.term)) {
|
|
127
|
+
return {
|
|
128
|
+
hueDevice: value.id, // Label for Display
|
|
129
|
+
value: value.name // Value
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}));
|
|
135
|
+
});
|
|
136
|
+
}, select: function (event, ui) {
|
|
137
|
+
// Sets the fields
|
|
138
|
+
$('#node-input-hueDevice').val(ui.item.hueDevice);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
// ########################
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
},
|
|
147
|
+
oneditsave: function () {
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
},
|
|
151
|
+
oneditcancel: function () {
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<script type="text/x-red" data-template-name="knxUltimateHueTapDial">
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
<div class="form-row">
|
|
162
|
+
<b>HUE Light</b>  <span style="color:red"
|
|
163
|
+
 <i class="fa fa-question-circle"></i></span>
|
|
164
|
+
 <a target="_blank" href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration"><u>Configuration</u></a>
|
|
165
|
+
<br />
|
|
166
|
+
<p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-knx-ultimate/master/img/hueTapDial.png'></p>
|
|
167
|
+
<br />
|
|
168
|
+
<label for="node-input-server" >
|
|
169
|
+
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAEZyIDYgQXVnIDIwMTAgMjE6NTI6MTkgKzAxMDD84aS8AAAAB3RJTUUH3gYYCicNV+4WIQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAACUSURBVHjaY2CgFZg5c+Z/ZEyWAZ8+f/6/ZsWs/xoamqMGkGrA6Wla/1+fVARjEBuGsSoGmY4eZSCNL59d/g8DIDbIAHR14OgFGQByKjIGKX5+6/T///8gGMQGiV1+/B0Fg70GIkD+RMYgxf/O5/7//2MSmAZhkBi6OrgB6Bg5DGB4ajr3f2xqsYYLSDE2THJUDg0AAAqyDVd4tp4YAAAAAElFTkSuQmCC"></img>
|
|
170
|
+
KNX GW
|
|
171
|
+
</label>
|
|
172
|
+
<input type="text" id="node-input-server" />
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="form-row">
|
|
176
|
+
<label for="node-input-serverHue">
|
|
177
|
+
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAABFUlEQVQ4EZWSsWoCQRCG1yiENEFEi6QSkjqWWoqFoBYJ+Br6JHkMn8Iibd4ihQpaJIhWNkry/ZtdGZY78Qa+m39nZ+dm9s4550awglNBluS/gVtAX6KgDclf68w2OThgfR9iT/jnoEv4TtByDThWTCDKW4SSZTf/zj9/eZbN+izTDuKGimu0vPF8B/YN8aC8LmcOj/AAn9CFTEs70Js/oGqy79C69bqJ5XbQI2kGO5N8QL9D08S8zBtBF5ZaVsznpCMoqJnVdjTpb1Db0fwIWmQV6BLXzFOYgA6/gDVfQN9bBWp2J2hdWDPoBV5FrKnAJutHikk/CHHR8i7x4iG7qQ720IYvu3GFbpHjx3pFrOFYkA354z/5bkK826phyAAAAABJRU5ErkJggg=="/>
|
|
178
|
+
HUE Bridge
|
|
179
|
+
</label>
|
|
180
|
+
<input type="text" id="node-input-serverHue" />
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<br/>
|
|
184
|
+
<p>
|
|
185
|
+
<b>Philips HUE</b>
|
|
186
|
+
</p>
|
|
187
|
+
|
|
188
|
+
<div class="form-row">
|
|
189
|
+
<label for="node-input-hueDevice" >
|
|
190
|
+
<i class="fa fa-play-circle"></i> Hue Device</label>
|
|
191
|
+
<input type="text" id="node-input-name" placeholder="Enter your hue device name" />
|
|
192
|
+
<input type="hidden" id="node-input-hueDevice" />
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<br/>
|
|
196
|
+
|
|
197
|
+
<p>
|
|
198
|
+
<b>PHILIPS HUE BUTTON EVENTS -> TO KNX</b>
|
|
199
|
+
</p>
|
|
200
|
+
|
|
201
|
+
<div class="form-row">
|
|
202
|
+
<label for="node-input-namerepeat" style="width:100px;"><i class="fa fa-play-circle-o"></i> Rotate</span></label>
|
|
203
|
+
|
|
204
|
+
<label for="node-input-GArepeat" style="width:20px;">GA</label>
|
|
205
|
+
<input type="text" id="node-input-GArepeat" placeholder="Ex: 1/1/1" style="width:70px;margin-left: 5px; text-align: left;">
|
|
206
|
+
|
|
207
|
+
<label for="node-input-dptrepeat" style="width:40px; margin-left: 0px; text-align: right;">dpt</label>
|
|
208
|
+
<select id="node-input-dptrepeat" style="width:140px;"></select>
|
|
209
|
+
|
|
210
|
+
<label for="node-input-namerepeat" style="width:50px; margin-left: 0px; text-align: right;">Name</label>
|
|
211
|
+
<input type="text" id="node-input-namerepeat" style="width:200px;margin-left: 5px; text-align: left;">
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
<br/>
|
|
219
|
+
<br/>
|
|
220
|
+
<br/>
|
|
221
|
+
<br/>
|
|
222
|
+
<br/>
|
|
223
|
+
<br/>
|
|
224
|
+
<br/>
|
|
225
|
+
<br/>
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<script type="text/markdown" data-help-name="knxUltimateHueTapDial">
|
|
231
|
+
<p> This node lets you get the events from your HUE rotary, for example Tap Dial.<br/>
|
|
232
|
+
|
|
233
|
+
The Tap Dial button has 4 buttons and 1 rotary knob.
|
|
234
|
+
You can find the 4 buttons in the **Button node**, not here!
|
|
235
|
+
The rotary service of the Tap Dial button, can send a DIM (Datapoint 3.007) or absolute brightness (Datapoint 5.001) to the KNX bus,
|
|
236
|
+
to the selected group address.
|
|
237
|
+
|
|
238
|
+
**General**
|
|
239
|
+
|Property|Description|
|
|
240
|
+
|--|--|
|
|
241
|
+
| KNX GW | Select the KNX gateway to be used |
|
|
242
|
+
| HUE Bridge | Select the HUE Bridge to be used |
|
|
243
|
+
| Hue Button | HUE button to be used. The avaiable buttons start showing up while you're typing.|
|
|
244
|
+
|
|
245
|
+
<br/>
|
|
246
|
+
|
|
247
|
+
**PHILIPS HUE BUTTON EVENTS -> TO KNX**
|
|
248
|
+
Here you can get the HUE rotary service events.<br/>
|
|
249
|
+
Start typing in the GA field, the name or group address of your KNX device, the avaiable devices start showing up while you're typing.
|
|
250
|
+
|
|
251
|
+
|Property|Description|
|
|
252
|
+
|--|--|
|
|
253
|
+
| Rotate | This command is used either to send DIM (increase/decrease) or true/false commands to the KNX group address. Only as info, in the HUE event chain, this is called *repeat* |
|
|
254
|
+
|
|
255
|
+
### Outputs
|
|
256
|
+
|
|
257
|
+
1. Standard output
|
|
258
|
+
: payload (string|object) : the standard output of the command.
|
|
259
|
+
|
|
260
|
+
### Details
|
|
261
|
+
|
|
262
|
+
`msg.payload` is used as the payload of the published message.
|
|
263
|
+
If it contains an Object it will be converted to a JSON string before being sent.
|
|
264
|
+
|
|
265
|
+
[Find it useful?](https://www.paypal.me/techtoday)
|
|
266
|
+
|
|
267
|
+
<br/>
|
|
268
|
+
|
|
269
|
+
</script>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
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
|
+
|
|
18
|
+
function knxUltimateHueTapDial(config) {
|
|
19
|
+
RED.nodes.createNode(this, config)
|
|
20
|
+
const node = this
|
|
21
|
+
node.server = RED.nodes.getNode(config.server)
|
|
22
|
+
node.serverHue = RED.nodes.getNode(config.serverHue)
|
|
23
|
+
node.topic = node.name
|
|
24
|
+
node.name = config.name === undefined ? 'Hue' : config.name
|
|
25
|
+
node.dpt = ''
|
|
26
|
+
node.notifyreadrequest = false
|
|
27
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
28
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
29
|
+
node.notifyresponse = false
|
|
30
|
+
node.notifywrite = true
|
|
31
|
+
node.initialread = true
|
|
32
|
+
node.listenallga = true // Don't remove
|
|
33
|
+
node.outputtype = 'write'
|
|
34
|
+
node.outputRBE = false // Apply or not RBE to the output (Messages coming from flow)
|
|
35
|
+
node.inputRBE = false // Apply or not RBE to the input (Messages coming from BUS)
|
|
36
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
37
|
+
node.passthrough = 'no'
|
|
38
|
+
node.formatmultiplyvalue = 1
|
|
39
|
+
node.formatnegativevalue = 'leave'
|
|
40
|
+
node.formatdecimalsvalue = 2
|
|
41
|
+
node.brightnessState = 0
|
|
42
|
+
|
|
43
|
+
// Read the state of the light and store it in the holding object
|
|
44
|
+
try {
|
|
45
|
+
if (config.hueLight !== undefined && config.hueLight !== '') getLightState(node, config.hueLight)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// Used to call the status update from the config node.
|
|
52
|
+
node.setNodeStatus = ({ fill, shape, text, payload }) => {
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
57
|
+
node.handleSend = msg => {
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
node.handleSendHUE = _event => {
|
|
61
|
+
try {
|
|
62
|
+
if (_event.id === config.hueDevice) {
|
|
63
|
+
const knxMsgPayload = {}
|
|
64
|
+
knxMsgPayload.ga = config.GArepeat
|
|
65
|
+
knxMsgPayload.dpt = config.dptrepeat
|
|
66
|
+
if (_event.relative_rotary.last_event.rotation.direction === 'clock_wise') {
|
|
67
|
+
if (knxMsgPayload.dpt.startsWith('3.007')) {
|
|
68
|
+
knxMsgPayload.payload = { decr_incr: 1, data: 5 }
|
|
69
|
+
} else if (knxMsgPayload.dpt.startsWith('5.001')) {
|
|
70
|
+
//0 – maximum: 32767
|
|
71
|
+
node.brightnessState < 100 ? node.brightnessState += 20 : node.brightnessState = 100
|
|
72
|
+
knxMsgPayload.payload = node.brightnessState
|
|
73
|
+
}
|
|
74
|
+
} else if (_event.relative_rotary.last_event.rotation.direction === 'counter_clock_wise') {
|
|
75
|
+
if (knxMsgPayload.dpt.startsWith('3.007')) {
|
|
76
|
+
knxMsgPayload.payload = { decr_incr: 0, data: 5 }
|
|
77
|
+
} else if (knxMsgPayload.dpt.startsWith('5.001')) {
|
|
78
|
+
node.brightnessState > 0 ? node.brightnessState -= 20 : node.brightnessState = 0
|
|
79
|
+
knxMsgPayload.payload = node.brightnessState
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Send to KNX bus
|
|
84
|
+
if (knxMsgPayload.ga !== '' && knxMsgPayload.ga !== undefined) node.server.writeQueueAdd({ grpaddr: knxMsgPayload.ga, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write', nodecallerid: node.id })
|
|
85
|
+
node.status({ fill: 'green', shape: 'dot', text: 'HUE->KNX ' + JSON.stringify(knxMsgPayload.payload) + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
86
|
+
|
|
87
|
+
// Setup the output msg
|
|
88
|
+
knxMsgPayload.topic = knxMsgPayload.ga
|
|
89
|
+
delete knxMsgPayload.ga
|
|
90
|
+
knxMsgPayload.name = node.name
|
|
91
|
+
knxMsgPayload.event = 'rotation ' + _event.relative_rotary.last_event.rotation.direction
|
|
92
|
+
knxMsgPayload.payload = _event
|
|
93
|
+
node.send(knxMsgPayload)
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
node.status({ fill: 'red', shape: 'dot', text: 'HUE->KNX error ' + error.message + ' (' + new Date().getDate() + ', ' + new Date().toLocaleTimeString() + ')' })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// On each deploy, unsubscribe+resubscribe
|
|
101
|
+
if (node.server) {
|
|
102
|
+
node.server.removeClient(node)
|
|
103
|
+
node.server.addClient(node)
|
|
104
|
+
}
|
|
105
|
+
if (node.serverHue) {
|
|
106
|
+
node.serverHue.removeClient(node)
|
|
107
|
+
node.serverHue.addClient(node)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
node.on('input', function (msg) {
|
|
111
|
+
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
node.on('close', function (done) {
|
|
115
|
+
if (node.server) {
|
|
116
|
+
node.server.removeClient(node)
|
|
117
|
+
}
|
|
118
|
+
done()
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
RED.nodes.registerType('knxUltimateHueTapDial', knxUltimateHueTapDial)
|
|
122
|
+
}
|
package/nodes/utils/hueUtils.js
CHANGED
|
@@ -39,6 +39,12 @@ class classHUE extends EventEmitter {
|
|
|
39
39
|
if (_rtype === 'light') {
|
|
40
40
|
retArray.push({ name: linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
|
|
41
41
|
}
|
|
42
|
+
if (_rtype === 'motion') {
|
|
43
|
+
retArray.push({ name: linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
|
|
44
|
+
}
|
|
45
|
+
if (_rtype === 'relative_rotary') {
|
|
46
|
+
retArray.push({ name: linkedDevName + (Room !== undefined ? ', room ' + Room.metadata.name : ''), id: device.id })
|
|
47
|
+
}
|
|
42
48
|
})
|
|
43
49
|
return { devices: retArray }
|
|
44
50
|
} catch (error) {
|
|
@@ -101,6 +107,13 @@ class classHUE extends EventEmitter {
|
|
|
101
107
|
startPushEvents = async () => {
|
|
102
108
|
try {
|
|
103
109
|
this.hue = await hueApiV2.connect({
|
|
110
|
+
log: {
|
|
111
|
+
trace: (msg) => { },
|
|
112
|
+
debug: (msg) => { },
|
|
113
|
+
info: (msg) => { },
|
|
114
|
+
warn: (msg) => { },
|
|
115
|
+
error: (msg) => { }
|
|
116
|
+
},
|
|
104
117
|
host: this.HUEBridgeIP,
|
|
105
118
|
key: this.username,
|
|
106
119
|
eventListener: this.listener // The eventlistener is given as option
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"engines": {
|
|
4
4
|
"node": ">=14.0.0"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.0.
|
|
6
|
+
"version": "2.0.9",
|
|
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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"xml2js": "0.5.0",
|
|
18
18
|
"dns-sync": "0.2.1",
|
|
19
19
|
"node-hue-api": "5.0.0-beta.16",
|
|
20
|
-
"node-hue": "1.0.
|
|
20
|
+
"node-hue": "1.0.4",
|
|
21
21
|
"color-convert": "2.0.1"
|
|
22
22
|
},
|
|
23
23
|
"node-red": {
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"knxUltimateViewer": "/nodes/knxUltimateViewer.js",
|
|
35
35
|
"hueConfig": "/nodes/hue-config.js",
|
|
36
36
|
"knxUltimateHueLight": "/nodes/knxUltimateHueLight.js",
|
|
37
|
-
"knxUltimateHueButton": "/nodes/knxUltimateHueButton.js"
|
|
37
|
+
"knxUltimateHueButton": "/nodes/knxUltimateHueButton.js",
|
|
38
|
+
"knxUltimateHueMotion": "/nodes/knxUltimateHueMotion.js",
|
|
39
|
+
"knxUltimateHueTapDial": "/nodes/knxUltimateHueTapDial.js"
|
|
38
40
|
}
|
|
39
41
|
},
|
|
40
42
|
"repository": {
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<script type="text/x-red" data-help-name="hue-config">
|
|
2
|
-
<h1>KNX Ultimate - HUE config node</h1>
|
|
3
|
-
|
|
4
|
-
<p>
|
|
5
|
-
<a href="https://github.com/Supergiovane/node-red-contrib-knx-ultimate/wiki/en-hue-configuration" target="_blank"><i class="fa fa-info-circle"></i>  Help configuring</a>
|
|
6
|
-
</p>
|
|
7
|
-
|
|
8
|
-
<p>
|
|
9
|
-
<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>
|
|
10
|
-
</p>
|
|
11
|
-
</script>
|