homey-lib 2.44.5 → 2.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ {
2
+ "type": "number",
3
+ "title": {
4
+ "en": "Target power",
5
+ "nl": "Doelvermogen",
6
+ "de": "Zielleistung",
7
+ "fr": "Puissance cible",
8
+ "it": "Potenza target",
9
+ "sv": "Måleffekt",
10
+ "no": "Ønsket effekt",
11
+ "es": "Potencia objetivo",
12
+ "da": "Måleffekt",
13
+ "ru": "Целевая мощность",
14
+ "pl": "Moc docelowa",
15
+ "ko": "충전전력 제한",
16
+ "ar": "الطاقة المستهدفة"
17
+ },
18
+ "desc": {
19
+ "en": "Target power in watt",
20
+ "nl": "Doelvermogen in watt",
21
+ "de": "Zielleistung in Watt",
22
+ "fr": "Puissance cible en watt",
23
+ "it": "Potenza target in watt",
24
+ "sv": "Måleffekt i watt",
25
+ "no": "Ønsket effekt i watt",
26
+ "es": "Potencia objetivo en vatios",
27
+ "da": "Måleffekt i watt",
28
+ "ru": "Целевая мощность в ваттах",
29
+ "pl": "Moc docelowa w watach",
30
+ "ko": "충전전력 제한(와트)",
31
+ "ar": "الطاقة المستهدفة بالواط"
32
+ },
33
+ "units": {
34
+ "en": "W",
35
+ "nl": "W",
36
+ "de": "W",
37
+ "fr": "W",
38
+ "it": "W",
39
+ "sv": "W",
40
+ "no": "W",
41
+ "es": "W",
42
+ "da": "W",
43
+ "ru": "Вт",
44
+ "pl": "W",
45
+ "ko": "W",
46
+ "ar": "واط"
47
+ },
48
+ "insights": true,
49
+ "chartType": "stepLine",
50
+ "min": -25000,
51
+ "max": 25000,
52
+ "step": 1,
53
+ "decimals": 0,
54
+ "color": "#6DD400",
55
+ "getable": true,
56
+ "setable": true,
57
+ "uiComponent": "slider",
58
+ "minCompatibility": "12.13.0",
59
+ "$flow": {
60
+ "triggers": [
61
+ {
62
+ "id": "target_power_changed",
63
+ "highlight": true,
64
+ "title": {
65
+ "en": "The target power changed",
66
+ "nl": "Het doelvermogen is veranderd",
67
+ "de": "Die Zielleistung hat sich geändert",
68
+ "fr": "La puissance cible a été modifiée",
69
+ "it": "La potenza target è cambiata",
70
+ "sv": "Måleffekten ändrades",
71
+ "no": "Ønsket effekt ble endret",
72
+ "es": "La potencia objetivo ha cambiado",
73
+ "da": "Måleffekten blev ændret",
74
+ "ru": "Целевая мощность изменена",
75
+ "pl": "Moc docelowa uległa zmianie",
76
+ "ko": "충전전력 제한이 변경되면",
77
+ "ar": "تغيرت الطاقة المستهدفة"
78
+ },
79
+ "tokens": [
80
+ {
81
+ "name": "$id",
82
+ "title": "$title",
83
+ "type": "$type",
84
+ "example": 2500
85
+ }
86
+ ]
87
+ }
88
+ ],
89
+ "actions": [
90
+ {
91
+ "id": "target_power_set",
92
+ "highlight": true,
93
+ "title": {
94
+ "en": "Set the target power",
95
+ "nl": "Stel het doelvermogen in",
96
+ "de": "Zielleistung festlegen",
97
+ "fr": "Définir la puissance cible",
98
+ "it": "Imposta la potenza target",
99
+ "sv": "Ställ in måleffekten",
100
+ "no": "Innstill ønsket effekt",
101
+ "es": "Establecer la potencia objetivo",
102
+ "da": "Indstil måleffekten",
103
+ "ru": "Установить целевую мощность",
104
+ "pl": "Ustaw moc docelową",
105
+ "ko": "충전전력 제한 설정",
106
+ "ar": "ضبط الطاقة المستهدفة"
107
+ },
108
+ "hint": {
109
+ "en": "This also switches the target power mode to Homey. For EV chargers, charging is automatically started or stopped.",
110
+ "nl": "Hiermee stel je de doelvermogensmodus in op Homey. Bij een EV-lader wordt het laden automatisch gestart of gestopt.",
111
+ "de": "Dies schaltet auch den Zielleistungsmodus auf Homey um. Bei EV-Ladegeräten wird der Ladevorgang automatisch gestartet oder gestoppt.",
112
+ "fr": "Cela règle également le mode de puissance cible sur Homey. Pour les bornes de recharge, la recharge démarre ou s'arrête automatiquement.",
113
+ "it": "Questo imposta anche la modalità potenza target su Homey. Per i caricatori EV, la ricarica viene avviata o interrotta automaticamente.",
114
+ "sv": "Detta växlar även måleffektläget till Homey. För EV-laddare startas eller stoppas laddningen automatiskt.",
115
+ "no": "Dette bytter også ønsket effekt-modus til Homey. For elbilladere startes eller stoppes lading automatisk.",
116
+ "es": "Esto también cambia el modo de potencia objetivo a Homey. En el caso de cargadores de vehículos eléctricos, la carga se inicia o detiene automáticamente.",
117
+ "da": "Dette skifter også måleffekttilstanden til Homey. For EV-opladere startes eller stoppes opladningen automatisk.",
118
+ "ru": "Эта опция также переключает режим целевой мощности на Homey. Для зарядных станций зарядка автоматически запускается или останавливается.",
119
+ "pl": "To również przełącza tryb mocy docelowej na Homey. W przypadku ładowarek EV ładowanie jest automatycznie uruchamiane lub zatrzymywane.",
120
+ "ko": "이렇게 하면 충전전력 제한 모드도 Homey로 전환돼요. 전기차 충전기의 충전이 상황에 맞게 자동으로 시작되거나 중지되죠.",
121
+ "ar": "يؤدي هذا أيضًا إلى تبديل وضع الطاقة المستهدفة إلى Homey. بالنسبة لشواحن السيارات الكهربائية، يتم بدء الشحن أو إيقافه تلقائيًا."
122
+ },
123
+ "args": [
124
+ {
125
+ "name": "target_power",
126
+ "type": "range",
127
+ "min": -25000,
128
+ "max": 25000,
129
+ "step": 1,
130
+ "label": "W",
131
+ "labelDecimals": 0
132
+ }
133
+ ]
134
+ }
135
+ ]
136
+ }
137
+ }
@@ -0,0 +1,204 @@
1
+ {
2
+ "type": "enum",
3
+ "title": {
4
+ "en": "Target power mode",
5
+ "nl": "Doelvermogensmodus",
6
+ "de": "Zielleistungsmodus",
7
+ "fr": "Mode de puissance cible",
8
+ "it": "Modalità potenza target",
9
+ "sv": "Måleffektläge",
10
+ "no": "Ønsket effekt-modus",
11
+ "es": "Modo de potencia objetivo",
12
+ "da": "Måleffekttilstand",
13
+ "ru": "Режим целевой мощности",
14
+ "pl": "Tryb mocy docelowej",
15
+ "ko": "충전전력 제한 모드",
16
+ "ar": "وضع الطاقة المستهدفة"
17
+ },
18
+ "desc": {
19
+ "en": "The target power control mode",
20
+ "nl": "Besturingsmodus met doelvermogen",
21
+ "de": "Der Zielleistungs-Steuermodus",
22
+ "fr": "Le mode de contrôle de puissance cible",
23
+ "it": "La modalità di controllo della potenza target",
24
+ "sv": "Måleffektens styrläge",
25
+ "no": "Styringsmodus for ønsket effekt",
26
+ "es": "El modo de control de potencia objetivo",
27
+ "da": "Måleffektens kontroltilstand",
28
+ "ru": "Режим управления целевой мощностью",
29
+ "pl": "Tryb sterowania mocą docelową",
30
+ "ko": "충전전력 제한 제어 모드",
31
+ "ar": "وضع التحكم في الطاقة المستهدفة"
32
+ },
33
+ "values": [
34
+ {
35
+ "id": "device",
36
+ "title": {
37
+ "en": "Automatic",
38
+ "nl": "Automatisch",
39
+ "de": "Automatisch",
40
+ "fr": "Automatique",
41
+ "it": "Automatico",
42
+ "sv": "Automatisk",
43
+ "no": "Automatisk",
44
+ "es": "Automático",
45
+ "da": "Automatisk",
46
+ "ru": "Автоматический",
47
+ "pl": "Automatyczny",
48
+ "ko": "자동",
49
+ "ar": "تلقائي"
50
+ }
51
+ },
52
+ {
53
+ "id": "homey",
54
+ "title": {
55
+ "en": "Homey",
56
+ "nl": "Homey",
57
+ "de": "Homey",
58
+ "fr": "Homey",
59
+ "it": "Homey",
60
+ "sv": "Homey",
61
+ "no": "Homey",
62
+ "es": "Homey",
63
+ "da": "Homey",
64
+ "ru": "Homey",
65
+ "pl": "Homey",
66
+ "ko": "Homey",
67
+ "ar": "Homey"
68
+ }
69
+ }
70
+ ],
71
+ "color": "#6DD400",
72
+ "getable": true,
73
+ "setable": true,
74
+ "uiComponent": "picker",
75
+ "minCompatibility": "12.13.0",
76
+ "$flow": {
77
+ "triggers": [
78
+ {
79
+ "id": "target_power_mode_changed",
80
+ "title": {
81
+ "en": "Target power mode changed to",
82
+ "nl": "Doelvermogensmodus is veranderd naar",
83
+ "de": "Zielleistungsmodus hat sich geändert in",
84
+ "fr": "Le mode de puissance cible a été défini sur",
85
+ "it": "La modalità potenza target è cambiata in",
86
+ "sv": "Måleffektläge ändrades till",
87
+ "no": "Ønsket effekt-modus ble endret til",
88
+ "es": "El modo de potencia objetivo ha cambiado a",
89
+ "da": "Måleffekttilstand blev ændret til",
90
+ "ru": "Режим целевой мощности изменен на",
91
+ "pl": "Tryb mocy docelowej zmieniono na",
92
+ "ko": "충전전력 제한 모드가 다음으로 변경되면",
93
+ "ar": "تم تغيير وضع الطاقة المستهدفة إلى"
94
+ },
95
+ "hint": {
96
+ "en": "\"Homey\" means Homey controls the device's target power. \"Automatic\" means the device controls its own power.",
97
+ "nl": "'Homey' betekent dat Homey het doelvermogen van het apparaat regelt. 'Automatisch' betekent dat het apparaat zelf het vermogen regelt.",
98
+ "de": "„Homey“ bedeutet, dass Homey die Zielleistung des Geräts steuert. „Automatisch“ bedeutet, dass das Gerät seine Leistung selbst steuert.",
99
+ "fr": "« Homey » signifie que Homey contrôle la puissance cible de l'appareil. « Automatique » signifie que l'appareil contrôle sa propre puissance.",
100
+ "it": "\"Homey\" significa che Homey controlla la potenza target del dispositivo. \"Automatico\" significa che il dispositivo controlla autonomamente la propria potenza.",
101
+ "sv": "\"Homey\" innebär att Homey styr enhetens måleffekt. \"Automatisk\" innebär att enheten styr sin egen effekt.",
102
+ "no": "«Homey» betyr at Homey styrer ønsket effekt for enheten. «Automatisk» betyr at enheten styrer effekten sin selv.",
103
+ "es": "\"Homey\" significa que Homey controla la potencia objetivo del dispositivo. \"Automático\" significa que el dispositivo controla su propia potencia.",
104
+ "da": "\"Homey\" betyder, at Homey styrer enhedens måleffekt. \"Automatisk\" betyder, at enheden styrer sin egen effekt.",
105
+ "ru": "Режим «Homey» означает, что Homey управляет целевой мощностью устройства. Режим «Автоматический» означает, что устройство само управляет своей мощностью.",
106
+ "pl": "\"Homey\" oznacza, że Homey steruje mocą docelową urządzenia. \"Automatyczny\" oznacza, że urządzenie samo steruje swoją mocą.",
107
+ "ko": "\"Homey\"는 Homey가 제품의 충전전력 제한을 제어한다는 의미예요. \"자동\"은 제품이 자체적으로 충전전력 제한을 제어한다는 의미이고요.",
108
+ "ar": "\"Homey\" يعني أن Homey يتحكم في الطاقة المستهدفة للجهاز. أما \"تلقائي\" يعني أن الجهاز يتحكم في طاقته بنفسه."
109
+ },
110
+ "args": [
111
+ {
112
+ "name": "target_power_mode",
113
+ "type": "dropdown",
114
+ "values": "$values"
115
+ }
116
+ ]
117
+ }
118
+ ],
119
+ "conditions": [
120
+ {
121
+ "id": "target_power_mode_is",
122
+ "title": {
123
+ "en": "Target power mode !{{is|is not}}",
124
+ "nl": "Doelvermogensmodus !{{is|is niet}}",
125
+ "de": "Zielleistungsmodus !{{ist|ist nicht}}",
126
+ "fr": "Le mode de puissance cible !{{est|n'est pas}}",
127
+ "it": "La modalità potenza target !{{è|non è}}",
128
+ "sv": "Måleffektläge !{{är|är inte}}",
129
+ "no": "Ønsket effekt-modus !{{er|er ikke}}",
130
+ "es": "El modo de potencia objetivo !{{es|no es}}",
131
+ "da": "Måleffekttilstand !{{er|er ikke}}",
132
+ "ru": "Режим целевой мощности !{{|не}}",
133
+ "pl": "Tryb mocy docelowej !{{to|to nie}}",
134
+ "ko": "충전전력 제한 모드가 다음!{{이면|이 아니면}}",
135
+ "ar": "وضع الطاقة المستهدفة !{{هو|ليس}}"
136
+ },
137
+ "hint": {
138
+ "en": "\"Homey\" means Homey controls the device's target power. \"Automatic\" means the device controls its own power.",
139
+ "nl": "'Homey' betekent dat Homey het doelvermogen van het apparaat regelt. 'Automatisch' betekent dat het apparaat zelf het vermogen regelt.",
140
+ "de": "„Homey“ bedeutet, dass Homey die Zielleistung des Geräts steuert. „Automatisch“ bedeutet, dass das Gerät seine Leistung selbst steuert.",
141
+ "fr": "« Homey » signifie que Homey contrôle la puissance cible de l'appareil. « Automatique » signifie que l'appareil contrôle sa propre puissance.",
142
+ "it": "\"Homey\" significa che Homey controlla la potenza target del dispositivo. \"Automatico\" significa che il dispositivo controlla autonomamente la propria potenza.",
143
+ "sv": "\"Homey\" innebär att Homey styr enhetens måleffekt. \"Automatisk\" innebär att enheten styr sin egen effekt.",
144
+ "no": "«Homey» betyr at Homey styrer ønsket effekt for enheten. «Automatisk» betyr at enheten styrer effekten sin selv.",
145
+ "es": "\"Homey\" significa que Homey controla la potencia objetivo del dispositivo. \"Automático\" significa que el dispositivo controla su propia potencia.",
146
+ "da": "\"Homey\" betyder, at Homey styrer enhedens måleffekt. \"Automatisk\" betyder, at enheden styrer sin egen effekt.",
147
+ "ru": "Режим «Homey» означает, что Homey управляет целевой мощностью устройства. Режим «Автоматический» означает, что устройство само управляет своей мощностью.",
148
+ "pl": "\"Homey\" oznacza, że Homey steruje mocą docelową urządzenia. \"Automatyczny\" oznacza, że urządzenie samo steruje swoją mocą.",
149
+ "ko": "\"Homey\"는 Homey가 제품의 충전전력 제한을 제어한다는 의미예요. \"자동\"은 제품이 자체적으로 충전전력 제한을 제어한다는 의미이고요.",
150
+ "ar": "\"Homey\" يعني أن Homey يتحكم في الطاقة المستهدفة للجهاز. أما \"تلقائي\" يعني أن الجهاز يتحكم في طاقته بنفسه."
151
+ },
152
+ "args": [
153
+ {
154
+ "name": "target_power_mode",
155
+ "type": "dropdown",
156
+ "values": "$values"
157
+ }
158
+ ]
159
+ }
160
+ ],
161
+ "actions": [
162
+ {
163
+ "id": "target_power_mode_set",
164
+ "title": {
165
+ "en": "Set target power mode to",
166
+ "nl": "Stel de doelvermogensmodus in op",
167
+ "de": "Zielleistungsmodus setzen auf",
168
+ "fr": "Définir le mode de puissance cible sur",
169
+ "it": "Imposta la modalità potenza target su",
170
+ "sv": "Ställ in måleffektläge på",
171
+ "no": "Sett ønsket effekt-modus til",
172
+ "es": "Establecer el modo de potencia objetivo en",
173
+ "da": "Indstil måleffekttilstand til",
174
+ "ru": "Установить режим целевой мощности на",
175
+ "pl": "Ustaw tryb mocy docelowej na",
176
+ "ko": "충전전력 제한 모드를 다음으로 설정",
177
+ "ar": "ضبط وضع الطاقة المستهدفة على"
178
+ },
179
+ "hint": {
180
+ "en": "To control the target power of this device via Homey, select \"Homey\". To let the device determine its own target power, select \"Automatic\".",
181
+ "nl": "Wil je via Homey het doelvermogen van dit apparaat aanpassen, selecteer dan 'Homey'. Wil je dat het apparaat zelf het doelvermogen bepaalt, gebruik dan 'Automatisch'.",
182
+ "de": "Um die Zielleistung dieses Geräts über Homey zu steuern, wähle „Homey” aus. Damit das Gerät seine Zielleistung selbst bestimmt, wähle „Automatisch” aus.",
183
+ "fr": "Pour contrôler la puissance cible de cet appareil via Homey, sélectionnez « Homey ». Pour laisser l'appareil déterminer sa propre puissance cible, sélectionnez « Automatique ».",
184
+ "it": "Per controllare la potenza target di questo dispositivo tramite Homey, seleziona \"Homey\". Per lasciare che il dispositivo determini la propria potenza target, seleziona \"Automatico\".",
185
+ "sv": "För att styra måleffekten för denna enhet via Homey, välj \"Homey\". För att låta enheten bestämma sin egen måleffekt, välj \"Automatisk\".",
186
+ "no": "For å styre ønsket effekt for denne enheten via Homey, velg «Homey». For å la enheten bestemme ønsket effekt selv, velg «Automatisk».",
187
+ "es": "Si deseas controlar la potencia objetivo de este dispositivo a través de Homey, selecciona \"Homey\". Si, en cambio, prefieres que el dispositivo determine su propia potencia objetivo, selecciona \"Automático\".",
188
+ "da": "For at styre måleffekten for denne enhed via Homey skal du vælge \"Homey\". For at lade enheden bestemme sin egen måleffekt skal du vælge \"Automatisk\".",
189
+ "ru": "Чтобы управлять целевой мощностью этого устройства через Homey, выберите «Homey». Чтобы устройство само определяло целевую мощность, выберите режим «Автоматический».",
190
+ "pl": "Aby sterować mocą docelową tego urządzenia przez Homey, wybierz \"Homey\". Aby urządzenie samo określało moc docelową, wybierz \"Automatyczny\".",
191
+ "ko": "이 제품의 충전전력 제한을 Homey를 통해 제어하려면 \"Homey\"를 선택하세요. 제품이 자체적으로 충전전력 제한을 결정할 수 있게 하려면 \"자동\"을 선택하세요.",
192
+ "ar": "للتحكم في الطاقة المستهدفة لهذا الجهاز عبر Homey، اختر \"Homey\". ولترك الجهاز يحدد الطاقة المستهدفة بنفسه، اختر \"تلقائي\"."
193
+ },
194
+ "args": [
195
+ {
196
+ "name": "target_power_mode",
197
+ "type": "dropdown",
198
+ "values": "$values"
199
+ }
200
+ ]
201
+ }
202
+ ]
203
+ }
204
+ }
@@ -163,6 +163,8 @@
163
163
  "target_humidity_max",
164
164
  "target_humidity_min",
165
165
  "target_humidity",
166
+ "target_power",
167
+ "target_power_mode",
166
168
  "target_temperature_level",
167
169
  "target_temperature_max",
168
170
  "target_temperature_min",
package/lib/App/index.js CHANGED
@@ -233,7 +233,7 @@ class App {
233
233
  if (levelPublish && appJson.runtime === 'python' && appJson.pythonPackages !== undefined && appJson.pythonPackages.length > 0) {
234
234
  const missingVenvs = [];
235
235
  for (const platform of App.REQUIRED_PYTHON_PLATFORMS) {
236
- const venvPath = join(this._path, 'venvs', platform);
236
+ const venvPath = join(this._path, 'python_packages', platform);
237
237
  await fs.promises.access(venvPath).catch(() => {
238
238
  missingVenvs.push(platform);
239
239
  })
@@ -368,6 +368,36 @@ class App {
368
368
  }
369
369
  });
370
370
 
371
+ // validate `appJson.drivers[].capabilitiesOptions`
372
+ if (driver.capabilitiesOptions) {
373
+ for (const [capabilityId, capabilityOptions] of Object.entries(driver.capabilitiesOptions)) {
374
+ // Enforce fixed values for target_power_mode — always use the canonical values from the capability definition
375
+ if (Capability.isInstanceOfId(capabilityId, 'target_power_mode')) {
376
+ capabilityOptions.values = Capability.getCapability('target_power_mode').values;
377
+ }
378
+
379
+ // validate target_power exclude range must include 0
380
+ if (Capability.isInstanceOfId(capabilityId, 'target_power')) {
381
+ if (typeof capabilityOptions.excludeMin === 'number' || typeof capabilityOptions.excludeMax === 'number') {
382
+ const excludeMin = typeof capabilityOptions.excludeMin === 'number' ? capabilityOptions.excludeMin : 0;
383
+ const excludeMax = typeof capabilityOptions.excludeMax === 'number' ? capabilityOptions.excludeMax : 0;
384
+ if (excludeMin > 0 || excludeMax < 0) {
385
+ throw new Error(`drivers.${driver.id}.capabilitiesOptions.${capabilityId}.excludeMin/excludeMax must include 0 (excludeMin <= 0 <= excludeMax)`);
386
+ }
387
+ }
388
+ }
389
+
390
+ // validate target_power min/max range must include 0 (all devices need idle)
391
+ if (Capability.isInstanceOfId(capabilityId, 'target_power')) {
392
+ const min = typeof capabilityOptions.min === 'number' ? capabilityOptions.min : 0;
393
+ const max = typeof capabilityOptions.max === 'number' ? capabilityOptions.max : 0;
394
+ if (min > 0 || max < 0) {
395
+ throw new Error(`drivers.${driver.id}.capabilitiesOptions.${capabilityId}.min/max must include 0 (min <= 0 <= max) to allow idle state`);
396
+ }
397
+ }
398
+ }
399
+ }
400
+
371
401
  // validate `appJson.drivers[].pair`
372
402
  if (Array.isArray(driver.pair)) {
373
403
  for (let j = 0; j < driver.pair.length; j++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homey-lib",
3
- "version": "2.44.5",
3
+ "version": "2.45.1",
4
4
  "description": "Shared Library for Homey",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -590,4 +590,246 @@ describe('HomeyLib.App#validate() driver manifest', function() {
590
590
  verified: /zigbee\.endpoints\['1'].bindings\[0] should be number/i,
591
591
  });
592
592
  });
593
+
594
+ /*
595
+ * target_power_mode values validation
596
+ */
597
+
598
+ it('`target_power_mode` with custom values should pass (values are silently replaced)', async function() {
599
+ const app = mockApp({
600
+ ...baseAppManifest,
601
+ compatibility: '>=12.13.0',
602
+ drivers: [{
603
+ ...baseDriverManifest,
604
+ capabilities: ['target_power', 'target_power_mode'],
605
+ capabilitiesOptions: {
606
+ target_power_mode: {
607
+ values: [
608
+ { id: 'custom', title: { en: 'Custom' } },
609
+ ],
610
+ },
611
+ },
612
+ }],
613
+ });
614
+
615
+ await assertValidates(app, {
616
+ debug: true,
617
+ publish: true,
618
+ verified: true,
619
+ });
620
+ });
621
+
622
+ it('`target_power_mode` without values array should pass', async function() {
623
+ const app = mockApp({
624
+ ...baseAppManifest,
625
+ compatibility: '>=12.13.0',
626
+ drivers: [{
627
+ ...baseDriverManifest,
628
+ capabilities: ['target_power', 'target_power_mode'],
629
+ capabilitiesOptions: {
630
+ target_power_mode: {
631
+ title: { en: 'Power Mode' },
632
+ },
633
+ },
634
+ }],
635
+ });
636
+
637
+ await assertValidates(app, {
638
+ debug: true,
639
+ publish: true,
640
+ verified: true,
641
+ });
642
+ });
643
+
644
+ /*
645
+ * target_power exclude validation
646
+ */
647
+
648
+ it('`target_power` exclude must include 0 (excludeMin > 0 should fail)', async function() {
649
+ const app = mockApp({
650
+ ...baseAppManifest,
651
+ compatibility: '>=12.13.0',
652
+ drivers: [{
653
+ ...baseDriverManifest,
654
+ capabilities: ['target_power'],
655
+ capabilitiesOptions: {
656
+ target_power: {
657
+ excludeMin: 100, excludeMax: 1380, // Invalid: excludeMin > 0
658
+ },
659
+ },
660
+ }],
661
+ });
662
+
663
+ await assertValidates(app, {
664
+ debug: /capabilitiesOptions\.target_power\.excludeMin\/excludeMax must include 0/i,
665
+ publish: /capabilitiesOptions\.target_power\.excludeMin\/excludeMax must include 0/i,
666
+ verified: /capabilitiesOptions\.target_power\.excludeMin\/excludeMax must include 0/i,
667
+ });
668
+ });
669
+
670
+ it('`target_power` exclude must include 0 (excludeMax < 0 should fail)', async function() {
671
+ const app = mockApp({
672
+ ...baseAppManifest,
673
+ compatibility: '>=12.13.0',
674
+ drivers: [{
675
+ ...baseDriverManifest,
676
+ capabilities: ['target_power'],
677
+ capabilitiesOptions: {
678
+ target_power: {
679
+ excludeMin: -1380, excludeMax: -100, // Invalid: excludeMax < 0
680
+ },
681
+ },
682
+ }],
683
+ });
684
+
685
+ await assertValidates(app, {
686
+ debug: /capabilitiesOptions\.target_power\.excludeMin\/excludeMax must include 0/i,
687
+ publish: /capabilitiesOptions\.target_power\.excludeMin\/excludeMax must include 0/i,
688
+ verified: /capabilitiesOptions\.target_power\.excludeMin\/excludeMax must include 0/i,
689
+ });
690
+ });
691
+
692
+ it('`target_power` valid exclude that includes 0 should pass', async function() {
693
+ const app = mockApp({
694
+ ...baseAppManifest,
695
+ compatibility: '>=12.13.0',
696
+ drivers: [{
697
+ ...baseDriverManifest,
698
+ capabilities: ['target_power'],
699
+ capabilitiesOptions: {
700
+ target_power: {
701
+ excludeMin: 0, // Valid: includes 0
702
+ excludeMax: 1380,
703
+ },
704
+ },
705
+ }],
706
+ });
707
+
708
+ await assertValidates(app, {
709
+ debug: true,
710
+ publish: true,
711
+ verified: true,
712
+ });
713
+ });
714
+
715
+ it('`target_power` valid bidirectional exclude should pass', async function() {
716
+ const app = mockApp({
717
+ ...baseAppManifest,
718
+ compatibility: '>=12.13.0',
719
+ drivers: [{
720
+ ...baseDriverManifest,
721
+ capabilities: ['target_power'],
722
+ capabilitiesOptions: {
723
+ target_power: {
724
+ min: -11000,
725
+ max: 22000,
726
+ excludeMin: -1380,
727
+ excludeMax: 1380, // Valid: symmetric around 0
728
+ },
729
+ },
730
+ }],
731
+ });
732
+
733
+ await assertValidates(app, {
734
+ debug: true,
735
+ publish: true,
736
+ verified: true,
737
+ });
738
+ });
739
+
740
+ it('`target_power` without excludeMin/excludeMax should pass', async function() {
741
+ const app = mockApp({
742
+ ...baseAppManifest,
743
+ compatibility: '>=12.13.0',
744
+ drivers: [{
745
+ ...baseDriverManifest,
746
+ capabilities: ['target_power'],
747
+ capabilitiesOptions: {
748
+ target_power: {
749
+ min: -5000,
750
+ max: 5000,
751
+ },
752
+ },
753
+ }],
754
+ });
755
+
756
+ await assertValidates(app, {
757
+ debug: true,
758
+ publish: true,
759
+ verified: true,
760
+ });
761
+ });
762
+
763
+ /*
764
+ * target_power min/max validation (all devices)
765
+ */
766
+
767
+ it('`target_power` min/max must include 0 (min > 0 should fail)', async function() {
768
+ const app = mockApp({
769
+ ...baseAppManifest,
770
+ compatibility: '>=12.13.0',
771
+ drivers: [{
772
+ ...baseDriverManifest,
773
+ capabilities: ['target_power'],
774
+ capabilitiesOptions: {
775
+ target_power: {
776
+ min: 100, // Invalid: min > 0
777
+ max: 5000,
778
+ },
779
+ },
780
+ }],
781
+ });
782
+
783
+ await assertValidates(app, {
784
+ debug: /capabilitiesOptions\.target_power\.min\/max must include 0/i,
785
+ publish: /capabilitiesOptions\.target_power\.min\/max must include 0/i,
786
+ verified: /capabilitiesOptions\.target_power\.min\/max must include 0/i,
787
+ });
788
+ });
789
+
790
+ it('`target_power` min/max must include 0 (max < 0 should fail)', async function() {
791
+ const app = mockApp({
792
+ ...baseAppManifest,
793
+ compatibility: '>=12.13.0',
794
+ drivers: [{
795
+ ...baseDriverManifest,
796
+ capabilities: ['target_power'],
797
+ capabilitiesOptions: {
798
+ target_power: {
799
+ min: -5000,
800
+ max: -100, // Invalid: max < 0
801
+ },
802
+ },
803
+ }],
804
+ });
805
+
806
+ await assertValidates(app, {
807
+ debug: /capabilitiesOptions\.target_power\.min\/max must include 0/i,
808
+ publish: /capabilitiesOptions\.target_power\.min\/max must include 0/i,
809
+ verified: /capabilitiesOptions\.target_power\.min\/max must include 0/i,
810
+ });
811
+ });
812
+
813
+ it('`target_power` valid min/max that includes 0 should pass', async function() {
814
+ const app = mockApp({
815
+ ...baseAppManifest,
816
+ compatibility: '>=12.13.0',
817
+ drivers: [{
818
+ ...baseDriverManifest,
819
+ capabilities: ['target_power'],
820
+ capabilitiesOptions: {
821
+ target_power: {
822
+ min: -5000,
823
+ max: 5000, // Valid: includes 0
824
+ },
825
+ },
826
+ }],
827
+ });
828
+
829
+ await assertValidates(app, {
830
+ debug: true,
831
+ publish: true,
832
+ verified: true,
833
+ });
834
+ });
593
835
  });