node-red-contrib-knx-ultimate 6.2.2 → 6.2.3-beta.0
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 +14 -0
- package/nodes/knxUltimateAI.js +7 -1
- package/nodes/knxUltimateHueAreaMotion.html +15 -4
- package/nodes/knxUltimateHueBattery.html +15 -4
- package/nodes/knxUltimateHueButton.html +15 -4
- package/nodes/knxUltimateHueCameraMotion.html +15 -4
- package/nodes/knxUltimateHueContactSensor.html +15 -4
- package/nodes/knxUltimateHueController.html +1386 -0
- package/nodes/knxUltimateHueController.js +28 -0
- package/nodes/knxUltimateHueHumiditySensor.html +15 -4
- package/nodes/knxUltimateHueLight.html +15 -4
- package/nodes/knxUltimateHueLightSensor.html +15 -4
- package/nodes/knxUltimateHueMotion.html +15 -4
- package/nodes/knxUltimateHuePlug.html +15 -4
- package/nodes/knxUltimateHueScene.html +18 -7
- package/nodes/knxUltimateHueTapDial.html +15 -4
- package/nodes/knxUltimateHueTemperatureSensor.html +15 -4
- package/nodes/knxUltimateHueZigbeeConnectivity.html +15 -4
- package/nodes/knxUltimateHuedevice_software_update.html +15 -4
- package/nodes/locales/de/knxUltimateHueAreaMotion.html +1 -1
- package/nodes/locales/de/knxUltimateHueBattery.html +1 -0
- package/nodes/locales/de/knxUltimateHueButton.html +1 -0
- package/nodes/locales/de/knxUltimateHueCameraMotion.html +1 -0
- package/nodes/locales/de/knxUltimateHueContactSensor.html +1 -0
- package/nodes/locales/de/knxUltimateHueController.html +669 -0
- package/nodes/locales/de/knxUltimateHueController.json +48 -0
- package/nodes/locales/de/knxUltimateHueHumiditySensor.html +1 -0
- package/nodes/locales/de/knxUltimateHueLight.html +1 -0
- package/nodes/locales/de/knxUltimateHueLightSensor.html +1 -0
- package/nodes/locales/de/knxUltimateHueMotion.html +1 -0
- package/nodes/locales/de/knxUltimateHuePlug.html +1 -0
- package/nodes/locales/de/knxUltimateHueScene.html +1 -0
- package/nodes/locales/de/knxUltimateHueTapDial.html +1 -0
- package/nodes/locales/de/knxUltimateHueTemperatureSensor.html +1 -0
- package/nodes/locales/de/knxUltimateHueZigbeeConnectivity.html +1 -0
- package/nodes/locales/de/knxUltimateHuedevice_software_update.html +1 -0
- package/nodes/locales/en/knxUltimateHueAreaMotion.html +1 -1
- package/nodes/locales/en/knxUltimateHueBattery.html +1 -0
- package/nodes/locales/en/knxUltimateHueButton.html +1 -0
- package/nodes/locales/en/knxUltimateHueCameraMotion.html +1 -0
- package/nodes/locales/en/knxUltimateHueContactSensor.html +1 -0
- package/nodes/locales/en/knxUltimateHueController.html +672 -0
- package/nodes/locales/en/knxUltimateHueController.json +48 -0
- package/nodes/locales/en/knxUltimateHueHumiditySensor.html +1 -0
- package/nodes/locales/en/knxUltimateHueLight.html +1 -0
- package/nodes/locales/en/knxUltimateHueLightSensor.html +1 -0
- package/nodes/locales/en/knxUltimateHueMotion.html +1 -0
- package/nodes/locales/en/knxUltimateHuePlug.html +1 -0
- package/nodes/locales/en/knxUltimateHueScene.html +1 -0
- package/nodes/locales/en/knxUltimateHueTapDial.html +1 -0
- package/nodes/locales/en/knxUltimateHueTemperatureSensor.html +1 -0
- package/nodes/locales/en/knxUltimateHueZigbeeConnectivity.html +1 -0
- package/nodes/locales/en/knxUltimateHuedevice_software_update.html +1 -0
- package/nodes/locales/es/knxUltimateHueAreaMotion.html +1 -1
- package/nodes/locales/es/knxUltimateHueBattery.html +1 -0
- package/nodes/locales/es/knxUltimateHueButton.html +1 -0
- package/nodes/locales/es/knxUltimateHueCameraMotion.html +1 -0
- package/nodes/locales/es/knxUltimateHueContactSensor.html +1 -0
- package/nodes/locales/es/knxUltimateHueController.html +672 -0
- package/nodes/locales/es/knxUltimateHueController.json +48 -0
- package/nodes/locales/es/knxUltimateHueHumiditySensor.html +1 -0
- package/nodes/locales/es/knxUltimateHueLight.html +1 -0
- package/nodes/locales/es/knxUltimateHueLightSensor.html +1 -0
- package/nodes/locales/es/knxUltimateHueMotion.html +1 -0
- package/nodes/locales/es/knxUltimateHuePlug.html +1 -0
- package/nodes/locales/es/knxUltimateHueScene.html +1 -0
- package/nodes/locales/es/knxUltimateHueTapDial.html +1 -0
- package/nodes/locales/es/knxUltimateHueTemperatureSensor.html +1 -0
- package/nodes/locales/es/knxUltimateHueZigbeeConnectivity.html +1 -0
- package/nodes/locales/es/knxUltimateHuedevice_software_update.html +1 -0
- package/nodes/locales/fr/knxUltimateHueAreaMotion.html +1 -1
- package/nodes/locales/fr/knxUltimateHueBattery.html +1 -0
- package/nodes/locales/fr/knxUltimateHueButton.html +1 -0
- package/nodes/locales/fr/knxUltimateHueCameraMotion.html +1 -0
- package/nodes/locales/fr/knxUltimateHueContactSensor.html +1 -0
- package/nodes/locales/fr/knxUltimateHueController.html +672 -0
- package/nodes/locales/fr/knxUltimateHueController.json +48 -0
- package/nodes/locales/fr/knxUltimateHueHumiditySensor.html +1 -0
- package/nodes/locales/fr/knxUltimateHueLight.html +1 -0
- package/nodes/locales/fr/knxUltimateHueLightSensor.html +1 -0
- package/nodes/locales/fr/knxUltimateHueMotion.html +1 -0
- package/nodes/locales/fr/knxUltimateHuePlug.html +1 -0
- package/nodes/locales/fr/knxUltimateHueScene.html +1 -0
- package/nodes/locales/fr/knxUltimateHueTapDial.html +1 -0
- package/nodes/locales/fr/knxUltimateHueTemperatureSensor.html +1 -0
- package/nodes/locales/fr/knxUltimateHueZigbeeConnectivity.html +1 -0
- package/nodes/locales/fr/knxUltimateHuedevice_software_update.html +1 -0
- package/nodes/locales/it/knxUltimateHueAreaMotion.html +1 -1
- package/nodes/locales/it/knxUltimateHueBattery.html +1 -0
- package/nodes/locales/it/knxUltimateHueButton.html +1 -0
- package/nodes/locales/it/knxUltimateHueCameraMotion.html +1 -0
- package/nodes/locales/it/knxUltimateHueContactSensor.html +1 -0
- package/nodes/locales/it/knxUltimateHueController.html +672 -0
- package/nodes/locales/it/knxUltimateHueController.json +48 -0
- package/nodes/locales/it/knxUltimateHueHumiditySensor.html +1 -0
- package/nodes/locales/it/knxUltimateHueLight.html +1 -0
- package/nodes/locales/it/knxUltimateHueLightSensor.html +1 -0
- package/nodes/locales/it/knxUltimateHueMotion.html +1 -0
- package/nodes/locales/it/knxUltimateHuePlug.html +1 -0
- package/nodes/locales/it/knxUltimateHueScene.html +1 -0
- package/nodes/locales/it/knxUltimateHueTapDial.html +1 -0
- package/nodes/locales/it/knxUltimateHueTemperatureSensor.html +1 -0
- package/nodes/locales/it/knxUltimateHueZigbeeConnectivity.html +1 -0
- package/nodes/locales/it/knxUltimateHuedevice_software_update.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueAreaMotion.html +1 -1
- package/nodes/locales/zh-CN/knxUltimateHueBattery.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueButton.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueCameraMotion.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueContactSensor.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueController.html +674 -0
- package/nodes/locales/zh-CN/knxUltimateHueController.json +48 -0
- package/nodes/locales/zh-CN/knxUltimateHueHumiditySensor.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueLight.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueLightSensor.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueMotion.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHuePlug.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueScene.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueTapDial.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueTemperatureSensor.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHueZigbeeConnectivity.html +1 -0
- package/nodes/locales/zh-CN/knxUltimateHuedevice_software_update.html +1 -0
- package/nodes/utils/hueControllerProfileAdapter.js +104 -0
- package/nodes/utils/hueControllerProfiles/runtime/area_motion.js +221 -0
- package/nodes/utils/hueControllerProfiles/runtime/button.js +295 -0
- package/nodes/utils/hueControllerProfiles/runtime/camera_motion.js +221 -0
- package/nodes/utils/hueControllerProfiles/runtime/contact.js +191 -0
- package/nodes/utils/hueControllerProfiles/runtime/device_power.js +185 -0
- package/nodes/utils/hueControllerProfiles/runtime/device_software_update.js +185 -0
- package/nodes/utils/hueControllerProfiles/runtime/humidity.js +204 -0
- package/nodes/utils/hueControllerProfiles/runtime/light.js +1583 -0
- package/nodes/utils/hueControllerProfiles/runtime/light_level.js +180 -0
- package/nodes/utils/hueControllerProfiles/runtime/motion.js +185 -0
- package/nodes/utils/hueControllerProfiles/runtime/plug.js +344 -0
- package/nodes/utils/hueControllerProfiles/runtime/relative_rotary.js +271 -0
- package/nodes/utils/hueControllerProfiles/runtime/scene.js +352 -0
- package/nodes/utils/hueControllerProfiles/runtime/temperature.js +187 -0
- package/nodes/utils/hueControllerProfiles/runtime/zigbee_connectivity.js +185 -0
- package/package.json +5 -2
- package/resources/hueControllerMigration.js +199 -0
- package/resources/hueControllerMigrationDialog.js +189 -0
- package/resources/hueControllerProfiles.js +9290 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: contact.
|
|
2
|
+
// This module is shipped with the Controller but is intentionally absent from
|
|
3
|
+
// package.json, so Node-RED never registers it as a separate palette node.
|
|
4
|
+
module.exports = function (RED) {
|
|
5
|
+
function knxUltimateHueContactSensor (config) {
|
|
6
|
+
RED.nodes.createNode(this, config)
|
|
7
|
+
const node = this
|
|
8
|
+
node.serverKNX = RED.nodes.getNode(config.server) || undefined
|
|
9
|
+
node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
|
|
10
|
+
node.topic = node.name
|
|
11
|
+
node.name = config.name === undefined ? 'Hue' : config.name
|
|
12
|
+
node.dpt = ''
|
|
13
|
+
node.notifyreadrequest = false
|
|
14
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
15
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
16
|
+
node.notifyresponse = false
|
|
17
|
+
node.notifywrite = true
|
|
18
|
+
node.initialread = true
|
|
19
|
+
node.listenallga = true // Don't remove
|
|
20
|
+
node.outputtype = 'write'
|
|
21
|
+
node.outputRBE = false // Apply or not RBE to the output (Messages coming from flow)
|
|
22
|
+
node.inputRBE = false // Apply or not RBE to the input (Messages coming from BUS)
|
|
23
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
24
|
+
node.passthrough = 'no'
|
|
25
|
+
node.formatmultiplyvalue = 1
|
|
26
|
+
node.formatnegativevalue = 'leave'
|
|
27
|
+
node.formatdecimalsvalue = 2
|
|
28
|
+
node.hueDevice = config.hueDevice
|
|
29
|
+
// Re-publish the authoritative contact state at startup and after every event-stream
|
|
30
|
+
// (re)connection. Without this, any contact_report event missed during an SSE reconnect
|
|
31
|
+
// gap leaves KNX stuck on the last value seen (see issue #514).
|
|
32
|
+
node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
|
|
33
|
+
// Timestamp (ms) of the last contact_report.changed we already applied to KNX. Used to
|
|
34
|
+
// ignore stale/out-of-order reports (e.g. replays after a reconnect).
|
|
35
|
+
node.lastContactChangedTs = undefined
|
|
36
|
+
|
|
37
|
+
const pushStatus = (status) => {
|
|
38
|
+
if (!status) return
|
|
39
|
+
const provider = node.serverKNX
|
|
40
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
41
|
+
provider.applyStatusUpdate(node, status)
|
|
42
|
+
} else {
|
|
43
|
+
node.status(status)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const updateStatus = (status) => {
|
|
48
|
+
if (!status) return
|
|
49
|
+
pushStatus(status)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formatTs = (date) => {
|
|
53
|
+
const d = date instanceof Date ? date : new Date(date)
|
|
54
|
+
const provider = node.serverKNX
|
|
55
|
+
if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
|
|
56
|
+
return `${d.getDate()}, ${d.toLocaleTimeString()}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const safeSendToKNX = (telegram, context = 'write') => {
|
|
60
|
+
try {
|
|
61
|
+
if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
|
|
62
|
+
const now = new Date()
|
|
63
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
|
|
67
|
+
} catch (error) {
|
|
68
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Used to call the status update from the config node.
|
|
73
|
+
node.setNodeStatus = ({
|
|
74
|
+
fill, shape, text, payload
|
|
75
|
+
}) => {
|
|
76
|
+
try {
|
|
77
|
+
if (payload === undefined) payload = ''
|
|
78
|
+
const dDate = new Date()
|
|
79
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
80
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
81
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
82
|
+
} catch (error) { }
|
|
83
|
+
}
|
|
84
|
+
// Used to call the status update from the HUE config node.
|
|
85
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
86
|
+
try {
|
|
87
|
+
if (payload === undefined) payload = ''
|
|
88
|
+
const dDate = new Date()
|
|
89
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
90
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
91
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
92
|
+
} catch (error) { }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
96
|
+
node.handleSend = (msg) => {
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
node.handleSendHUE = (_event) => {
|
|
100
|
+
try {
|
|
101
|
+
if (_event.id === config.hueDevice) {
|
|
102
|
+
if (!_event.hasOwnProperty('contact_report')) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// contact_report.state is always the real current state (per Hue API v2), and
|
|
107
|
+
// contact_report.changed is when it last changed. Ignore reports that are not newer
|
|
108
|
+
// than the last one applied, so stale/replayed events after an SSE reconnect can't
|
|
109
|
+
// flip KNX to a wrong state (issue #514).
|
|
110
|
+
const changedTs = _event.contact_report.changed !== undefined ? new Date(_event.contact_report.changed).getTime() : NaN
|
|
111
|
+
if (!Number.isNaN(changedTs)) {
|
|
112
|
+
if (node.lastContactChangedTs !== undefined && changedTs <= node.lastContactChangedTs) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
node.lastContactChangedTs = changedTs
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const knxMsgPayload = {}
|
|
119
|
+
knxMsgPayload.topic = config.GAcontact
|
|
120
|
+
knxMsgPayload.dpt = config.dptcontact
|
|
121
|
+
|
|
122
|
+
if (_event.hasOwnProperty('contact_report')) {
|
|
123
|
+
knxMsgPayload.payload = _event.contact_report.state === 'contact'
|
|
124
|
+
|
|
125
|
+
// Send to KNX bus
|
|
126
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
127
|
+
safeSendToKNX({
|
|
128
|
+
grpaddr: knxMsgPayload.topic,
|
|
129
|
+
payload: knxMsgPayload.payload,
|
|
130
|
+
dpt: knxMsgPayload.dpt,
|
|
131
|
+
outputtype: 'write'
|
|
132
|
+
}, 'write')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
updateStatus({
|
|
136
|
+
fill: 'green',
|
|
137
|
+
shape: 'dot',
|
|
138
|
+
text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})`
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Set up the output msg
|
|
142
|
+
knxMsgPayload.name = node.name
|
|
143
|
+
knxMsgPayload.event = 'contact'
|
|
144
|
+
|
|
145
|
+
// Send payload
|
|
146
|
+
knxMsgPayload.rawEvent = _event
|
|
147
|
+
node.send(knxMsgPayload)
|
|
148
|
+
node.setNodeStatusHue({
|
|
149
|
+
fill: 'blue',
|
|
150
|
+
shape: 'ring',
|
|
151
|
+
text: 'HUE->KNX',
|
|
152
|
+
payload: knxMsgPayload.payload
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
updateStatus({
|
|
158
|
+
fill: 'red',
|
|
159
|
+
shape: 'dot',
|
|
160
|
+
text: `HUE->KNX error ${error.message} (${formatTs(new Date())})`
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// On each deploy, unsubscribe+resubscribe
|
|
166
|
+
if (node.serverKNX) {
|
|
167
|
+
node.serverKNX.removeClient(node)
|
|
168
|
+
node.serverKNX.addClient(node)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (node.serverHue) {
|
|
172
|
+
node.serverHue.removeClient(node)
|
|
173
|
+
node.serverHue.addClient(node)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
node.on('input', (msg) => {
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
node.on('close', (done) => {
|
|
180
|
+
if (node.serverKNX) {
|
|
181
|
+
node.serverKNX.removeClient(node)
|
|
182
|
+
}
|
|
183
|
+
if (node.serverHue) {
|
|
184
|
+
node.serverHue.removeClient(node)
|
|
185
|
+
}
|
|
186
|
+
done()
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
RED.nodes.registerType('knxUltimateHueContactSensor', knxUltimateHueContactSensor)
|
|
191
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: device_power.
|
|
2
|
+
// This module is shipped with the Controller but is intentionally absent from
|
|
3
|
+
// package.json, so Node-RED never registers it as a separate palette node.
|
|
4
|
+
module.exports = function (RED) {
|
|
5
|
+
function knxUltimateHueBattery (config) {
|
|
6
|
+
RED.nodes.createNode(this, config)
|
|
7
|
+
const node = this
|
|
8
|
+
node.serverKNX = RED.nodes.getNode(config.server) || undefined
|
|
9
|
+
node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
|
|
10
|
+
node.topic = node.name
|
|
11
|
+
node.name = config.name === undefined ? 'Hue' : config.name
|
|
12
|
+
node.dpt = ''
|
|
13
|
+
node.notifyreadrequest = true
|
|
14
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
15
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
16
|
+
node.notifyresponse = false
|
|
17
|
+
node.notifywrite = true
|
|
18
|
+
node.initialread = true
|
|
19
|
+
node.listenallga = true // Don't remove
|
|
20
|
+
node.outputtype = 'write'
|
|
21
|
+
node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
|
|
22
|
+
node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
|
|
23
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
24
|
+
node.passthrough = 'no'
|
|
25
|
+
node.formatmultiplyvalue = 1
|
|
26
|
+
node.formatnegativevalue = 'leave'
|
|
27
|
+
node.formatdecimalsvalue = 2
|
|
28
|
+
node.hueDevice = config.hueDevice
|
|
29
|
+
node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
|
|
30
|
+
node.currentDeviceValue = 0
|
|
31
|
+
const pinsSetting = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes' || config.enableNodePINS === true)
|
|
32
|
+
node.enableNodePINS = pinsSetting ? 'yes' : 'no'
|
|
33
|
+
node.outputs = pinsSetting ? 1 : 0
|
|
34
|
+
if (!node.serverKNX && node.outputs === 0) {
|
|
35
|
+
node.enableNodePINS = 'yes'
|
|
36
|
+
node.outputs = 1
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pushStatus = (status) => {
|
|
40
|
+
if (!status) return
|
|
41
|
+
const provider = node.serverKNX
|
|
42
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
43
|
+
provider.applyStatusUpdate(node, status)
|
|
44
|
+
} else {
|
|
45
|
+
node.status(status)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const updateStatus = (status) => {
|
|
50
|
+
if (!status) return
|
|
51
|
+
pushStatus(status)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const formatTs = (date) => {
|
|
55
|
+
const d = date instanceof Date ? date : new Date(date)
|
|
56
|
+
const provider = node.serverKNX
|
|
57
|
+
if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
|
|
58
|
+
return `${d.getDate()}, ${d.toLocaleTimeString()}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const safeSendToKNX = (telegram, context = 'write') => {
|
|
62
|
+
try {
|
|
63
|
+
if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
|
|
64
|
+
const now = new Date()
|
|
65
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
|
|
69
|
+
} catch (error) {
|
|
70
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Used to call the status update from the config node.
|
|
75
|
+
node.setNodeStatus = ({
|
|
76
|
+
fill, shape, text, payload
|
|
77
|
+
}) => {
|
|
78
|
+
try {
|
|
79
|
+
if (payload === undefined) payload = ''
|
|
80
|
+
const dDate = new Date()
|
|
81
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
82
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
83
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
84
|
+
} catch (error) { }
|
|
85
|
+
}
|
|
86
|
+
// Used to call the status update from the HUE config node.
|
|
87
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
88
|
+
try {
|
|
89
|
+
if (payload === undefined) payload = ''
|
|
90
|
+
const dDate = new Date()
|
|
91
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
92
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
93
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
94
|
+
} catch (error) { }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
98
|
+
node.handleSend = (msg) => {
|
|
99
|
+
// Respond to KNX read telegram, by sending the current value as response telegram.
|
|
100
|
+
if (msg.knx.event === 'GroupValue_Read') {
|
|
101
|
+
switch (msg.knx.destination) {
|
|
102
|
+
case config.GAbatterysensor:
|
|
103
|
+
// To the KNX bus wires
|
|
104
|
+
node.sendResponseToKNX(node.currentDeviceValue)
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
node.handleSendHUE = (_event) => {
|
|
113
|
+
try {
|
|
114
|
+
if (_event.id === config.hueDevice) {
|
|
115
|
+
if (!_event.hasOwnProperty('power_state') || _event.power_state.battery_level === undefined) return
|
|
116
|
+
|
|
117
|
+
const knxMsgPayload = {}
|
|
118
|
+
knxMsgPayload.topic = config.GAbatterysensor
|
|
119
|
+
knxMsgPayload.dpt = config.dptbatterysensor
|
|
120
|
+
|
|
121
|
+
knxMsgPayload.payload = _event.power_state.battery_level
|
|
122
|
+
// Send to KNX bus
|
|
123
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
124
|
+
safeSendToKNX({
|
|
125
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write'
|
|
126
|
+
}, 'write')
|
|
127
|
+
}
|
|
128
|
+
node.currentDeviceValue = knxMsgPayload.payload
|
|
129
|
+
|
|
130
|
+
// Setup the output msg
|
|
131
|
+
knxMsgPayload.name = node.name
|
|
132
|
+
knxMsgPayload.event = 'power_state'
|
|
133
|
+
|
|
134
|
+
// Send payload
|
|
135
|
+
knxMsgPayload.rawEvent = _event
|
|
136
|
+
node.send(knxMsgPayload)
|
|
137
|
+
node.setNodeStatusHue({
|
|
138
|
+
fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
node.sendResponseToKNX = (_level) => {
|
|
147
|
+
const knxMsgPayload = {}
|
|
148
|
+
knxMsgPayload.topic = config.GAbatterysensor
|
|
149
|
+
knxMsgPayload.dpt = config.dptbatterysensor
|
|
150
|
+
|
|
151
|
+
knxMsgPayload.payload = _level
|
|
152
|
+
// Send to KNX bus
|
|
153
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
154
|
+
safeSendToKNX({
|
|
155
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response'
|
|
156
|
+
}, 'response')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// On each deploy, unsubscribe+resubscribe
|
|
161
|
+
if (node.serverKNX) {
|
|
162
|
+
node.serverKNX.removeClient(node)
|
|
163
|
+
node.serverKNX.addClient(node)
|
|
164
|
+
}
|
|
165
|
+
if (node.serverHue) {
|
|
166
|
+
node.serverHue.removeClient(node)
|
|
167
|
+
node.serverHue.addClient(node)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
node.on('input', (msg) => {
|
|
171
|
+
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
node.on('close', (done) => {
|
|
175
|
+
if (node.serverKNX) {
|
|
176
|
+
node.serverKNX.removeClient(node)
|
|
177
|
+
}
|
|
178
|
+
if (node.serverHue) {
|
|
179
|
+
node.serverHue.removeClient(node)
|
|
180
|
+
}
|
|
181
|
+
done()
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
RED.nodes.registerType('knxUltimateHueBattery', knxUltimateHueBattery)
|
|
185
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: device_software_update.
|
|
2
|
+
// This module is shipped with the Controller but is intentionally absent from
|
|
3
|
+
// package.json, so Node-RED never registers it as a separate palette node.
|
|
4
|
+
module.exports = function (RED) {
|
|
5
|
+
function knxUltimateHuedevice_software_update (config) {
|
|
6
|
+
RED.nodes.createNode(this, config)
|
|
7
|
+
const node = this
|
|
8
|
+
node.serverKNX = RED.nodes.getNode(config.server) || undefined
|
|
9
|
+
node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
|
|
10
|
+
node.topic = node.name
|
|
11
|
+
node.name = config.name === undefined ? 'Hue' : config.name
|
|
12
|
+
node.dpt = ''
|
|
13
|
+
node.notifyreadrequest = true
|
|
14
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
15
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
16
|
+
node.notifyresponse = false
|
|
17
|
+
node.notifywrite = true
|
|
18
|
+
node.initialread = true
|
|
19
|
+
node.listenallga = true // Don't remove
|
|
20
|
+
node.outputtype = 'write'
|
|
21
|
+
node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
|
|
22
|
+
node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
|
|
23
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
24
|
+
node.passthrough = 'no'
|
|
25
|
+
node.formatmultiplyvalue = 1
|
|
26
|
+
node.formatnegativevalue = 'leave'
|
|
27
|
+
node.formatdecimalsvalue = 2
|
|
28
|
+
node.hueDevice = config.hueDevice
|
|
29
|
+
const pinsSetting = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes' || config.enableNodePINS === true)
|
|
30
|
+
node.enableNodePINS = pinsSetting ? 'yes' : 'no'
|
|
31
|
+
node.outputs = pinsSetting ? 1 : 0
|
|
32
|
+
if (!node.serverKNX && node.outputs === 0) {
|
|
33
|
+
node.enableNodePINS = 'yes'
|
|
34
|
+
node.outputs = 1
|
|
35
|
+
}
|
|
36
|
+
node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
|
|
37
|
+
node.currentDeviceValue = false
|
|
38
|
+
|
|
39
|
+
const pushStatus = (status) => {
|
|
40
|
+
if (!status) return
|
|
41
|
+
const provider = node.serverKNX
|
|
42
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
43
|
+
provider.applyStatusUpdate(node, status)
|
|
44
|
+
} else {
|
|
45
|
+
node.status(status)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const updateStatus = (status) => {
|
|
50
|
+
if (!status) return
|
|
51
|
+
pushStatus(status)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const formatTs = (date) => {
|
|
55
|
+
const d = date instanceof Date ? date : new Date(date)
|
|
56
|
+
const provider = node.serverKNX
|
|
57
|
+
if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
|
|
58
|
+
return `${d.getDate()}, ${d.toLocaleTimeString()}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const safeSendToKNX = (telegram, context = 'write') => {
|
|
62
|
+
try {
|
|
63
|
+
if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
|
|
64
|
+
const now = new Date()
|
|
65
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
|
|
69
|
+
} catch (error) {
|
|
70
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Used to call the status update from the config node.
|
|
75
|
+
node.setNodeStatus = ({
|
|
76
|
+
fill, shape, text, payload
|
|
77
|
+
}) => {
|
|
78
|
+
try {
|
|
79
|
+
if (payload === undefined) payload = ''
|
|
80
|
+
const dDate = new Date()
|
|
81
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
82
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
83
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
84
|
+
} catch (error) { }
|
|
85
|
+
}
|
|
86
|
+
// Used to call the status update from the HUE config node.
|
|
87
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
88
|
+
try {
|
|
89
|
+
if (payload === undefined) payload = ''
|
|
90
|
+
const dDate = new Date()
|
|
91
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
92
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
93
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
94
|
+
} catch (error) { }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
98
|
+
node.handleSend = (msg) => {
|
|
99
|
+
// Respond to KNX read telegram, by sending the current value as response telegram.
|
|
100
|
+
if (msg.knx.event === 'GroupValue_Read') {
|
|
101
|
+
switch (msg.knx.destination) {
|
|
102
|
+
case config.GAdevice_software_update:
|
|
103
|
+
// To the KNX bus wires
|
|
104
|
+
node.sendResponseToKNX(node.currentDeviceValue)
|
|
105
|
+
break
|
|
106
|
+
default:
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
node.handleSendHUE = (_event) => {
|
|
113
|
+
try {
|
|
114
|
+
// Event status: one of connected, disconnected, connectivity_issue, unidirectional_incoming
|
|
115
|
+
if (_event.id === config.hueDevice) {
|
|
116
|
+
if (!_event.hasOwnProperty('state') || _event.state === undefined) return
|
|
117
|
+
|
|
118
|
+
const knxMsgPayload = {}
|
|
119
|
+
knxMsgPayload.topic = config.GAdevice_software_update
|
|
120
|
+
knxMsgPayload.dpt = config.dptdevice_software_update
|
|
121
|
+
|
|
122
|
+
knxMsgPayload.payload = (_event.state !== 'no_update')
|
|
123
|
+
// Send to KNX bus
|
|
124
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
125
|
+
safeSendToKNX({
|
|
126
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write'
|
|
127
|
+
}, 'write')
|
|
128
|
+
}
|
|
129
|
+
node.currentDeviceValue = knxMsgPayload.payload
|
|
130
|
+
// Setup the output msg
|
|
131
|
+
knxMsgPayload.name = node.name
|
|
132
|
+
knxMsgPayload.status = _event.state
|
|
133
|
+
knxMsgPayload.event = 'device_software_update'
|
|
134
|
+
// Send payload
|
|
135
|
+
knxMsgPayload.rawEvent = _event
|
|
136
|
+
node.send(knxMsgPayload)
|
|
137
|
+
node.setNodeStatusHue({
|
|
138
|
+
fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
pushStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
node.sendResponseToKNX = (_level) => {
|
|
147
|
+
const knxMsgPayload = {}
|
|
148
|
+
knxMsgPayload.topic = config.GAdevice_software_update
|
|
149
|
+
knxMsgPayload.dpt = config.dptdevice_software_update
|
|
150
|
+
|
|
151
|
+
knxMsgPayload.payload = _level
|
|
152
|
+
// Send to KNX bus
|
|
153
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
154
|
+
safeSendToKNX({
|
|
155
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response'
|
|
156
|
+
}, 'response')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// On each deploy, unsubscribe+resubscribe
|
|
161
|
+
if (node.serverKNX) {
|
|
162
|
+
node.serverKNX.removeClient(node)
|
|
163
|
+
node.serverKNX.addClient(node)
|
|
164
|
+
}
|
|
165
|
+
if (node.serverHue) {
|
|
166
|
+
node.serverHue.removeClient(node)
|
|
167
|
+
node.serverHue.addClient(node)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
node.on('input', (msg) => {
|
|
171
|
+
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
node.on('close', (done) => {
|
|
175
|
+
if (node.serverKNX) {
|
|
176
|
+
node.serverKNX.removeClient(node)
|
|
177
|
+
}
|
|
178
|
+
if (node.serverHue) {
|
|
179
|
+
node.serverHue.removeClient(node)
|
|
180
|
+
}
|
|
181
|
+
done()
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
RED.nodes.registerType('knxUltimateHuedevice_software_update', knxUltimateHuedevice_software_update)
|
|
185
|
+
}
|