@smarthome.exchange/integrations 2.0.0 → 4.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 +46 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/classes.nativeintegrationowner.d.ts +2 -0
- package/dist_ts/core/classes.nativeintegrationowner.js +5 -1
- package/dist_ts/core/classes.runtimemanager.d.ts +2 -0
- package/dist_ts/core/classes.runtimemanager.js +11 -3
- package/dist_ts/core/types.d.ts +2 -0
- package/dist_ts/integrations/deconz/deconz.classes.client.d.ts +37 -29
- package/dist_ts/integrations/deconz/deconz.classes.client.js +521 -294
- package/dist_ts/integrations/deconz/deconz.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/deconz/deconz.classes.configflow.js +63 -33
- package/dist_ts/integrations/deconz/deconz.classes.integration.d.ts +4 -3
- package/dist_ts/integrations/deconz/deconz.classes.integration.js +85 -40
- package/dist_ts/integrations/deconz/deconz.mapper.d.ts +1 -0
- package/dist_ts/integrations/deconz/deconz.mapper.js +24 -6
- package/dist_ts/integrations/deconz/deconz.types.d.ts +43 -6
- package/dist_ts/integrations/deconz/deconz.validation.d.ts +7 -1
- package/dist_ts/integrations/deconz/deconz.validation.js +215 -4
- package/dist_ts/integrations/esphome/esphome.classes.client.d.ts +19 -7
- package/dist_ts/integrations/esphome/esphome.classes.client.js +183 -89
- package/dist_ts/integrations/esphome/esphome.classes.configflow.d.ts +5 -2
- package/dist_ts/integrations/esphome/esphome.classes.configflow.js +43 -33
- package/dist_ts/integrations/esphome/esphome.classes.integration.d.ts +8 -6
- package/dist_ts/integrations/esphome/esphome.classes.integration.js +71 -43
- package/dist_ts/integrations/esphome/esphome.discovery.js +4 -12
- package/dist_ts/integrations/esphome/esphome.mapper.d.ts +2 -2
- package/dist_ts/integrations/esphome/esphome.mapper.js +6 -4
- package/dist_ts/integrations/esphome/esphome.types.d.ts +31 -6
- package/dist_ts/integrations/esphome/esphome.types.js +1 -1
- package/dist_ts/integrations/esphome/esphome.validation.d.ts +7 -0
- package/dist_ts/integrations/esphome/esphome.validation.js +66 -0
- package/dist_ts/integrations/homeassistant/homeassistant.classes.client.d.ts +32 -4
- package/dist_ts/integrations/homeassistant/homeassistant.classes.client.js +345 -116
- package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.d.ts +8 -5
- package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.js +34 -8
- package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.d.ts +7 -4
- package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.js +44 -56
- package/dist_ts/integrations/homeassistant/homeassistant.types.d.ts +12 -1
- package/dist_ts/integrations/hue/hue.classes.client.d.ts +32 -4
- package/dist_ts/integrations/hue/hue.classes.client.js +331 -57
- package/dist_ts/integrations/hue/hue.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/hue/hue.classes.configflow.js +79 -27
- package/dist_ts/integrations/hue/hue.classes.integration.d.ts +3 -1
- package/dist_ts/integrations/hue/hue.classes.integration.js +83 -62
- package/dist_ts/integrations/hue/hue.mapper.d.ts +2 -0
- package/dist_ts/integrations/hue/hue.mapper.js +8 -6
- package/dist_ts/integrations/hue/hue.types.d.ts +32 -1
- package/dist_ts/integrations/hue/hue.validation.d.ts +9 -3
- package/dist_ts/integrations/hue/hue.validation.js +112 -11
- package/dist_ts/integrations/mqtt/mqtt.classes.client.d.ts +15 -2
- package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
- package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
- package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
- package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
- package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
- package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
- package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
- package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
- package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
- package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
- package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
- package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -0
- package/dist_ts/integrations/samsungtv/samsungtv.classes.client.d.ts +4 -2
- package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
- package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
- package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
- package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
- package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
- package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
- package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
- package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
- package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
- package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
- package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
- package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
- package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
- package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
- package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
- package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
- package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
- package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
- package/dist_ts/integrations/shelly/shelly.types.js +11 -2
- package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
- package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
- package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
- package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
- package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
- package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
- package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
- package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
- package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
- package/dist_ts/integrations/sonos/sonos.config.js +23 -2
- package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
- package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
- package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
- package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
- package/dist_ts/integrations/tasmota/tasmota.classes.integration.d.ts +5 -0
- package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
- package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
- package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
- package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
- package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
- package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
- package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
- package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
- package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
- package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
- package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
- package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
- package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
- package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
- package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
- package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
- package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
- package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
- package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
- package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
- package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
- package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
- package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
- package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
- package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
- package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
- package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
- package/dist_ts/integrations/webostv/webostv.types.js +1 -1
- package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
- package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
- package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
- package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
- package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
- package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
- package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
- package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
- package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
- package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
- package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
- package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +10 -10
- package/readme.md +25 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/classes.nativeintegrationowner.ts +5 -0
- package/ts/core/classes.runtimemanager.ts +11 -2
- package/ts/core/types.ts +2 -0
- package/ts/integrations/deconz/deconz.classes.client.ts +348 -252
- package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
- package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
- package/ts/integrations/deconz/deconz.mapper.ts +22 -5
- package/ts/integrations/deconz/deconz.types.ts +49 -6
- package/ts/integrations/deconz/deconz.validation.ts +177 -4
- package/ts/integrations/deconz/readme.md +15 -1
- package/ts/integrations/esphome/esphome.classes.client.ts +179 -94
- package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
- package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
- package/ts/integrations/esphome/esphome.discovery.ts +3 -12
- package/ts/integrations/esphome/esphome.mapper.ts +9 -7
- package/ts/integrations/esphome/esphome.types.ts +35 -6
- package/ts/integrations/esphome/esphome.validation.ts +66 -0
- package/ts/integrations/esphome/readme.md +20 -6
- package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
- package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
- package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
- package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
- package/ts/integrations/homeassistant/readme.md +22 -6
- package/ts/integrations/hue/hue.classes.client.ts +288 -55
- package/ts/integrations/hue/hue.classes.configflow.ts +68 -25
- package/ts/integrations/hue/hue.classes.integration.ts +75 -54
- package/ts/integrations/hue/hue.mapper.ts +8 -5
- package/ts/integrations/hue/hue.types.ts +37 -1
- package/ts/integrations/hue/hue.validation.ts +102 -13
- package/ts/integrations/hue/readme.md +49 -12
- package/ts/integrations/mqtt/mqtt.classes.client.ts +136 -19
- package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
- package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
- package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
- package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
- package/ts/integrations/mqtt/mqtt.types.ts +52 -0
- package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
- package/ts/integrations/mqtt/readme.md +12 -3
- package/ts/integrations/samsungtv/readme.md +11 -5
- package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
- package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
- package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
- package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
- package/ts/integrations/shelly/readme.md +22 -7
- package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
- package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
- package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
- package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
- package/ts/integrations/shelly/shelly.mapper.ts +19 -20
- package/ts/integrations/shelly/shelly.rpc.ts +2 -0
- package/ts/integrations/shelly/shelly.types.ts +50 -2
- package/ts/integrations/shelly/shelly.validation.ts +76 -0
- package/ts/integrations/sonos/readme.md +117 -38
- package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
- package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
- package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
- package/ts/integrations/sonos/sonos.config.ts +24 -2
- package/ts/integrations/sonos/sonos.mapper.ts +13 -8
- package/ts/integrations/sonos/sonos.matchers.ts +2 -2
- package/ts/integrations/sonos/sonos.types.ts +30 -1
- package/ts/integrations/tasmota/readme.md +16 -5
- package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
- package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
- package/ts/integrations/tasmota/tasmota.config.ts +17 -0
- package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
- package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
- package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
- package/ts/integrations/tasmota/tasmota.types.ts +43 -2
- package/ts/integrations/tuya/readme.md +52 -10
- package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
- package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
- package/ts/integrations/tuya/tuya.mapper.ts +1 -1
- package/ts/integrations/tuya/tuya.types.ts +4 -0
- package/ts/integrations/tuya/tuya.validation.ts +5 -8
- package/ts/integrations/webostv/readme.md +115 -22
- package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
- package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
- package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
- package/ts/integrations/webostv/webostv.mapper.ts +4 -4
- package/ts/integrations/webostv/webostv.types.ts +18 -3
- package/ts/integrations/webostv/webostv.validation.ts +22 -1
- package/ts/integrations/zigbee2mqtt/readme.md +22 -27
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
- package/ts/integrations/zwave_js/readme.md +42 -9
- package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
- package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
- package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
- package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
- package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
- package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
- package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
- package/ts/plugins.ts +2 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { IShellyConfig, IShellyIntegrationOptions } from './shelly.types.js';
|
|
2
|
+
|
|
3
|
+
export const shellyData = (valueArg: unknown, allowedArg?: readonly string[]): Record<string, unknown> => {
|
|
4
|
+
if (!valueArg || typeof valueArg !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(valueArg))) {
|
|
5
|
+
throw new TypeError('Shelly input must be a plain data object.');
|
|
6
|
+
}
|
|
7
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
8
|
+
for (const key of Reflect.ownKeys(valueArg)) {
|
|
9
|
+
const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
|
|
10
|
+
if (typeof key !== 'string' || allowedArg && !allowedArg.includes(key) || !descriptor || !descriptor.enumerable || !('value' in descriptor)) {
|
|
11
|
+
throw new TypeError('Shelly input contains an unsupported field or accessor.');
|
|
12
|
+
}
|
|
13
|
+
result[key] = descriptor.value as unknown;
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const shellyText = (valueArg: unknown, labelArg: string, maximumArg = 128): string => {
|
|
19
|
+
if (typeof valueArg !== 'string' || !valueArg || valueArg.trim() !== valueArg || valueArg.length > maximumArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
|
|
20
|
+
throw new TypeError(`Shelly ${labelArg} must be bounded text without control characters.`);
|
|
21
|
+
}
|
|
22
|
+
return valueArg;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const integer = (valueArg: unknown, minimumArg: number, maximumArg: number): number => {
|
|
26
|
+
if (typeof valueArg !== 'number' || !Number.isSafeInteger(valueArg) || valueArg < minimumArg || valueArg > maximumArg) {
|
|
27
|
+
throw new TypeError('Shelly integer is outside its supported bounds.');
|
|
28
|
+
}
|
|
29
|
+
return valueArg;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const shellyConfigFields = ['homeId', 'host', 'port', 'protocol', 'expectedDeviceId', 'expectedMacAddress',
|
|
33
|
+
'expectedModel', 'expectedGeneration', 'credentialRef', 'allowControl', 'allowUnencryptedLan',
|
|
34
|
+
'requestTimeoutMs', 'maxPendingOperations', 'maxResponseBytes'] as const;
|
|
35
|
+
|
|
36
|
+
export type TNormalizedShellyConfig = Readonly<Required<Omit<IShellyConfig, 'credentialRef'>> & Pick<IShellyConfig, 'credentialRef'>>;
|
|
37
|
+
|
|
38
|
+
export const normalizeShellyConfig = (valueArg: unknown): TNormalizedShellyConfig => {
|
|
39
|
+
const config = shellyData(valueArg, shellyConfigFields);
|
|
40
|
+
const host = shellyText(config.host, 'host', 253);
|
|
41
|
+
if (/[\s\/?#@]/.test(host)) throw new TypeError('Shelly host must be a hostname or IP address.');
|
|
42
|
+
const protocol = config.protocol ?? 'http';
|
|
43
|
+
if (protocol !== 'http' && protocol !== 'https') throw new TypeError('Shelly protocol must be http or https.');
|
|
44
|
+
const expectedDeviceId = shellyText(config.expectedDeviceId, 'selected device id');
|
|
45
|
+
if (!/^shelly[\w-]+$/i.test(expectedDeviceId)) throw new TypeError('Shelly requires an exact reported device id.');
|
|
46
|
+
const mac = shellyText(config.expectedMacAddress, 'selected MAC', 12);
|
|
47
|
+
if (!/^[0-9a-f]{12}$/i.test(mac) || /^(?:0{12}|f{12})$/i.test(mac) || (Number.parseInt(mac.slice(0, 2), 16) & 1) !== 0) {
|
|
48
|
+
throw new TypeError('Shelly requires an exact usable reported 12-digit MAC.');
|
|
49
|
+
}
|
|
50
|
+
if (config.expectedGeneration !== 2 && config.expectedGeneration !== 3) throw new TypeError('Shelly selected generation must be 2 or 3.');
|
|
51
|
+
if (config.allowControl !== undefined && typeof config.allowControl !== 'boolean' || config.allowUnencryptedLan !== undefined && typeof config.allowUnencryptedLan !== 'boolean') {
|
|
52
|
+
throw new TypeError('Shelly consent settings must be boolean.');
|
|
53
|
+
}
|
|
54
|
+
if (protocol === 'http' && config.allowUnencryptedLan !== true) throw new TypeError('Shelly HTTP requires explicit unencrypted LAN consent.');
|
|
55
|
+
const port = integer(config.port ?? (protocol === 'https' ? 443 : 80), 1, 65535);
|
|
56
|
+
const urlHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
|
|
57
|
+
try { new URL(`${protocol}://${urlHost}:${port}`); } catch { throw new TypeError('Shelly host must be a hostname or IP address.'); }
|
|
58
|
+
return Object.freeze({ homeId: shellyText(config.homeId, 'home ID', 256), host, port, protocol,
|
|
59
|
+
expectedDeviceId, expectedMacAddress: mac.toUpperCase(), expectedModel: shellyText(config.expectedModel, 'selected model'),
|
|
60
|
+
expectedGeneration: config.expectedGeneration,
|
|
61
|
+
...(config.credentialRef === undefined ? {} : { credentialRef: shellyText(config.credentialRef, 'credential reference', 256) }),
|
|
62
|
+
allowControl: config.allowControl === true, allowUnencryptedLan: config.allowUnencryptedLan === true,
|
|
63
|
+
requestTimeoutMs: integer(config.requestTimeoutMs ?? 10000, 10, 60000),
|
|
64
|
+
maxPendingOperations: integer(config.maxPendingOperations ?? 16, 1, 128),
|
|
65
|
+
maxResponseBytes: integer(config.maxResponseBytes ?? 1048576, 1024, 1048576) });
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const normalizeShellyOptions = (valueArg: unknown = {}): Readonly<IShellyIntegrationOptions> => {
|
|
69
|
+
const options = shellyData(valueArg, ['homeId', 'capacity', 'resolvePassword']);
|
|
70
|
+
if (options.resolvePassword !== undefined && typeof options.resolvePassword !== 'function') throw new TypeError('Shelly password resolver must be a function.');
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
...(options.homeId === undefined ? {} : { homeId: shellyText(options.homeId, 'owning home ID', 256) }),
|
|
73
|
+
...(options.capacity === undefined ? {} : { capacity: integer(options.capacity, 1, Number.MAX_SAFE_INTEGER) }),
|
|
74
|
+
...(options.resolvePassword === undefined ? {} : { resolvePassword: options.resolvePassword as IShellyIntegrationOptions['resolvePassword'] }),
|
|
75
|
+
});
|
|
76
|
+
};
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# Sonos
|
|
2
2
|
|
|
3
3
|
The native local runtime uses the public UPnP client and transport/volume controllers
|
|
4
|
-
from `@ecobridge.xyz/devicemanager`. It reads the
|
|
5
|
-
|
|
6
|
-
HTTP/SOAP. No Home Assistant instance
|
|
4
|
+
from `@ecobridge.xyz/devicemanager`. It reads the selected renderer's device
|
|
5
|
+
identity, transport, position, volume, mute, reported topology and service
|
|
6
|
+
capabilities through bounded HTTP/SOAP. No Home Assistant instance, Sonos cloud
|
|
7
|
+
account or invented authentication provider is required for this selected protocol.
|
|
7
8
|
|
|
8
9
|
```typescript
|
|
9
|
-
const
|
|
10
|
+
const integration = new SonosIntegration({ allowControl: true });
|
|
11
|
+
const runtime = await integration.setup({
|
|
12
|
+
homeId: 'home:example',
|
|
10
13
|
host: '192.168.1.55',
|
|
11
14
|
expectedUid: 'RINCON_000E58ABCDEF01400',
|
|
12
15
|
pollIntervalMs: 5000,
|
|
13
|
-
}, { abortSignal });
|
|
16
|
+
}, { homeId: 'home:example', abortSignal });
|
|
14
17
|
|
|
15
18
|
const [device] = await runtime.devices();
|
|
16
19
|
await runtime.writeFeature!({
|
|
@@ -18,57 +21,133 @@ await runtime.writeFeature!({
|
|
|
18
21
|
feature: device.features.find((feature) => feature.id === 'volume')!,
|
|
19
22
|
value: 35,
|
|
20
23
|
});
|
|
21
|
-
await
|
|
24
|
+
await integration.destroy();
|
|
22
25
|
```
|
|
23
26
|
|
|
27
|
+
The embedding host must independently select `context.homeId`; configuration,
|
|
28
|
+
discovery, form values and storage scope cannot select it or grant control. The
|
|
29
|
+
owning home binds once, including after failed setup. An optional constructor
|
|
30
|
+
`homeId` only constrains that explicit context. The exported `SonosClient` also
|
|
31
|
+
requires configuration and context homes before allocating protocol resources:
|
|
32
|
+
`new SonosClient(config, { homeId, abortSignal }, { allowControl: true })`.
|
|
33
|
+
A matching actual `NoSqlConfigStore` is optional and borrowed; the adapter never
|
|
34
|
+
closes the caller's store, database or key provider. This credential-free adapter
|
|
35
|
+
has no persistence requirement and does not write application state to disk.
|
|
36
|
+
|
|
37
|
+
Controls default to denied. Only the embedding host's immutable constructor
|
|
38
|
+
`allowControl: true` enables them; saved configuration and config-flow submissions
|
|
39
|
+
cannot set this option. A home identifier is a host scope, not an authentication
|
|
40
|
+
credential. UPnP assumes a trusted local network. Sonos currently documents its
|
|
41
|
+
UPnP interface as unsupported and provides an owner-controlled connection-security
|
|
42
|
+
setting; the adapter does not change that setting or bypass it.
|
|
43
|
+
|
|
24
44
|
Configuration requires a bare IPv4 address or DNS hostname. The default port is
|
|
25
|
-
1400. Optional
|
|
45
|
+
1400. Optional UUID pinning rejects a different renderer. Whole-operation deadlines
|
|
26
46
|
default to 10 seconds and response bodies to 1 MiB. Production snapshot fixtures,
|
|
27
|
-
credentials in URLs and arbitrary configuration fields are rejected.
|
|
47
|
+
credentials in URLs and arbitrary configuration fields are rejected. Config flow
|
|
48
|
+
captures the selected home and endpoint/identity hint; it exposes host and port,
|
|
49
|
+
rejects a conflicting submitted home, and returns the captured home without
|
|
50
|
+
performing discovery or granting authority.
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
Device IDs are `sonos.player.<encoded-home>.<renderer-uuid>` and entity IDs are
|
|
53
|
+
`media_player.sonos.<encoded-home>.<renderer-uuid>`. Home segments use
|
|
54
|
+
`encodeURIComponent`; UUIDs are lowercase. These home-prefixed IDs intentionally
|
|
55
|
+
replace the previous unscoped IDs. Room renames preserve identity. Group metadata
|
|
56
|
+
comes from fresh reported topology; advertised member URLs are never followed.
|
|
57
|
+
Playback requires the selected renderer to remain its group coordinator. A grouped
|
|
58
|
+
member exposes its actual state and supports its own advertised rendering controls
|
|
59
|
+
without redirecting playback. Bonded satellites require selecting their parent.
|
|
36
60
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
61
|
+
Before each transport effect, the adapter refreshes renderer identity, topology,
|
|
62
|
+
the selected service description and `GetCurrentTransportActions`. Play, Pause,
|
|
63
|
+
Stop, Next and Previous require both a declared action and its current action token.
|
|
64
|
+
`SetAVTransportURI` is declared by its service description; the standard current
|
|
65
|
+
transport list describes actions on the current resource and does not include URI
|
|
66
|
+
assignment. Volume and mute use separate, fresh RenderingControl declarations.
|
|
67
|
+
Volume also checks `GetOutputFixed` when that action is advertised; devices without
|
|
68
|
+
that action are judged by their actual `SetVolume` declaration, without fabricating
|
|
69
|
+
a fixed-output observation. AVTransport actions never grant volume permission.
|
|
70
|
+
Writable flags describe the observed capability and owner permission; they do not
|
|
71
|
+
reserve authority for a later write.
|
|
43
72
|
|
|
44
73
|
Feature writes support integer volume from 0 to 100, boolean mute and playback
|
|
45
74
|
values `playing`, `paused` or `stopped`. Media-player services support `play`,
|
|
46
75
|
`pause`, `stop`, `next_track`, `previous_track`, `volume_set`, `volume_mute` and
|
|
47
76
|
`play_media`. Each service requires an exact selected device or entity target and
|
|
48
77
|
its documented arguments. `volume_level` is a fraction from 0 to 1; `volume` is an
|
|
49
|
-
integer percent.
|
|
50
|
-
|
|
78
|
+
integer percent.
|
|
79
|
+
|
|
80
|
+
Each supported action is sent once and is never automatically replayed. A SOAP
|
|
81
|
+
acknowledgement is followed by fresh independent observations. Returned
|
|
82
|
+
`acknowledgedActions`, `outcome`, `requestedValue` and `observedValue` distinguish
|
|
83
|
+
an observed target, a different observed value, or an observation without a
|
|
84
|
+
comparable target. `success: true` means acknowledged actions and successful fresh
|
|
85
|
+
readback; it does not assert physical or acoustic causation. Results explicitly
|
|
86
|
+
carry `physicalEffectConfirmed: false`; runtime service receipts additionally carry
|
|
87
|
+
`observationAtomic: false`. A failed
|
|
88
|
+
command rejects with `SonosCommandError`, retaining acknowledged actions and a
|
|
89
|
+
conservative `not-sent`, `rejected` or `indeterminate` effect outcome. An acknowledged
|
|
90
|
+
write followed by read failure is indeterminate, without cached success or retry.
|
|
91
|
+
|
|
92
|
+
`play_media` preserves the documented sequential `SetAVTransportURI` then `Play`
|
|
93
|
+
behavior within one admitted operation and one whole-operation deadline. Identity,
|
|
94
|
+
coordinator and relevant action authority are refreshed between those distinct
|
|
95
|
+
steps. If the second step fails, the URI assignment can remain acknowledged and
|
|
96
|
+
is not rolled back or replayed. Separate preflight, action and readback exchanges
|
|
97
|
+
can race another controller or a topology change; UPnP offers no atomic transaction
|
|
98
|
+
across them. Successful readback cannot prove that this action caused the observed
|
|
99
|
+
state or that audio was heard.
|
|
100
|
+
|
|
51
101
|
`play_media` accepts `uri` or `media_content_id`. Supplying `title` also requires
|
|
52
|
-
an explicit `mime_type`, such as `audio/mpeg` or `audio/flac`, for
|
|
53
|
-
metadata. The runtime does not guess
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
102
|
+
an explicit `mime_type`, such as `audio/mpeg` or `audio/flac`, for audio DIDL
|
|
103
|
+
metadata. The runtime does not guess media format. Stream URLs require an explicit
|
|
104
|
+
hostname and `//` authority, using `http:`, `https:`, `x-sonos-http:` or
|
|
105
|
+
`x-rincon-mp3radio:` without embedded credentials. Direct clients use
|
|
106
|
+
`playUri(uri, { title, mimeType }, abortSignal)` and return the acknowledged actions,
|
|
107
|
+
outcome and fresh snapshot as `ISonosControlResult`.
|
|
108
|
+
|
|
109
|
+
Setup, live runtimes, runtime/client operations, config-flow continuations and
|
|
110
|
+
retained failed cleanup share one owner with capacity 16. A live runtime and one
|
|
111
|
+
operation occupy two slots. Admission occurs before protocol allocation or caller
|
|
112
|
+
callbacks. The integration closes its flow and all owned runtimes; runtime destroy
|
|
113
|
+
closes that runtime's lease. Close promises are stable before abort callbacks,
|
|
114
|
+
terminal admission is enforced, and cleanup settles every sibling plus actual
|
|
115
|
+
pending commands and subscriber promises. Failed cleanup retains the actual handle
|
|
116
|
+
until explicit `integration.retryCleanup()` or `client.retryCleanup()` succeeds;
|
|
117
|
+
this retries cleanup only, never an effect. A failed original close promise remains
|
|
118
|
+
failed after retry, preserving its reported outcome.
|
|
57
119
|
|
|
58
120
|
Subscriptions are explicitly `local_poll`, with a default five-second interval.
|
|
59
|
-
Each subscription
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
121
|
+
Each subscription reserves a slot before its initial observation/callback, owns
|
|
122
|
+
one sequential poll, cancellation and actual callback drain, and is limited to
|
|
123
|
+
16 per runtime. Async subscriber promises are awaited even though the generic
|
|
124
|
+
callback signature returns void. Unsubscribe and destroy abort protocol work and
|
|
125
|
+
wait for actual callback completion; a callback that ignores cancellation must
|
|
126
|
+
settle before cleanup completes. Read failures emit unavailable/error events;
|
|
127
|
+
subsequent scheduled reads can report recovery. Subscriber failures remain
|
|
128
|
+
observable through cleanup. There is no GENA push capability, grouping mutation,
|
|
129
|
+
queue management, favorites, alarms or EQ in this runtime.
|
|
130
|
+
|
|
131
|
+
The discovery descriptor owns an active native SSDP probe. Its observation window
|
|
132
|
+
defaults to three seconds, with a separate five-second startup budget. Caller
|
|
133
|
+
cancellation drains sockets and description requests.
|
|
134
|
+
`IDiscoveryContext.networkInterfaces` limits discovery to selected actual
|
|
135
|
+
interfaces; an explicitly empty scope performs no network I/O. Passive SSDP and
|
|
136
|
+
explicit Sonos mDNS/manual candidates use the same strict admission rules.
|
|
137
|
+
Discovered endpoints are verified during setup before becoming available devices.
|
|
64
138
|
|
|
65
139
|
Protocol references:
|
|
66
140
|
|
|
67
141
|
- [devicemanager UPnP contracts](https://code.foss.global/ecobridge.xyz/devicemanager)
|
|
142
|
+
- [UPnP AVTransport:1 specification, especially sections 2.2.26 and 2.4.17](https://upnp.org/specs/av/UPnP-av-AVTransport-v1-Service.pdf)
|
|
143
|
+
- [UPnP RenderingControl:1 specification](https://upnp.org/specs/av/UPnP-av-RenderingControl-v1-Service.pdf)
|
|
144
|
+
- [Sonos connection security settings](https://support.sonos.com/en/article/adjust-connection-security-settings)
|
|
68
145
|
- [SoCo's native Sonos service implementation](https://github.com/SoCo/SoCo/blob/master/soco/services.py)
|
|
69
|
-
- [SoCo's ZoneGroupState
|
|
146
|
+
- [SoCo's ZoneGroupState contract](https://github.com/SoCo/SoCo/blob/master/soco/zonegroupstate.py)
|
|
147
|
+
- [SoCo's current-action and fixed-output implementation](https://github.com/SoCo/SoCo/blob/master/soco/core.py)
|
|
70
148
|
|
|
71
|
-
The
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
149
|
+
The selected wire contract was checked against the standards, maintained primary
|
|
150
|
+
implementation and actual installed public devicemanager 4.8.0 source/declarations.
|
|
151
|
+
Sonos's public developer portal documents its Control API; a complete current
|
|
152
|
+
Sonos-authored local UPnP specification was not found. Verification uses real local
|
|
153
|
+
HTTP/SOAP and SSDP fixtures; owner hardware was not accessed.
|