meross-cli 0.1.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/cli/commands/control/execute.js +23 -0
  5. package/cli/commands/control/index.js +12 -0
  6. package/cli/commands/control/menu.js +193 -0
  7. package/cli/commands/control/params/generic.js +229 -0
  8. package/cli/commands/control/params/index.js +56 -0
  9. package/cli/commands/control/params/light.js +188 -0
  10. package/cli/commands/control/params/thermostat.js +166 -0
  11. package/cli/commands/control/params/timer.js +242 -0
  12. package/cli/commands/control/params/trigger.js +206 -0
  13. package/cli/commands/dump.js +35 -0
  14. package/cli/commands/index.js +34 -0
  15. package/cli/commands/info.js +221 -0
  16. package/cli/commands/list.js +112 -0
  17. package/cli/commands/mqtt.js +187 -0
  18. package/cli/commands/sniffer/device-sniffer.js +217 -0
  19. package/cli/commands/sniffer/fake-app.js +233 -0
  20. package/cli/commands/sniffer/index.js +7 -0
  21. package/cli/commands/sniffer/message-queue.js +65 -0
  22. package/cli/commands/sniffer/sniffer-menu.js +676 -0
  23. package/cli/commands/stats.js +90 -0
  24. package/cli/commands/status/device-status.js +1403 -0
  25. package/cli/commands/status/hub-status.js +72 -0
  26. package/cli/commands/status/index.js +50 -0
  27. package/cli/commands/status/subdevices/hub-smoke-detector.js +82 -0
  28. package/cli/commands/status/subdevices/hub-temp-hum-sensor.js +43 -0
  29. package/cli/commands/status/subdevices/hub-thermostat-valve.js +83 -0
  30. package/cli/commands/status/subdevices/hub-water-leak-sensor.js +27 -0
  31. package/cli/commands/status/subdevices/index.js +23 -0
  32. package/cli/commands/test/index.js +185 -0
  33. package/cli/config/users.js +108 -0
  34. package/cli/control-registry.js +875 -0
  35. package/cli/helpers/client.js +89 -0
  36. package/cli/helpers/meross.js +106 -0
  37. package/cli/menu/index.js +10 -0
  38. package/cli/menu/main.js +648 -0
  39. package/cli/menu/settings.js +789 -0
  40. package/cli/meross-cli.js +547 -0
  41. package/cli/tests/README.md +365 -0
  42. package/cli/tests/test-alarm.js +144 -0
  43. package/cli/tests/test-child-lock.js +248 -0
  44. package/cli/tests/test-config.js +133 -0
  45. package/cli/tests/test-control.js +189 -0
  46. package/cli/tests/test-diffuser.js +505 -0
  47. package/cli/tests/test-dnd.js +246 -0
  48. package/cli/tests/test-electricity.js +209 -0
  49. package/cli/tests/test-encryption.js +281 -0
  50. package/cli/tests/test-garage.js +259 -0
  51. package/cli/tests/test-helper.js +313 -0
  52. package/cli/tests/test-hub-mts100.js +355 -0
  53. package/cli/tests/test-hub-sensors.js +489 -0
  54. package/cli/tests/test-light.js +253 -0
  55. package/cli/tests/test-presence.js +497 -0
  56. package/cli/tests/test-registry.js +419 -0
  57. package/cli/tests/test-roller-shutter.js +628 -0
  58. package/cli/tests/test-runner.js +415 -0
  59. package/cli/tests/test-runtime.js +234 -0
  60. package/cli/tests/test-screen.js +133 -0
  61. package/cli/tests/test-sensor-history.js +146 -0
  62. package/cli/tests/test-smoke-config.js +138 -0
  63. package/cli/tests/test-spray.js +131 -0
  64. package/cli/tests/test-temp-unit.js +133 -0
  65. package/cli/tests/test-template.js +238 -0
  66. package/cli/tests/test-thermostat.js +919 -0
  67. package/cli/tests/test-timer.js +372 -0
  68. package/cli/tests/test-toggle.js +342 -0
  69. package/cli/tests/test-trigger.js +279 -0
  70. package/cli/utils/display.js +86 -0
  71. package/cli/utils/terminal.js +137 -0
  72. package/package.json +53 -0
@@ -0,0 +1,919 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Thermostat Device Tests
5
+ * Tests thermostat mode control, temperature settings, and additional features
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+ const { ThermostatMode } = require('meross-iot');
10
+
11
+ const metadata = {
12
+ name: 'thermostat',
13
+ description: 'Tests thermostat mode control, temperature settings, and additional features',
14
+ requiredAbilities: ['Appliance.Control.Thermostat.Mode'],
15
+ minDevices: 1
16
+ };
17
+
18
+ async function runTests(context) {
19
+ const { manager, devices, options = {} } = context;
20
+ const timeout = options.timeout || 30000;
21
+ const results = [];
22
+
23
+ // If no devices provided, discover them
24
+ let testDevices = devices || [];
25
+ if (testDevices.length === 0) {
26
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.Thermostat.Mode', OnlineStatus.ONLINE);
27
+ }
28
+
29
+ // Wait for devices to be connected
30
+ for (const device of testDevices) {
31
+ await waitForDeviceConnection(device, timeout);
32
+ await device.getThermostatMode();
33
+ await new Promise(resolve => setTimeout(resolve, 1000));
34
+ }
35
+
36
+ if (testDevices.length === 0) {
37
+ results.push({
38
+ name: 'should turn thermostat on and off',
39
+ passed: false,
40
+ skipped: true,
41
+ error: 'No thermostat device has been found to run this test',
42
+ device: null
43
+ });
44
+ return results;
45
+ }
46
+
47
+ const testDevice = testDevices[0];
48
+ const deviceName = getDeviceName(testDevice);
49
+
50
+ // Test 1: Turn thermostat on and off
51
+ try {
52
+ await testDevice.getThermostatMode();
53
+ const state = testDevice.getCachedThermostatState(0);
54
+
55
+ if (!state) {
56
+ results.push({
57
+ name: 'should turn thermostat on and off',
58
+ passed: false,
59
+ skipped: false,
60
+ error: 'Could not get cached thermostat state',
61
+ device: deviceName
62
+ });
63
+ } else {
64
+ const toggledState = !state.isOn;
65
+
66
+ // Set the new state
67
+ await testDevice.setThermostatMode({
68
+ channel: 0,
69
+ onoff: toggledState ? 1 : 0,
70
+ partialUpdate: true
71
+ });
72
+
73
+ await new Promise(resolve => setTimeout(resolve, 1000));
74
+
75
+ // Refresh state to get updated on/off status
76
+ await testDevice.getThermostatMode();
77
+
78
+ // Verify the state
79
+ const newState = testDevice.getCachedThermostatState(0);
80
+
81
+ if (!newState || newState.isOn !== toggledState) {
82
+ results.push({
83
+ name: 'should turn thermostat on and off',
84
+ passed: false,
85
+ skipped: false,
86
+ error: `State mismatch. Expected ${toggledState}, got ${newState?.isOn}`,
87
+ device: deviceName
88
+ });
89
+ } else {
90
+ results.push({
91
+ name: 'should turn thermostat on and off',
92
+ passed: true,
93
+ skipped: false,
94
+ error: null,
95
+ device: deviceName
96
+ });
97
+ }
98
+ }
99
+ } catch (error) {
100
+ results.push({
101
+ name: 'should turn thermostat on and off',
102
+ passed: false,
103
+ skipped: false,
104
+ error: error.message,
105
+ device: deviceName
106
+ });
107
+ }
108
+
109
+ // Test 2: Read ambient temperature
110
+ try {
111
+ await testDevice.getThermostatMode();
112
+ const state = testDevice.getCachedThermostatState(0);
113
+
114
+ if (!state) {
115
+ results.push({
116
+ name: 'should read ambient temperature',
117
+ passed: false,
118
+ skipped: false,
119
+ error: 'Could not get cached thermostat state',
120
+ device: deviceName
121
+ });
122
+ } else {
123
+ const temperature = state.currentTemperatureCelsius;
124
+
125
+ if (typeof temperature !== 'number') {
126
+ results.push({
127
+ name: 'should read ambient temperature',
128
+ passed: false,
129
+ skipped: false,
130
+ error: `Temperature is not a number: ${temperature}`,
131
+ device: deviceName
132
+ });
133
+ } else {
134
+ results.push({
135
+ name: 'should read ambient temperature',
136
+ passed: true,
137
+ skipped: false,
138
+ error: null,
139
+ device: deviceName,
140
+ details: { temperature: temperature }
141
+ });
142
+ }
143
+ }
144
+ } catch (error) {
145
+ results.push({
146
+ name: 'should read ambient temperature',
147
+ passed: false,
148
+ skipped: false,
149
+ error: error.message,
150
+ device: deviceName
151
+ });
152
+ }
153
+
154
+ // Test 3: Change thermostat mode
155
+ try {
156
+ await testDevice.getThermostatMode();
157
+ const state = testDevice.getCachedThermostatState(0);
158
+
159
+ if (!state || state.mode === undefined) {
160
+ results.push({
161
+ name: 'should change thermostat mode',
162
+ passed: false,
163
+ skipped: true,
164
+ error: 'Could not get current mode or mode is undefined',
165
+ device: deviceName
166
+ });
167
+ } else {
168
+ // Get available modes (using enum values)
169
+ const currentMode = state.mode;
170
+ const modes = [
171
+ ThermostatMode.HEAT,
172
+ ThermostatMode.COOL,
173
+ ThermostatMode.ECONOMY,
174
+ ThermostatMode.AUTO,
175
+ ThermostatMode.MANUAL
176
+ ].filter(m => m !== currentMode);
177
+
178
+ if (modes.length === 0) {
179
+ results.push({
180
+ name: 'should change thermostat mode',
181
+ passed: false,
182
+ skipped: true,
183
+ error: 'No alternative mode available',
184
+ device: deviceName
185
+ });
186
+ } else {
187
+ const targetMode = modes[Math.floor(Math.random() * modes.length)];
188
+
189
+ await testDevice.setThermostatMode({
190
+ channel: 0,
191
+ mode: targetMode
192
+ });
193
+
194
+ await new Promise(resolve => setTimeout(resolve, 1000));
195
+
196
+ // Refresh state to get updated mode
197
+ await testDevice.getThermostatMode();
198
+
199
+ const newState = testDevice.getCachedThermostatState(0);
200
+
201
+ if (!newState || newState.mode !== targetMode) {
202
+ results.push({
203
+ name: 'should change thermostat mode',
204
+ passed: false,
205
+ skipped: false,
206
+ error: `Mode mismatch. Expected ${targetMode}, got ${newState?.mode}`,
207
+ device: deviceName
208
+ });
209
+ } else {
210
+ results.push({
211
+ name: 'should change thermostat mode',
212
+ passed: true,
213
+ skipped: false,
214
+ error: null,
215
+ device: deviceName
216
+ });
217
+ }
218
+ }
219
+ }
220
+ } catch (error) {
221
+ results.push({
222
+ name: 'should change thermostat mode',
223
+ passed: false,
224
+ skipped: false,
225
+ error: error.message,
226
+ device: deviceName
227
+ });
228
+ }
229
+
230
+ // Test 4: Set heat temperature
231
+ try {
232
+ await testDevice.getThermostatMode();
233
+ const state = testDevice.getCachedThermostatState(0);
234
+
235
+ if (!state) {
236
+ results.push({
237
+ name: 'should set heat temperature',
238
+ passed: false,
239
+ skipped: true,
240
+ error: 'Could not get cached thermostat state',
241
+ device: deviceName
242
+ });
243
+ } else if (state.minTemperatureCelsius === undefined || state.maxTemperatureCelsius === undefined) {
244
+ results.push({
245
+ name: 'should set heat temperature',
246
+ passed: false,
247
+ skipped: true,
248
+ error: 'Device does not support temperature range',
249
+ device: deviceName
250
+ });
251
+ } else {
252
+ const minTemp = state.minTemperatureCelsius;
253
+ const maxTemp = state.maxTemperatureCelsius;
254
+ const targetTemp = minTemp + Math.random() * (maxTemp - minTemp);
255
+ const alignedTemp = Math.round(targetTemp * 2) / 2; // Round to 0.5
256
+
257
+ // Set heat temperature
258
+ await testDevice.setThermostatMode({
259
+ channel: 0,
260
+ heatTemperature: alignedTemp,
261
+ partialUpdate: true
262
+ });
263
+
264
+ await new Promise(resolve => setTimeout(resolve, 1000));
265
+
266
+ // Refresh state to get updated temperature
267
+ await testDevice.getThermostatMode();
268
+
269
+ // Verify heat temperature was set
270
+ const stateAfterTemp = testDevice.getCachedThermostatState(0);
271
+
272
+ if (!stateAfterTemp || stateAfterTemp.heatTemperatureCelsius !== alignedTemp) {
273
+ results.push({
274
+ name: 'should set heat temperature',
275
+ passed: false,
276
+ skipped: false,
277
+ error: `Temperature mismatch. Expected ${alignedTemp}, got ${stateAfterTemp?.heatTemperatureCelsius}`,
278
+ device: deviceName
279
+ });
280
+ } else {
281
+ // Set heat mode
282
+ await testDevice.setThermostatMode({
283
+ channel: 0,
284
+ mode: ThermostatMode.HEAT
285
+ });
286
+
287
+ await new Promise(resolve => setTimeout(resolve, 1000));
288
+
289
+ // Refresh state to get updated mode
290
+ await testDevice.getThermostatMode();
291
+
292
+ const newState = testDevice.getCachedThermostatState(0);
293
+
294
+ if (!newState || newState.mode !== ThermostatMode.HEAT) {
295
+ results.push({
296
+ name: 'should set heat temperature',
297
+ passed: false,
298
+ skipped: false,
299
+ error: `Mode mismatch. Expected ${ThermostatMode.HEAT}, got ${newState?.mode}`,
300
+ device: deviceName
301
+ });
302
+ } else {
303
+ results.push({
304
+ name: 'should set heat temperature',
305
+ passed: true,
306
+ skipped: false,
307
+ error: null,
308
+ device: deviceName
309
+ });
310
+ }
311
+ }
312
+ }
313
+ } catch (error) {
314
+ results.push({
315
+ name: 'should set heat temperature',
316
+ passed: false,
317
+ skipped: false,
318
+ error: error.message,
319
+ device: deviceName
320
+ });
321
+ }
322
+
323
+ // Test 5: Set eco temperature
324
+ try {
325
+ await testDevice.getThermostatMode();
326
+ const state = testDevice.getCachedThermostatState(0);
327
+
328
+ if (!state || state.minTemperatureCelsius === undefined || state.maxTemperatureCelsius === undefined) {
329
+ results.push({
330
+ name: 'should set eco temperature',
331
+ passed: false,
332
+ skipped: true,
333
+ error: 'Device does not support temperature range',
334
+ device: deviceName
335
+ });
336
+ } else {
337
+ const minTemp = state.minTemperatureCelsius;
338
+ const maxTemp = state.maxTemperatureCelsius;
339
+ const targetTemp = minTemp + Math.random() * (maxTemp - minTemp);
340
+ const alignedTemp = Math.round(targetTemp * 2) / 2;
341
+
342
+ // Set eco temperature
343
+ await testDevice.setThermostatMode({
344
+ channel: 0,
345
+ ecoTemperature: alignedTemp,
346
+ partialUpdate: true
347
+ });
348
+
349
+ await new Promise(resolve => setTimeout(resolve, 1000));
350
+
351
+ await testDevice.getThermostatMode();
352
+
353
+ const stateAfterTemp = testDevice.getCachedThermostatState(0);
354
+
355
+ if (!stateAfterTemp || stateAfterTemp.ecoTemperatureCelsius !== alignedTemp) {
356
+ results.push({
357
+ name: 'should set eco temperature',
358
+ passed: false,
359
+ skipped: false,
360
+ error: `Temperature mismatch. Expected ${alignedTemp}, got ${stateAfterTemp?.ecoTemperatureCelsius}`,
361
+ device: deviceName
362
+ });
363
+ } else {
364
+ // Set eco mode
365
+ await testDevice.setThermostatMode({
366
+ channel: 0,
367
+ mode: ThermostatMode.ECONOMY
368
+ });
369
+
370
+ await new Promise(resolve => setTimeout(resolve, 1000));
371
+
372
+ await testDevice.getThermostatMode();
373
+
374
+ const newState = testDevice.getCachedThermostatState(0);
375
+
376
+ if (!newState || newState.mode !== ThermostatMode.ECONOMY) {
377
+ results.push({
378
+ name: 'should set eco temperature',
379
+ passed: false,
380
+ skipped: false,
381
+ error: `Mode mismatch. Expected ${ThermostatMode.ECONOMY}, got ${newState?.mode}`,
382
+ device: deviceName
383
+ });
384
+ } else {
385
+ results.push({
386
+ name: 'should set eco temperature',
387
+ passed: true,
388
+ skipped: false,
389
+ error: null,
390
+ device: deviceName
391
+ });
392
+ }
393
+ }
394
+ }
395
+ } catch (error) {
396
+ results.push({
397
+ name: 'should set eco temperature',
398
+ passed: false,
399
+ skipped: false,
400
+ error: error.message,
401
+ device: deviceName
402
+ });
403
+ }
404
+
405
+ // Test 6: Set cool temperature
406
+ try {
407
+ await testDevice.getThermostatMode();
408
+ const state = testDevice.getCachedThermostatState(0);
409
+
410
+ if (!state || state.minTemperatureCelsius === undefined || state.maxTemperatureCelsius === undefined) {
411
+ results.push({
412
+ name: 'should set cool temperature',
413
+ passed: false,
414
+ skipped: true,
415
+ error: 'Device does not support temperature range',
416
+ device: deviceName
417
+ });
418
+ } else {
419
+ const minTemp = state.minTemperatureCelsius;
420
+ const maxTemp = state.maxTemperatureCelsius;
421
+ const targetTemp = minTemp + Math.random() * (maxTemp - minTemp);
422
+ const alignedTemp = Math.round(targetTemp * 2) / 2;
423
+
424
+ await testDevice.setThermostatMode({
425
+ channel: 0,
426
+ coolTemperature: alignedTemp,
427
+ partialUpdate: true
428
+ });
429
+
430
+ await new Promise(resolve => setTimeout(resolve, 1000));
431
+
432
+ await testDevice.getThermostatMode();
433
+
434
+ const stateAfterTemp = testDevice.getCachedThermostatState(0);
435
+
436
+ if (!stateAfterTemp || stateAfterTemp.coolTemperatureCelsius !== alignedTemp) {
437
+ results.push({
438
+ name: 'should set cool temperature',
439
+ passed: false,
440
+ skipped: false,
441
+ error: `Temperature mismatch. Expected ${alignedTemp}, got ${stateAfterTemp?.coolTemperatureCelsius}`,
442
+ device: deviceName
443
+ });
444
+ } else {
445
+ await testDevice.setThermostatMode({
446
+ channel: 0,
447
+ mode: ThermostatMode.COOL
448
+ });
449
+
450
+ await new Promise(resolve => setTimeout(resolve, 1000));
451
+
452
+ await testDevice.getThermostatMode();
453
+
454
+ const newState = testDevice.getCachedThermostatState(0);
455
+
456
+ if (!newState || newState.mode !== ThermostatMode.COOL) {
457
+ results.push({
458
+ name: 'should set cool temperature',
459
+ passed: false,
460
+ skipped: false,
461
+ error: `Mode mismatch. Expected ${ThermostatMode.COOL}, got ${newState?.mode}`,
462
+ device: deviceName
463
+ });
464
+ } else {
465
+ results.push({
466
+ name: 'should set cool temperature',
467
+ passed: true,
468
+ skipped: false,
469
+ error: null,
470
+ device: deviceName
471
+ });
472
+ }
473
+ }
474
+ }
475
+ } catch (error) {
476
+ results.push({
477
+ name: 'should set cool temperature',
478
+ passed: false,
479
+ skipped: false,
480
+ error: error.message,
481
+ device: deviceName
482
+ });
483
+ }
484
+
485
+ // Test 7: Set manual temperature
486
+ try {
487
+ await testDevice.getThermostatMode();
488
+ const state = testDevice.getCachedThermostatState(0);
489
+
490
+ if (!state || state.minTemperatureCelsius === undefined || state.maxTemperatureCelsius === undefined) {
491
+ results.push({
492
+ name: 'should set manual temperature',
493
+ passed: false,
494
+ skipped: true,
495
+ error: 'Device does not support temperature range',
496
+ device: deviceName
497
+ });
498
+ } else {
499
+ const minTemp = state.minTemperatureCelsius;
500
+ const maxTemp = state.maxTemperatureCelsius;
501
+ const targetTemp = minTemp + Math.random() * (maxTemp - minTemp);
502
+ const alignedTemp = Math.round(targetTemp * 2) / 2;
503
+
504
+ await testDevice.setThermostatMode({
505
+ channel: 0,
506
+ manualTemperature: alignedTemp,
507
+ partialUpdate: true
508
+ });
509
+
510
+ await new Promise(resolve => setTimeout(resolve, 1000));
511
+
512
+ await testDevice.getThermostatMode();
513
+
514
+ const stateAfterTemp = testDevice.getCachedThermostatState(0);
515
+
516
+ if (!stateAfterTemp || stateAfterTemp.manualTemperatureCelsius !== alignedTemp) {
517
+ results.push({
518
+ name: 'should set manual temperature',
519
+ passed: false,
520
+ skipped: false,
521
+ error: `Temperature mismatch. Expected ${alignedTemp}, got ${stateAfterTemp?.manualTemperatureCelsius}`,
522
+ device: deviceName
523
+ });
524
+ } else {
525
+ await testDevice.setThermostatMode({
526
+ channel: 0,
527
+ mode: ThermostatMode.MANUAL
528
+ });
529
+
530
+ await new Promise(resolve => setTimeout(resolve, 1000));
531
+
532
+ await testDevice.getThermostatMode();
533
+
534
+ const newState = testDevice.getCachedThermostatState(0);
535
+
536
+ if (!newState || newState.mode !== ThermostatMode.MANUAL) {
537
+ results.push({
538
+ name: 'should set manual temperature',
539
+ passed: false,
540
+ skipped: false,
541
+ error: `Mode mismatch. Expected ${ThermostatMode.MANUAL}, got ${newState?.mode}`,
542
+ device: deviceName
543
+ });
544
+ } else {
545
+ results.push({
546
+ name: 'should set manual temperature',
547
+ passed: true,
548
+ skipped: false,
549
+ error: null,
550
+ device: deviceName
551
+ });
552
+ }
553
+ }
554
+ }
555
+ } catch (error) {
556
+ results.push({
557
+ name: 'should set manual temperature',
558
+ passed: false,
559
+ skipped: false,
560
+ error: error.message,
561
+ device: deviceName
562
+ });
563
+ }
564
+
565
+ // Test 8: Turn thermostat on and off multiple times
566
+ try {
567
+ await testDevice.getThermostatMode();
568
+
569
+ // Turn off
570
+ await testDevice.setThermostatMode({
571
+ channel: 0,
572
+ onoff: 0,
573
+ partialUpdate: true
574
+ });
575
+ await new Promise(resolve => setTimeout(resolve, 1000));
576
+ await testDevice.getThermostatMode();
577
+
578
+ const state1 = testDevice.getCachedThermostatState(0);
579
+ if (!state1 || state1.isOn !== false) {
580
+ results.push({
581
+ name: 'should turn thermostat on and off multiple times',
582
+ passed: false,
583
+ skipped: false,
584
+ error: 'Failed to turn off. Expected false, got ' + state1?.isOn,
585
+ device: deviceName
586
+ });
587
+ return results;
588
+ }
589
+
590
+ // Turn on
591
+ await testDevice.setThermostatMode({
592
+ channel: 0,
593
+ onoff: 1,
594
+ partialUpdate: true
595
+ });
596
+ await new Promise(resolve => setTimeout(resolve, 1000));
597
+ await testDevice.getThermostatMode();
598
+
599
+ const state2 = testDevice.getCachedThermostatState(0);
600
+ if (!state2 || state2.isOn !== true) {
601
+ results.push({
602
+ name: 'should turn thermostat on and off multiple times',
603
+ passed: false,
604
+ skipped: false,
605
+ error: 'Failed to turn on. Expected true, got ' + state2?.isOn,
606
+ device: deviceName
607
+ });
608
+ return results;
609
+ }
610
+
611
+ // Turn off again
612
+ await testDevice.setThermostatMode({
613
+ channel: 0,
614
+ onoff: 0,
615
+ partialUpdate: true
616
+ });
617
+ await new Promise(resolve => setTimeout(resolve, 1000));
618
+ await testDevice.getThermostatMode();
619
+
620
+ const state3 = testDevice.getCachedThermostatState(0);
621
+ if (!state3 || state3.isOn !== false) {
622
+ results.push({
623
+ name: 'should turn thermostat on and off multiple times',
624
+ passed: false,
625
+ skipped: false,
626
+ error: 'Failed to turn off again. Expected false, got ' + state3?.isOn,
627
+ device: deviceName
628
+ });
629
+ } else {
630
+ results.push({
631
+ name: 'should turn thermostat on and off multiple times',
632
+ passed: true,
633
+ skipped: false,
634
+ error: null,
635
+ device: deviceName
636
+ });
637
+ }
638
+ } catch (error) {
639
+ results.push({
640
+ name: 'should turn thermostat on and off multiple times',
641
+ passed: false,
642
+ skipped: false,
643
+ error: error.message,
644
+ device: deviceName
645
+ });
646
+ }
647
+
648
+ // Test 9: Get thermostat window opened status
649
+ try {
650
+ if (typeof testDevice.getThermostatWindowOpened !== 'function' ||
651
+ typeof testDevice.setThermostatWindowOpened !== 'function') {
652
+ results.push({
653
+ name: 'should get and set thermostat window opened status',
654
+ passed: false,
655
+ skipped: true,
656
+ error: 'Device does not support window opened feature',
657
+ device: deviceName
658
+ });
659
+ } else {
660
+ const windowStatus = await testDevice.getThermostatWindowOpened();
661
+
662
+ if (!windowStatus) {
663
+ results.push({
664
+ name: 'should get and set thermostat window opened status',
665
+ passed: false,
666
+ skipped: false,
667
+ error: 'getThermostatWindowOpened returned null or undefined',
668
+ device: deviceName
669
+ });
670
+ } else {
671
+ // Note: We don't actually toggle the window status as it's a physical state
672
+ // that shouldn't be changed during testing. We just verify the method works.
673
+ results.push({
674
+ name: 'should get and set thermostat window opened status',
675
+ passed: true,
676
+ skipped: false,
677
+ error: null,
678
+ device: deviceName,
679
+ details: { windowStatus: windowStatus }
680
+ });
681
+ }
682
+ }
683
+ } catch (error) {
684
+ results.push({
685
+ name: 'should get and set thermostat window opened status',
686
+ passed: false,
687
+ skipped: false,
688
+ error: error.message,
689
+ device: deviceName
690
+ });
691
+ }
692
+
693
+ // Test 10: Get thermostat mode B
694
+ try {
695
+ if (typeof testDevice.getThermostatModeB !== 'function') {
696
+ results.push({
697
+ name: 'should get thermostat mode B',
698
+ passed: false,
699
+ skipped: true,
700
+ error: 'Device does not support getThermostatModeB',
701
+ device: deviceName
702
+ });
703
+ } else {
704
+ const response = await testDevice.getThermostatModeB({ channel: 0 });
705
+
706
+ if (!response) {
707
+ results.push({
708
+ name: 'should get thermostat mode B',
709
+ passed: false,
710
+ skipped: false,
711
+ error: 'getThermostatModeB returned null or undefined',
712
+ device: deviceName
713
+ });
714
+ } else {
715
+ results.push({
716
+ name: 'should get thermostat mode B',
717
+ passed: true,
718
+ skipped: false,
719
+ error: null,
720
+ device: deviceName,
721
+ details: { modeB: response }
722
+ });
723
+ }
724
+ }
725
+ } catch (error) {
726
+ if (error.message && error.message.includes('does not support')) {
727
+ results.push({
728
+ name: 'should get thermostat mode B',
729
+ passed: false,
730
+ skipped: true,
731
+ error: `Device does not support ModeB: ${error.message}`,
732
+ device: deviceName
733
+ });
734
+ } else {
735
+ results.push({
736
+ name: 'should get thermostat mode B',
737
+ passed: false,
738
+ skipped: false,
739
+ error: error.message,
740
+ device: deviceName
741
+ });
742
+ }
743
+ }
744
+
745
+ // Test 11: Get thermostat schedule
746
+ try {
747
+ if (typeof testDevice.getThermostatSchedule !== 'function') {
748
+ results.push({
749
+ name: 'should get thermostat schedule',
750
+ passed: false,
751
+ skipped: true,
752
+ error: 'Device does not support getThermostatSchedule',
753
+ device: deviceName
754
+ });
755
+ } else {
756
+ const response = await testDevice.getThermostatSchedule({ channel: 0 });
757
+
758
+ if (!response) {
759
+ results.push({
760
+ name: 'should get thermostat schedule',
761
+ passed: false,
762
+ skipped: false,
763
+ error: 'getThermostatSchedule returned null or undefined',
764
+ device: deviceName
765
+ });
766
+ } else {
767
+ results.push({
768
+ name: 'should get thermostat schedule',
769
+ passed: true,
770
+ skipped: false,
771
+ error: null,
772
+ device: deviceName,
773
+ details: { schedule: response }
774
+ });
775
+ }
776
+ }
777
+ } catch (error) {
778
+ results.push({
779
+ name: 'should get thermostat schedule',
780
+ passed: false,
781
+ skipped: false,
782
+ error: error.message,
783
+ device: deviceName
784
+ });
785
+ }
786
+
787
+ // Test 12: Get thermostat hold action
788
+ try {
789
+ if (typeof testDevice.getThermostatHoldAction !== 'function') {
790
+ results.push({
791
+ name: 'should get thermostat hold action',
792
+ passed: false,
793
+ skipped: true,
794
+ error: 'Device does not support getThermostatHoldAction',
795
+ device: deviceName
796
+ });
797
+ } else {
798
+ const response = await testDevice.getThermostatHoldAction({ channel: 0 });
799
+
800
+ if (!response) {
801
+ results.push({
802
+ name: 'should get thermostat hold action',
803
+ passed: false,
804
+ skipped: false,
805
+ error: 'getThermostatHoldAction returned null or undefined',
806
+ device: deviceName
807
+ });
808
+ } else {
809
+ results.push({
810
+ name: 'should get thermostat hold action',
811
+ passed: true,
812
+ skipped: false,
813
+ error: null,
814
+ device: deviceName,
815
+ details: { holdAction: response }
816
+ });
817
+ }
818
+ }
819
+ } catch (error) {
820
+ results.push({
821
+ name: 'should get thermostat hold action',
822
+ passed: false,
823
+ skipped: false,
824
+ error: error.message,
825
+ device: deviceName
826
+ });
827
+ }
828
+
829
+ // Test 13: Get thermostat calibration
830
+ try {
831
+ if (typeof testDevice.getThermostatCalibration !== 'function') {
832
+ results.push({
833
+ name: 'should get thermostat calibration',
834
+ passed: false,
835
+ skipped: true,
836
+ error: 'Device does not support getThermostatCalibration',
837
+ device: deviceName
838
+ });
839
+ } else {
840
+ const response = await testDevice.getThermostatCalibration({ channel: 0 });
841
+
842
+ if (!response) {
843
+ results.push({
844
+ name: 'should get thermostat calibration',
845
+ passed: false,
846
+ skipped: false,
847
+ error: 'getThermostatCalibration returned null or undefined',
848
+ device: deviceName
849
+ });
850
+ } else {
851
+ results.push({
852
+ name: 'should get thermostat calibration',
853
+ passed: true,
854
+ skipped: false,
855
+ error: null,
856
+ device: deviceName,
857
+ details: { calibration: response }
858
+ });
859
+ }
860
+ }
861
+ } catch (error) {
862
+ results.push({
863
+ name: 'should get thermostat calibration',
864
+ passed: false,
865
+ skipped: false,
866
+ error: error.message,
867
+ device: deviceName
868
+ });
869
+ }
870
+
871
+ // Test 14: Get thermostat sensor
872
+ try {
873
+ if (typeof testDevice.getThermostatSensor !== 'function') {
874
+ results.push({
875
+ name: 'should get thermostat sensor',
876
+ passed: false,
877
+ skipped: true,
878
+ error: 'Device does not support getThermostatSensor',
879
+ device: deviceName
880
+ });
881
+ } else {
882
+ const response = await testDevice.getThermostatSensor({ channel: 0 });
883
+
884
+ if (!response) {
885
+ results.push({
886
+ name: 'should get thermostat sensor',
887
+ passed: false,
888
+ skipped: false,
889
+ error: 'getThermostatSensor returned null or undefined',
890
+ device: deviceName
891
+ });
892
+ } else {
893
+ results.push({
894
+ name: 'should get thermostat sensor',
895
+ passed: true,
896
+ skipped: false,
897
+ error: null,
898
+ device: deviceName,
899
+ details: { sensor: response }
900
+ });
901
+ }
902
+ }
903
+ } catch (error) {
904
+ results.push({
905
+ name: 'should get thermostat sensor',
906
+ passed: false,
907
+ skipped: false,
908
+ error: error.message,
909
+ device: deviceName
910
+ });
911
+ }
912
+
913
+ return results;
914
+ }
915
+
916
+ module.exports = {
917
+ metadata,
918
+ runTests
919
+ };