iobroker.iot 5.0.13 → 6.0.3
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/README.md +9 -14
- package/admin/assets/{index-DCr4hGKK.js → index-BdqghSg7.js} +40 -40
- package/admin/index_m.html +1 -1
- package/admin/rules/@mf-types/compiled-types/ActionVisu.d.ts +1 -1
- package/admin/rules/@mf-types.zip +0 -0
- package/admin/rules/assets/{ActionVisu-BN0Kjfo9.js → ActionVisu-BCZFwf_Y.js} +1 -1
- package/admin/rules/assets/{ActionVisu__loadShare__react__loadShare__.js-aa1tDx2C.js → ActionVisu__loadShare__react__loadShare__.js-B4USkKO5.js} +1 -1
- package/admin/rules/assets/{ActionVisu__loadShare__react__loadShare__.js_commonjs-proxy-DX6cx281.js → ActionVisu__loadShare__react__loadShare__.js_commonjs-proxy-CEUqE4IA.js} +1 -1
- package/admin/rules/assets/{ActionVisu__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-27bLZRGy.js → ActionVisu__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-MphP5XTI.js} +1 -1
- package/admin/rules/assets/{bootstrap-QjsDSZNA.js → bootstrap-BKoQjf4I.js} +1 -1
- package/admin/rules/assets/{index-DiSsXjpZ.js → index-BlSAfGU-.js} +4 -4
- package/admin/rules/assets/index-Dk5YUp3j.js +1187 -0
- package/admin/rules/assets/{index-C1ZEp-2N.js → index-pMP6L5OL.js} +2 -2
- package/admin/rules/assets/{jsx-runtime-CnsYFZsa.js → jsx-runtime-D-zp5RaI.js} +1 -1
- package/admin/rules/assets/{localSharedImportMap-8wECk2hR.js → localSharedImportMap-BsAeuDnA.js} +1 -1
- package/admin/rules/assets/{virtualExposes-b6CxLzZb.js → virtualExposes-B2aPQUg7.js} +1 -1
- package/admin/rules/customRuleBlocks.js +5 -5
- package/build/lib/AlexaSmartHomeV3/Alexa/Directives/Discovery.js +11 -1
- package/build/lib/AlexaSmartHomeV3/Alexa/Directives/Discovery.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Alexa/Properties/TargetSetpoint.js +8 -1
- package/build/lib/AlexaSmartHomeV3/Alexa/Properties/TargetSetpoint.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Alexa/Properties/Temperature.js +8 -1
- package/build/lib/AlexaSmartHomeV3/Alexa/Properties/Temperature.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/AirCondition.js +2 -2
- package/build/lib/AlexaSmartHomeV3/Controls/AirCondition.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Control.js +7 -0
- package/build/lib/AlexaSmartHomeV3/Controls/Control.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Ct.js +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Ct.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Rgb.js +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Rgb.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/RgbSingle.js +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/RgbSingle.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/RgbwSingle.js +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/RgbwSingle.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Thermostat.js +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Thermostat.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Controls/Volume.js +2 -2
- package/build/lib/AlexaSmartHomeV3/Controls/Volume.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/DeviceManager.js +18 -9
- package/build/lib/AlexaSmartHomeV3/DeviceManager.js.map +1 -1
- package/build/lib/AlexaSmartHomeV3/Helpers/DiscoveryValidator.js +283 -0
- package/build/lib/AlexaSmartHomeV3/Helpers/DiscoveryValidator.js.map +1 -0
- package/build/lib/AlexaSmartHomeV3/Helpers/Utils.js +4 -0
- package/build/lib/AlexaSmartHomeV3/Helpers/Utils.js.map +1 -1
- package/build/lib/alisa.js +15 -14
- package/build/lib/googleHome.js +151 -207
- package/build/lib/remote.js +1 -1
- package/build/lib/remote.js.map +1 -1
- package/io-package.json +27 -27
- package/package.json +5 -5
- package/admin/rules/assets/index-FrXvyqvW.js +0 -1187
- package/admin/rules/mf-stats.json +0 -1
- /package/admin/rules/assets/{index-Ccpql4vu.js → index-BPY9gF0q.js} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateDiscoveryResponse = validateDiscoveryResponse;
|
|
4
|
+
// Alexa limits: https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html
|
|
5
|
+
const MAX_ENDPOINTS = 300;
|
|
6
|
+
const MAX_ENDPOINT_ID_LENGTH = 256;
|
|
7
|
+
const MAX_FRIENDLY_NAME_LENGTH = 128;
|
|
8
|
+
const MAX_DESCRIPTION_LENGTH = 128;
|
|
9
|
+
const MAX_MANUFACTURER_NAME_LENGTH = 128;
|
|
10
|
+
const MAX_CAPABILITIES_PER_ENDPOINT = 100;
|
|
11
|
+
// AWS IoT MQTT message size limit (128 KB minus overhead)
|
|
12
|
+
const MAX_RESPONSE_SIZE_BYTES = 127 * 1024;
|
|
13
|
+
const RESPONSE_SIZE_WARNING_BYTES = 100 * 1024;
|
|
14
|
+
// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html#display-categories
|
|
15
|
+
const VALID_DISPLAY_CATEGORIES = new Set([
|
|
16
|
+
'ACTIVITY_TRIGGER',
|
|
17
|
+
'AIR_CONDITIONER',
|
|
18
|
+
'AIR_FRESHENER',
|
|
19
|
+
'AIR_PURIFIER',
|
|
20
|
+
'AIR_QUALITY_MONITOR',
|
|
21
|
+
'ALEXA_VOICE_ENABLED',
|
|
22
|
+
'AUTO_ACCESSORY',
|
|
23
|
+
'BLUETOOTH_SPEAKER',
|
|
24
|
+
'CAMERA',
|
|
25
|
+
'CHRISTMAS_TREE',
|
|
26
|
+
'COFFEE_MAKER',
|
|
27
|
+
'COMPUTER',
|
|
28
|
+
'CONTACT_SENSOR',
|
|
29
|
+
'DISHWASHER',
|
|
30
|
+
'DOOR',
|
|
31
|
+
'DOORBELL',
|
|
32
|
+
'DRYER',
|
|
33
|
+
'EXTERIOR_BLIND',
|
|
34
|
+
'FAN',
|
|
35
|
+
'GAME_CONSOLE',
|
|
36
|
+
'GARAGE_DOOR',
|
|
37
|
+
'HEADPHONES',
|
|
38
|
+
'HUB',
|
|
39
|
+
'INTERIOR_BLIND',
|
|
40
|
+
'LAPTOP',
|
|
41
|
+
'LIGHT',
|
|
42
|
+
'MICROWAVE',
|
|
43
|
+
'MOBILE_PHONE',
|
|
44
|
+
'MOTION_SENSOR',
|
|
45
|
+
'MUSIC_SYSTEM',
|
|
46
|
+
'NETWORK_HARDWARE',
|
|
47
|
+
'OTHER',
|
|
48
|
+
'OVEN',
|
|
49
|
+
'PHONE',
|
|
50
|
+
'PRINTER',
|
|
51
|
+
'REMOTE',
|
|
52
|
+
'ROUTER',
|
|
53
|
+
'SCENE_TRIGGER',
|
|
54
|
+
'SCREEN',
|
|
55
|
+
'SECURITY_PANEL',
|
|
56
|
+
'SECURITY_SYSTEM',
|
|
57
|
+
'SLOW_COOKER',
|
|
58
|
+
'SMARTLOCK',
|
|
59
|
+
'SMARTPLUG',
|
|
60
|
+
'SPEAKER',
|
|
61
|
+
'STREAMING_DEVICE',
|
|
62
|
+
'SWITCH',
|
|
63
|
+
'TABLET',
|
|
64
|
+
'TEMPERATURE_SENSOR',
|
|
65
|
+
'THERMOSTAT',
|
|
66
|
+
'TV',
|
|
67
|
+
'VACUUM_CLEANER',
|
|
68
|
+
'VACUUM',
|
|
69
|
+
'VEHICLE',
|
|
70
|
+
'WASHER',
|
|
71
|
+
'WATER_HEATER',
|
|
72
|
+
'WEARABLE',
|
|
73
|
+
]);
|
|
74
|
+
const VALID_NAMESPACES = new Set([
|
|
75
|
+
'Alexa',
|
|
76
|
+
'Alexa.BrightnessController',
|
|
77
|
+
'Alexa.ColorController',
|
|
78
|
+
'Alexa.ColorTemperatureController',
|
|
79
|
+
'Alexa.ContactSensor',
|
|
80
|
+
'Alexa.EndpointHealth',
|
|
81
|
+
'Alexa.HumiditySensor',
|
|
82
|
+
'Alexa.LockController',
|
|
83
|
+
'Alexa.ModeController',
|
|
84
|
+
'Alexa.MotionSensor',
|
|
85
|
+
'Alexa.PercentageController',
|
|
86
|
+
'Alexa.PowerController',
|
|
87
|
+
'Alexa.RangeController',
|
|
88
|
+
'Alexa.SceneController',
|
|
89
|
+
'Alexa.Speaker',
|
|
90
|
+
'Alexa.TemperatureSensor',
|
|
91
|
+
'Alexa.ThermostatController',
|
|
92
|
+
]);
|
|
93
|
+
// Multi-instance capabilities that require an instance property
|
|
94
|
+
const REQUIRES_INSTANCE = new Set(['Alexa.ModeController', 'Alexa.RangeController', 'Alexa.ToggleController']);
|
|
95
|
+
// Alexa wake words — devices named like this can't be controlled
|
|
96
|
+
const WAKE_WORDS = ['alexa', 'echo', 'amazon', 'computer', 'ziggy'];
|
|
97
|
+
/**
|
|
98
|
+
* Validates the Alexa Discovery response and removes invalid endpoints.
|
|
99
|
+
* Returns the sanitized response.
|
|
100
|
+
*/
|
|
101
|
+
function validateDiscoveryResponse(response, log) {
|
|
102
|
+
// Validate response header structure
|
|
103
|
+
const header = response?.event?.header;
|
|
104
|
+
if (!header) {
|
|
105
|
+
log.error('Discovery: response has no event.header');
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
// @ts-expect-error
|
|
109
|
+
if (header.namespace !== 'Alexa.Discovery') {
|
|
110
|
+
log.error(`Discovery: unexpected namespace "${header.namespace}", expected "Alexa.Discovery"`);
|
|
111
|
+
}
|
|
112
|
+
if (header.name !== 'Discover.Response') {
|
|
113
|
+
log.error(`Discovery: unexpected name "${header.name}", expected "Discover.Response"`);
|
|
114
|
+
}
|
|
115
|
+
if (header.payloadVersion !== '3') {
|
|
116
|
+
log.error(`Discovery: unexpected payloadVersion "${header.payloadVersion}", expected "3"`);
|
|
117
|
+
}
|
|
118
|
+
const endpoints = response?.event?.payload?.endpoints;
|
|
119
|
+
if (!endpoints || !Array.isArray(endpoints)) {
|
|
120
|
+
return response;
|
|
121
|
+
}
|
|
122
|
+
const errors = [];
|
|
123
|
+
const warnings = [];
|
|
124
|
+
const seenIds = new Set();
|
|
125
|
+
const seenNames = new Set();
|
|
126
|
+
// Validate and filter endpoints in place (backwards to safely splice)
|
|
127
|
+
for (let i = endpoints.length - 1; i >= 0; i--) {
|
|
128
|
+
const ep = endpoints[i];
|
|
129
|
+
const epIssues = validateEndpoint(ep, seenIds, seenNames);
|
|
130
|
+
const epErrors = epIssues.filter(e => e.severity === 'error');
|
|
131
|
+
const epWarnings = epIssues.filter(e => e.severity === 'warning');
|
|
132
|
+
warnings.push(...epWarnings);
|
|
133
|
+
if (epErrors.length) {
|
|
134
|
+
errors.push(...epErrors);
|
|
135
|
+
endpoints.splice(i, 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Enforce 300 endpoint limit
|
|
139
|
+
if (endpoints.length > MAX_ENDPOINTS) {
|
|
140
|
+
log.warn(`Discovery: ${endpoints.length} endpoints exceeds Alexa limit of ${MAX_ENDPOINTS}, truncating`);
|
|
141
|
+
endpoints.length = MAX_ENDPOINTS;
|
|
142
|
+
}
|
|
143
|
+
// Check total response size against AWS IoT MQTT limit
|
|
144
|
+
const responseSize = JSON.stringify(response).length;
|
|
145
|
+
if (responseSize > MAX_RESPONSE_SIZE_BYTES) {
|
|
146
|
+
const originalCount = endpoints.length;
|
|
147
|
+
while (endpoints.length > 1 && JSON.stringify(response).length > MAX_RESPONSE_SIZE_BYTES) {
|
|
148
|
+
const removed = endpoints.pop();
|
|
149
|
+
log.warn(`Discovery: removed "${removed.friendlyName}" to fit AWS IoT message size limit`);
|
|
150
|
+
}
|
|
151
|
+
log.warn(`Discovery: response was ${Math.round(responseSize / 1024)} KB (limit: ${Math.round(MAX_RESPONSE_SIZE_BYTES / 1024)} KB), reduced from ${originalCount} to ${endpoints.length} endpoints`);
|
|
152
|
+
}
|
|
153
|
+
else if (responseSize > RESPONSE_SIZE_WARNING_BYTES) {
|
|
154
|
+
log.warn(`Discovery: response is ${Math.round(responseSize / 1024)} KB with ${endpoints.length} endpoints — approaching AWS IoT limit of ${Math.round(MAX_RESPONSE_SIZE_BYTES / 1024)} KB`);
|
|
155
|
+
}
|
|
156
|
+
// Log warnings (non-fatal)
|
|
157
|
+
for (const w of warnings) {
|
|
158
|
+
log.warn(`Discovery: "${w.friendlyName}" (${w.endpointId}): ${w.field} — ${w.message}`);
|
|
159
|
+
}
|
|
160
|
+
// Log errors (endpoint was removed)
|
|
161
|
+
for (const err of errors) {
|
|
162
|
+
log.warn(`Discovery: removed "${err.friendlyName}" (${err.endpointId}): ${err.field} — ${err.message}`);
|
|
163
|
+
}
|
|
164
|
+
if (errors.length || warnings.length) {
|
|
165
|
+
log.info(`Discovery: ${endpoints.length} endpoint(s) valid, ${errors.length} removed, ${warnings.length} warning(s)`);
|
|
166
|
+
}
|
|
167
|
+
return response;
|
|
168
|
+
}
|
|
169
|
+
function validateEndpoint(ep, seenIds, seenNames) {
|
|
170
|
+
const issues = [];
|
|
171
|
+
const id = ep.endpointId || '(empty)';
|
|
172
|
+
const name = ep.friendlyName || '(empty)';
|
|
173
|
+
const error = (field, message) => ({
|
|
174
|
+
endpointId: id,
|
|
175
|
+
friendlyName: name,
|
|
176
|
+
field,
|
|
177
|
+
message,
|
|
178
|
+
severity: 'error',
|
|
179
|
+
});
|
|
180
|
+
const warning = (field, message) => ({
|
|
181
|
+
endpointId: id,
|
|
182
|
+
friendlyName: name,
|
|
183
|
+
field,
|
|
184
|
+
message,
|
|
185
|
+
severity: 'warning',
|
|
186
|
+
});
|
|
187
|
+
// --- endpointId ---
|
|
188
|
+
if (!ep.endpointId) {
|
|
189
|
+
issues.push(error('endpointId', 'missing'));
|
|
190
|
+
}
|
|
191
|
+
else if (ep.endpointId.length > MAX_ENDPOINT_ID_LENGTH) {
|
|
192
|
+
issues.push(error('endpointId', `exceeds ${MAX_ENDPOINT_ID_LENGTH} chars`));
|
|
193
|
+
}
|
|
194
|
+
else if (!/^[\w#;:!@"$%&'()*+,\-./>=<?[\\\]^`{|}~ ]+$/.test(ep.endpointId)) {
|
|
195
|
+
issues.push(error('endpointId', 'contains invalid characters'));
|
|
196
|
+
}
|
|
197
|
+
else if (seenIds.has(ep.endpointId)) {
|
|
198
|
+
issues.push(error('endpointId', 'duplicate'));
|
|
199
|
+
}
|
|
200
|
+
seenIds.add(ep.endpointId);
|
|
201
|
+
// --- friendlyName ---
|
|
202
|
+
if (!ep.friendlyName || !ep.friendlyName.trim()) {
|
|
203
|
+
issues.push(error('friendlyName', 'missing or empty'));
|
|
204
|
+
}
|
|
205
|
+
else if (ep.friendlyName.length > MAX_FRIENDLY_NAME_LENGTH) {
|
|
206
|
+
issues.push(error('friendlyName', `exceeds ${MAX_FRIENDLY_NAME_LENGTH} chars`));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Alexa rejects names that are only digits
|
|
210
|
+
if (/^\d+$/.test(ep.friendlyName.trim())) {
|
|
211
|
+
issues.push(error('friendlyName', 'must not be only digits'));
|
|
212
|
+
}
|
|
213
|
+
// Wake words as device name make the device unusable
|
|
214
|
+
const lower = ep.friendlyName.toLowerCase().trim();
|
|
215
|
+
if (WAKE_WORDS.includes(lower)) {
|
|
216
|
+
issues.push(warning('friendlyName', `"${ep.friendlyName}" is an Alexa wake word — device may not be controllable`));
|
|
217
|
+
}
|
|
218
|
+
// Duplicate names confuse Alexa ("which one did you mean?")
|
|
219
|
+
if (seenNames.has(lower)) {
|
|
220
|
+
issues.push(warning('friendlyName', 'duplicate name — Alexa will ask "which one did you mean?"'));
|
|
221
|
+
}
|
|
222
|
+
seenNames.add(lower);
|
|
223
|
+
}
|
|
224
|
+
// --- description ---
|
|
225
|
+
if (ep.description && ep.description.length > MAX_DESCRIPTION_LENGTH) {
|
|
226
|
+
issues.push(warning('description', `exceeds ${MAX_DESCRIPTION_LENGTH} chars, will be truncated`));
|
|
227
|
+
}
|
|
228
|
+
// --- manufacturerName ---
|
|
229
|
+
if (ep.manufacturerName && ep.manufacturerName.length > MAX_MANUFACTURER_NAME_LENGTH) {
|
|
230
|
+
issues.push(warning('manufacturerName', `exceeds ${MAX_MANUFACTURER_NAME_LENGTH} chars`));
|
|
231
|
+
}
|
|
232
|
+
// --- displayCategories ---
|
|
233
|
+
if (!ep.displayCategories || !ep.displayCategories.length) {
|
|
234
|
+
issues.push(error('displayCategories', 'missing or empty'));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
for (const cat of ep.displayCategories) {
|
|
238
|
+
if (!VALID_DISPLAY_CATEGORIES.has(cat)) {
|
|
239
|
+
issues.push(error('displayCategories', `unknown category "${cat}"`));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// --- capabilities ---
|
|
244
|
+
if (!ep.capabilities || !ep.capabilities.length) {
|
|
245
|
+
issues.push(error('capabilities', 'missing or empty'));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
if (ep.capabilities.length > MAX_CAPABILITIES_PER_ENDPOINT) {
|
|
249
|
+
issues.push(error('capabilities', `exceeds ${MAX_CAPABILITIES_PER_ENDPOINT} capabilities`));
|
|
250
|
+
}
|
|
251
|
+
// Alexa interface is required on every endpoint
|
|
252
|
+
const hasAlexa = ep.capabilities.some(c => c.interface === 'Alexa');
|
|
253
|
+
if (!hasAlexa) {
|
|
254
|
+
issues.push(error('capabilities', 'missing required Alexa interface'));
|
|
255
|
+
}
|
|
256
|
+
// Check for duplicate capabilities (same interface+instance)
|
|
257
|
+
const capKeys = new Set();
|
|
258
|
+
for (const cap of ep.capabilities) {
|
|
259
|
+
const capKey = `${cap.interface || ''}::${cap.instance || ''}`;
|
|
260
|
+
if (cap.interface !== 'Alexa' && capKeys.has(capKey)) {
|
|
261
|
+
issues.push(error('capabilities', `duplicate capability ${cap.interface}${cap.instance ? ` (${cap.instance})` : ''}`));
|
|
262
|
+
}
|
|
263
|
+
capKeys.add(capKey);
|
|
264
|
+
// Validate individual capability
|
|
265
|
+
if (cap.interface && !VALID_NAMESPACES.has(cap.interface)) {
|
|
266
|
+
issues.push(warning('capabilities', `unknown interface "${cap.interface}"`));
|
|
267
|
+
}
|
|
268
|
+
if (cap.version && cap.version !== '3' && cap.version !== '3.2') {
|
|
269
|
+
issues.push(error('capabilities', `unexpected version "${cap.version}" for ${cap.interface}`));
|
|
270
|
+
}
|
|
271
|
+
// ModeController and RangeController require instance
|
|
272
|
+
if (cap.interface && REQUIRES_INSTANCE.has(cap.interface) && !cap.instance) {
|
|
273
|
+
issues.push(error('capabilities', `${cap.interface} requires an instance property`));
|
|
274
|
+
}
|
|
275
|
+
// properties.supported must not be empty if defined
|
|
276
|
+
if (cap.properties && Array.isArray(cap.properties.supported) && cap.properties.supported.length === 0) {
|
|
277
|
+
issues.push(warning('capabilities', `${cap.interface} has empty properties.supported array`));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return issues;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=DiscoveryValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiscoveryValidator.js","sourceRoot":"","sources":["../../../../src/lib/AlexaSmartHomeV3/Helpers/DiscoveryValidator.ts"],"names":[],"mappings":"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","sourcesContent":["import type Logger from './Logger';\nimport type AlexaResponse from '../Alexa/AlexaResponse';\n\n// Alexa limits: https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html\nconst MAX_ENDPOINTS = 300;\nconst MAX_ENDPOINT_ID_LENGTH = 256;\nconst MAX_FRIENDLY_NAME_LENGTH = 128;\nconst MAX_DESCRIPTION_LENGTH = 128;\nconst MAX_MANUFACTURER_NAME_LENGTH = 128;\nconst MAX_CAPABILITIES_PER_ENDPOINT = 100;\n\n// AWS IoT MQTT message size limit (128 KB minus overhead)\nconst MAX_RESPONSE_SIZE_BYTES = 127 * 1024;\nconst RESPONSE_SIZE_WARNING_BYTES = 100 * 1024;\n\n// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html#display-categories\nconst VALID_DISPLAY_CATEGORIES = new Set([\n 'ACTIVITY_TRIGGER',\n 'AIR_CONDITIONER',\n 'AIR_FRESHENER',\n 'AIR_PURIFIER',\n 'AIR_QUALITY_MONITOR',\n 'ALEXA_VOICE_ENABLED',\n 'AUTO_ACCESSORY',\n 'BLUETOOTH_SPEAKER',\n 'CAMERA',\n 'CHRISTMAS_TREE',\n 'COFFEE_MAKER',\n 'COMPUTER',\n 'CONTACT_SENSOR',\n 'DISHWASHER',\n 'DOOR',\n 'DOORBELL',\n 'DRYER',\n 'EXTERIOR_BLIND',\n 'FAN',\n 'GAME_CONSOLE',\n 'GARAGE_DOOR',\n 'HEADPHONES',\n 'HUB',\n 'INTERIOR_BLIND',\n 'LAPTOP',\n 'LIGHT',\n 'MICROWAVE',\n 'MOBILE_PHONE',\n 'MOTION_SENSOR',\n 'MUSIC_SYSTEM',\n 'NETWORK_HARDWARE',\n 'OTHER',\n 'OVEN',\n 'PHONE',\n 'PRINTER',\n 'REMOTE',\n 'ROUTER',\n 'SCENE_TRIGGER',\n 'SCREEN',\n 'SECURITY_PANEL',\n 'SECURITY_SYSTEM',\n 'SLOW_COOKER',\n 'SMARTLOCK',\n 'SMARTPLUG',\n 'SPEAKER',\n 'STREAMING_DEVICE',\n 'SWITCH',\n 'TABLET',\n 'TEMPERATURE_SENSOR',\n 'THERMOSTAT',\n 'TV',\n 'VACUUM_CLEANER',\n 'VACUUM',\n 'VEHICLE',\n 'WASHER',\n 'WATER_HEATER',\n 'WEARABLE',\n]);\n\nconst VALID_NAMESPACES = new Set([\n 'Alexa',\n 'Alexa.BrightnessController',\n 'Alexa.ColorController',\n 'Alexa.ColorTemperatureController',\n 'Alexa.ContactSensor',\n 'Alexa.EndpointHealth',\n 'Alexa.HumiditySensor',\n 'Alexa.LockController',\n 'Alexa.ModeController',\n 'Alexa.MotionSensor',\n 'Alexa.PercentageController',\n 'Alexa.PowerController',\n 'Alexa.RangeController',\n 'Alexa.SceneController',\n 'Alexa.Speaker',\n 'Alexa.TemperatureSensor',\n 'Alexa.ThermostatController',\n]);\n\n// Multi-instance capabilities that require an instance property\nconst REQUIRES_INSTANCE = new Set(['Alexa.ModeController', 'Alexa.RangeController', 'Alexa.ToggleController']);\n\n// Alexa wake words — devices named like this can't be controlled\nconst WAKE_WORDS = ['alexa', 'echo', 'amazon', 'computer', 'ziggy'];\n\ninterface DiscoveryEndpoint {\n endpointId: string;\n friendlyName: string;\n description?: string;\n manufacturerName?: string;\n displayCategories?: string[];\n capabilities?: DiscoveryCapability[];\n}\n\ninterface DiscoveryCapability {\n type?: string;\n interface?: string;\n instance?: string;\n version?: string;\n properties?: {\n supported?: { name: string }[];\n };\n}\n\nexport interface ValidationError {\n endpointId: string;\n friendlyName: string;\n field: string;\n message: string;\n severity: 'error' | 'warning';\n}\n\n/**\n * Validates the Alexa Discovery response and removes invalid endpoints.\n * Returns the sanitized response.\n */\nexport function validateDiscoveryResponse(response: AlexaResponse, log: Logger): AlexaResponse {\n // Validate response header structure\n const header = response?.event?.header;\n if (!header) {\n log.error('Discovery: response has no event.header');\n return response;\n }\n // @ts-expect-error\n if (header.namespace !== 'Alexa.Discovery') {\n log.error(`Discovery: unexpected namespace \"${header.namespace}\", expected \"Alexa.Discovery\"`);\n }\n if (header.name !== 'Discover.Response') {\n log.error(`Discovery: unexpected name \"${header.name}\", expected \"Discover.Response\"`);\n }\n if (header.payloadVersion !== '3') {\n log.error(`Discovery: unexpected payloadVersion \"${header.payloadVersion as unknown as string}\", expected \"3\"`);\n }\n\n const endpoints: DiscoveryEndpoint[] | undefined = response?.event?.payload?.endpoints;\n if (!endpoints || !Array.isArray(endpoints)) {\n return response;\n }\n\n const errors: ValidationError[] = [];\n const warnings: ValidationError[] = [];\n const seenIds = new Set<string>();\n const seenNames = new Set<string>();\n\n // Validate and filter endpoints in place (backwards to safely splice)\n for (let i = endpoints.length - 1; i >= 0; i--) {\n const ep = endpoints[i];\n const epIssues = validateEndpoint(ep, seenIds, seenNames);\n const epErrors = epIssues.filter(e => e.severity === 'error');\n const epWarnings = epIssues.filter(e => e.severity === 'warning');\n warnings.push(...epWarnings);\n if (epErrors.length) {\n errors.push(...epErrors);\n endpoints.splice(i, 1);\n }\n }\n\n // Enforce 300 endpoint limit\n if (endpoints.length > MAX_ENDPOINTS) {\n log.warn(`Discovery: ${endpoints.length} endpoints exceeds Alexa limit of ${MAX_ENDPOINTS}, truncating`);\n endpoints.length = MAX_ENDPOINTS;\n }\n\n // Check total response size against AWS IoT MQTT limit\n const responseSize = JSON.stringify(response).length;\n if (responseSize > MAX_RESPONSE_SIZE_BYTES) {\n const originalCount = endpoints.length;\n while (endpoints.length > 1 && JSON.stringify(response).length > MAX_RESPONSE_SIZE_BYTES) {\n const removed = endpoints.pop()!;\n log.warn(`Discovery: removed \"${removed.friendlyName}\" to fit AWS IoT message size limit`);\n }\n log.warn(\n `Discovery: response was ${Math.round(responseSize / 1024)} KB (limit: ${Math.round(MAX_RESPONSE_SIZE_BYTES / 1024)} KB), reduced from ${originalCount} to ${endpoints.length} endpoints`,\n );\n } else if (responseSize > RESPONSE_SIZE_WARNING_BYTES) {\n log.warn(\n `Discovery: response is ${Math.round(responseSize / 1024)} KB with ${endpoints.length} endpoints — approaching AWS IoT limit of ${Math.round(MAX_RESPONSE_SIZE_BYTES / 1024)} KB`,\n );\n }\n\n // Log warnings (non-fatal)\n for (const w of warnings) {\n log.warn(`Discovery: \"${w.friendlyName}\" (${w.endpointId}): ${w.field} — ${w.message}`);\n }\n\n // Log errors (endpoint was removed)\n for (const err of errors) {\n log.warn(`Discovery: removed \"${err.friendlyName}\" (${err.endpointId}): ${err.field} — ${err.message}`);\n }\n\n if (errors.length || warnings.length) {\n log.info(\n `Discovery: ${endpoints.length} endpoint(s) valid, ${errors.length} removed, ${warnings.length} warning(s)`,\n );\n }\n\n return response;\n}\n\nfunction validateEndpoint(ep: DiscoveryEndpoint, seenIds: Set<string>, seenNames: Set<string>): ValidationError[] {\n const issues: ValidationError[] = [];\n const id = ep.endpointId || '(empty)';\n const name = ep.friendlyName || '(empty)';\n\n const error = (field: string, message: string): ValidationError => ({\n endpointId: id,\n friendlyName: name,\n field,\n message,\n severity: 'error',\n });\n const warning = (field: string, message: string): ValidationError => ({\n endpointId: id,\n friendlyName: name,\n field,\n message,\n severity: 'warning',\n });\n\n // --- endpointId ---\n if (!ep.endpointId) {\n issues.push(error('endpointId', 'missing'));\n } else if (ep.endpointId.length > MAX_ENDPOINT_ID_LENGTH) {\n issues.push(error('endpointId', `exceeds ${MAX_ENDPOINT_ID_LENGTH} chars`));\n } else if (!/^[\\w#;:!@\"$%&'()*+,\\-./>=<?[\\\\\\]^`{|}~ ]+$/.test(ep.endpointId)) {\n issues.push(error('endpointId', 'contains invalid characters'));\n } else if (seenIds.has(ep.endpointId)) {\n issues.push(error('endpointId', 'duplicate'));\n }\n seenIds.add(ep.endpointId);\n\n // --- friendlyName ---\n if (!ep.friendlyName || !ep.friendlyName.trim()) {\n issues.push(error('friendlyName', 'missing or empty'));\n } else if (ep.friendlyName.length > MAX_FRIENDLY_NAME_LENGTH) {\n issues.push(error('friendlyName', `exceeds ${MAX_FRIENDLY_NAME_LENGTH} chars`));\n } else {\n // Alexa rejects names that are only digits\n if (/^\\d+$/.test(ep.friendlyName.trim())) {\n issues.push(error('friendlyName', 'must not be only digits'));\n }\n\n // Wake words as device name make the device unusable\n const lower = ep.friendlyName.toLowerCase().trim();\n if (WAKE_WORDS.includes(lower)) {\n issues.push(\n warning('friendlyName', `\"${ep.friendlyName}\" is an Alexa wake word — device may not be controllable`),\n );\n }\n\n // Duplicate names confuse Alexa (\"which one did you mean?\")\n if (seenNames.has(lower)) {\n issues.push(warning('friendlyName', 'duplicate name — Alexa will ask \"which one did you mean?\"'));\n }\n seenNames.add(lower);\n }\n\n // --- description ---\n if (ep.description && ep.description.length > MAX_DESCRIPTION_LENGTH) {\n issues.push(warning('description', `exceeds ${MAX_DESCRIPTION_LENGTH} chars, will be truncated`));\n }\n\n // --- manufacturerName ---\n if (ep.manufacturerName && ep.manufacturerName.length > MAX_MANUFACTURER_NAME_LENGTH) {\n issues.push(warning('manufacturerName', `exceeds ${MAX_MANUFACTURER_NAME_LENGTH} chars`));\n }\n\n // --- displayCategories ---\n if (!ep.displayCategories || !ep.displayCategories.length) {\n issues.push(error('displayCategories', 'missing or empty'));\n } else {\n for (const cat of ep.displayCategories) {\n if (!VALID_DISPLAY_CATEGORIES.has(cat)) {\n issues.push(error('displayCategories', `unknown category \"${cat}\"`));\n }\n }\n }\n\n // --- capabilities ---\n if (!ep.capabilities || !ep.capabilities.length) {\n issues.push(error('capabilities', 'missing or empty'));\n } else {\n if (ep.capabilities.length > MAX_CAPABILITIES_PER_ENDPOINT) {\n issues.push(error('capabilities', `exceeds ${MAX_CAPABILITIES_PER_ENDPOINT} capabilities`));\n }\n\n // Alexa interface is required on every endpoint\n const hasAlexa = ep.capabilities.some(c => c.interface === 'Alexa');\n if (!hasAlexa) {\n issues.push(error('capabilities', 'missing required Alexa interface'));\n }\n\n // Check for duplicate capabilities (same interface+instance)\n const capKeys = new Set<string>();\n for (const cap of ep.capabilities) {\n const capKey = `${cap.interface || ''}::${cap.instance || ''}`;\n if (cap.interface !== 'Alexa' && capKeys.has(capKey)) {\n issues.push(\n error(\n 'capabilities',\n `duplicate capability ${cap.interface}${cap.instance ? ` (${cap.instance})` : ''}`,\n ),\n );\n }\n capKeys.add(capKey);\n\n // Validate individual capability\n if (cap.interface && !VALID_NAMESPACES.has(cap.interface)) {\n issues.push(warning('capabilities', `unknown interface \"${cap.interface}\"`));\n }\n\n if (cap.version && cap.version !== '3' && cap.version !== '3.2') {\n issues.push(error('capabilities', `unexpected version \"${cap.version}\" for ${cap.interface}`));\n }\n\n // ModeController and RangeController require instance\n if (cap.interface && REQUIRES_INSTANCE.has(cap.interface) && !cap.instance) {\n issues.push(error('capabilities', `${cap.interface} requires an instance property`));\n }\n\n // properties.supported must not be empty if defined\n if (cap.properties && Array.isArray(cap.properties.supported) && cap.properties.supported.length === 0) {\n issues.push(warning('capabilities', `${cap.interface} has empty properties.supported array`));\n }\n }\n }\n\n return issues;\n}\n"]}
|
|
@@ -991,6 +991,10 @@ function hal2rgbw(hal) {
|
|
|
991
991
|
g = 0;
|
|
992
992
|
b = 0;
|
|
993
993
|
}
|
|
994
|
+
// Extract the common white component (min of R,G,B = p) into the dedicated white channel
|
|
995
|
+
// const w = p;
|
|
996
|
+
// return `#${toHex(to255(r - w))}${toHex(to255(g - w))}${toHex(to255(b - w))}${toHex(to255(w))}`;
|
|
997
|
+
// Simple calculations
|
|
994
998
|
return `#${toHex(to255(r))}${toHex(to255(g))}${toHex(to255(b))}${toHex(to255(255))}`;
|
|
995
999
|
}
|
|
996
1000
|
/**
|