node-red-contrib-knx-ultimate 5.2.5 → 6.0.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 +9 -0
- package/nodes/commonFunctions.js +40 -3
- package/nodes/knxUltimate-config.js +379 -335
- package/nodes/knxUltimate.html +8 -9
- package/nodes/knxUltimate.js +5 -5
- package/nodes/knxUltimateAI.js +1 -1
- package/nodes/knxUltimateHueLight.html +3 -3
- package/nodes/knxUltimateHueLight.js +1 -1
- package/nodes/knxUltimateMatterBridge.html +321 -247
- package/nodes/knxUltimateMatterBridge.js +48 -1
- package/nodes/knxUltimateMatterControllerDevice.html +2119 -0
- package/nodes/knxUltimateMatterControllerDevice.js +1701 -0
- package/nodes/locales/de/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/de/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/de/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/de/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/de/matter-config.json +5 -1
- package/nodes/locales/de/matterbridge-config.json +1 -0
- package/nodes/locales/en/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/en/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/en/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/en/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/en/matter-config.json +6 -1
- package/nodes/locales/en/matterbridge-config.json +1 -0
- package/nodes/locales/es/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/es/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/es/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/es/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/es/matter-config.json +5 -1
- package/nodes/locales/es/matterbridge-config.json +1 -0
- package/nodes/locales/fr/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/fr/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/fr/knxUltimateMatterControllerDevice.json +89 -0
- package/nodes/locales/fr/matter-config.json +5 -1
- package/nodes/locales/fr/matterbridge-config.json +1 -0
- package/nodes/locales/it/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/it/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/it/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/it/knxUltimateMatterControllerDevice.json +97 -0
- package/nodes/locales/it/matter-config.json +6 -1
- package/nodes/locales/it/matterbridge-config.json +1 -0
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.html +6 -2
- package/nodes/locales/zh-CN/knxUltimateMatterBridge.json +14 -1
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.html +27 -0
- package/nodes/locales/zh-CN/knxUltimateMatterControllerDevice.json +89 -0
- package/nodes/locales/zh-CN/matter-config.json +5 -1
- package/nodes/locales/zh-CN/matterbridge-config.json +1 -0
- package/nodes/matter-config.html +58 -6
- package/nodes/matter-config.js +7 -2
- package/nodes/matterbridge-config.html +1 -0
- package/nodes/utils/lightEngines/canonicalLight.js +75 -0
- package/nodes/utils/lightEngines/hueLightEngine.js +51 -0
- package/nodes/utils/lightEngines/index.js +25 -0
- package/nodes/utils/lightEngines/knxLightCommand.js +79 -0
- package/nodes/utils/lightEngines/lightEngine.js +46 -0
- package/nodes/utils/lightEngines/matterHueShim.js +59 -0
- package/nodes/utils/lightEngines/matterLightEngine.js +117 -0
- package/nodes/utils/matterEngine.mjs +185 -14
- package/nodes/utils/matterKnxConverter.js +1 -1
- package/package.json +11 -3
- package/resources/KNXSendSnippets.js +53 -53
- package/resources/htmlUtils.js +6 -4
- package/nodes/knxUltimateMatterDevice.html +0 -632
- package/nodes/knxUltimateMatterDevice.js +0 -308
- package/nodes/locales/de/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/de/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/en/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/en/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/es/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/es/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/fr/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/fr/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/it/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/it/knxUltimateMatterDevice.json +0 -109
- package/nodes/locales/zh-CN/knxUltimateMatterDevice.html +0 -56
- package/nodes/locales/zh-CN/knxUltimateMatterDevice.json +0 -109
|
@@ -0,0 +1,1701 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
/* eslint-disable camelcase */
|
|
3
|
+
/* eslint-disable max-len */
|
|
4
|
+
/* eslint-disable no-lonely-if */
|
|
5
|
+
/* eslint-disable no-case-declarations */
|
|
6
|
+
/* eslint-disable no-unused-vars */
|
|
7
|
+
const cloneDeep = require('lodash/cloneDeep')
|
|
8
|
+
const dptlib = require('knxultimate').dptlib
|
|
9
|
+
const hueColorConverter = require('./utils/colorManipulators/hueColorConverter')
|
|
10
|
+
const { createLightEngine } = require('./utils/lightEngines')
|
|
11
|
+
const { hueStateToCanonical, matterEventToHuePatch } = require('./utils/lightEngines/matterHueShim')
|
|
12
|
+
|
|
13
|
+
module.exports = function (RED) {
|
|
14
|
+
function knxUltimateMatterControllerDevice (config) {
|
|
15
|
+
RED.nodes.createNode(this, config)
|
|
16
|
+
const node = this
|
|
17
|
+
node.serverKNX = RED.nodes.getNode(config.server) || undefined
|
|
18
|
+
node.serverMatter = RED.nodes.getNode(config.serverMatter) || undefined
|
|
19
|
+
node.matterNodeId = config.matterNodeId !== undefined ? String(config.matterNodeId) : ''
|
|
20
|
+
node.matterEndpointId = config.matterEndpointId !== undefined ? Number(config.matterEndpointId) : 1
|
|
21
|
+
node.matterDeviceName = config.matterDeviceName !== undefined ? String(config.matterDeviceName) : ''
|
|
22
|
+
|
|
23
|
+
// Matter engine (adapter). The manager is resolved lazily at each write, because
|
|
24
|
+
// matter-config creates its matterManager asynchronously after the deploy.
|
|
25
|
+
node.lightEngine = createLightEngine('matter', {
|
|
26
|
+
deviceId: node.matterNodeId,
|
|
27
|
+
nodeId: node.matterNodeId,
|
|
28
|
+
endpointId: node.matterEndpointId
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Facade impersonating the hue-config node. The entire light logic below was born
|
|
32
|
+
// in the Hue Light node and is kept UNCHANGED: it speaks Hue-native state objects,
|
|
33
|
+
// and this boundary translates them to the Matter engine (strategy/adapter).
|
|
34
|
+
node.serverHue = {
|
|
35
|
+
get linkStatus () { return node.serverMatter ? node.serverMatter.linkStatus : 'disconnected' },
|
|
36
|
+
hueManager: {
|
|
37
|
+
writeHueQueueAdd: (_lightID, _state, _operation) => {
|
|
38
|
+
try {
|
|
39
|
+
if (!node.lightEngine) return
|
|
40
|
+
const patch = hueStateToCanonical(_state)
|
|
41
|
+
if (!patch) return
|
|
42
|
+
node.lightEngine.manager = node.serverMatter ? node.serverMatter.matterManager : undefined
|
|
43
|
+
node.lightEngine.applyState(patch)
|
|
44
|
+
} catch (error) {
|
|
45
|
+
RED.log.error('knxUltimateMatterControllerDevice: writeHueQueueAdd shim: ' + (error && error.message))
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
deleteHueQueue: () => { }
|
|
49
|
+
},
|
|
50
|
+
addClient: (_node) => { try { if (node.serverMatter) node.serverMatter.addClient(_node) } catch (error) { /* empty */ } },
|
|
51
|
+
removeClient: (_node) => { if (node.serverMatter) { try { node.serverMatter.removeClient(_node) } catch (error) { /* empty */ } } },
|
|
52
|
+
getAllLightsBelongingToTheGroup: async () => [],
|
|
53
|
+
getHueResourceSnapshot: async () => node.currentHUEDevice,
|
|
54
|
+
getAverageColorsXYBrightnessAndTemperature: async () => undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Convert for backward compatibility
|
|
58
|
+
if (config.nameLightKelvinDIM === undefined) {
|
|
59
|
+
config.nameLightKelvinDIM = config.nameLightHSV
|
|
60
|
+
config.GALightKelvinDIM = config.GALightHSV
|
|
61
|
+
config.dptLightKelvinDIM = config.dptLightHSV
|
|
62
|
+
|
|
63
|
+
config.nameLightKelvinPercentage = config.nameLightHSVPercentage
|
|
64
|
+
config.GALightKelvinPercentage = config.GALightHSVPercentage
|
|
65
|
+
config.dptLightKelvinPercentage = config.dptLightHSVPercentage
|
|
66
|
+
|
|
67
|
+
config.nameLightKelvinPercentageState = config.nameLightHSVState
|
|
68
|
+
config.GALightKelvinPercentageState = config.GALightHSVState
|
|
69
|
+
config.dptLightKelvinPercentageState = config.dptLightHSVState
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
node.name = config.name || node.matterDeviceName || 'Control Matter from KNX'
|
|
73
|
+
node.topic = node.name
|
|
74
|
+
node.outputtopic = node.name
|
|
75
|
+
node.dpt = ''
|
|
76
|
+
node.notifyreadrequest = true
|
|
77
|
+
node.notifyreadrequestalsorespondtobus = 'false'
|
|
78
|
+
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
|
|
79
|
+
node.notifyresponse = false
|
|
80
|
+
node.notifywrite = true
|
|
81
|
+
node.initialread = true
|
|
82
|
+
node.listenallga = true // Don't remove
|
|
83
|
+
node.outputtype = 'write'
|
|
84
|
+
node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
|
|
85
|
+
node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
|
|
86
|
+
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
|
|
87
|
+
node.passthrough = 'no'
|
|
88
|
+
node.formatmultiplyvalue = 1
|
|
89
|
+
node.formatnegativevalue = 'leave'
|
|
90
|
+
node.formatdecimalsvalue = 2
|
|
91
|
+
node.knxUltimateAcceptedGAs = [
|
|
92
|
+
config.GALightSwitch,
|
|
93
|
+
config.GALightState,
|
|
94
|
+
config.GALightDIM,
|
|
95
|
+
config.GALightColor,
|
|
96
|
+
config.GALightColorState,
|
|
97
|
+
config.GALightHSV_H_DIM,
|
|
98
|
+
config.GALightHSV_H_State,
|
|
99
|
+
config.GALightHSV_S_DIM,
|
|
100
|
+
config.GALightHSV_S_State,
|
|
101
|
+
config.GALightKelvinDIM,
|
|
102
|
+
config.GALightKelvinPercentage,
|
|
103
|
+
config.GALightKelvinPercentageState,
|
|
104
|
+
config.GALightKelvin,
|
|
105
|
+
config.GALightKelvinState,
|
|
106
|
+
config.GALightBrightness,
|
|
107
|
+
config.GALightBrightnessState,
|
|
108
|
+
config.GALightBlink,
|
|
109
|
+
config.GALightColorCycle,
|
|
110
|
+
config.GADaylightSensor
|
|
111
|
+
].map((ga) => String(ga || '').trim()).filter((ga) => ga !== '')
|
|
112
|
+
const isConfiguredKNXGA = (ga) => node.knxUltimateAcceptedGAs.includes(String(ga || '').trim())
|
|
113
|
+
// Synthetic "current device" in Hue shape, kept in sync from the Matter attribute
|
|
114
|
+
// reports. It lets the whole Hue-born logic below run untouched on a Matter light.
|
|
115
|
+
node.currentHUEDevice = {
|
|
116
|
+
id: node.matterNodeId,
|
|
117
|
+
type: 'light',
|
|
118
|
+
on: { on: false },
|
|
119
|
+
dimming: { brightness: 100, min_dim_level: 0 },
|
|
120
|
+
color: { xy: { x: 0.3127, y: 0.329 }, gamut: undefined, gamut_type: undefined },
|
|
121
|
+
color_temperature: { mirek: 333, mirek_valid: true, mirek_schema: { mirek_minimum: 153, mirek_maximum: 500 } }
|
|
122
|
+
}
|
|
123
|
+
node.HUEDeviceWhileDaytime = null// This retains the HUE device status while daytime, to be restored after nighttime elapsed.
|
|
124
|
+
node.HUELightsBelongingToGroupWhileDaytime = null // Array contains all light belonging to the grouped_light (if grouped_light is selected)
|
|
125
|
+
node.DayTime = true
|
|
126
|
+
node.isGrouped_light = false // No grouped lights on Matter
|
|
127
|
+
node.hueDevice = node.matterNodeId
|
|
128
|
+
node.initializingAtStart = (config.readStatusAtStartup === undefined || config.readStatusAtStartup === 'yes')
|
|
129
|
+
config.specifySwitchOnBrightness = (config.specifySwitchOnBrightness === undefined || config.specifySwitchOnBrightness === '') ? 'temperature' : config.specifySwitchOnBrightness
|
|
130
|
+
config.specifySwitchOnBrightnessNightTime = (config.specifySwitchOnBrightnessNightTime === undefined || config.specifySwitchOnBrightnessNightTime === '') ? 'no' : config.specifySwitchOnBrightnessNightTime
|
|
131
|
+
config.colorAtSwitchOnDayTime = (config.colorAtSwitchOnDayTime === '' || config.colorAtSwitchOnDayTime === undefined) ? '{ "kelvin":3000, "brightness":100 }' : config.colorAtSwitchOnDayTime
|
|
132
|
+
config.colorAtSwitchOnNightTime = (config.colorAtSwitchOnNightTime === '' || config.colorAtSwitchOnNightTime === undefined) ? '{ "kelvin":2700, "brightness":20 }' : config.colorAtSwitchOnNightTime
|
|
133
|
+
config.colorAtSwitchOnDayTime = config.colorAtSwitchOnDayTime.replace('geen', 'green')
|
|
134
|
+
config.colorAtSwitchOnNightTime = config.colorAtSwitchOnNightTime.replace('geen', 'green')
|
|
135
|
+
config.dimSpeed = (config.dimSpeed === undefined || config.dimSpeed === '') ? 5000 : Number(config.dimSpeed)
|
|
136
|
+
config.HSVDimSpeed = (config.HSVDimSpeed === undefined || config.HSVDimSpeed === '') ? 5000 : Number(config.HSVDimSpeed)
|
|
137
|
+
config.invertDimTunableWhiteDirection = config.invertDimTunableWhiteDirection !== undefined
|
|
138
|
+
config.restoreDayMode = config.restoreDayMode === undefined ? 'no' : config.restoreDayMode // no or setDayByFastSwitchLightSingle or setDayByFastSwitchLightALL
|
|
139
|
+
config.updateLocalStateFromKNXWrite = config.updateLocalStateFromKNXWrite === true || config.updateLocalStateFromKNXWrite === 'true' // Starting from v 4.1.31
|
|
140
|
+
node.timerCheckForFastLightSwitch = null
|
|
141
|
+
config.invertDayNight = config.invertDayNight === undefined ? false : config.invertDayNight
|
|
142
|
+
node.HSVObject = null // { h, s, v };// Store the current light calculated HSV
|
|
143
|
+
|
|
144
|
+
// Transform HEX in RGB and stringified json in json oblects.
|
|
145
|
+
if (config.colorAtSwitchOnDayTime.indexOf('#') !== -1) {
|
|
146
|
+
// Transform to rgb.
|
|
147
|
+
try {
|
|
148
|
+
config.colorAtSwitchOnDayTime = hueColorConverter.ColorConverter.hexRgb(config.colorAtSwitchOnDayTime.replace('#', ''))
|
|
149
|
+
} catch (error) {
|
|
150
|
+
config.colorAtSwitchOnDayTime = { kelvin: 3000, brightness: 100 }
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
try {
|
|
154
|
+
config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnDayTime)
|
|
155
|
+
} catch (error) {
|
|
156
|
+
RED.log.error(`knxUltimateMatterControllerDevice: config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnDayTime): ${error.message} : ${error.stack || ''} `)
|
|
157
|
+
config.colorAtSwitchOnDayTime = ''
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Same thing, but with night color
|
|
161
|
+
if (config.colorAtSwitchOnNightTime.indexOf('#') !== -1) {
|
|
162
|
+
// Transform to rgb.
|
|
163
|
+
try {
|
|
164
|
+
config.colorAtSwitchOnNightTime = hueColorConverter.ColorConverter.hexRgb(config.colorAtSwitchOnNightTime.replace('#', ''))
|
|
165
|
+
} catch (error) {
|
|
166
|
+
config.colorAtSwitchOnNightTime = { kelvin: 2700, brightness: 20 }
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
try {
|
|
170
|
+
config.colorAtSwitchOnNightTime = JSON.parse(config.colorAtSwitchOnNightTime)
|
|
171
|
+
} catch (error) {
|
|
172
|
+
RED.log.error(`knxUltimateMatterControllerDevice: config.colorAtSwitchOnDayTime = JSON.parse(config.colorAtSwitchOnNightTime): ${error.message} : ${error.stack || ''} `)
|
|
173
|
+
config.colorAtSwitchOnNightTime = ''
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const formatTs = (date) => {
|
|
178
|
+
const d = date instanceof Date ? date : new Date(date)
|
|
179
|
+
const provider = node.serverKNX
|
|
180
|
+
if (provider && typeof provider.formatStatusTimestamp === 'function') return provider.formatStatusTimestamp(d)
|
|
181
|
+
return `${d.getDate()}, ${d.toLocaleTimeString()}`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
node.syncCurrentHUEDeviceFromKNXState = function syncCurrentHUEDeviceFromKNXState (_state) { // Starting from v 4.1.31
|
|
185
|
+
if (config.updateLocalStateFromKNXWrite !== true) return // Starting from v 4.1.31
|
|
186
|
+
if (_state === undefined || _state === null || typeof _state !== 'object') return // Starting from v 4.1.31
|
|
187
|
+
if (node.currentHUEDevice === undefined || node.currentHUEDevice === null) return // Starting from v 4.1.31
|
|
188
|
+
if (_state.on !== undefined && typeof _state.on.on === 'boolean') { // Starting from v 4.1.31
|
|
189
|
+
if (node.currentHUEDevice.on === undefined || node.currentHUEDevice.on === null) node.currentHUEDevice.on = {} // Starting from v 4.1.31
|
|
190
|
+
node.currentHUEDevice.on.on = _state.on.on // Starting from v 4.1.31
|
|
191
|
+
} // Starting from v 4.1.31
|
|
192
|
+
if (_state.dimming !== undefined && _state.dimming !== null && _state.dimming.brightness !== undefined) { // Starting from v 4.1.31
|
|
193
|
+
if (node.currentHUEDevice.dimming === undefined || node.currentHUEDevice.dimming === null) node.currentHUEDevice.dimming = {} // Starting from v 4.1.31
|
|
194
|
+
node.currentHUEDevice.dimming.brightness = _state.dimming.brightness // Starting from v 4.1.31
|
|
195
|
+
} // Starting from v 4.1.31
|
|
196
|
+
if (_state.color !== undefined && _state.color !== null && _state.color.xy !== undefined) { // Starting from v 4.1.31
|
|
197
|
+
if (node.currentHUEDevice.color === undefined || node.currentHUEDevice.color === null) node.currentHUEDevice.color = {} // Starting from v 4.1.31
|
|
198
|
+
node.currentHUEDevice.color.xy = cloneDeep(_state.color.xy) // Starting from v 4.1.31
|
|
199
|
+
} // Starting from v 4.1.31
|
|
200
|
+
if (_state.color_temperature !== undefined && _state.color_temperature !== null && _state.color_temperature.mirek !== undefined) { // Starting from v 4.1.31
|
|
201
|
+
if (node.currentHUEDevice.color_temperature === undefined || node.currentHUEDevice.color_temperature === null) node.currentHUEDevice.color_temperature = {} // Starting from v 4.1.31
|
|
202
|
+
node.currentHUEDevice.color_temperature.mirek = _state.color_temperature.mirek // Starting from v 4.1.31
|
|
203
|
+
} // Starting from v 4.1.31
|
|
204
|
+
} // Starting from v 4.1.31
|
|
205
|
+
|
|
206
|
+
// Used to call the status update from the config node.
|
|
207
|
+
node.setNodeStatus = ({
|
|
208
|
+
fill, shape, text, payload
|
|
209
|
+
}) => {
|
|
210
|
+
try {
|
|
211
|
+
if (node.currentHUEDevice?.on?.on === true) { fill = 'blue'; shape = 'dot' } else { fill = 'blue'; shape = 'ring' };
|
|
212
|
+
if (payload === undefined) payload = ''
|
|
213
|
+
const dDate = new Date()
|
|
214
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
215
|
+
node.sKNXNodeStatusText = `|KNX: ${text} ${payload} (${formatTs(dDate)})`
|
|
216
|
+
node.status({ fill, shape, text: (node.sHUENodeStatusText || '') + ' ' + (node.sKNXNodeStatusText || '') })
|
|
217
|
+
} catch (error) { }
|
|
218
|
+
}
|
|
219
|
+
// Used to call the status update from the HUE config node.
|
|
220
|
+
node.setNodeStatusHue = ({ fill, shape, text, payload }) => {
|
|
221
|
+
try {
|
|
222
|
+
if (node.currentHUEDevice?.on?.on === true) { fill = 'blue'; shape = 'dot' } else { fill = 'blue'; shape = 'ring' };
|
|
223
|
+
if (payload === undefined) payload = ''
|
|
224
|
+
const dDate = new Date()
|
|
225
|
+
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload.toString()
|
|
226
|
+
node.sHUENodeStatusText = `|HUE: ${text} ${payload} (${formatTs(dDate)})`
|
|
227
|
+
node.status({ fill, shape, text: node.sHUENodeStatusText + ' ' + (node.sKNXNodeStatusText || '') })
|
|
228
|
+
} catch (error) { }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
node.writeHueState = function writeHueState (_state) {
|
|
232
|
+
const defaultOperation = node.isGrouped_light === false ? 'setLight' : 'setGroupedLight'
|
|
233
|
+
const isGroupedLightOff = node.isGrouped_light === true && node.currentHUEDevice?.on?.on === false
|
|
234
|
+
const stateKeys = _state && typeof _state === 'object' ? Object.keys(_state) : []
|
|
235
|
+
const presetKeys = ['dimming', 'color', 'color_temperature', 'gradient']
|
|
236
|
+
const actionableKeys = ['dimming', ...presetKeys]
|
|
237
|
+
const hasActionablePayload = stateKeys.some((key) => actionableKeys.includes(key))
|
|
238
|
+
const mustPresetGroupedLightChildren = isGroupedLightOff &&
|
|
239
|
+
stateKeys.some((key) => presetKeys.includes(key))
|
|
240
|
+
|
|
241
|
+
if (!mustPresetGroupedLightChildren) {
|
|
242
|
+
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, _state, defaultOperation)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
(async () => {
|
|
247
|
+
try {
|
|
248
|
+
const groupLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
|
|
249
|
+
RED.log.debug(`knxUltimateMatterControllerDevice: preset grouped_light children before group on. Group=${node.hueDevice} lights=${Array.isArray(groupLights) ? groupLights.length : 0}`)
|
|
250
|
+
let hasWrittenAtLeastOneLight = false
|
|
251
|
+
for (let index = 0; index < groupLights.length; index++) {
|
|
252
|
+
const light = groupLights[index]
|
|
253
|
+
if (!light?.id) continue
|
|
254
|
+
const lightState = {}
|
|
255
|
+
let hasActionableStateForLight = false
|
|
256
|
+
|
|
257
|
+
if (_state.dimming !== undefined && light.dimming !== undefined) {
|
|
258
|
+
lightState.dimming = cloneDeep(_state.dimming)
|
|
259
|
+
hasActionableStateForLight = true
|
|
260
|
+
}
|
|
261
|
+
if (_state.color !== undefined && light.color !== undefined) {
|
|
262
|
+
lightState.color = cloneDeep(_state.color)
|
|
263
|
+
hasActionableStateForLight = true
|
|
264
|
+
}
|
|
265
|
+
if (_state.color_temperature !== undefined && light.color_temperature !== undefined) {
|
|
266
|
+
lightState.color_temperature = cloneDeep(_state.color_temperature)
|
|
267
|
+
hasActionableStateForLight = true
|
|
268
|
+
}
|
|
269
|
+
if (_state.gradient !== undefined && light.gradient !== undefined) {
|
|
270
|
+
lightState.gradient = cloneDeep(_state.gradient)
|
|
271
|
+
hasActionableStateForLight = true
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!hasActionableStateForLight) continue
|
|
275
|
+
node.serverHue.hueManager.writeHueQueueAdd(light.id, lightState, 'setLight')
|
|
276
|
+
hasWrittenAtLeastOneLight = true
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!hasWrittenAtLeastOneLight || !hasActionablePayload) {
|
|
280
|
+
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, _state, defaultOperation)
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (_state?.on?.on !== true) return
|
|
285
|
+
|
|
286
|
+
const groupedLightState = { on: cloneDeep(_state.on) }
|
|
287
|
+
if (_state.dynamics !== undefined) groupedLightState.dynamics = cloneDeep(_state.dynamics)
|
|
288
|
+
RED.log.debug(`knxUltimateMatterControllerDevice: turning on grouped_light after children preset. Group=${node.hueDevice}`)
|
|
289
|
+
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, groupedLightState, defaultOperation)
|
|
290
|
+
} catch (error) {
|
|
291
|
+
RED.log.debug(`knxUltimateMatterControllerDevice: node.writeHueState fallback to grouped_light write: ${error.message}`)
|
|
292
|
+
node.serverHue.hueManager.writeHueQueueAdd(node.hueDevice, _state, defaultOperation)
|
|
293
|
+
}
|
|
294
|
+
})()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
node.deleteHueStateQueue = function deleteHueStateQueue () {
|
|
298
|
+
node.serverHue.hueManager.deleteHueQueue(node.hueDevice)
|
|
299
|
+
if (node.isGrouped_light !== true) return;
|
|
300
|
+
|
|
301
|
+
(async () => {
|
|
302
|
+
try {
|
|
303
|
+
const groupLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
|
|
304
|
+
for (let index = 0; index < groupLights.length; index++) {
|
|
305
|
+
const light = groupLights[index]
|
|
306
|
+
if (!light?.id) continue
|
|
307
|
+
node.serverHue.hueManager.deleteHueQueue(light.id)
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
RED.log.debug(`knxUltimateMatterControllerDevice: node.deleteHueStateQueue: ${error.message}`)
|
|
311
|
+
}
|
|
312
|
+
})()
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function getRandomIntInclusive (min, max) {
|
|
316
|
+
min = Math.ceil(min)
|
|
317
|
+
max = Math.floor(max)
|
|
318
|
+
return Math.floor(Math.random() * (max - min + 1) + min) // The maximum is inclusive and the minimum is inclusive
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// This function is called by the hue-config.js
|
|
322
|
+
node.handleSend = (msg) => {
|
|
323
|
+
if (!msg || !msg.knx || !isConfiguredKNXGA(msg.knx.destination)) return
|
|
324
|
+
if (node.currentHUEDevice === undefined && node.serverHue.linkStatus === 'connected') {
|
|
325
|
+
node.setNodeStatusHue({
|
|
326
|
+
fill: 'yellow',
|
|
327
|
+
shape: 'ring',
|
|
328
|
+
text: 'Initializing. Please wait.',
|
|
329
|
+
payload: ''
|
|
330
|
+
})
|
|
331
|
+
return
|
|
332
|
+
}
|
|
333
|
+
if (msg.knx.event !== 'GroupValue_Read' && node.currentHUEDevice !== undefined) {
|
|
334
|
+
let state = {}
|
|
335
|
+
try {
|
|
336
|
+
switch (msg.knx.destination) {
|
|
337
|
+
case config.GALightSwitch:
|
|
338
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightSwitch))
|
|
339
|
+
|
|
340
|
+
// 15/05/2024 Supergiovane: check the Override to Day option
|
|
341
|
+
// config.restoreDayMode can be: no or setDayByFastSwitchLightSingle or setDayByFastSwitchLightALL
|
|
342
|
+
// ----------------------------------------------------------
|
|
343
|
+
if (config.restoreDayMode === 'setDayByFastSwitchLightSingle' || config.restoreDayMode === 'setDayByFastSwitchLightALL') {
|
|
344
|
+
if (node.DayTime === false) {
|
|
345
|
+
if (msg.payload === true) {
|
|
346
|
+
if (node.timerCheckForFastLightSwitch === null) {
|
|
347
|
+
node.timerCheckForFastLightSwitch = setTimeout(() => {
|
|
348
|
+
node.DayTime = false
|
|
349
|
+
RED.log.debug('knxUltimateMatterControllerDevice: node.timerCheckForFastLightSwitch: set daytime to false after node.timerCheckForFastLightSwitch elapsed')
|
|
350
|
+
node.timerCheckForFastLightSwitch = null
|
|
351
|
+
}, 10000) // 10 seconds
|
|
352
|
+
} else {
|
|
353
|
+
if (config.restoreDayMode === 'setDayByFastSwitchLightALL') {
|
|
354
|
+
// Turn off the Day/Night group address
|
|
355
|
+
if (config.GADaylightSensor !== undefined && config.GADaylightSensor !== '') {
|
|
356
|
+
if (node.timerCheckForFastLightSwitch !== null) { clearTimeout(node.timerCheckForFastLightSwitch); node.timerCheckForFastLightSwitch = null }
|
|
357
|
+
RED.log.debug(`knxUltimateMatterControllerDevice: node.timerCheckForFastLightSwitch: set daytime the group address ${config.GADaylightSensor}`)
|
|
358
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
359
|
+
grpaddr: config.GADaylightSensor,
|
|
360
|
+
payload: config.invertDayNight === false,
|
|
361
|
+
dpt: config.dptDaylightSensor,
|
|
362
|
+
outputtype: 'write',
|
|
363
|
+
nodecallerid: node.id
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
node.DayTime = true
|
|
368
|
+
RED.log.debug('knxUltimateMatterControllerDevice: node.timerCheckForFastLightSwitch: set daytime to true')
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// ----------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
if (msg.payload === true) {
|
|
376
|
+
// From HUE Api core concepts:
|
|
377
|
+
// If you try and control multiple conflicting parameters at once e.g. {"color": {"xy": {"x":0.5,"y":0.5}}, "color_temperature": {"mirek": 250}}
|
|
378
|
+
// the lights can only physically do one, for this we apply the rule that xy beats ct. Simple.
|
|
379
|
+
// color_temperature.mirek: color temperature in mirek is null when the light color is not in the ct spectrum
|
|
380
|
+
if ((node.DayTime === true && config.specifySwitchOnBrightness === 'no') && ((node.isGrouped_light === false && node.HUEDeviceWhileDaytime !== null) || (node.isGrouped_light === true && node.HUELightsBelongingToGroupWhileDaytime !== null))) {
|
|
381
|
+
if (node.isGrouped_light === false && node.HUEDeviceWhileDaytime !== null) {
|
|
382
|
+
// The DayNight has switched into day, so restore the previous light status
|
|
383
|
+
state = { on: { on: true }, dimming: node.HUEDeviceWhileDaytime.dimming, color: node.HUEDeviceWhileDaytime.color, color_temperature: node.HUEDeviceWhileDaytime.color_temperature }
|
|
384
|
+
if (node.HUEDeviceWhileDaytime.color_temperature !== undefined && node.HUEDeviceWhileDaytime.color_temperature.mirek === null) delete state.color_temperature // Otherwise the lamp will not turn on due to an error. color_temperature.mirek: color temperature in mirek is null when the light color is not in the ct spectrum
|
|
385
|
+
node.writeHueState(state)
|
|
386
|
+
node.setNodeStatusHue({
|
|
387
|
+
fill: 'green',
|
|
388
|
+
shape: 'dot',
|
|
389
|
+
text: 'KNX->Matter',
|
|
390
|
+
payload: 'Restore light status'
|
|
391
|
+
})
|
|
392
|
+
node.HUEDeviceWhileDaytime = null // Nullize the object.
|
|
393
|
+
} else if (node.isGrouped_light === true && node.HUELightsBelongingToGroupWhileDaytime !== null) {
|
|
394
|
+
// The DayNight has switched into day, so restore the previous light state, belonging to the group
|
|
395
|
+
let bAtLeastOneIsOn = false
|
|
396
|
+
for (let index = 0; index < node.HUELightsBelongingToGroupWhileDaytime.length; index++) { // Ensure, at least 1 lamp was on, otherwise turn all lamps on
|
|
397
|
+
const element = node.HUELightsBelongingToGroupWhileDaytime[index].light[0]
|
|
398
|
+
if (element.on.on === true) {
|
|
399
|
+
bAtLeastOneIsOn = true
|
|
400
|
+
break
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (let index = 0; index < node.HUELightsBelongingToGroupWhileDaytime.length; index++) {
|
|
404
|
+
const element = node.HUELightsBelongingToGroupWhileDaytime[index].light[0]
|
|
405
|
+
if (bAtLeastOneIsOn === true) {
|
|
406
|
+
state = { on: element.on, dimming: element.dimming, color: element.color, color_temperature: element.color_temperature }
|
|
407
|
+
} else {
|
|
408
|
+
// Failsafe all on
|
|
409
|
+
state = { on: { on: true }, dimming: element.dimming, color: element.color, color_temperature: element.color_temperature }
|
|
410
|
+
}
|
|
411
|
+
if (element.color_temperature !== undefined && element.color_temperature.mirek === null) delete state.color_temperature // Otherwise the lamp will not turn on due to an error. color_temperature.mirek: color temperature in mirek is null when the light color is not in the ct spectrum
|
|
412
|
+
node.serverHue.hueManager.writeHueQueueAdd(element.id, state, 'setLight')
|
|
413
|
+
}
|
|
414
|
+
node.setNodeStatusHue({
|
|
415
|
+
fill: 'green',
|
|
416
|
+
shape: 'dot',
|
|
417
|
+
text: 'KNX->Matter',
|
|
418
|
+
payload: "Resuming all group's light"
|
|
419
|
+
})
|
|
420
|
+
node.HUELightsBelongingToGroupWhileDaytime = null // Nullize the object.
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
let colorChoosen
|
|
425
|
+
let temperatureChoosen
|
|
426
|
+
let brightnessChoosen
|
|
427
|
+
// The light must support the temperature (in this case, colorAtSwitchOnNightTime is an object {kelvin:xx, brightness:yy})
|
|
428
|
+
if (node.currentHUEDevice.color_temperature !== undefined) {
|
|
429
|
+
if (node.DayTime === true && config.specifySwitchOnBrightness === 'temperature') {
|
|
430
|
+
temperatureChoosen = config.colorAtSwitchOnDayTime.kelvin
|
|
431
|
+
} else if (node.DayTime === false && config.enableDayNightLighting === 'temperature') {
|
|
432
|
+
temperatureChoosen = config.colorAtSwitchOnNightTime.kelvin
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (node.currentHUEDevice.dimming !== undefined) {
|
|
436
|
+
// Check wether the user selected specific brightness at switch on (in this case, colorAtSwitchOnNightTime is an object {kelvin:xx, brightness:yy})
|
|
437
|
+
if (node.DayTime === true && config.specifySwitchOnBrightness === 'temperature') {
|
|
438
|
+
brightnessChoosen = config.colorAtSwitchOnDayTime.brightness
|
|
439
|
+
} else if (node.DayTime === false && config.enableDayNightLighting === 'temperature') {
|
|
440
|
+
brightnessChoosen = config.colorAtSwitchOnNightTime.brightness
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (node.currentHUEDevice.color !== undefined) {
|
|
444
|
+
// Check wether the user selected specific color at switch on (in this case, colorAtSwitchOnDayTime is a text with HTML web color)
|
|
445
|
+
if (node.DayTime === true && config.specifySwitchOnBrightness === 'yes') {
|
|
446
|
+
colorChoosen = config.colorAtSwitchOnDayTime
|
|
447
|
+
} else if (node.DayTime === false && config.enableDayNightLighting === 'yes') {
|
|
448
|
+
colorChoosen = config.colorAtSwitchOnNightTime
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// Create the HUE command
|
|
452
|
+
if (colorChoosen !== undefined) {
|
|
453
|
+
// Now we have a jColorChoosen. Proceed illuminating the light
|
|
454
|
+
let gamut = null
|
|
455
|
+
if (node.currentHUEDevice.color.gamut !== undefined) {
|
|
456
|
+
gamut = node.currentHUEDevice.color.gamut
|
|
457
|
+
}
|
|
458
|
+
const dretXY = hueColorConverter.ColorConverter.calculateXYFromRGB(colorChoosen.red, colorChoosen.green, colorChoosen.blue, gamut)
|
|
459
|
+
const dbright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(colorChoosen.red, colorChoosen.green, colorChoosen.blue)
|
|
460
|
+
node.currentHUEDevice.dimming.brightness = Math.round(dbright, 0)
|
|
461
|
+
if (node.currentHUEDevice.color !== undefined) node.currentHUEDevice.color.xy = dretXY // 26/03/2024
|
|
462
|
+
node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness)
|
|
463
|
+
state = dbright > 0 ? { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } } : { on: { on: false } }
|
|
464
|
+
// state = { on: { on: true }, dimming: { brightness: dbright }, color: { xy: dretXY } };
|
|
465
|
+
} else if (temperatureChoosen !== undefined) {
|
|
466
|
+
// Kelvin
|
|
467
|
+
const mirek = hueColorConverter.ColorConverter.kelvinToMirek(temperatureChoosen)
|
|
468
|
+
node.currentHUEDevice.color_temperature.mirek = mirek
|
|
469
|
+
node.currentHUEDevice.dimming.brightness = brightnessChoosen
|
|
470
|
+
node.updateKNXBrightnessState(node.currentHUEDevice.dimming.brightness)
|
|
471
|
+
// Kelvin temp
|
|
472
|
+
state = brightnessChoosen > 0 ? { on: { on: true }, dimming: { brightness: brightnessChoosen }, color_temperature: { mirek } } : { on: { on: false } }
|
|
473
|
+
// state = { on: { on: true }, dimming: { brightness: brightnessChoosen }, color_temperature: { mirek: mirek } };
|
|
474
|
+
} else if (brightnessChoosen !== undefined) {
|
|
475
|
+
state = brightnessChoosen > 0 ? { on: { on: true }, dimming: { brightness: brightnessChoosen } } : { on: { on: false } }
|
|
476
|
+
// state = { on: { on: true }, dimming: { brightness: brightnessChoosen } };
|
|
477
|
+
} else {
|
|
478
|
+
state = { on: { on: true } }
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
// Stop color cycle
|
|
483
|
+
if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
|
|
484
|
+
// Stop Blinking
|
|
485
|
+
if (node.timerBlink !== undefined) clearInterval(node.timerBlink)
|
|
486
|
+
state = { on: { on: false } }
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
|
|
490
|
+
node.writeHueState(state)
|
|
491
|
+
node.setNodeStatusHue({
|
|
492
|
+
fill: 'green',
|
|
493
|
+
shape: 'dot',
|
|
494
|
+
text: 'KNX->Matter',
|
|
495
|
+
payload: state
|
|
496
|
+
})
|
|
497
|
+
break
|
|
498
|
+
case config.GALightDIM:
|
|
499
|
+
// { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
|
|
500
|
+
// { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
|
|
501
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
|
|
502
|
+
node.hueDimming(msg.payload.decr_incr, msg.payload.data, config.dimSpeed)
|
|
503
|
+
node.setNodeStatusHue({
|
|
504
|
+
fill: 'green', shape: 'dot', text: 'KNX->Matter', payload: JSON.stringify(msg.payload)
|
|
505
|
+
})
|
|
506
|
+
break
|
|
507
|
+
case config.GALightHSV_H_DIM:
|
|
508
|
+
// { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
|
|
509
|
+
// { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
|
|
510
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
|
|
511
|
+
node.hueDimmingHSV_H(msg.payload.decr_incr, msg.payload.data, config.HSVDimSpeed)
|
|
512
|
+
node.setNodeStatusHue({
|
|
513
|
+
fill: 'green', shape: 'dot', text: 'KNX->Matter', payload: JSON.stringify(msg.payload)
|
|
514
|
+
})
|
|
515
|
+
break
|
|
516
|
+
case config.GALightHSV_S_DIM:
|
|
517
|
+
// { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
|
|
518
|
+
// { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
|
|
519
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightDIM))
|
|
520
|
+
node.hueDimmingHSV_S(msg.payload.decr_incr, msg.payload.data, config.HSVDimSpeed)
|
|
521
|
+
node.setNodeStatusHue({
|
|
522
|
+
fill: 'green', shape: 'dot', text: 'KNX->Matter', payload: JSON.stringify(msg.payload)
|
|
523
|
+
})
|
|
524
|
+
break
|
|
525
|
+
case config.GALightKelvin:
|
|
526
|
+
let retMirek
|
|
527
|
+
let kelvinValue = 0
|
|
528
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvin))
|
|
529
|
+
if (config.dptLightKelvin === '7.600') {
|
|
530
|
+
if (msg.payload > 6535) msg.payload = 6535
|
|
531
|
+
if (msg.payload < 2000) msg.payload = 2000
|
|
532
|
+
kelvinValue = msg.payload// hueColorConverter.ColorConverter.scale(msg.payload, [0, 65535], [2000, 6535]);
|
|
533
|
+
retMirek = hueColorConverter.ColorConverter.kelvinToMirek(kelvinValue)
|
|
534
|
+
} else if (config.dptLightKelvin === '9.002') {
|
|
535
|
+
// Relative temperature in Kelvin. Use HUE scale.
|
|
536
|
+
if (msg.payload > 6535) msg.payload = 6535
|
|
537
|
+
if (msg.payload < 2000) msg.payload = 2000
|
|
538
|
+
retMirek = hueColorConverter.ColorConverter.kelvinToMirek(msg.payload)
|
|
539
|
+
}
|
|
540
|
+
state = { color_temperature: { mirek: retMirek } }
|
|
541
|
+
node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
|
|
542
|
+
node.writeHueState(state)
|
|
543
|
+
node.setNodeStatusHue({
|
|
544
|
+
fill: 'green',
|
|
545
|
+
shape: 'dot',
|
|
546
|
+
text: 'KNX->Matter',
|
|
547
|
+
payload: kelvinValue
|
|
548
|
+
})
|
|
549
|
+
break
|
|
550
|
+
case config.GADaylightSensor:
|
|
551
|
+
node.DayTime = Boolean(dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptDaylightSensor)))
|
|
552
|
+
if (config.invertDayNight === true) node.DayTime = !node.DayTime
|
|
553
|
+
if (config.specifySwitchOnBrightness === 'no') {
|
|
554
|
+
// This retains the HUE device status while daytime, to be restored after nighttime elapsed. https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/298
|
|
555
|
+
if (node.DayTime === false) {
|
|
556
|
+
if (node.isGrouped_light === false) node.HUEDeviceWhileDaytime = cloneDeep(node.currentHUEDevice) // DayTime has switched to false: save the currentHUEDevice into the HUEDeviceWhileDaytime
|
|
557
|
+
if (node.isGrouped_light === true) {
|
|
558
|
+
(async () => {
|
|
559
|
+
try {
|
|
560
|
+
const retLights = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
|
|
561
|
+
node.HUELightsBelongingToGroupWhileDaytime = cloneDeep(retLights) // DayTime has switched to false: save the lights belonging to the group into the HUELightsBelongingToGroupWhileDaytime array
|
|
562
|
+
} catch (error) { /* empty */ }
|
|
563
|
+
})()
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
node.HUEDeviceWhileDaytime = null
|
|
568
|
+
node.HUELightsBelongingToGroupWhileDaytime = null
|
|
569
|
+
}
|
|
570
|
+
node.setNodeStatusHue({
|
|
571
|
+
fill: 'green',
|
|
572
|
+
shape: 'dot',
|
|
573
|
+
text: 'KNX->Matter Daytime',
|
|
574
|
+
payload: node.DayTime
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
break
|
|
578
|
+
case config.GALightKelvinDIM:
|
|
579
|
+
if (config.dptLightKelvinDIM === '3.007') {
|
|
580
|
+
// MDT smartbutton will dim the color temperature
|
|
581
|
+
// { decr_incr: 1, data: 1 } : Start increasing until { decr_incr: 0, data: 0 } is received.
|
|
582
|
+
// { decr_incr: 0, data: 1 } : Start decreasing until { decr_incr: 0, data: 0 } is received.
|
|
583
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvinDIM))
|
|
584
|
+
node.hueDimmingTunableWhite(msg.payload.decr_incr, msg.payload.data, 5000)
|
|
585
|
+
node.setNodeStatusHue({
|
|
586
|
+
fill: 'green', shape: 'dot', text: 'KNX->Matter', payload: JSON.stringify(msg.payload)
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
break
|
|
590
|
+
case config.GALightKelvinPercentage:
|
|
591
|
+
if (config.dptLightKelvinPercentage === '5.001') {
|
|
592
|
+
// 0-100% tunable white
|
|
593
|
+
msg.payload = 100 - dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightKelvinPercentage))
|
|
594
|
+
// msg.payload = msg.payload <= 0 ? 1 : msg.payload
|
|
595
|
+
const retMirek = hueColorConverter.ColorConverter.scale(msg.payload, [0, 100], [153, 500])
|
|
596
|
+
msg.payload = retMirek
|
|
597
|
+
state = { color_temperature: { mirek: msg.payload } }
|
|
598
|
+
node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
|
|
599
|
+
node.writeHueState(state)
|
|
600
|
+
node.setNodeStatusHue({
|
|
601
|
+
fill: 'green',
|
|
602
|
+
shape: 'dot',
|
|
603
|
+
text: 'KNX->Matter',
|
|
604
|
+
payload: state
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
break
|
|
608
|
+
case config.GALightBrightness:
|
|
609
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBrightness))
|
|
610
|
+
if (typeof msg.payload !== 'number' || Number.isNaN(msg.payload)) throw new Error('Invalid KNX brightness payload')
|
|
611
|
+
state = { dimming: { brightness: msg.payload } }
|
|
612
|
+
if (msg.payload === 0) {
|
|
613
|
+
state.on = { on: false }
|
|
614
|
+
} else if (node.currentHUEDevice !== undefined && node.currentHUEDevice.on?.on === true) {
|
|
615
|
+
state.on = { on: true }
|
|
616
|
+
}
|
|
617
|
+
node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
|
|
618
|
+
node.writeHueState(state)
|
|
619
|
+
node.setNodeStatusHue({
|
|
620
|
+
fill: 'green',
|
|
621
|
+
shape: 'dot',
|
|
622
|
+
text: 'KNX->Matter',
|
|
623
|
+
payload: state
|
|
624
|
+
})
|
|
625
|
+
break
|
|
626
|
+
case config.GALightColor:
|
|
627
|
+
msg.payload = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColor))
|
|
628
|
+
let gamut = null
|
|
629
|
+
if (
|
|
630
|
+
node.currentHUEDevice !== undefined &&
|
|
631
|
+
node.currentHUEDevice.color !== undefined &&
|
|
632
|
+
node.currentHUEDevice.color.gamut !== undefined
|
|
633
|
+
) {
|
|
634
|
+
gamut = node.currentHUEDevice.color.gamut
|
|
635
|
+
}
|
|
636
|
+
const retXY = hueColorConverter.ColorConverter.calculateXYFromRGB(msg.payload.red, msg.payload.green, msg.payload.blue, gamut)
|
|
637
|
+
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(msg.payload.red, msg.payload.green, msg.payload.blue)
|
|
638
|
+
state = { dimming: { brightness: bright }, color: { xy: retXY } }
|
|
639
|
+
if (node.currentHUEDevice === undefined) {
|
|
640
|
+
// Grouped light
|
|
641
|
+
state.on = { on: bright > 0 }
|
|
642
|
+
} else {
|
|
643
|
+
// Light
|
|
644
|
+
if (node.currentHUEDevice.on.on === false && bright > 0) state.on = { on: true }
|
|
645
|
+
if (node.currentHUEDevice.on.on === true && bright === 0) state = { on: { on: false }, dimming: { brightness: bright } }
|
|
646
|
+
}
|
|
647
|
+
node.syncCurrentHUEDeviceFromKNXState(state) // Starting from v 4.1.31
|
|
648
|
+
node.writeHueState(state)
|
|
649
|
+
node.setNodeStatusHue({
|
|
650
|
+
fill: 'green',
|
|
651
|
+
shape: 'dot',
|
|
652
|
+
text: 'KNX->Matter',
|
|
653
|
+
payload: state
|
|
654
|
+
})
|
|
655
|
+
break
|
|
656
|
+
case config.GALightBlink:
|
|
657
|
+
const gaVal = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightBlink))
|
|
658
|
+
if (gaVal) {
|
|
659
|
+
node.timerBlink = setInterval(() => {
|
|
660
|
+
if (node.blinkValue === undefined) node.blinkValue = true
|
|
661
|
+
node.blinkValue = !node.blinkValue
|
|
662
|
+
msg.payload = node.blinkValue
|
|
663
|
+
// state = msg.payload === true ? { on: { on: true } } : { on: { on: false } }
|
|
664
|
+
state = msg.payload === true
|
|
665
|
+
? { on: { on: true }, dimming: { brightness: 100 }, dynamics: { duration: 0 } }
|
|
666
|
+
: { on: { on: false }, dimming: { brightness: 0 }, dynamics: { duration: 0 } }
|
|
667
|
+
node.writeHueState(state)
|
|
668
|
+
}, 1500)
|
|
669
|
+
} else {
|
|
670
|
+
if (node.timerBlink !== undefined) clearInterval(node.timerBlink)
|
|
671
|
+
node.writeHueState({ on: { on: false } })
|
|
672
|
+
}
|
|
673
|
+
node.setNodeStatusHue({
|
|
674
|
+
fill: 'green',
|
|
675
|
+
shape: 'dot',
|
|
676
|
+
text: 'KNX->Matter',
|
|
677
|
+
payload: gaVal
|
|
678
|
+
})
|
|
679
|
+
break
|
|
680
|
+
case config.GALightColorCycle:
|
|
681
|
+
{
|
|
682
|
+
if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
|
|
683
|
+
const gaValColorCycle = dptlib.fromBuffer(msg.knx.rawValue, dptlib.resolve(config.dptLightColorCycle))
|
|
684
|
+
if (gaValColorCycle === true) {
|
|
685
|
+
node.writeHueState({ on: { on: true } })
|
|
686
|
+
node.timerColorCycle = setInterval(() => {
|
|
687
|
+
try {
|
|
688
|
+
const red = getRandomIntInclusive(0, 255)
|
|
689
|
+
const green = getRandomIntInclusive(0, 255)
|
|
690
|
+
const blue = getRandomIntInclusive(0, 255)
|
|
691
|
+
let gamut = null
|
|
692
|
+
if (
|
|
693
|
+
node.currentHUEDevice !== undefined &&
|
|
694
|
+
node.currentHUEDevice.color !== undefined &&
|
|
695
|
+
node.currentHUEDevice.color.gamut !== undefined
|
|
696
|
+
) {
|
|
697
|
+
gamut = node.currentHUEDevice.color.gamut
|
|
698
|
+
}
|
|
699
|
+
const retXY = hueColorConverter.ColorConverter.calculateXYFromRGB(red, green, blue, gamut)
|
|
700
|
+
const bright = hueColorConverter.ColorConverter.getBrightnessFromRGBOrHex(red, green, blue)
|
|
701
|
+
state = bright > 0 ? { on: { on: true }, dimming: { brightness: bright }, color: { xy: retXY } } : { on: { on: false } }
|
|
702
|
+
node.writeHueState(state)
|
|
703
|
+
} catch (error) { }
|
|
704
|
+
}, 10000)
|
|
705
|
+
} else {
|
|
706
|
+
if (node.timerColorCycle !== undefined) clearInterval(node.timerColorCycle)
|
|
707
|
+
node.writeHueState({ on: { on: false } })
|
|
708
|
+
}
|
|
709
|
+
node.setNodeStatusHue({
|
|
710
|
+
fill: 'green',
|
|
711
|
+
shape: 'dot',
|
|
712
|
+
text: 'KNX->Matter',
|
|
713
|
+
payload: gaValColorCycle
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
break
|
|
717
|
+
default:
|
|
718
|
+
break
|
|
719
|
+
}
|
|
720
|
+
} catch (error) {
|
|
721
|
+
node.status({
|
|
722
|
+
fill: 'red',
|
|
723
|
+
shape: 'dot',
|
|
724
|
+
text: `KNX->Matter errorRead ${error.message} (${formatTs(new Date())})`
|
|
725
|
+
})
|
|
726
|
+
RED.log.error(`knxUltimateMatterControllerDevice: node.handleSend: if (msg.knx.event !== "GroupValue_Read"): ${error.message} : ${error.stack || ''} `)
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// I must respond to query requests (read request) sent from the KNX BUS
|
|
731
|
+
try {
|
|
732
|
+
if (msg.knx.event === 'GroupValue_Read' && node.currentHUEDevice !== undefined) {
|
|
733
|
+
const zeroBrightnessWhenOff = (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')
|
|
734
|
+
let ret
|
|
735
|
+
switch (msg.knx.destination) {
|
|
736
|
+
case config.GALightState:
|
|
737
|
+
ret = node.currentHUEDevice.on.on
|
|
738
|
+
if (ret !== undefined) node.updateKNXLightState(ret, 'response')
|
|
739
|
+
break
|
|
740
|
+
case config.GALightColorState:
|
|
741
|
+
ret = node.currentHUEDevice.color.xy
|
|
742
|
+
if (ret !== undefined) node.updateKNXLightColorState(node.currentHUEDevice.color, 'response')
|
|
743
|
+
break
|
|
744
|
+
case config.GALightKelvinPercentageState:
|
|
745
|
+
// The kelvin level belongs to the group defice, so i don't need to get the first light in the collection (if the device is a grouped_light)
|
|
746
|
+
ret = node.currentHUEDevice.color_temperature.mirek
|
|
747
|
+
if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, 'response')
|
|
748
|
+
// if (node.isGrouped_light === false) {
|
|
749
|
+
// ret = node.currentHUEDevice.color_temperature.mirek;
|
|
750
|
+
// if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
|
|
751
|
+
// } else {
|
|
752
|
+
// (async () => {
|
|
753
|
+
// try {
|
|
754
|
+
// // Find the first light in the collection, having the color_temperature capabilities
|
|
755
|
+
// const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
|
|
756
|
+
// for (let index = 0; index < devices.length; index++) {
|
|
757
|
+
// const element = devices[index];
|
|
758
|
+
// if (element.light[0].color_temperature !== undefined) {
|
|
759
|
+
// ret = element.light[0].color_temperature.mirek;
|
|
760
|
+
// break;
|
|
761
|
+
// }
|
|
762
|
+
// }
|
|
763
|
+
// if (ret !== undefined) node.updateKNXLightKelvinPercentageState(ret, "response");
|
|
764
|
+
// } catch (error) { /* empty */ }
|
|
765
|
+
// })();
|
|
766
|
+
// }
|
|
767
|
+
break
|
|
768
|
+
case config.GALightBrightnessState:
|
|
769
|
+
// Hue keeps the last brightness even when the light is OFF. If the user enabled "brightness status -> 0 on HUE off",
|
|
770
|
+
// keep returning 0 while OFF to avoid the KNX status jumping back to the last/start value after some time/read-requests.
|
|
771
|
+
if (zeroBrightnessWhenOff === true && node.currentHUEDevice?.on?.on === false) {
|
|
772
|
+
ret = 0
|
|
773
|
+
} else {
|
|
774
|
+
ret = node.currentHUEDevice.dimming.brightness
|
|
775
|
+
}
|
|
776
|
+
if (ret !== undefined) node.updateKNXBrightnessState(ret, 'response')
|
|
777
|
+
break
|
|
778
|
+
case config.GALightKelvinState:
|
|
779
|
+
// The kelvin level belongs to the group defice, so i don't need to get the first light in the collection (if the device is a grouped_light)
|
|
780
|
+
ret = node.currentHUEDevice.color_temperature.mirek
|
|
781
|
+
if (ret !== undefined) node.updateKNXLightKelvinState(ret, 'response')
|
|
782
|
+
// if (node.isGrouped_light === false) {
|
|
783
|
+
// ret = node.currentHUEDevice.color_temperature.mirek;
|
|
784
|
+
// if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
|
|
785
|
+
// } else {
|
|
786
|
+
// (async () => {
|
|
787
|
+
// try {
|
|
788
|
+
// // Find the first light in the collection, having the color_temperature capabilities
|
|
789
|
+
// const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false);
|
|
790
|
+
// for (let index = 0; index < devices.length; index++) {
|
|
791
|
+
// const element = devices[index];
|
|
792
|
+
// if (element.light[0].color_temperature !== undefined) {
|
|
793
|
+
// ret = element.light[0].color_temperature.mirek;
|
|
794
|
+
// break;
|
|
795
|
+
// }
|
|
796
|
+
// }
|
|
797
|
+
// if (ret !== undefined) node.updateKNXLightKelvinState(ret, "response");
|
|
798
|
+
// } catch (error) { /* empty */ }
|
|
799
|
+
// })();
|
|
800
|
+
// }
|
|
801
|
+
break
|
|
802
|
+
default:
|
|
803
|
+
break
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} catch (error) {
|
|
807
|
+
node.status({
|
|
808
|
+
fill: 'red',
|
|
809
|
+
shape: 'dot',
|
|
810
|
+
text: `KNX->Matter error :-( ${error.message} (${formatTs(new Date())})`
|
|
811
|
+
})
|
|
812
|
+
RED.log.error(`knxUltimateMatterControllerDevice: node.handleSend: if (msg.knx.event === "GroupValue_Read" && node.currentHUEDevice !== undefined): ${error.message} : ${error.stack || ''} `)
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Start dimming
|
|
817
|
+
// ***********************************************************
|
|
818
|
+
node.hueDimming = function hueDimming (_KNXaction, _KNXbrightness_Direction, _dimSpeedInMillisecs = undefined) {
|
|
819
|
+
// 31/10/2023 after many attempts to use dimming_delta function of the HueApeV2, loosing days of my life, without a decent success, will use the standard dimming calculations
|
|
820
|
+
// i decide to go to the "step brightness" way.
|
|
821
|
+
try {
|
|
822
|
+
let hueTelegram = {}
|
|
823
|
+
let numStep = 10 // Steps from 0 to 100 by 10
|
|
824
|
+
const extendedConf = {}
|
|
825
|
+
|
|
826
|
+
if (_KNXbrightness_Direction === 0 && _KNXaction === 0) {
|
|
827
|
+
// STOP DIM
|
|
828
|
+
if (node.timerStepDim !== undefined) clearInterval(node.timerStepDim)
|
|
829
|
+
node.brightnessStep = null
|
|
830
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
831
|
+
return
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// 26/03/2024 set the extended configuration as well, because the light was off (so i've been unable to set it up elsewhere)
|
|
835
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
|
|
836
|
+
// if (node.currentHUEDevice.color !== undefined) extendedConf.color = { xy: node.currentHUEDevice.color.xy }; // DO NOT ADD THE COLOR, BECAUSE THE COLOR HAS ALSO THE BRIGHTNESS, SO ALL THE DATA NEEDED TO BE SWITCHED ON CORRECLY
|
|
837
|
+
if (node.currentHUEDevice.color_temperature !== undefined) extendedConf.color_temperature = { mirek: node.currentHUEDevice.color_temperature.mirek }
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// If i'm dimming up while the light is off, start the dim with the initial brightness set to zero
|
|
841
|
+
if (_KNXbrightness_Direction > 0 && _KNXaction === 1 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
|
|
842
|
+
node.brightnessStep = null
|
|
843
|
+
node.currentHUEDevice.dimming.brightness = 0
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Set the actual brightness to start with
|
|
847
|
+
if (node.brightnessStep === null || node.brightnessStep === undefined) node.brightnessStep = node.currentHUEDevice.dimming.brightness !== undefined ? node.currentHUEDevice.dimming.brightness : 50
|
|
848
|
+
node.brightnessStep = Math.ceil(Number(node.brightnessStep))
|
|
849
|
+
|
|
850
|
+
// We have also minDimLevelLight and maxDimLevelLight to take care of.
|
|
851
|
+
let minDimLevelLight
|
|
852
|
+
if (config.minDimLevelLight === undefined) {
|
|
853
|
+
minDimLevelLight = 10
|
|
854
|
+
} else if (config.minDimLevelLight === 'useHueLightLevel') {
|
|
855
|
+
minDimLevelLight = node.currentHUEDevice.dimming.min_dim_level === undefined ? 10 : node.currentHUEDevice.dimming.min_dim_level
|
|
856
|
+
} else {
|
|
857
|
+
minDimLevelLight = Number(config.minDimLevelLight)
|
|
858
|
+
}
|
|
859
|
+
const maxDimLevelLight = config.maxDimLevelLight === undefined ? 100 : Number(config.maxDimLevelLight)
|
|
860
|
+
|
|
861
|
+
// Set the speed
|
|
862
|
+
_dimSpeedInMillisecs /= numStep
|
|
863
|
+
numStep = Math.round((maxDimLevelLight - minDimLevelLight) / numStep, 0)
|
|
864
|
+
|
|
865
|
+
if (_KNXbrightness_Direction > 0 && _KNXaction === 1) {
|
|
866
|
+
// DIM UP
|
|
867
|
+
if (node.timerStepDim !== undefined) clearInterval(node.timerStepDim)
|
|
868
|
+
node.timerStepDim = setInterval(() => {
|
|
869
|
+
node.updateKNXBrightnessState(node.brightnessStep) // Unnecessary, but necessary to set the KNX Status in real time.
|
|
870
|
+
node.brightnessStep += numStep
|
|
871
|
+
if (node.brightnessStep > maxDimLevelLight) node.brightnessStep = maxDimLevelLight
|
|
872
|
+
hueTelegram = { dimming: { brightness: node.brightnessStep }, dynamics: { duration: _dimSpeedInMillisecs + 500 } } // + is to avoid ladder effect
|
|
873
|
+
// Switch on the light if off
|
|
874
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
|
|
875
|
+
hueTelegram.on = { on: true }
|
|
876
|
+
Object.assign(hueTelegram, extendedConf) // 26/03/2024 add extended conf
|
|
877
|
+
}
|
|
878
|
+
// console.log(hueTelegram)
|
|
879
|
+
node.writeHueState(hueTelegram)
|
|
880
|
+
if (node.brightnessStep >= maxDimLevelLight) clearInterval(node.timerStepDim)
|
|
881
|
+
}, _dimSpeedInMillisecs)
|
|
882
|
+
}
|
|
883
|
+
if (_KNXbrightness_Direction > 0 && _KNXaction === 0) {
|
|
884
|
+
if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
|
|
885
|
+
// DIM DOWN
|
|
886
|
+
if (node.timerStepDim !== undefined) clearInterval(node.timerStepDim)
|
|
887
|
+
node.timerStepDim = setInterval(() => {
|
|
888
|
+
node.updateKNXBrightnessState(node.brightnessStep) // Unnecessary, but necessary to set the KNX Status in real time.
|
|
889
|
+
node.brightnessStep -= numStep
|
|
890
|
+
if (node.brightnessStep < minDimLevelLight) node.brightnessStep = minDimLevelLight
|
|
891
|
+
hueTelegram = { dimming: { brightness: node.brightnessStep }, dynamics: { duration: _dimSpeedInMillisecs + 500 } }// + 100 is to avoid ladder effect
|
|
892
|
+
// Switch off the light if on
|
|
893
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStep === 0) {
|
|
894
|
+
hueTelegram.on = { on: false }
|
|
895
|
+
}
|
|
896
|
+
node.writeHueState(hueTelegram)
|
|
897
|
+
if (node.brightnessStep <= minDimLevelLight) clearInterval(node.timerStepDim)
|
|
898
|
+
}, _dimSpeedInMillisecs)
|
|
899
|
+
}
|
|
900
|
+
} catch (error) { }
|
|
901
|
+
}
|
|
902
|
+
// ***********************************************************
|
|
903
|
+
|
|
904
|
+
// Start dimming tunable white
|
|
905
|
+
// mirek: required(integer minimum: 153, maximum: 500)
|
|
906
|
+
// ***********************************************************
|
|
907
|
+
node.hueDimmingTunableWhite = function hueDimmingTunableWhite (_KNXaction, _KNXbrightness_DirectionTunableWhite, _dimSpeedInMillisecsTunableWhite = undefined) {
|
|
908
|
+
// 23/23/2023 after many attempts to use dimming_delta function of the HueApeV2, loosing days of my life, without a decent success, will use the standard dimming calculations
|
|
909
|
+
// i decide to go to the "step brightness" way.
|
|
910
|
+
try {
|
|
911
|
+
if (_KNXbrightness_DirectionTunableWhite === 0 && _KNXaction === 0) {
|
|
912
|
+
// STOP DIM
|
|
913
|
+
if (node.timerStepDimTunableWhite !== undefined) clearInterval(node.timerStepDimTunableWhite)
|
|
914
|
+
node.brightnessStepTunableWhite = null
|
|
915
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
916
|
+
return
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
let numStepTunableWhite = 10 // Steps from 153 to 500
|
|
920
|
+
if (config.invertDimTunableWhiteDirection === true) {
|
|
921
|
+
if (_KNXaction === 1) { _KNXaction = 0 } else { _KNXaction = 1 }
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Set the actual brightness to start with
|
|
925
|
+
if (node.brightnessStepTunableWhite === null || node.brightnessStepTunableWhite === undefined) node.brightnessStepTunableWhite = node.currentHUEDevice.color_temperature.mirek || 372
|
|
926
|
+
node.brightnessStepTunableWhite = Math.ceil(Number(node.brightnessStepTunableWhite))
|
|
927
|
+
|
|
928
|
+
// Set the speed
|
|
929
|
+
_dimSpeedInMillisecsTunableWhite = Math.ceil(_dimSpeedInMillisecsTunableWhite / numStepTunableWhite)
|
|
930
|
+
|
|
931
|
+
const minDimLevelLightTunableWhite = 153
|
|
932
|
+
const maxDimLevelLightTunableWhite = 500
|
|
933
|
+
// numStepTunableWhite = hueColorConverter.ColorConverter.scale(numStepTunableWhite, [0, 100], [minDimLevelLightTunableWhite, maxDimLevelLightTunableWhite]);
|
|
934
|
+
// numStepTunableWhite = hueColorConverter.ColorConverter.scale(numStepTunableWhite, [node.brightnessStepTunableWhite, maxDimLevelLightTunableWhite], [node.brightnessStepTunableWhite, maxDimLevelLightTunableWhite]);
|
|
935
|
+
numStepTunableWhite = Math.round((maxDimLevelLightTunableWhite - minDimLevelLightTunableWhite) / numStepTunableWhite, 0)
|
|
936
|
+
|
|
937
|
+
if (_KNXbrightness_DirectionTunableWhite > 0 && _KNXaction === 1) {
|
|
938
|
+
// DIM UP
|
|
939
|
+
if (node.timerStepDimTunableWhite !== undefined) clearInterval(node.timerStepDimTunableWhite)
|
|
940
|
+
node.timerStepDimTunableWhite = setInterval(() => {
|
|
941
|
+
node.updateKNXLightKelvinPercentageState(node.brightnessStepTunableWhite) // Unnecessary, but necessary to set the KNX Status in real time.
|
|
942
|
+
node.brightnessStepTunableWhite += numStepTunableWhite // *2 to speed up the things
|
|
943
|
+
if (node.brightnessStepTunableWhite > maxDimLevelLightTunableWhite) node.brightnessStepTunableWhite = maxDimLevelLightTunableWhite
|
|
944
|
+
const hueTelegram = { color_temperature: { mirek: node.brightnessStepTunableWhite }, dynamics: { duration: _dimSpeedInMillisecsTunableWhite } }
|
|
945
|
+
// Switch on the light if off
|
|
946
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
|
|
947
|
+
hueTelegram.on = { on: true }
|
|
948
|
+
}
|
|
949
|
+
node.writeHueState(hueTelegram)
|
|
950
|
+
if (node.brightnessStepTunableWhite >= maxDimLevelLightTunableWhite) clearInterval(node.timerStepDimTunableWhite)
|
|
951
|
+
}, _dimSpeedInMillisecsTunableWhite)
|
|
952
|
+
}
|
|
953
|
+
if (_KNXbrightness_DirectionTunableWhite > 0 && _KNXaction === 0) {
|
|
954
|
+
if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
|
|
955
|
+
// DIM DOWN
|
|
956
|
+
if (node.timerStepDimTunableWhite !== undefined) clearInterval(node.timerStepDimTunableWhite)
|
|
957
|
+
node.timerStepDimTunableWhite = setInterval(() => {
|
|
958
|
+
node.updateKNXLightKelvinPercentageState(node.brightnessStepTunableWhite) // Unnecessary, but necessary to set the KNX Status in real time.
|
|
959
|
+
node.brightnessStepTunableWhite -= numStepTunableWhite // *2 to speed up the things
|
|
960
|
+
if (node.brightnessStepTunableWhite < minDimLevelLightTunableWhite) node.brightnessStepTunableWhite = minDimLevelLightTunableWhite
|
|
961
|
+
const hueTelegram = { color_temperature: { mirek: node.brightnessStepTunableWhite }, dynamics: { duration: _dimSpeedInMillisecsTunableWhite } }
|
|
962
|
+
// Switch off the light if on
|
|
963
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStepTunableWhite === 0) {
|
|
964
|
+
hueTelegram.on = { on: false }
|
|
965
|
+
}
|
|
966
|
+
node.writeHueState(hueTelegram)
|
|
967
|
+
if (node.brightnessStepTunableWhite <= minDimLevelLightTunableWhite) clearInterval(node.timerStepDimTunableWhite)
|
|
968
|
+
}, _dimSpeedInMillisecsTunableWhite)
|
|
969
|
+
}
|
|
970
|
+
} catch (error) { }
|
|
971
|
+
}
|
|
972
|
+
// ***********************************************************
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Starts dimming / stop dimming of the HUE
|
|
976
|
+
* @param {number} _KNXaction 1 for start, 0 for stop
|
|
977
|
+
* @param {number} _KNXbrightness_DirectionHSV_H 1 for up, 0 for down
|
|
978
|
+
* @param {number} _dimSpeedInMillisecsHSV Speed time in milliseconds
|
|
979
|
+
* @returns {}
|
|
980
|
+
*/
|
|
981
|
+
node.hueDimmingHSV_H = function hueDimmingHSV_H (_KNXaction, _KNXbrightness_DirectionHSV_H, _dimSpeedInMillisecsHSV = undefined) {
|
|
982
|
+
// After many attempts to use dimming_delta function of the HueApiV2, loosing days of my life, without a decent success, will use the standard dimming calculations
|
|
983
|
+
// i decide to go to the "step brightness" way.
|
|
984
|
+
try {
|
|
985
|
+
if (_KNXbrightness_DirectionHSV_H === 0 && _KNXaction === 0) {
|
|
986
|
+
// STOP DIM
|
|
987
|
+
if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
|
|
988
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
989
|
+
return
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
let xyBrightnessToHsv
|
|
993
|
+
if (node.currentHUEDevice.color !== undefined && node.currentHUEDevice.color.xy !== undefined) {
|
|
994
|
+
// Get the XY + brightness from the lamp and transform it into HSV
|
|
995
|
+
// xyBrightnessToHsv = hueColorConverter.ColorConverter.xyBrightnessToHsv(node.currentHUEDevice.color.xy.x, node.currentHUEDevice.color.xy.y, node.currentHUEDevice.dimming.brightness);
|
|
996
|
+
xyBrightnessToHsv = hueColorConverter.ColorConverter.xyBrightnessToHsv(hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.x, [0, 1], [0, 100]), hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.y, [0, 1], [0, 100]), node.currentHUEDevice.dimming.brightness)
|
|
997
|
+
} else {
|
|
998
|
+
return // Something wrong here.
|
|
999
|
+
}
|
|
1000
|
+
let numStepHSV_H = 10 // Steps from 0 to 100
|
|
1001
|
+
|
|
1002
|
+
// Set the actual brightness to start with
|
|
1003
|
+
if (node.brightnessStepHSV_H === null || node.brightnessStepHSV_H === undefined) node.brightnessStepHSV_H = node.currentHUEDevice.dimming.brightness
|
|
1004
|
+
node.brightnessStepHSV_H = Math.ceil(Number(node.brightnessStepHSV_H))
|
|
1005
|
+
|
|
1006
|
+
const minDimLevelLightHSV_H = 1
|
|
1007
|
+
const maxDimLevelLightHSV_H = 100
|
|
1008
|
+
numStepHSV_H = Math.round((maxDimLevelLightHSV_H - minDimLevelLightHSV_H) / numStepHSV_H, 0)
|
|
1009
|
+
// Set the speed
|
|
1010
|
+
_dimSpeedInMillisecsHSV = Math.ceil(_dimSpeedInMillisecsHSV / (maxDimLevelLightHSV_H / numStepHSV_H))
|
|
1011
|
+
|
|
1012
|
+
if (_KNXbrightness_DirectionHSV_H > 0 && _KNXaction === 1) {
|
|
1013
|
+
// DIM UP
|
|
1014
|
+
if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
|
|
1015
|
+
node.timerStepDimHSV_H = setInterval(() => {
|
|
1016
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
1017
|
+
node.brightnessStepHSV_H += numStepHSV_H
|
|
1018
|
+
if (node.brightnessStepHSV_H > maxDimLevelLightHSV_H) node.brightnessStepHSV_H = maxDimLevelLightHSV_H
|
|
1019
|
+
// node.updateKNXLightHSV_H_State(node.brightnessStepHSV_H); // Unnecessary, but necessary to set the KNX Status in real time.
|
|
1020
|
+
xyBrightnessToHsv.h = node.brightnessStepHSV_H
|
|
1021
|
+
const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
|
|
1022
|
+
const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } } // Add some ms, to prevent the "ladder" step effect.
|
|
1023
|
+
// Switch on the light if off
|
|
1024
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
|
|
1025
|
+
hueTelegram.on = { on: true }
|
|
1026
|
+
}
|
|
1027
|
+
node.currentHUEDevice.color.xy = hsvToXYBri// Unnecessary, but necessary to set the KNX Status in real time.
|
|
1028
|
+
node.updateKNXLightColorState(node.currentHUEDevice.color)
|
|
1029
|
+
node.writeHueState(hueTelegram)
|
|
1030
|
+
if (node.brightnessStepHSV_H >= maxDimLevelLightHSV_H) {
|
|
1031
|
+
if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
|
|
1032
|
+
}
|
|
1033
|
+
}, _dimSpeedInMillisecsHSV)
|
|
1034
|
+
}
|
|
1035
|
+
if (_KNXbrightness_DirectionHSV_H > 0 && _KNXaction === 0) {
|
|
1036
|
+
if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
|
|
1037
|
+
// DIM DOWN
|
|
1038
|
+
if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
|
|
1039
|
+
node.timerStepDimHSV_H = setInterval(() => {
|
|
1040
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
1041
|
+
node.brightnessStepHSV_H -= numStepHSV_H // *2 to speed up the things
|
|
1042
|
+
if (node.brightnessStepHSV_H < minDimLevelLightHSV_H) node.brightnessStepHSV_H = minDimLevelLightHSV_H
|
|
1043
|
+
// node.updateKNXLightHSV_H_State(node.brightnessStepHSV_H); // Unnecessary, but necessary to set the KNX Status in real time.
|
|
1044
|
+
// Produco l'RGB
|
|
1045
|
+
xyBrightnessToHsv.h = node.brightnessStepHSV_H
|
|
1046
|
+
const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
|
|
1047
|
+
const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } }
|
|
1048
|
+
// Switch off the light if on
|
|
1049
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStepHSV_H === 0) {
|
|
1050
|
+
hueTelegram.on = { on: false }
|
|
1051
|
+
}
|
|
1052
|
+
node.currentHUEDevice.color.xy = hsvToXYBri// Unnecessary, but necessary to set the KNX Status in real time.
|
|
1053
|
+
node.updateKNXLightColorState(node.currentHUEDevice.color)
|
|
1054
|
+
node.writeHueState(hueTelegram)
|
|
1055
|
+
if (node.brightnessStepHSV_H <= minDimLevelLightHSV_H) {
|
|
1056
|
+
if (node.timerStepDimHSV_H !== undefined) clearInterval(node.timerStepDimHSV_H)
|
|
1057
|
+
}
|
|
1058
|
+
}, _dimSpeedInMillisecsHSV)
|
|
1059
|
+
}
|
|
1060
|
+
} catch (error) { /* empty */ }
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Starts dimming / stop dimming of the Saturation
|
|
1064
|
+
* @param {number} _KNXaction 1 for start, 0 for stop
|
|
1065
|
+
* @param {number} _KNXbrightness_DirectionHSV_S 1 for up, 0 for down
|
|
1066
|
+
* @param {number} _dimSpeedInMillisecsHSV Speed time in milliseconds
|
|
1067
|
+
* @returns {}
|
|
1068
|
+
*/
|
|
1069
|
+
node.hueDimmingHSV_S = function hueDimmingHSV_S (_KNXaction, _KNXbrightness_DirectionHSV_S, _dimSpeedInMillisecsHSV = undefined) {
|
|
1070
|
+
// After many attempts to use dimming_delta function of the HueApiV2, loosing days of my life, without a decent success, will use the standard dimming calculations
|
|
1071
|
+
// i decide to go to the "step brightness" way.
|
|
1072
|
+
try {
|
|
1073
|
+
if (_KNXbrightness_DirectionHSV_S === 0 && _KNXaction === 0) {
|
|
1074
|
+
// STOP DIM
|
|
1075
|
+
if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
|
|
1076
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
1077
|
+
return
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
let xyBrightnessToHsv
|
|
1081
|
+
if (node.currentHUEDevice.color !== undefined && node.currentHUEDevice.color.xy !== undefined) {
|
|
1082
|
+
// Get the XY + brightness from the lamp and transform it into HSV
|
|
1083
|
+
xyBrightnessToHsv = hueColorConverter.ColorConverter.xyBrightnessToHsv(hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.x, [0, 1], [0, 100]), hueColorConverter.ColorConverter.scale(node.currentHUEDevice.color.xy.y, [0, 1], [0, 100]), node.currentHUEDevice.dimming.brightness)
|
|
1084
|
+
} else {
|
|
1085
|
+
return // Something wrong here.
|
|
1086
|
+
}
|
|
1087
|
+
let numStepHSV_S = 10 // Steps from 0 to 100
|
|
1088
|
+
|
|
1089
|
+
// Set the actual brightness to start with
|
|
1090
|
+
if (node.brightnessStepHSV_S === null || node.brightnessStepHSV_S === undefined) node.brightnessStepHSV_S = node.currentHUEDevice.dimming.brightness
|
|
1091
|
+
node.brightnessStepHSV_S = Math.ceil(Number(node.brightnessStepHSV_S))
|
|
1092
|
+
|
|
1093
|
+
const minDimLevelLightHSV_S = 1
|
|
1094
|
+
const maxDimLevelLightHSV_S = 100
|
|
1095
|
+
numStepHSV_S = Math.round((maxDimLevelLightHSV_S - minDimLevelLightHSV_S) / numStepHSV_S, 0)
|
|
1096
|
+
// Set the speed
|
|
1097
|
+
_dimSpeedInMillisecsHSV = Math.ceil(_dimSpeedInMillisecsHSV / (maxDimLevelLightHSV_S / numStepHSV_S))
|
|
1098
|
+
|
|
1099
|
+
if (_KNXbrightness_DirectionHSV_S > 0 && _KNXaction === 1) {
|
|
1100
|
+
// DIM UP
|
|
1101
|
+
if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
|
|
1102
|
+
node.timerStepDimHSV_S = setInterval(() => {
|
|
1103
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
1104
|
+
node.brightnessStepHSV_S += numStepHSV_S
|
|
1105
|
+
if (node.brightnessStepHSV_S > maxDimLevelLightHSV_S) node.brightnessStepHSV_S = maxDimLevelLightHSV_S
|
|
1106
|
+
// node.updateKNXLightHSV_S_State(node.brightnessStepHSV_S); // Unnecessary, but necessary to set the KNX Status in real time.
|
|
1107
|
+
xyBrightnessToHsv.s = node.brightnessStepHSV_S
|
|
1108
|
+
const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
|
|
1109
|
+
const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } } // Add some ms, to prevent the "ladder" step effect.
|
|
1110
|
+
// Switch on the light if off
|
|
1111
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false) {
|
|
1112
|
+
hueTelegram.on = { on: true }
|
|
1113
|
+
}
|
|
1114
|
+
node.currentHUEDevice.color.xy = cloneDeep(hsvToXYBri)// Unnecessary, but necessary to set the KNX Status in real time.
|
|
1115
|
+
node.updateKNXLightColorState(node.currentHUEDevice.color)
|
|
1116
|
+
node.writeHueState(hueTelegram)
|
|
1117
|
+
if (node.brightnessStepHSV_S >= maxDimLevelLightHSV_S) {
|
|
1118
|
+
if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
|
|
1119
|
+
}
|
|
1120
|
+
}, _dimSpeedInMillisecsHSV)
|
|
1121
|
+
}
|
|
1122
|
+
if (_KNXbrightness_DirectionHSV_S > 0 && _KNXaction === 0) {
|
|
1123
|
+
if (node.currentHUEDevice.on.on === false) return // Don't dim down, if the light is already off.
|
|
1124
|
+
// DIM DOWN
|
|
1125
|
+
if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
|
|
1126
|
+
node.timerStepDimHSV_S = setInterval(() => {
|
|
1127
|
+
node.deleteHueStateQueue() // Clear dimming queue.
|
|
1128
|
+
node.brightnessStepHSV_S -= numStepHSV_S // *2 to speed up the things
|
|
1129
|
+
if (node.brightnessStepHSV_S < minDimLevelLightHSV_S) node.brightnessStepHSV_S = minDimLevelLightHSV_S
|
|
1130
|
+
// node.updateKNXLightHSV_S_State(node.brightnessStepHSV_S); // Unnecessary, but necessary to set the KNX Status in real time.
|
|
1131
|
+
// Produco l'RGB
|
|
1132
|
+
xyBrightnessToHsv.s = node.brightnessStepHSV_S
|
|
1133
|
+
const hsvToXYBri = hueColorConverter.ColorConverter.hsvToxyBrightness(xyBrightnessToHsv, node.currentHUEDevice.color.gamut)
|
|
1134
|
+
const hueTelegram = { color: { xy: { x: hsvToXYBri.x, y: hsvToXYBri.y } }, dynamics: { duration: _dimSpeedInMillisecsHSV + 500 } }
|
|
1135
|
+
// Switch off the light if on
|
|
1136
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true && node.brightnessStepHSV_S === 0) {
|
|
1137
|
+
hueTelegram.on = { on: false }
|
|
1138
|
+
}
|
|
1139
|
+
node.currentHUEDevice.color.xy = cloneDeep(hsvToXYBri)// Unnecessary, but necessary to set the KNX Status in real time.
|
|
1140
|
+
node.updateKNXLightColorState(node.currentHUEDevice.color)
|
|
1141
|
+
node.writeHueState(hueTelegram)
|
|
1142
|
+
if (node.brightnessStepHSV_S <= minDimLevelLightHSV_S) {
|
|
1143
|
+
if (node.timerStepDimHSV_S !== undefined) clearInterval(node.timerStepDimHSV_S)
|
|
1144
|
+
}
|
|
1145
|
+
}, _dimSpeedInMillisecsHSV)
|
|
1146
|
+
}
|
|
1147
|
+
} catch (error) { /* empty */ }
|
|
1148
|
+
}
|
|
1149
|
+
// ***********************************************************
|
|
1150
|
+
|
|
1151
|
+
node.handleSendHUE = async (_event) => {
|
|
1152
|
+
if (_event === undefined) return
|
|
1153
|
+
if (_event.type !== 'grouped_light' && _event.type !== 'light') return
|
|
1154
|
+
|
|
1155
|
+
// !!!! >>> if the node is a grouped_light, the only values required, thus present, by HUE apis are "on" and "dimming".
|
|
1156
|
+
// !!!! >>> For all others properties like for example color and tunable white, i must get the data from one of the child lights.
|
|
1157
|
+
|
|
1158
|
+
// (async () => {
|
|
1159
|
+
try {
|
|
1160
|
+
if (node.currentHUEDevice === undefined || node.serverHue === null || node.serverHue === undefined) {
|
|
1161
|
+
node.setNodeStatusHue({
|
|
1162
|
+
fill: 'red',
|
|
1163
|
+
shape: 'ring',
|
|
1164
|
+
text: "Rejected HUE light settings. I'm still not ready...",
|
|
1165
|
+
payload: ''
|
|
1166
|
+
})
|
|
1167
|
+
return
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const receivedHUEObject = cloneDeep(_event)
|
|
1171
|
+
|
|
1172
|
+
// #region "CALCULATE COLOR AND COLOR TEMPERATURE OF THE LIGHTS BELONGING TO THE GROUPED_LIGHT"
|
|
1173
|
+
// Check wether the incoming _event belongs to a light belonging to the group_light
|
|
1174
|
+
if (node.isGrouped_light === true && receivedHUEObject.type === 'light') {
|
|
1175
|
+
// Handling of not by HUE handled Color Temperature and ColorXY
|
|
1176
|
+
let modifiedLight
|
|
1177
|
+
const groupChilds = []
|
|
1178
|
+
let AverageColorsXYBrightnessAndTemperature // Average color xy and color temp if the node is a grouped light.
|
|
1179
|
+
// If the current node is a grouped lights, i must check wether the receivedHUEObject (containing a light) belongs to the node lights collection.
|
|
1180
|
+
// Find all the lights in the collection, having the color_temperature capabilities, belonging to the group.
|
|
1181
|
+
const devices = await node.serverHue.getAllLightsBelongingToTheGroup(node.hueDevice, false)
|
|
1182
|
+
if (devices.length === 0) {
|
|
1183
|
+
devices.push(receivedHUEObject)
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
for (let index = 0; index < devices.length; index++) {
|
|
1187
|
+
const element = devices[index]
|
|
1188
|
+
if (receivedHUEObject.id === element.id) {
|
|
1189
|
+
modifiedLight = element
|
|
1190
|
+
// The dimming is not necessary, beacause the HUE API already sends a group_light event with the average brightness //if (receivedHUEObject.dimming !== undefined) modifiedLight.dimming = { brightness: receivedHUEObject.dimming.brightness };
|
|
1191
|
+
if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined) modifiedLight.color = receivedHUEObject.color
|
|
1192
|
+
if (receivedHUEObject.color_temperature !== undefined) modifiedLight.color_temperature = receivedHUEObject.color_temperature
|
|
1193
|
+
groupChilds.push(modifiedLight)
|
|
1194
|
+
} else {
|
|
1195
|
+
// Simply append the light
|
|
1196
|
+
if ((element.color_temperature !== undefined && element.color_temperature.mirek !== undefined) ||
|
|
1197
|
+
(element.color !== undefined && element.color.xy !== undefined)) {
|
|
1198
|
+
groupChilds.push(element)
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Use the arithmetic average of color xy, brightness and color temperature "averageColorXYandKelvinOfGroupedLights"
|
|
1204
|
+
if (groupChilds !== undefined) AverageColorsXYBrightnessAndTemperature = await node.serverHue.getAverageColorsXYBrightnessAndTemperature(groupChilds)
|
|
1205
|
+
|
|
1206
|
+
// Set the new values based on average calculated above
|
|
1207
|
+
// CHECK FIRST THE COLOR_TEMPERATURE, because it can be undefined, because the current selected color
|
|
1208
|
+
// is out of the mirek range, so it cannot be represented with the colore temperature.
|
|
1209
|
+
if (receivedHUEObject.color_temperature !== undefined && AverageColorsXYBrightnessAndTemperature.mirek !== undefined && receivedHUEObject.color_temperature.mirel_valid === true) {
|
|
1210
|
+
receivedHUEObject.color_temperature.mirek = AverageColorsXYBrightnessAndTemperature.mirek
|
|
1211
|
+
node.currentHUEDevice.color_temperature = { mirek: AverageColorsXYBrightnessAndTemperature.mirek }
|
|
1212
|
+
node.updateKNXLightKelvinPercentageState(receivedHUEObject.color_temperature.mirek)
|
|
1213
|
+
node.updateKNXLightKelvinState(receivedHUEObject.color_temperature.mirek)
|
|
1214
|
+
} else if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined && AverageColorsXYBrightnessAndTemperature.x !== undefined) {
|
|
1215
|
+
receivedHUEObject.color.xy.x = AverageColorsXYBrightnessAndTemperature.x
|
|
1216
|
+
receivedHUEObject.color.xy.y = AverageColorsXYBrightnessAndTemperature.y
|
|
1217
|
+
node.currentHUEDevice.color = { xy: { x: AverageColorsXYBrightnessAndTemperature.x, y: AverageColorsXYBrightnessAndTemperature.y } }
|
|
1218
|
+
node.updateKNXLightColorState(receivedHUEObject.color)
|
|
1219
|
+
}
|
|
1220
|
+
// The dimming is not necessary, beacause the HUE API already sends a group_light event with the average brightness
|
|
1221
|
+
// if (receivedHUEObject.dimming !== undefined && AverageColorsXYBrightnessAndTemperature.brightness !== undefined) {
|
|
1222
|
+
// receivedHUEObject.dimming = { brightness: AverageColorsXYBrightnessAndTemperature.brightness };
|
|
1223
|
+
// }
|
|
1224
|
+
// --------------------------------------------------------------------------------------------------
|
|
1225
|
+
if (modifiedLight !== undefined && modifiedLight.metadata !== undefined && modifiedLight.metadata.name !== undefined) {
|
|
1226
|
+
receivedHUEObject.lightName = modifiedLight.metadata.name
|
|
1227
|
+
}
|
|
1228
|
+
node.send(receivedHUEObject)
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
// #endregion
|
|
1232
|
+
|
|
1233
|
+
if (receivedHUEObject.id === node.hueDevice) {
|
|
1234
|
+
// Output the msg to the flow
|
|
1235
|
+
node.send(receivedHUEObject)
|
|
1236
|
+
|
|
1237
|
+
// // DEBUG testing enable/disable HTML UI Tabs
|
|
1238
|
+
// delete _event.dimming;
|
|
1239
|
+
// delete _event.color;
|
|
1240
|
+
// delete _event.color_temperature;
|
|
1241
|
+
// delete _event.color_temperature_delta;
|
|
1242
|
+
|
|
1243
|
+
if (receivedHUEObject.on !== undefined) {
|
|
1244
|
+
node.updateKNXLightState(receivedHUEObject.on.on)
|
|
1245
|
+
// In case of switch off, set the dim to zero
|
|
1246
|
+
if (receivedHUEObject.on.on === false && (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')) {
|
|
1247
|
+
node.updateKNXBrightnessState(0)
|
|
1248
|
+
if (receivedHUEObject.dimming !== undefined) delete receivedHUEObject.dimming // Remove event.dimming, because has beem handled by this function and i don't want the function below to take care of it.
|
|
1249
|
+
} else if (receivedHUEObject.on.on === true && node.currentHUEDevice.on.on === false) {
|
|
1250
|
+
// Turn on: update the dimming KNX Status value as well, but only if we have a reliable value.
|
|
1251
|
+
// For grouped_light, the bridge exposes an aggregated brightness value via /resource/grouped_light/{id}.
|
|
1252
|
+
let brightVal
|
|
1253
|
+
if (receivedHUEObject.dimming !== undefined && receivedHUEObject.dimming.brightness !== undefined) {
|
|
1254
|
+
brightVal = receivedHUEObject.dimming.brightness
|
|
1255
|
+
} else if (node.isGrouped_light === true && typeof node.serverHue?.getHueResourceSnapshot === 'function' && config.GALightBrightnessState) {
|
|
1256
|
+
try {
|
|
1257
|
+
const snapshot = await node.serverHue.getHueResourceSnapshot(node.hueDevice, { forceRefresh: true })
|
|
1258
|
+
if (snapshot?.dimming?.brightness !== undefined) brightVal = snapshot.dimming.brightness
|
|
1259
|
+
} catch (error) { /* empty */ }
|
|
1260
|
+
} else if (node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness !== undefined) {
|
|
1261
|
+
brightVal = node.currentHUEDevice.dimming.brightness
|
|
1262
|
+
}
|
|
1263
|
+
if (brightVal !== undefined) node.updateKNXBrightnessState(brightVal)
|
|
1264
|
+
}
|
|
1265
|
+
node.currentHUEDevice.on.on = receivedHUEObject.on.on
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (receivedHUEObject.color !== undefined && receivedHUEObject.color.xy !== undefined) { // fixed https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/287
|
|
1269
|
+
node.updateKNXLightColorState(receivedHUEObject.color)
|
|
1270
|
+
node.currentHUEDevice.color = receivedHUEObject.color
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if (receivedHUEObject.dimming !== undefined && receivedHUEObject.dimming.brightness !== undefined) {
|
|
1274
|
+
const zeroBrightnessWhenOff = (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')
|
|
1275
|
+
// Once upon n a time, the light transmit the brightness value of 0.39.
|
|
1276
|
+
// To avoid wrongly turn light state on, exit
|
|
1277
|
+
if (receivedHUEObject.dimming.brightness < 1) receivedHUEObject.dimming.brightness = 0
|
|
1278
|
+
// If the light is OFF and the user wants KNX brightness status to stay 0 while OFF,
|
|
1279
|
+
// don't propagate Hue's cached brightness (usually the last/boot brightness) back to KNX.
|
|
1280
|
+
if (zeroBrightnessWhenOff === true && node.currentHUEDevice?.on?.on === false && (receivedHUEObject.on === undefined || receivedHUEObject.on.on === false)) {
|
|
1281
|
+
if (node.currentHUEDevice.dimming !== undefined) node.currentHUEDevice.dimming.brightness = receivedHUEObject.dimming.brightness
|
|
1282
|
+
} else if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && receivedHUEObject.dimming.brightness === 0) {
|
|
1283
|
+
// Do nothing, because the light is off and the dimming also is 0
|
|
1284
|
+
} else {
|
|
1285
|
+
if (node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === false && (receivedHUEObject.on === undefined || (receivedHUEObject.on !== undefined && receivedHUEObject.on.on === true))) node.updateKNXLightState(receivedHUEObject.dimming.brightness > 0)
|
|
1286
|
+
node.updateKNXBrightnessState(receivedHUEObject.dimming.brightness)
|
|
1287
|
+
// If the brightness reaches zero, the hue lamp "on" property must be set to zero as well
|
|
1288
|
+
if (receivedHUEObject.dimming.brightness === 0 && node.currentHUEDevice.on !== undefined && node.currentHUEDevice.on.on === true) {
|
|
1289
|
+
node.writeHueState({ on: { on: false } })
|
|
1290
|
+
node.currentHUEDevice.on.on = false
|
|
1291
|
+
}
|
|
1292
|
+
node.currentHUEDevice.dimming.brightness = receivedHUEObject.dimming.brightness
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (receivedHUEObject.color_temperature !== undefined && receivedHUEObject.color_temperature.mirek !== undefined) {
|
|
1297
|
+
node.updateKNXLightKelvinPercentageState(receivedHUEObject.color_temperature.mirek)
|
|
1298
|
+
node.updateKNXLightKelvinState(receivedHUEObject.color_temperature.mirek)
|
|
1299
|
+
node.currentHUEDevice.color_temperature.mirek = receivedHUEObject.color_temperature.mirek
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
node.status({
|
|
1304
|
+
fill: 'red',
|
|
1305
|
+
shape: 'dot',
|
|
1306
|
+
text: `Matter->KNX error ${node.id} ${error.message}. Seee Log`
|
|
1307
|
+
})
|
|
1308
|
+
RED.log.error(`knxUltimateMatterControllerDevice: node.handleSendHUE = (_event): ${error.stack}`)
|
|
1309
|
+
}
|
|
1310
|
+
// })();
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Leave the name after "function", to avoid <anonymous function> in the stack trace, in caso of errors.
|
|
1314
|
+
// ---- Matter -> KNX feedback (replaces the Hue event stream) -----------------
|
|
1315
|
+
// Applies a feedback patch from the shim: updates the synthetic currentHUEDevice
|
|
1316
|
+
// and reuses the very same updateKNX* writers of the Hue-born logic below.
|
|
1317
|
+
node.applyMatterFeedback = function applyMatterFeedback (patch, _outputtype = 'write') {
|
|
1318
|
+
if (!patch || node.currentHUEDevice === undefined) return
|
|
1319
|
+
const dev = node.currentHUEDevice
|
|
1320
|
+
const zeroBrightnessWhenOff = (config.updateKNXBrightnessStatusOnHUEOnOff === undefined || config.updateKNXBrightnessStatusOnHUEOnOff === 'onhueoff')
|
|
1321
|
+
if (patch.on !== undefined) {
|
|
1322
|
+
dev.on.on = !!patch.on
|
|
1323
|
+
node.updateKNXLightState(dev.on.on, _outputtype)
|
|
1324
|
+
if (zeroBrightnessWhenOff) node.updateKNXBrightnessState(dev.on.on ? dev.dimming.brightness : 0, _outputtype)
|
|
1325
|
+
}
|
|
1326
|
+
if (patch.brightnessPct !== undefined) {
|
|
1327
|
+
dev.dimming.brightness = Number(patch.brightnessPct)
|
|
1328
|
+
if (dev.on.on === true || !zeroBrightnessWhenOff) node.updateKNXBrightnessState(dev.dimming.brightness, _outputtype)
|
|
1329
|
+
}
|
|
1330
|
+
if (patch.mirek !== undefined) {
|
|
1331
|
+
dev.color_temperature.mirek = patch.mirek
|
|
1332
|
+
node.updateKNXLightKelvinState(patch.mirek, _outputtype)
|
|
1333
|
+
node.updateKNXLightKelvinPercentageState(patch.mirek, _outputtype)
|
|
1334
|
+
}
|
|
1335
|
+
if (patch.xyX !== undefined) dev.color.xy.x = patch.xyX
|
|
1336
|
+
if (patch.xyY !== undefined) dev.color.xy.y = patch.xyY
|
|
1337
|
+
if (patch.xyX !== undefined || patch.xyY !== undefined) node.updateKNXLightColorState({ xy: { x: dev.color.xy.x, y: dev.color.xy.y } }, _outputtype)
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Called by matter-config on every attribute report of the subscription.
|
|
1341
|
+
node.handleSendMatter = (_event) => {
|
|
1342
|
+
try {
|
|
1343
|
+
if (_event === undefined || _event.attributeName === undefined) return
|
|
1344
|
+
if (String(_event.nodeId) !== String(node.matterNodeId)) return
|
|
1345
|
+
if (Number(_event.endpointId) !== Number(node.matterEndpointId)) return
|
|
1346
|
+
const patch = matterEventToHuePatch(_event)
|
|
1347
|
+
if (patch) node.applyMatterFeedback(patch)
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
RED.log.error(`knxUltimateMatterControllerDevice: node.handleSendMatter: ${error.message}`)
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
node.handleMatterClusterEvent = () => { }
|
|
1353
|
+
node.handleMatterNodeInitialized = () => { }
|
|
1354
|
+
|
|
1355
|
+
node.updateKNXBrightnessState = function updateKNXBrightnessState (_value, _outputtype = 'write') {
|
|
1356
|
+
if (config.GALightBrightnessState !== undefined && config.GALightBrightnessState !== '') {
|
|
1357
|
+
const knxMsgPayload = {}
|
|
1358
|
+
knxMsgPayload.topic = config.GALightBrightnessState
|
|
1359
|
+
knxMsgPayload.dpt = config.dptLightBrightnessState
|
|
1360
|
+
knxMsgPayload.payload = _value
|
|
1361
|
+
// Send to KNX bus
|
|
1362
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1363
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1364
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1365
|
+
grpaddr: knxMsgPayload.topic,
|
|
1366
|
+
payload: knxMsgPayload.payload,
|
|
1367
|
+
dpt: knxMsgPayload.dpt,
|
|
1368
|
+
outputtype: _outputtype,
|
|
1369
|
+
nodecallerid: node.id
|
|
1370
|
+
})
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
node.setNodeStatusHue({
|
|
1374
|
+
fill: 'blue',
|
|
1375
|
+
shape: 'ring',
|
|
1376
|
+
text: 'Matter->KNX Bright',
|
|
1377
|
+
payload: knxMsgPayload.payload
|
|
1378
|
+
})
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
node.updateKNXLightState = function updateKNXLightState (_value, _outputtype = 'write') {
|
|
1383
|
+
try {
|
|
1384
|
+
const knxMsgPayload = {}
|
|
1385
|
+
knxMsgPayload.topic = config.GALightState
|
|
1386
|
+
knxMsgPayload.dpt = config.dptLightState
|
|
1387
|
+
knxMsgPayload.payload = _value
|
|
1388
|
+
if (config.GALightState !== undefined && config.GALightState !== '') {
|
|
1389
|
+
// Check not to have already sent the value
|
|
1390
|
+
// Send to KNX bus
|
|
1391
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1392
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1393
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1394
|
+
grpaddr: knxMsgPayload.topic,
|
|
1395
|
+
payload: knxMsgPayload.payload,
|
|
1396
|
+
dpt: knxMsgPayload.dpt,
|
|
1397
|
+
outputtype: _outputtype,
|
|
1398
|
+
nodecallerid: node.id
|
|
1399
|
+
})
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
node.setNodeStatusHue({
|
|
1403
|
+
fill: 'blue',
|
|
1404
|
+
shape: 'ring',
|
|
1405
|
+
text: 'Matter->KNX On/Off',
|
|
1406
|
+
payload: knxMsgPayload.payload
|
|
1407
|
+
})
|
|
1408
|
+
}
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
/* empty */
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
node.updateKNXLightKelvinPercentageState = function updateKNXLightKelvinPercentageState (_value, _outputtype = 'write') {
|
|
1415
|
+
if (config.GALightKelvinPercentageState !== undefined && config.GALightKelvinPercentageState !== '') {
|
|
1416
|
+
const knxMsgPayload = {}
|
|
1417
|
+
knxMsgPayload.topic = config.GALightKelvinPercentageState
|
|
1418
|
+
knxMsgPayload.dpt = config.dptLightKelvinPercentageState
|
|
1419
|
+
if (config.dptLightKelvinPercentageState === '5.001') {
|
|
1420
|
+
const retPercent = hueColorConverter.ColorConverter.scale(_value, [153, 500], [0, 100])
|
|
1421
|
+
knxMsgPayload.payload = 100 - retPercent
|
|
1422
|
+
}
|
|
1423
|
+
// Send to KNX bus
|
|
1424
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1425
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1426
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1427
|
+
grpaddr: knxMsgPayload.topic,
|
|
1428
|
+
payload: knxMsgPayload.payload,
|
|
1429
|
+
dpt: knxMsgPayload.dpt,
|
|
1430
|
+
outputtype: _outputtype,
|
|
1431
|
+
nodecallerid: node.id
|
|
1432
|
+
})
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
node.setNodeStatusHue({
|
|
1436
|
+
fill: 'blue',
|
|
1437
|
+
shape: 'ring',
|
|
1438
|
+
text: 'Matter->KNX Tunable White',
|
|
1439
|
+
payload: knxMsgPayload.payload
|
|
1440
|
+
})
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// /**
|
|
1445
|
+
// * Update the internal HSV variable status and send the valuge in % to the KNX BUS
|
|
1446
|
+
// * @param {number} _value 0-100%
|
|
1447
|
+
// * @param {string} _outputtype "write" is the default KNX command
|
|
1448
|
+
// * * @returns {}
|
|
1449
|
+
// */
|
|
1450
|
+
// node.updateKNXLightHSV_H_State = function updateKNXLightHSV_H_State(_value, _outputtype = "write") {
|
|
1451
|
+
// if (config.GALightHSV_H_State !== undefined && config.GALightHSV_H_State !== "") {
|
|
1452
|
+
// const knxMsgPayload = {};
|
|
1453
|
+
// knxMsgPayload.topic = config.GALightHSV_H_State;
|
|
1454
|
+
// knxMsgPayload.dpt = config.dptLightHSV_H_State;
|
|
1455
|
+
// if (config.dptLightHSV_H_State === "5.001") {
|
|
1456
|
+
// knxMsgPayload.payload = 100 - _value;
|
|
1457
|
+
// }
|
|
1458
|
+
// // Send to KNX bus
|
|
1459
|
+
// if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined) {
|
|
1460
|
+
// if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1461
|
+
// node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1462
|
+
// grpaddr: knxMsgPayload.topic,
|
|
1463
|
+
// payload: knxMsgPayload.payload,
|
|
1464
|
+
// dpt: knxMsgPayload.dpt,
|
|
1465
|
+
// outputtype: _outputtype || "write",
|
|
1466
|
+
// nodecallerid: node.id,
|
|
1467
|
+
// });
|
|
1468
|
+
// }
|
|
1469
|
+
// }
|
|
1470
|
+
// node.setNodeStatusHue({
|
|
1471
|
+
// fill: "blue",
|
|
1472
|
+
// shape: "ring",
|
|
1473
|
+
// text: "Matter->KNX HSV (H)",
|
|
1474
|
+
// payload: knxMsgPayload.payload,
|
|
1475
|
+
// });
|
|
1476
|
+
// }
|
|
1477
|
+
// };
|
|
1478
|
+
|
|
1479
|
+
// /**
|
|
1480
|
+
// * Update the internal HSV variable status and send the valuge in % to the KNX BUS
|
|
1481
|
+
// * @param {number} _value 0-100%
|
|
1482
|
+
// * @param {string} _outputtype "write" is the default KNX command
|
|
1483
|
+
// * @returns {}
|
|
1484
|
+
// */
|
|
1485
|
+
// node.updateKNXLightHSV_S_State = function updateKNXLightHSV_S_State(_value, _outputtype = "write") {
|
|
1486
|
+
// if (config.GALightHSV_S_State !== undefined && config.GALightHSV_S_State !== "") {
|
|
1487
|
+
// const knxMsgPayload = {};
|
|
1488
|
+
// knxMsgPayload.topic = config.GALightHSV_S_State;
|
|
1489
|
+
// knxMsgPayload.dpt = config.dptLightHSV_S_State;
|
|
1490
|
+
// if (config.dptLightHSV_S_State === "5.001") {
|
|
1491
|
+
// knxMsgPayload.payload = _value;
|
|
1492
|
+
// }
|
|
1493
|
+
// // Send to KNX bus
|
|
1494
|
+
// if (knxMsgPayload.topic !== "" && knxMsgPayload.topic !== undefined) {
|
|
1495
|
+
// if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1496
|
+
// node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1497
|
+
// grpaddr: knxMsgPayload.topic,
|
|
1498
|
+
// payload: knxMsgPayload.payload,
|
|
1499
|
+
// dpt: knxMsgPayload.dpt,
|
|
1500
|
+
// outputtype: _outputtype || "write",
|
|
1501
|
+
// nodecallerid: node.id,
|
|
1502
|
+
// });
|
|
1503
|
+
// }
|
|
1504
|
+
// }
|
|
1505
|
+
// node.setNodeStatusHue({
|
|
1506
|
+
// fill: "blue",
|
|
1507
|
+
// shape: "ring",
|
|
1508
|
+
// text: "Matter->KNX HSV (H)",
|
|
1509
|
+
// payload: knxMsgPayload.payload,
|
|
1510
|
+
// });
|
|
1511
|
+
// }
|
|
1512
|
+
// };
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Update the KNC colors and HSV states group addresses
|
|
1516
|
+
* @param {object} _value {xy:{x,y}} in 0-1 scale
|
|
1517
|
+
* @param {string} _outputtype "write" is the default KNX command
|
|
1518
|
+
* @returns {}
|
|
1519
|
+
*/
|
|
1520
|
+
node.updateKNXLightColorState = function updateKNXLightColorState (_value, _outputtype = 'write') {
|
|
1521
|
+
if (config.GALightColorState !== undefined && config.GALightColorState !== '') {
|
|
1522
|
+
if (_value.xy === undefined || _value.xy.x === undefined) return
|
|
1523
|
+
const knxMsgPayload = {}
|
|
1524
|
+
knxMsgPayload.topic = config.GALightColorState
|
|
1525
|
+
knxMsgPayload.dpt = config.dptLightColorState
|
|
1526
|
+
try {
|
|
1527
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBriToRgb(
|
|
1528
|
+
_value.xy.x,
|
|
1529
|
+
_value.xy.y,
|
|
1530
|
+
node.currentHUEDevice !== undefined && node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness === undefined ? node.currentHUEDevice.dimming.brightness : 100
|
|
1531
|
+
)
|
|
1532
|
+
knxMsgPayload.payload = { red: knxMsgPayload.payload.r, green: knxMsgPayload.payload.g, blue: knxMsgPayload.payload.b }
|
|
1533
|
+
// Send to KNX bus
|
|
1534
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1535
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1536
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1537
|
+
grpaddr: knxMsgPayload.topic,
|
|
1538
|
+
payload: knxMsgPayload.payload,
|
|
1539
|
+
dpt: knxMsgPayload.dpt,
|
|
1540
|
+
outputtype: _outputtype,
|
|
1541
|
+
nodecallerid: node.id
|
|
1542
|
+
})
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
node.setNodeStatusHue({
|
|
1546
|
+
fill: 'blue',
|
|
1547
|
+
shape: 'ring',
|
|
1548
|
+
text: 'Matter->KNX Color',
|
|
1549
|
+
payload: knxMsgPayload.payload
|
|
1550
|
+
})
|
|
1551
|
+
} catch (error) { }
|
|
1552
|
+
}
|
|
1553
|
+
if (config.GALightHSV_S_State !== undefined && config.GALightHSV_S_State !== '') {
|
|
1554
|
+
const knxMsgPayload = {}
|
|
1555
|
+
knxMsgPayload.topic = config.GALightHSV_S_State
|
|
1556
|
+
knxMsgPayload.dpt = config.dptLightHSV_S_State
|
|
1557
|
+
if (config.dptLightHSV_S_State === '5.001') {
|
|
1558
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBrightnessToHsv(_value.xy.x, _value.xy.y, node.currentHUEDevice !== undefined && node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness === undefined ? node.currentHUEDevice.dimming.brightness : 100, false)
|
|
1559
|
+
knxMsgPayload.payload = knxMsgPayload.payload.s
|
|
1560
|
+
}
|
|
1561
|
+
// Send to KNX bus
|
|
1562
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1563
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1564
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1565
|
+
grpaddr: knxMsgPayload.topic,
|
|
1566
|
+
payload: knxMsgPayload.payload,
|
|
1567
|
+
dpt: knxMsgPayload.dpt,
|
|
1568
|
+
outputtype: _outputtype || 'write',
|
|
1569
|
+
nodecallerid: node.id
|
|
1570
|
+
})
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
node.setNodeStatusHue({
|
|
1574
|
+
fill: 'blue',
|
|
1575
|
+
shape: 'ring',
|
|
1576
|
+
text: 'Matter->KNX HSV (S)',
|
|
1577
|
+
payload: knxMsgPayload.payload
|
|
1578
|
+
})
|
|
1579
|
+
}
|
|
1580
|
+
if (config.GALightHSV_H_State !== undefined && config.GALightHSV_H_State !== '') {
|
|
1581
|
+
const knxMsgPayload = {}
|
|
1582
|
+
knxMsgPayload.topic = config.GALightHSV_H_State
|
|
1583
|
+
knxMsgPayload.dpt = config.dptLightHSV_H_State
|
|
1584
|
+
if (config.dptLightHSV_H_State === '5.001') {
|
|
1585
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.xyBrightnessToHsv(_value.xy.x, _value.xy.y, node.currentHUEDevice !== undefined && node.currentHUEDevice.dimming !== undefined && node.currentHUEDevice.dimming.brightness === undefined ? node.currentHUEDevice.dimming.brightness : 100, false)
|
|
1586
|
+
knxMsgPayload.payload = 100 - knxMsgPayload.payload.h
|
|
1587
|
+
}
|
|
1588
|
+
// Send to KNX bus
|
|
1589
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1590
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1591
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1592
|
+
grpaddr: knxMsgPayload.topic,
|
|
1593
|
+
payload: knxMsgPayload.payload,
|
|
1594
|
+
dpt: knxMsgPayload.dpt,
|
|
1595
|
+
outputtype: _outputtype || 'write',
|
|
1596
|
+
nodecallerid: node.id
|
|
1597
|
+
})
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
node.setNodeStatusHue({
|
|
1601
|
+
fill: 'blue',
|
|
1602
|
+
shape: 'ring',
|
|
1603
|
+
text: 'Matter->KNX HSV (H)',
|
|
1604
|
+
payload: knxMsgPayload.payload
|
|
1605
|
+
})
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
node.updateKNXLightKelvinState = function updateKNXLightKelvinState (_value, _outputtype = 'write') {
|
|
1610
|
+
if (config.GALightKelvinState !== undefined && config.GALightKelvinState !== '') {
|
|
1611
|
+
const knxMsgPayload = {}
|
|
1612
|
+
const kelvinValue = 0
|
|
1613
|
+
knxMsgPayload.topic = config.GALightKelvinState
|
|
1614
|
+
knxMsgPayload.dpt = config.dptLightKelvinState
|
|
1615
|
+
if (config.dptLightKelvinState === '7.600') {
|
|
1616
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value)
|
|
1617
|
+
// knxMsgPayload.payload = hueColorConverter.ColorConverter.scale(kelvinValue, [2000, 6535], [0, 65535]);
|
|
1618
|
+
} else if (config.dptLightKelvinState === '9.002') {
|
|
1619
|
+
knxMsgPayload.payload = hueColorConverter.ColorConverter.mirekToKelvin(_value)
|
|
1620
|
+
}
|
|
1621
|
+
// Send to KNX bus
|
|
1622
|
+
if (knxMsgPayload.topic !== '' && knxMsgPayload.topic !== undefined) {
|
|
1623
|
+
if (node.serverKNX !== null && node.serverKNX !== undefined) {
|
|
1624
|
+
node.serverKNX.sendKNXTelegramToKNXEngine({
|
|
1625
|
+
grpaddr: knxMsgPayload.topic,
|
|
1626
|
+
payload: knxMsgPayload.payload,
|
|
1627
|
+
dpt: knxMsgPayload.dpt,
|
|
1628
|
+
outputtype: _outputtype,
|
|
1629
|
+
nodecallerid: node.id
|
|
1630
|
+
})
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
node.setNodeStatusHue({
|
|
1634
|
+
fill: 'blue',
|
|
1635
|
+
shape: 'ring',
|
|
1636
|
+
text: 'Matter->KNX Kelvin',
|
|
1637
|
+
payload: knxMsgPayload.payload
|
|
1638
|
+
})
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// On each deploy, unsubscribe+resubscribe
|
|
1644
|
+
if (node.serverKNX) {
|
|
1645
|
+
node.serverKNX.removeClient(node)
|
|
1646
|
+
node.serverKNX.addClient(node)
|
|
1647
|
+
} else {
|
|
1648
|
+
node.setNodeStatus({
|
|
1649
|
+
fill: 'yellow',
|
|
1650
|
+
shape: 'ring',
|
|
1651
|
+
text: 'No KNX gateway selected',
|
|
1652
|
+
payload: ''
|
|
1653
|
+
})
|
|
1654
|
+
}
|
|
1655
|
+
if (node.serverHue) {
|
|
1656
|
+
try {
|
|
1657
|
+
node.serverHue.removeClient(node)
|
|
1658
|
+
node.serverHue.addClient(node)
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
RED.log.error('knxUltimateMatterControllerDevice: if (node.serverKNX): ' + error.message)
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
node.on('input', (msg, send, done) => {
|
|
1665
|
+
try {
|
|
1666
|
+
const state = RED.util.cloneMessage(msg)
|
|
1667
|
+
node.writeHueState(state)
|
|
1668
|
+
node.setNodeStatusHue({
|
|
1669
|
+
fill: 'green',
|
|
1670
|
+
shape: 'dot',
|
|
1671
|
+
text: '->Matter',
|
|
1672
|
+
payload: 'Flow msg.'
|
|
1673
|
+
})
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
node.setNodeStatusHue({
|
|
1676
|
+
fill: 'red',
|
|
1677
|
+
shape: 'dot',
|
|
1678
|
+
text: '->Matter',
|
|
1679
|
+
payload: error.message
|
|
1680
|
+
})
|
|
1681
|
+
}
|
|
1682
|
+
// Once finished, call 'done'.
|
|
1683
|
+
// This call is wrapped in a check that 'done' exists
|
|
1684
|
+
// so the node will work in earlier versions of Node-RED (<1.0)
|
|
1685
|
+
if (done) {
|
|
1686
|
+
done()
|
|
1687
|
+
}
|
|
1688
|
+
})
|
|
1689
|
+
|
|
1690
|
+
node.on('close', (done) => {
|
|
1691
|
+
if (node.serverKNX) {
|
|
1692
|
+
node.serverKNX.removeClient(node)
|
|
1693
|
+
}
|
|
1694
|
+
if (node.serverHue) {
|
|
1695
|
+
node.serverHue.removeClient(node)
|
|
1696
|
+
}
|
|
1697
|
+
done()
|
|
1698
|
+
})
|
|
1699
|
+
}
|
|
1700
|
+
RED.nodes.registerType('knxUltimateMatterControllerDevice', knxUltimateMatterControllerDevice)
|
|
1701
|
+
}
|