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,628 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Roller Shutter Device Tests
5
+ * Tests open/close control, position, timers, and configuration for roller shutters
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const DEFAULT_OPEN_TIMER = 15;
11
+ const DEFAULT_CLOSE_TIMER = 15;
12
+
13
+ const metadata = {
14
+ name: 'roller-shutter',
15
+ description: 'Tests open/close control, position, timers, and configuration for roller shutters',
16
+ requiredAbilities: ['Appliance.RollerShutter.State'],
17
+ minDevices: 1
18
+ };
19
+
20
+ async function runTests(context) {
21
+ const { manager, devices, options = {} } = context;
22
+ const timeout = options.timeout || 120000; // Roller shutters take time
23
+ const results = [];
24
+
25
+ // If no devices provided, discover them
26
+ let testDevices = devices || [];
27
+ if (testDevices.length === 0) {
28
+ testDevices = await findDevicesByAbility(manager, 'Appliance.RollerShutter.State', OnlineStatus.ONLINE);
29
+ }
30
+
31
+ if (testDevices.length === 0) {
32
+ results.push({
33
+ name: 'should open roller shutter',
34
+ passed: false,
35
+ skipped: true,
36
+ error: 'No RollerShutter device has been found to run this test on',
37
+ device: null
38
+ });
39
+ return results;
40
+ }
41
+
42
+ const testDevice = testDevices[0];
43
+ const deviceName = getDeviceName(testDevice);
44
+
45
+ await waitForDeviceConnection(testDevice, timeout);
46
+ await testDevice.getRollerShutterState();
47
+ await new Promise(resolve => setTimeout(resolve, 1000));
48
+
49
+ // Test 1: Open roller shutter
50
+ try {
51
+ await testDevice.getRollerShutterState();
52
+
53
+ // Set timers (if method exists)
54
+ if (typeof testDevice.setRollerShutterConfig === 'function') {
55
+ await testDevice.setRollerShutterConfig({
56
+ openTimerSeconds: DEFAULT_OPEN_TIMER,
57
+ closeTimerSeconds: DEFAULT_CLOSE_TIMER
58
+ });
59
+ }
60
+
61
+ // Set up event listeners for state changes
62
+ let stateOpening = false;
63
+ let stateIdle = false;
64
+ let positionOpened = false;
65
+
66
+ const onData = (namespace, payload) => {
67
+ if (namespace === 'Appliance.RollerShutter.State') {
68
+ if (payload.state) {
69
+ const state = Array.isArray(payload.state) ? payload.state[0] : payload.state;
70
+ if (state.channel === 0) {
71
+ if (state.state === 1) { // OPENING
72
+ stateOpening = true;
73
+ } else if (state.state === 0) { // IDLE
74
+ stateIdle = true;
75
+ }
76
+ }
77
+ }
78
+ }
79
+ if (namespace === 'Appliance.RollerShutter.Position') {
80
+ if (payload.position) {
81
+ const position = Array.isArray(payload.position) ? payload.position[0] : payload.position;
82
+ if (position.channel === 0 && position.position === 100) {
83
+ positionOpened = true;
84
+ }
85
+ }
86
+ }
87
+ };
88
+
89
+ testDevice.on('data', onData);
90
+
91
+ // Trigger the opening
92
+ if (typeof testDevice.openRollerShutter === 'function') {
93
+ await testDevice.openRollerShutter({ channel: 0 });
94
+ } else {
95
+ await testDevice.setRollerShutterPosition({ position: 100, channel: 0 });
96
+ }
97
+
98
+ // Wait for state changes
99
+ const startTime = Date.now();
100
+ while (!stateOpening && Date.now() - startTime < 30000) {
101
+ await new Promise(resolve => setTimeout(resolve, 500));
102
+ }
103
+
104
+ if (!stateOpening) {
105
+ testDevice.removeListener('data', onData);
106
+ results.push({
107
+ name: 'should open roller shutter',
108
+ passed: false,
109
+ skipped: false,
110
+ error: 'Did not receive OPENING state within 30 seconds',
111
+ device: deviceName
112
+ });
113
+ return results;
114
+ }
115
+
116
+ while (!stateIdle && Date.now() - startTime < 90000) {
117
+ await new Promise(resolve => setTimeout(resolve, 500));
118
+ }
119
+
120
+ if (!stateIdle) {
121
+ testDevice.removeListener('data', onData);
122
+ results.push({
123
+ name: 'should open roller shutter',
124
+ passed: false,
125
+ skipped: false,
126
+ error: 'Did not receive IDLE state within 90 seconds',
127
+ device: deviceName
128
+ });
129
+ return results;
130
+ }
131
+
132
+ while (!positionOpened && Date.now() - startTime < 120000) {
133
+ await new Promise(resolve => setTimeout(resolve, 500));
134
+ }
135
+
136
+ testDevice.removeListener('data', onData);
137
+
138
+ if (!positionOpened) {
139
+ results.push({
140
+ name: 'should open roller shutter',
141
+ passed: false,
142
+ skipped: false,
143
+ error: 'Did not receive position 100 within 120 seconds',
144
+ device: deviceName
145
+ });
146
+ } else {
147
+ results.push({
148
+ name: 'should open roller shutter',
149
+ passed: true,
150
+ skipped: false,
151
+ error: null,
152
+ device: deviceName
153
+ });
154
+ }
155
+ } catch (error) {
156
+ results.push({
157
+ name: 'should open roller shutter',
158
+ passed: false,
159
+ skipped: false,
160
+ error: error.message,
161
+ device: deviceName
162
+ });
163
+ }
164
+
165
+ // Test 2: Close roller shutter
166
+ try {
167
+ // Set timers (if method exists)
168
+ if (typeof testDevice.setRollerShutterConfig === 'function') {
169
+ await testDevice.setRollerShutterConfig({
170
+ openTimerSeconds: DEFAULT_OPEN_TIMER,
171
+ closeTimerSeconds: DEFAULT_CLOSE_TIMER
172
+ });
173
+ }
174
+
175
+ // Update its status
176
+ await testDevice.getRollerShutterState();
177
+
178
+ // Set up event listeners for state changes
179
+ let stateClosing = false;
180
+ let stateIdle = false;
181
+ let positionClosed = false;
182
+
183
+ const onData = (namespace, payload) => {
184
+ if (namespace === 'Appliance.RollerShutter.State') {
185
+ if (payload.state) {
186
+ const state = Array.isArray(payload.state) ? payload.state[0] : payload.state;
187
+ if (state.channel === 0) {
188
+ if (state.state === 2) { // CLOSING
189
+ stateClosing = true;
190
+ } else if (state.state === 0) { // IDLE
191
+ stateIdle = true;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ if (namespace === 'Appliance.RollerShutter.Position') {
197
+ if (payload.position) {
198
+ const position = Array.isArray(payload.position) ? payload.position[0] : payload.position;
199
+ if (position.channel === 0 && position.position === 0) {
200
+ positionClosed = true;
201
+ }
202
+ }
203
+ }
204
+ };
205
+
206
+ testDevice.on('data', onData);
207
+
208
+ // Trigger the closing
209
+ if (typeof testDevice.closeRollerShutter === 'function') {
210
+ await testDevice.closeRollerShutter({ channel: 0 });
211
+ } else {
212
+ await testDevice.setRollerShutterPosition({ position: 0, channel: 0 });
213
+ }
214
+
215
+ // Wait for state changes
216
+ const startTime = Date.now();
217
+ while (!stateClosing && Date.now() - startTime < 30000) {
218
+ await new Promise(resolve => setTimeout(resolve, 500));
219
+ }
220
+
221
+ if (!stateClosing) {
222
+ testDevice.removeListener('data', onData);
223
+ results.push({
224
+ name: 'should close roller shutter',
225
+ passed: false,
226
+ skipped: false,
227
+ error: 'Did not receive CLOSING state within 30 seconds',
228
+ device: deviceName
229
+ });
230
+ return results;
231
+ }
232
+
233
+ while (!stateIdle && Date.now() - startTime < 90000) {
234
+ await new Promise(resolve => setTimeout(resolve, 500));
235
+ }
236
+
237
+ if (!stateIdle) {
238
+ testDevice.removeListener('data', onData);
239
+ results.push({
240
+ name: 'should close roller shutter',
241
+ passed: false,
242
+ skipped: false,
243
+ error: 'Did not receive IDLE state within 90 seconds',
244
+ device: deviceName
245
+ });
246
+ return results;
247
+ }
248
+
249
+ while (!positionClosed && Date.now() - startTime < 120000) {
250
+ await new Promise(resolve => setTimeout(resolve, 500));
251
+ }
252
+
253
+ testDevice.removeListener('data', onData);
254
+
255
+ if (!positionClosed) {
256
+ results.push({
257
+ name: 'should close roller shutter',
258
+ passed: false,
259
+ skipped: false,
260
+ error: 'Did not receive position 0 within 120 seconds',
261
+ device: deviceName
262
+ });
263
+ } else {
264
+ results.push({
265
+ name: 'should close roller shutter',
266
+ passed: true,
267
+ skipped: false,
268
+ error: null,
269
+ device: deviceName
270
+ });
271
+ }
272
+ } catch (error) {
273
+ results.push({
274
+ name: 'should close roller shutter',
275
+ passed: false,
276
+ skipped: false,
277
+ error: error.message,
278
+ device: deviceName
279
+ });
280
+ }
281
+
282
+ // Test 3: Get opening timer duration
283
+ try {
284
+ await testDevice.getRollerShutterState();
285
+
286
+ const state = testDevice.getCachedRollerShutterState(0);
287
+
288
+ if (!state) {
289
+ results.push({
290
+ name: 'should get opening timer duration',
291
+ passed: false,
292
+ skipped: false,
293
+ error: 'Could not get cached roller shutter state',
294
+ device: deviceName
295
+ });
296
+ } else {
297
+ const openingTimer = state.open_timer_duration_millis;
298
+
299
+ if (!openingTimer || openingTimer <= 0) {
300
+ results.push({
301
+ name: 'should get opening timer duration',
302
+ passed: false,
303
+ skipped: false,
304
+ error: `Invalid opening timer: ${openingTimer}`,
305
+ device: deviceName
306
+ });
307
+ } else {
308
+ results.push({
309
+ name: 'should get opening timer duration',
310
+ passed: true,
311
+ skipped: false,
312
+ error: null,
313
+ device: deviceName,
314
+ details: { openingTimer: openingTimer }
315
+ });
316
+ }
317
+ }
318
+ } catch (error) {
319
+ results.push({
320
+ name: 'should get opening timer duration',
321
+ passed: false,
322
+ skipped: false,
323
+ error: error.message,
324
+ device: deviceName
325
+ });
326
+ }
327
+
328
+ // Test 4: Get closing timer duration
329
+ try {
330
+ await testDevice.getRollerShutterState();
331
+
332
+ const state = testDevice.getCachedRollerShutterState(0);
333
+
334
+ if (!state) {
335
+ results.push({
336
+ name: 'should get closing timer duration',
337
+ passed: false,
338
+ skipped: false,
339
+ error: 'Could not get cached roller shutter state',
340
+ device: deviceName
341
+ });
342
+ } else {
343
+ const closingTimer = state.close_timer_duration_millis;
344
+
345
+ if (!closingTimer || closingTimer <= 0) {
346
+ results.push({
347
+ name: 'should get closing timer duration',
348
+ passed: false,
349
+ skipped: false,
350
+ error: `Invalid closing timer: ${closingTimer}`,
351
+ device: deviceName
352
+ });
353
+ } else {
354
+ results.push({
355
+ name: 'should get closing timer duration',
356
+ passed: true,
357
+ skipped: false,
358
+ error: null,
359
+ device: deviceName,
360
+ details: { closingTimer: closingTimer }
361
+ });
362
+ }
363
+ }
364
+ } catch (error) {
365
+ results.push({
366
+ name: 'should get closing timer duration',
367
+ passed: false,
368
+ skipped: false,
369
+ error: error.message,
370
+ device: deviceName
371
+ });
372
+ }
373
+
374
+ // Test 5: Set roller shutter config timers
375
+ try {
376
+ await testDevice.getRollerShutterState();
377
+
378
+ // Retrieve original values
379
+ const originalState = testDevice.getCachedRollerShutterState(0);
380
+
381
+ if (!originalState) {
382
+ results.push({
383
+ name: 'should set roller shutter config timers',
384
+ passed: false,
385
+ skipped: true,
386
+ error: 'Could not get original state',
387
+ device: deviceName
388
+ });
389
+ } else if (typeof testDevice.setRollerShutterConfig !== 'function') {
390
+ results.push({
391
+ name: 'should set roller shutter config timers',
392
+ passed: false,
393
+ skipped: true,
394
+ error: 'Device does not support setRollerShutterConfig',
395
+ device: deviceName
396
+ });
397
+ } else {
398
+ const originalOpenTimer = originalState.open_timer_duration_millis;
399
+ const originalCloseTimer = originalState.close_timer_duration_millis;
400
+
401
+ // Set new random values
402
+ const openTimer = Math.floor(Math.random() * (120 - 10 + 1)) + 10; // 10-120 seconds
403
+ const closeTimer = Math.floor(Math.random() * (120 - 10 + 1)) + 10; // 10-120 seconds
404
+
405
+ await testDevice.setRollerShutterConfig({
406
+ openTimerSeconds: openTimer,
407
+ closeTimerSeconds: closeTimer,
408
+ channel: 0
409
+ });
410
+
411
+ await new Promise(resolve => setTimeout(resolve, 1000));
412
+
413
+ await testDevice.getRollerShutterState();
414
+ const newState = testDevice.getCachedRollerShutterState(0);
415
+
416
+ if (!newState) {
417
+ results.push({
418
+ name: 'should set roller shutter config timers',
419
+ passed: false,
420
+ skipped: false,
421
+ error: 'Could not get new state after setting config',
422
+ device: deviceName
423
+ });
424
+ } else if (newState.open_timer_duration_millis !== openTimer * 1000 ||
425
+ newState.close_timer_duration_millis !== closeTimer * 1000) {
426
+ results.push({
427
+ name: 'should set roller shutter config timers',
428
+ passed: false,
429
+ skipped: false,
430
+ error: `Timer mismatch. Expected open=${openTimer * 1000}, close=${closeTimer * 1000}, got open=${newState.open_timer_duration_millis}, close=${newState.close_timer_duration_millis}`,
431
+ device: deviceName
432
+ });
433
+ } else {
434
+ // Restore original values
435
+ await testDevice.setRollerShutterConfig({
436
+ openTimerSeconds: Math.floor(originalOpenTimer / 1000),
437
+ closeTimerSeconds: Math.floor(originalCloseTimer / 1000),
438
+ channel: 0
439
+ });
440
+
441
+ results.push({
442
+ name: 'should set roller shutter config timers',
443
+ passed: true,
444
+ skipped: false,
445
+ error: null,
446
+ device: deviceName
447
+ });
448
+ }
449
+ }
450
+ } catch (error) {
451
+ results.push({
452
+ name: 'should set roller shutter config timers',
453
+ passed: false,
454
+ skipped: false,
455
+ error: error.message,
456
+ device: deviceName
457
+ });
458
+ }
459
+
460
+ // Test 6: Get roller shutter config
461
+ try {
462
+ if (typeof testDevice.getRollerShutterConfig !== 'function') {
463
+ results.push({
464
+ name: 'should get roller shutter config',
465
+ passed: false,
466
+ skipped: true,
467
+ error: 'Device does not support getRollerShutterConfig',
468
+ device: deviceName
469
+ });
470
+ } else {
471
+ const config = await testDevice.getRollerShutterConfig();
472
+
473
+ if (!config) {
474
+ results.push({
475
+ name: 'should get roller shutter config',
476
+ passed: false,
477
+ skipped: false,
478
+ error: 'getRollerShutterConfig returned null or undefined',
479
+ device: deviceName
480
+ });
481
+ } else {
482
+ results.push({
483
+ name: 'should get roller shutter config',
484
+ passed: true,
485
+ skipped: false,
486
+ error: null,
487
+ device: deviceName,
488
+ details: { config: config.config || config }
489
+ });
490
+ }
491
+ }
492
+ } catch (error) {
493
+ results.push({
494
+ name: 'should get roller shutter config',
495
+ passed: false,
496
+ skipped: false,
497
+ error: error.message,
498
+ device: deviceName
499
+ });
500
+ }
501
+
502
+ // Test 7: Get roller shutter position
503
+ try {
504
+ if (typeof testDevice.getRollerShutterPosition !== 'function') {
505
+ results.push({
506
+ name: 'should get roller shutter position',
507
+ passed: false,
508
+ skipped: true,
509
+ error: 'Device does not support getRollerShutterPosition',
510
+ device: deviceName
511
+ });
512
+ } else {
513
+ const response = await testDevice.getRollerShutterPosition({ channel: 0 });
514
+
515
+ if (!response) {
516
+ results.push({
517
+ name: 'should get roller shutter position',
518
+ passed: false,
519
+ skipped: false,
520
+ error: 'getRollerShutterPosition returned null or undefined',
521
+ device: deviceName
522
+ });
523
+ } else {
524
+ results.push({
525
+ name: 'should get roller shutter position',
526
+ passed: true,
527
+ skipped: false,
528
+ error: null,
529
+ device: deviceName,
530
+ details: { position: response }
531
+ });
532
+ }
533
+ }
534
+ } catch (error) {
535
+ results.push({
536
+ name: 'should get roller shutter position',
537
+ passed: false,
538
+ skipped: false,
539
+ error: error.message,
540
+ device: deviceName
541
+ });
542
+ }
543
+
544
+ // Test 8: Control roller shutter position
545
+ try {
546
+ if (typeof testDevice.controlRollerShutterPosition !== 'function') {
547
+ results.push({
548
+ name: 'should control roller shutter position',
549
+ passed: false,
550
+ skipped: true,
551
+ error: 'Device does not support controlRollerShutterPosition',
552
+ device: deviceName
553
+ });
554
+ } else {
555
+ // Get current position first
556
+ await testDevice.getRollerShutterPosition({ channel: 0 });
557
+ await new Promise(resolve => setTimeout(resolve, 1000));
558
+
559
+ // Note: We don't actually change the position to avoid disrupting the device
560
+ // We just verify the method exists
561
+ results.push({
562
+ name: 'should control roller shutter position',
563
+ passed: true,
564
+ skipped: false,
565
+ error: null,
566
+ device: deviceName,
567
+ details: { note: 'Method exists, but not changing position to avoid disruption' }
568
+ });
569
+ }
570
+ } catch (error) {
571
+ results.push({
572
+ name: 'should control roller shutter position',
573
+ passed: false,
574
+ skipped: false,
575
+ error: error.message,
576
+ device: deviceName
577
+ });
578
+ }
579
+
580
+ // Test 9: Get roller shutter adjust
581
+ try {
582
+ if (typeof testDevice.getRollerShutterAdjust !== 'function') {
583
+ results.push({
584
+ name: 'should get roller shutter adjust',
585
+ passed: false,
586
+ skipped: true,
587
+ error: 'Device does not support getRollerShutterAdjust',
588
+ device: deviceName
589
+ });
590
+ } else {
591
+ const response = await testDevice.getRollerShutterAdjust({ channel: 0 });
592
+
593
+ if (!response) {
594
+ results.push({
595
+ name: 'should get roller shutter adjust',
596
+ passed: false,
597
+ skipped: false,
598
+ error: 'getRollerShutterAdjust returned null or undefined',
599
+ device: deviceName
600
+ });
601
+ } else {
602
+ results.push({
603
+ name: 'should get roller shutter adjust',
604
+ passed: true,
605
+ skipped: false,
606
+ error: null,
607
+ device: deviceName,
608
+ details: { adjust: response }
609
+ });
610
+ }
611
+ }
612
+ } catch (error) {
613
+ results.push({
614
+ name: 'should get roller shutter adjust',
615
+ passed: false,
616
+ skipped: false,
617
+ error: error.message,
618
+ device: deviceName
619
+ });
620
+ }
621
+
622
+ return results;
623
+ }
624
+
625
+ module.exports = {
626
+ metadata,
627
+ runTests
628
+ };