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,352 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: scene.
|
|
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
|
+
/* eslint-disable max-len */
|
|
5
|
+
const dptlib = require('knxultimate').dptlib// require('knxultimate').dptlib;
|
|
6
|
+
const loggerClass = require('../../sysLogger')
|
|
7
|
+
module.exports = function (RED) {
|
|
8
|
+
function knxUltimateHueScene (config) {
|
|
9
|
+
RED.nodes.createNode(this, config)
|
|
10
|
+
const node = this
|
|
11
|
+
node.serverKNX = RED.nodes.getNode(config.server) || undefined
|
|
12
|
+
node.serverHue = RED.nodes.getNode(config.serverHue) || undefined
|
|
13
|
+
node.topic = node.name
|
|
14
|
+
node.name = config.name === undefined ? 'Hue' : config.name
|
|
15
|
+
node.dpt = ''
|
|
16
|
+
node.notifyreadrequest = false
|
|
17
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
18
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
19
|
+
node.notifyresponse = false
|
|
20
|
+
node.notifywrite = true
|
|
21
|
+
node.initialread = true
|
|
22
|
+
node.listenallga = true // Don't remove
|
|
23
|
+
node.outputtype = 'write'
|
|
24
|
+
node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
|
|
25
|
+
node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
|
|
26
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
27
|
+
node.passthrough = 'no'
|
|
28
|
+
node.formatmultiplyvalue = 1
|
|
29
|
+
node.formatnegativevalue = 'leave'
|
|
30
|
+
node.formatdecimalsvalue = 2
|
|
31
|
+
node.hueDevice = config.hueDevice
|
|
32
|
+
node.initializingAtStart = false
|
|
33
|
+
try {
|
|
34
|
+
const baseLogLevel = (node.serverKNX && node.serverKNX.loglevel) ? node.serverKNX.loglevel : 'error'
|
|
35
|
+
node.sysLogger = new loggerClass({ loglevel: baseLogLevel, setPrefix: node.type + ' <' + (node.name || node.id || '') + '>' })
|
|
36
|
+
} catch (error) { console.log(error.stack) }
|
|
37
|
+
// Multi scene
|
|
38
|
+
config.GAsceneMulti = config.GAsceneMulti === undefined ? '' : config.GAsceneMulti
|
|
39
|
+
config.namesceneMulti = config.namesceneMulti === undefined ? '' : config.namesceneMulti
|
|
40
|
+
config.dptsceneMulti = config.dptsceneMulti === undefined ? '' : config.dptsceneMulti
|
|
41
|
+
config.rules = config.rules === undefined ? [] : config.rules
|
|
42
|
+
config.selectedModeTabNumber = config.selectedModeTabNumber === undefined ? 0 : Number(config.selectedModeTabNumber) // Transform as number
|
|
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
|
+
updateStatus({ 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
|
+
updateStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
99
|
+
} catch (error) { }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let hueSceneReady = false
|
|
103
|
+
let pendingHueSceneSnapshot = null
|
|
104
|
+
const needsSceneReady = () => Number(config.selectedModeTabNumber) === 0 && config.hueDevice !== undefined && config.hueDevice !== ''
|
|
105
|
+
|
|
106
|
+
const ensureHueSceneReady = async ({ forceRefresh = false } = {}) => {
|
|
107
|
+
if (!needsSceneReady()) {
|
|
108
|
+
hueSceneReady = true
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
if (!node.serverHue || typeof node.serverHue.getHueResourceSnapshot !== 'function') {
|
|
112
|
+
hueSceneReady = false
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
if (pendingHueSceneSnapshot) {
|
|
116
|
+
try {
|
|
117
|
+
return await pendingHueSceneSnapshot
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
pendingHueSceneSnapshot = (async () => {
|
|
123
|
+
try {
|
|
124
|
+
const resource = await node.serverHue.getHueResourceSnapshot(config.hueDevice, { forceRefresh })
|
|
125
|
+
if (!resource) {
|
|
126
|
+
hueSceneReady = false
|
|
127
|
+
node.setNodeStatusHue({
|
|
128
|
+
fill: 'red',
|
|
129
|
+
shape: 'ring',
|
|
130
|
+
text: 'Hue scene not found',
|
|
131
|
+
payload: ''
|
|
132
|
+
})
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
hueSceneReady = true
|
|
136
|
+
try {
|
|
137
|
+
node.handleSendHUE(resource)
|
|
138
|
+
} catch (error) {
|
|
139
|
+
RED.log.debug(`knxUltimateHueScene: ensureHueSceneReady handleSendHUE error ${error.message}`)
|
|
140
|
+
}
|
|
141
|
+
return true
|
|
142
|
+
} catch (error) {
|
|
143
|
+
hueSceneReady = false
|
|
144
|
+
RED.log.warn(`knxUltimateHueScene: ensureHueSceneReady error ${error.message}`)
|
|
145
|
+
node.setNodeStatusHue({
|
|
146
|
+
fill: 'red',
|
|
147
|
+
shape: 'ring',
|
|
148
|
+
text: `Sync failed ${error.message}`,
|
|
149
|
+
payload: ''
|
|
150
|
+
})
|
|
151
|
+
return false
|
|
152
|
+
} finally {
|
|
153
|
+
pendingHueSceneSnapshot = null
|
|
154
|
+
}
|
|
155
|
+
})()
|
|
156
|
+
return pendingHueSceneSnapshot
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const processKnxSceneCommand = (msg) => {
|
|
160
|
+
let state = {}
|
|
161
|
+
if (Number(config.selectedModeTabNumber) === 0) {
|
|
162
|
+
try {
|
|
163
|
+
switch (msg.knx.destination) {
|
|
164
|
+
case config.GAscene: {
|
|
165
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptscene))
|
|
166
|
+
if (config.dptscene.startsWith('1.')) {
|
|
167
|
+
if (msg.payload === true) {
|
|
168
|
+
state = { recall: { action: config.hueSceneRecallType } }
|
|
169
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene')
|
|
170
|
+
} else {
|
|
171
|
+
(async () => {
|
|
172
|
+
try {
|
|
173
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, { on: { on: false } }, 'stopScene')
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`KNXUltimateHUEConfig: stopScene: ${error.message}`)
|
|
176
|
+
}
|
|
177
|
+
})()
|
|
178
|
+
}
|
|
179
|
+
} else if (Number(config.valscene) === msg.payload.scenenumber && msg.payload.save_recall === 0) {
|
|
180
|
+
state = { recall: { action: config.hueSceneRecallType } }
|
|
181
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene')
|
|
182
|
+
}
|
|
183
|
+
node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload })
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX->HUE single: ${error.message} (${formatTs(new Date())})` })
|
|
191
|
+
}
|
|
192
|
+
} else if (Number(config.selectedModeTabNumber) === 1) {
|
|
193
|
+
try {
|
|
194
|
+
if (config.GAsceneMulti !== undefined && config.dptsceneMulti !== undefined && config.rules.length !== 0 && config.GAsceneMulti === msg.knx.destination) {
|
|
195
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptsceneMulti))
|
|
196
|
+
config.rules.forEach((row) => {
|
|
197
|
+
if (row.rowRuleKNXSceneNumber !== undefined &&
|
|
198
|
+
row.rowRuleHUESceneID !== undefined &&
|
|
199
|
+
row.rowRuleRecallAs !== undefined &&
|
|
200
|
+
Number(row.rowRuleKNXSceneNumber) === Number(msg.payload.scenenumber) &&
|
|
201
|
+
Number(msg.payload.save_recall) === 0 &&
|
|
202
|
+
node.serverHue.hueManager !== undefined) {
|
|
203
|
+
state = { recall: { action: row.rowRuleRecallAs } }
|
|
204
|
+
node.serverHue.hueManager.writeHueQueueAdd(row.rowRuleHUESceneID, state, 'setScene')
|
|
205
|
+
node.setNodeStatusHue({ fill: 'green', shape: 'dot', text: 'KNX->HUE', payload: msg.payload })
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX->HUE multi: ${error.message} (${formatTs(new Date())})` })
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// This function is called by the knx-hue config node
|
|
216
|
+
node.handleSend = (msg) => {
|
|
217
|
+
if (needsSceneReady() && !hueSceneReady) {
|
|
218
|
+
node.setNodeStatusHue({
|
|
219
|
+
fill: 'yellow',
|
|
220
|
+
shape: 'ring',
|
|
221
|
+
text: 'Syncing with HUE bridge',
|
|
222
|
+
payload: ''
|
|
223
|
+
})
|
|
224
|
+
const clonedMsg = RED.util.cloneMessage(msg)
|
|
225
|
+
ensureHueSceneReady({ forceRefresh: true }).then((ready) => {
|
|
226
|
+
if (ready) processKnxSceneCommand(clonedMsg)
|
|
227
|
+
}).catch(() => { /* handled in ensure */ })
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
processKnxSceneCommand(msg)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
node.handleSendHUE = (_event) => {
|
|
234
|
+
try {
|
|
235
|
+
if (Number(config.selectedModeTabNumber) === 0 && config.hueDevice !== undefined && _event.id === config.hueDevice) {
|
|
236
|
+
// Single mode
|
|
237
|
+
const knxMsgPayload = {}
|
|
238
|
+
knxMsgPayload.topic = config.GAsceneStatus
|
|
239
|
+
knxMsgPayload.dpt = config.dptsceneStatus
|
|
240
|
+
if (_event.hasOwnProperty('status') && _event.status.hasOwnProperty('active')) {
|
|
241
|
+
knxMsgPayload.payload = _event.status.active !== 'inactive'
|
|
242
|
+
// Send to KNX bus
|
|
243
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
244
|
+
safeSendToKNX({
|
|
245
|
+
grpaddr: knxMsgPayload.topic,
|
|
246
|
+
payload: knxMsgPayload.payload,
|
|
247
|
+
dpt: knxMsgPayload.dpt,
|
|
248
|
+
outputtype: 'write'
|
|
249
|
+
}, 'write')
|
|
250
|
+
}
|
|
251
|
+
updateStatus({
|
|
252
|
+
fill: 'blue',
|
|
253
|
+
shape: 'dot',
|
|
254
|
+
text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})`
|
|
255
|
+
})
|
|
256
|
+
// Output the msg to the flow
|
|
257
|
+
node.send(_event)
|
|
258
|
+
}
|
|
259
|
+
} else if (Number(config.selectedModeTabNumber) === 1 && config.rules !== undefined) {
|
|
260
|
+
// Multi mode
|
|
261
|
+
config.rules.forEach(row => {
|
|
262
|
+
if (row.rowRuleHUESceneID !== undefined && _event.id === row.rowRuleHUESceneID) {
|
|
263
|
+
// Output the msg to the flow
|
|
264
|
+
node.send(_event)
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// On each deploy, unsubscribe+resubscribe
|
|
274
|
+
if (node.serverKNX) {
|
|
275
|
+
node.serverKNX.removeClient(node)
|
|
276
|
+
node.serverKNX.addClient(node)
|
|
277
|
+
}
|
|
278
|
+
if (node.serverHue) {
|
|
279
|
+
node.serverHue.removeClient(node)
|
|
280
|
+
node.serverHue.addClient(node)
|
|
281
|
+
ensureHueSceneReady({ forceRefresh: true })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const processFlowSceneCommand = (msg) => {
|
|
285
|
+
try {
|
|
286
|
+
const state = RED.util.cloneMessage(msg)
|
|
287
|
+
node.serverHue.hueManager.writeHueQueueAdd(config.hueDevice, state, 'setScene')
|
|
288
|
+
const statusValue = state.payload !== undefined ? state.payload : state
|
|
289
|
+
node.setNodeStatusHue({
|
|
290
|
+
fill: 'green',
|
|
291
|
+
shape: 'dot',
|
|
292
|
+
text: '->HUE',
|
|
293
|
+
payload: statusValue
|
|
294
|
+
})
|
|
295
|
+
} catch (error) {
|
|
296
|
+
node.setNodeStatusHue({
|
|
297
|
+
fill: 'red',
|
|
298
|
+
shape: 'dot',
|
|
299
|
+
text: '->HUE',
|
|
300
|
+
payload: error.message
|
|
301
|
+
})
|
|
302
|
+
throw error
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
node.on('input', (msg, send, done) => {
|
|
307
|
+
const finalize = (error) => {
|
|
308
|
+
if (done) done(error)
|
|
309
|
+
}
|
|
310
|
+
if (needsSceneReady() && !hueSceneReady) {
|
|
311
|
+
node.setNodeStatusHue({
|
|
312
|
+
fill: 'yellow',
|
|
313
|
+
shape: 'ring',
|
|
314
|
+
text: 'Syncing with HUE bridge',
|
|
315
|
+
payload: ''
|
|
316
|
+
})
|
|
317
|
+
const clonedMsg = RED.util.cloneMessage(msg)
|
|
318
|
+
ensureHueSceneReady({ forceRefresh: true }).then((ready) => {
|
|
319
|
+
if (ready) {
|
|
320
|
+
try {
|
|
321
|
+
processFlowSceneCommand(clonedMsg)
|
|
322
|
+
finalize()
|
|
323
|
+
} catch (error) {
|
|
324
|
+
finalize(error)
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
finalize(new Error('Hue scene initialization failed'))
|
|
328
|
+
}
|
|
329
|
+
}).catch((error) => {
|
|
330
|
+
finalize(error)
|
|
331
|
+
})
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
processFlowSceneCommand(msg)
|
|
336
|
+
finalize()
|
|
337
|
+
} catch (error) {
|
|
338
|
+
finalize(error)
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
node.on('close', function (done) {
|
|
342
|
+
if (node.serverKNX) {
|
|
343
|
+
node.serverKNX.removeClient(node)
|
|
344
|
+
}
|
|
345
|
+
if (node.serverHue) {
|
|
346
|
+
node.serverHue.removeClient(node)
|
|
347
|
+
}
|
|
348
|
+
done()
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
RED.nodes.registerType('knxUltimateHueScene', knxUltimateHueScene)
|
|
352
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Canonical private runtime profile for HUE Controller: temperature.
|
|
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 knxUltimateHueTemperatureSensor (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
|
+
// Used to call the status update from the config node.
|
|
62
|
+
node.setNodeStatus = ({
|
|
63
|
+
fill, shape, text, payload
|
|
64
|
+
}) => {
|
|
65
|
+
try {
|
|
66
|
+
if (payload === undefined) payload = ''
|
|
67
|
+
const dDate = new Date()
|
|
68
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
69
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
70
|
+
pushStatus({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
71
|
+
} catch (error) { }
|
|
72
|
+
}
|
|
73
|
+
// Used to call the status update from the HUE config node.
|
|
74
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
75
|
+
try {
|
|
76
|
+
if (payload === undefined) payload = ''
|
|
77
|
+
const dDate = new Date()
|
|
78
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
79
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
80
|
+
pushStatus({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
81
|
+
} catch (error) { }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// This function is called by the knx-ultimate config node, to output a msg.payload.
|
|
85
|
+
node.handleSend = (msg) => {
|
|
86
|
+
// Respond to KNX read telegram, by sending the current value as response telegram.
|
|
87
|
+
if (msg.knx.event === 'GroupValue_Read') {
|
|
88
|
+
switch (msg.knx.destination) {
|
|
89
|
+
case config.GAtemperaturesensor:
|
|
90
|
+
// To the KNX bus wires
|
|
91
|
+
node.sendResponseToKNX(node.currentDeviceValue)
|
|
92
|
+
break
|
|
93
|
+
default:
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const safeSendToKNX = (telegram, context = 'write') => {
|
|
100
|
+
try {
|
|
101
|
+
if (!node.serverKNX || typeof node.serverKNX.sendKNXTelegramToKNXEngine !== 'function') {
|
|
102
|
+
const now = new Date()
|
|
103
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX server missing (${context}) (${formatTs(now)})` })
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({ ...telegram, nodecallerid: node.id })
|
|
107
|
+
} catch (error) {
|
|
108
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `KNX send error ${error.message}` })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
node.handleSendHUE = (_event) => {
|
|
113
|
+
try {
|
|
114
|
+
if (_event.id === config.hueDevice) {
|
|
115
|
+
const knxMsgPayload = {}
|
|
116
|
+
knxMsgPayload.topic = config.GAtemperaturesensor
|
|
117
|
+
knxMsgPayload.dpt = config.dpttemperaturesensor
|
|
118
|
+
|
|
119
|
+
if (_event.hasOwnProperty('temperature') && _event.temperature.hasOwnProperty('temperature')) {
|
|
120
|
+
knxMsgPayload.payload = _event.temperature.temperature
|
|
121
|
+
// Send to KNX bus
|
|
122
|
+
if (knxMsgPayload.topic) {
|
|
123
|
+
safeSendToKNX({
|
|
124
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'write'
|
|
125
|
+
}, 'write')
|
|
126
|
+
}
|
|
127
|
+
node.currentDeviceValue = knxMsgPayload.payload
|
|
128
|
+
|
|
129
|
+
updateStatus({ fill: 'green', shape: 'dot', text: `HUE->KNX ${JSON.stringify(knxMsgPayload.payload)} (${formatTs(new Date())})` })
|
|
130
|
+
|
|
131
|
+
// Setup the output msg
|
|
132
|
+
knxMsgPayload.name = node.name
|
|
133
|
+
knxMsgPayload.event = 'temperature'
|
|
134
|
+
|
|
135
|
+
// Send payload
|
|
136
|
+
knxMsgPayload.rawEvent = _event
|
|
137
|
+
node.send(knxMsgPayload)
|
|
138
|
+
node.setNodeStatusHue({
|
|
139
|
+
fill: 'blue', shape: 'ring', text: 'HUE->KNX', payload: knxMsgPayload.payload
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
updateStatus({ fill: 'red', shape: 'dot', text: `HUE->KNX error ${error.message} (${formatTs(new Date())})` })
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
node.sendResponseToKNX = (_level) => {
|
|
149
|
+
const knxMsgPayload = {}
|
|
150
|
+
knxMsgPayload.topic = config.GAtemperaturesensor
|
|
151
|
+
knxMsgPayload.dpt = config.dpttemperaturesensor
|
|
152
|
+
|
|
153
|
+
knxMsgPayload.payload = _level
|
|
154
|
+
// Send to KNX bus
|
|
155
|
+
if (knxMsgPayload.topic) {
|
|
156
|
+
safeSendToKNX({
|
|
157
|
+
grpaddr: knxMsgPayload.topic, payload: knxMsgPayload.payload, dpt: knxMsgPayload.dpt, outputtype: 'response'
|
|
158
|
+
}, 'response')
|
|
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
|
+
})
|
|
175
|
+
|
|
176
|
+
node.on('close', (done) => {
|
|
177
|
+
if (node.serverKNX) {
|
|
178
|
+
node.serverKNX.removeClient(node)
|
|
179
|
+
}
|
|
180
|
+
if (node.serverHue) {
|
|
181
|
+
node.serverHue.removeClient(node)
|
|
182
|
+
}
|
|
183
|
+
done()
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
RED.nodes.registerType('knxUltimateHueTemperatureSensor', knxUltimateHueTemperatureSensor)
|
|
187
|
+
}
|