node-red-contrib-knx-ultimate 6.2.2 → 6.2.3-beta.1
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 +16 -4
- package/nodes/knxUltimateHueBattery.html +16 -4
- package/nodes/knxUltimateHueButton.html +16 -4
- package/nodes/knxUltimateHueCameraMotion.html +16 -4
- package/nodes/knxUltimateHueContactSensor.html +16 -4
- package/nodes/knxUltimateHueController.html +1259 -0
- package/nodes/knxUltimateHueController.js +28 -0
- package/nodes/knxUltimateHueHumiditySensor.html +16 -4
- package/nodes/knxUltimateHueLight.html +16 -4
- package/nodes/knxUltimateHueLightSensor.html +16 -4
- package/nodes/knxUltimateHueMotion.html +16 -4
- package/nodes/knxUltimateHuePlug.html +16 -4
- package/nodes/knxUltimateHueScene.html +19 -7
- package/nodes/knxUltimateHueTapDial.html +16 -4
- package/nodes/knxUltimateHueTemperatureSensor.html +16 -4
- package/nodes/knxUltimateHueZigbeeConnectivity.html +16 -4
- package/nodes/knxUltimateHuedevice_software_update.html +16 -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 +668 -0
- package/nodes/locales/de/knxUltimateHueController.json +47 -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 +671 -0
- package/nodes/locales/en/knxUltimateHueController.json +47 -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 +671 -0
- package/nodes/locales/es/knxUltimateHueController.json +47 -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 +671 -0
- package/nodes/locales/fr/knxUltimateHueController.json +47 -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 +671 -0
- package/nodes/locales/it/knxUltimateHueController.json +47 -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 +673 -0
- package/nodes/locales/zh-CN/knxUltimateHueController.json +47 -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 +338 -0
- package/resources/hueControllerMigrationDialog.js +235 -0
- package/resources/hueControllerProfiles.js +9290 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: light_level.
|
|
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 knxUltimateHueLightSensor (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
|
+
|
|
32
|
+
const pushStatus = (status) => {
|
|
33
|
+
if (!status) return
|
|
34
|
+
const provider = node.serverKNX
|
|
35
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
36
|
+
provider.applyStatusUpdate(node, status)
|
|
37
|
+
} else {
|
|
38
|
+
node.status(status)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const updateStatus = (status) => {
|
|
43
|
+
if (!status) return
|
|
44
|
+
pushStatus(status)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const formatTs = (date) => {
|
|
48
|
+
const d = date instanceof Date ? date : new Date(date)
|
|
49
|
+
const provider = node.serverKNX
|
|
50
|
+
if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
|
|
51
|
+
return `${d.getDate()}, ${d.toLocaleTimeString()}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const safeSendToKNX = (telegram, context = 'write') => {
|
|
55
|
+
try {
|
|
56
|
+
if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
|
|
57
|
+
const now = new Date()
|
|
58
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
|
|
62
|
+
} catch (error) {
|
|
63
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Used to call the status update from the config node.
|
|
68
|
+
node.setNodeStatus = ({
|
|
69
|
+
fill, shape, text, payload
|
|
70
|
+
}) => {
|
|
71
|
+
try {
|
|
72
|
+
if (payload === undefined) payload = ''
|
|
73
|
+
const dDate = new Date()
|
|
74
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
75
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
76
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
77
|
+
} catch (error) { }
|
|
78
|
+
}
|
|
79
|
+
// Used to call the status update from the HUE config node.
|
|
80
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
81
|
+
try {
|
|
82
|
+
if (payload === undefined) payload = ''
|
|
83
|
+
const dDate = new Date()
|
|
84
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
85
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
86
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
87
|
+
} catch (error) { }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
91
|
+
node.handleSend = msg => {
|
|
92
|
+
// Respond to KNX read telegram, by sending the current value as response telegram.
|
|
93
|
+
if (msg.knx.event === 'GroupValue_Read') {
|
|
94
|
+
switch (msg.knx.destination) {
|
|
95
|
+
case config.GAlightsensor:
|
|
96
|
+
// To the KNX bus wires
|
|
97
|
+
node.sendResponseToKNX(node.currentDeviceValue)
|
|
98
|
+
break
|
|
99
|
+
default:
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
node.handleSendHUE = _event => {
|
|
106
|
+
try {
|
|
107
|
+
if (_event.id === config.hueDevice) {
|
|
108
|
+
if (!_event.hasOwnProperty('light') || _event.light.light_level === undefined) return
|
|
109
|
+
|
|
110
|
+
const knxMsgPayload = {}
|
|
111
|
+
knxMsgPayload.topic = config.GAlightsensor
|
|
112
|
+
knxMsgPayload.dpt = config.dptlightsensor
|
|
113
|
+
|
|
114
|
+
if (_event.hasOwnProperty('light') && _event.light.hasOwnProperty('light_level')) {
|
|
115
|
+
// console.log(Math.round(10 ** ((_event.light.light_level - 1) / 10000)))
|
|
116
|
+
// console.log(_event.light.light_level === 0 ? 0 : Math.round(Math.pow(10, (_event.light.light_level - 1) / 10000)))
|
|
117
|
+
knxMsgPayload.payload = _event.light.light_level === 0 ? 0 : Math.round(Math.pow(10, (_event.light.light_level - 1) / 10000))
|
|
118
|
+
// Send to KNX bus
|
|
119
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
120
|
+
safeSendToKNX({ grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write' }, 'write')
|
|
121
|
+
}
|
|
122
|
+
node.currentDeviceValue = knxMsgPayload.payload
|
|
123
|
+
|
|
124
|
+
updateStatus({ fill: 'green', shape: 'dot', text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})` })
|
|
125
|
+
|
|
126
|
+
// Setup the output msg
|
|
127
|
+
knxMsgPayload.name = node.name
|
|
128
|
+
knxMsgPayload.event = 'light_level'
|
|
129
|
+
|
|
130
|
+
// Send payload
|
|
131
|
+
knxMsgPayload.rawEvent = _event
|
|
132
|
+
node.send(knxMsgPayload)
|
|
133
|
+
node.setNodeStatusHue({ fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
node.sendResponseToKNX = (_level) => {
|
|
142
|
+
const knxMsgPayload = {}
|
|
143
|
+
knxMsgPayload.topic = config.GAlightsensor
|
|
144
|
+
knxMsgPayload.dpt = config.dptlightsensor
|
|
145
|
+
|
|
146
|
+
knxMsgPayload.payload = _level
|
|
147
|
+
// Send to KNX bus
|
|
148
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
149
|
+
safeSendToKNX({
|
|
150
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response'
|
|
151
|
+
}, 'response')
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// On each deploy, unsubscribe+resubscribe
|
|
156
|
+
if (node.serverKNX) {
|
|
157
|
+
node.serverKNX.removeClient(node)
|
|
158
|
+
node.serverKNX.addClient(node)
|
|
159
|
+
}
|
|
160
|
+
if (node.serverHue) {
|
|
161
|
+
node.serverHue.removeClient(node)
|
|
162
|
+
node.serverHue.addClient(node)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
node.on('input', function (msg) {
|
|
166
|
+
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
node.on('close', function (done) {
|
|
170
|
+
if (node.serverKNX) {
|
|
171
|
+
node.serverKNX.removeClient(node)
|
|
172
|
+
}
|
|
173
|
+
if (node.serverHue) {
|
|
174
|
+
node.serverHue.removeClient(node)
|
|
175
|
+
}
|
|
176
|
+
done()
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
RED.nodes.registerType('knxUltimateHueLightSensor', knxUltimateHueLightSensor)
|
|
180
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: motion.
|
|
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 knxUltimateHueMotion (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 motion state at startup and after every event-stream
|
|
30
|
+
// (re)connection, so events missed during an SSE reconnect gap don't leave KNX stuck
|
|
31
|
+
// on a wrong value (same root cause as the contact sensor, issue #514).
|
|
32
|
+
node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
|
|
33
|
+
// Timestamp (ms) of the last motion_report.changed we already applied to KNX. Used to
|
|
34
|
+
// ignore stale/out-of-order reports (e.g. replays after a reconnect).
|
|
35
|
+
node.lastMotionChangedTs = undefined
|
|
36
|
+
const pinsSetting = (config.enableNodePINS === undefined || config.enableNodePINS === 'yes' || config.enableNodePINS === true)
|
|
37
|
+
node.enableNodePINS = pinsSetting ? 'yes' : 'no'
|
|
38
|
+
node.outputs = pinsSetting ? 1 : 0
|
|
39
|
+
if (!node.serverKNX && node.outputs === 0) {
|
|
40
|
+
node.enableNodePINS = 'yes'
|
|
41
|
+
node.outputs = 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const pushStatus = (status) => {
|
|
45
|
+
if (!status) return
|
|
46
|
+
const provider = node.serverKNX
|
|
47
|
+
if (provider && typeof provider.applyStatusUpdate === 'function') {
|
|
48
|
+
provider.applyStatusUpdate(node, status)
|
|
49
|
+
} else {
|
|
50
|
+
node.status(status)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const updateStatus = (status) => {
|
|
55
|
+
if (!status) return
|
|
56
|
+
pushStatus(status)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const formatTs = (date) => {
|
|
60
|
+
const d = date instanceof Date ? date : new Date(date)
|
|
61
|
+
const provider = node.serverKNX
|
|
62
|
+
if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
|
|
63
|
+
return `${d.getDate()}, ${d.toLocaleTimeString()}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const safeSendToKNX = (telegram, context = 'write') => {
|
|
67
|
+
try {
|
|
68
|
+
if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
|
|
69
|
+
const now = new Date()
|
|
70
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
|
|
74
|
+
} catch (error) {
|
|
75
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Used to call the status update from the config node.
|
|
80
|
+
node.setNodeStatus = ({
|
|
81
|
+
fill, shape, text, payload
|
|
82
|
+
}) => {
|
|
83
|
+
try {
|
|
84
|
+
if (payload === undefined) payload = ''
|
|
85
|
+
const dDate = new Date()
|
|
86
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
87
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
88
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
89
|
+
} catch (error) { }
|
|
90
|
+
}
|
|
91
|
+
// Used to call the status update from the HUE config node.
|
|
92
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
93
|
+
try {
|
|
94
|
+
if (payload === undefined) payload = ''
|
|
95
|
+
const dDate = new Date()
|
|
96
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
97
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
98
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
99
|
+
} catch (error) { }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
103
|
+
node.handleSend = (msg) => { }
|
|
104
|
+
|
|
105
|
+
node.handleSendHUE = (_event) => {
|
|
106
|
+
try {
|
|
107
|
+
if (_event.id === config.hueDevice) {
|
|
108
|
+
if (!_event.hasOwnProperty('motion') || _event.motion.motion === undefined) return
|
|
109
|
+
|
|
110
|
+
// Ignore reports that are not newer than the last one applied, so stale/replayed
|
|
111
|
+
// events after an SSE reconnect can't push a wrong state to KNX (issue #514).
|
|
112
|
+
const motionChanged = _event.motion.motion_report !== undefined ? _event.motion.motion_report.changed : undefined
|
|
113
|
+
const changedTs = motionChanged !== undefined ? new Date(motionChanged).getTime() : NaN
|
|
114
|
+
if (!Number.isNaN(changedTs)) {
|
|
115
|
+
if (node.lastMotionChangedTs !== undefined && changedTs <= node.lastMotionChangedTs) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
node.lastMotionChangedTs = changedTs
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const knxMsgPayload = {}
|
|
122
|
+
knxMsgPayload.topic = config.GAmotion
|
|
123
|
+
knxMsgPayload.dpt = config.dptmotion
|
|
124
|
+
|
|
125
|
+
if (_event.hasOwnProperty('motion') && _event.motion.hasOwnProperty('motion')) {
|
|
126
|
+
knxMsgPayload.payload = _event.motion.motion_report.motion
|
|
127
|
+
// Send to KNX bus
|
|
128
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
129
|
+
safeSendToKNX({
|
|
130
|
+
grpaddr: knxMsgPayload.topic,
|
|
131
|
+
payload: knxMsgPayload.payload,
|
|
132
|
+
dpt: knxMsgPayload.dpt,
|
|
133
|
+
outputtype: 'write'
|
|
134
|
+
}, 'write')
|
|
135
|
+
}
|
|
136
|
+
updateStatus({
|
|
137
|
+
fill: 'green',
|
|
138
|
+
shape: 'dot',
|
|
139
|
+
text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})`
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Setup the output msg
|
|
143
|
+
knxMsgPayload.name = node.name
|
|
144
|
+
knxMsgPayload.event = 'motion'
|
|
145
|
+
|
|
146
|
+
// Send payload
|
|
147
|
+
knxMsgPayload.rawEvent = _event
|
|
148
|
+
node.send(knxMsgPayload)
|
|
149
|
+
node.setNodeStatusHue({
|
|
150
|
+
fill: 'blue',
|
|
151
|
+
shape: 'ring',
|
|
152
|
+
text: 'HUE->KNX',
|
|
153
|
+
payload: knxMsgPayload.payload
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// On each deploy, unsubscribe+resubscribe
|
|
163
|
+
if (node.serverKNX) {
|
|
164
|
+
node.serverKNX.removeClient(node)
|
|
165
|
+
node.serverKNX.addClient(node)
|
|
166
|
+
}
|
|
167
|
+
if (node.serverHue) {
|
|
168
|
+
node.serverHue.removeClient(node)
|
|
169
|
+
node.serverHue.addClient(node)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
node.on('input', (msg) => { })
|
|
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('knxUltimateHueMotion', knxUltimateHueMotion)
|
|
185
|
+
}
|