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,489 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Hub Sensors Tests
5
+ * Tests hub sensor discovery and readings for various sensor types
6
+ */
7
+
8
+ const { findDevicesByType, findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'hub-sensors',
12
+ description: 'Tests hub sensor discovery and readings for various sensor types',
13
+ requiredAbilities: ['Appliance.Hub.Sensor.All'],
14
+ minDevices: 1
15
+ };
16
+
17
+ async function runTests(context) {
18
+ const { manager, devices, options = {} } = context;
19
+ const timeout = options.timeout || 30000;
20
+ const results = [];
21
+
22
+ // If no devices provided, discover them
23
+ let testHub = null;
24
+ if (devices && devices.length > 0) {
25
+ testHub = devices[0];
26
+ } else {
27
+ // Find hub devices (test-runner.js already filters out subdevices, so we can use findDevicesByAbility)
28
+ const hubTypes = ['ms100', 'msh300', 'msh300h', 'msh450'];
29
+ let hubDevices = [];
30
+
31
+ for (const hubType of hubTypes) {
32
+ const foundDevices = await findDevicesByType(manager, hubType, OnlineStatus.ONLINE);
33
+ hubDevices = hubDevices.concat(foundDevices);
34
+ }
35
+
36
+ // Also try finding by hub sensor abilities
37
+ const sensorHubs = await findDevicesByAbility(manager, 'Appliance.Hub.Sensor.All', OnlineStatus.ONLINE);
38
+ for (const device of sensorHubs) {
39
+ if (!hubDevices.find(d => (d.dev?.uuid || d.uuid) === (device.dev?.uuid || device.uuid))) {
40
+ hubDevices.push(device);
41
+ }
42
+ }
43
+
44
+ if (hubDevices.length > 0) {
45
+ testHub = hubDevices[0];
46
+ }
47
+ }
48
+
49
+ if (!testHub) {
50
+ results.push({
51
+ name: 'should get all sensors',
52
+ passed: false,
53
+ skipped: true,
54
+ error: 'No Hub device has been found to run this test on',
55
+ device: null
56
+ });
57
+ return results;
58
+ }
59
+
60
+ const deviceName = getDeviceName(testHub);
61
+
62
+ await waitForDeviceConnection(testHub, timeout);
63
+ await new Promise(resolve => setTimeout(resolve, 2000));
64
+
65
+ // With single-phase initialization, abilities are already loaded and features are applied
66
+ // when the device is created. No need to query or apply abilities here.
67
+ // Just wait for connection to stabilize.
68
+ if (testHub.deviceConnected) {
69
+ await new Promise(resolve => setTimeout(resolve, 1000));
70
+ }
71
+
72
+ // Test 1: Get all sensors
73
+ try {
74
+ if (typeof testHub.getAllSensors !== 'function') {
75
+ results.push({
76
+ name: 'should get all sensors',
77
+ passed: false,
78
+ skipped: true,
79
+ error: 'Hub does not support getAllSensors',
80
+ device: deviceName
81
+ });
82
+ } else {
83
+ // Get subdevices to find sensor IDs
84
+ const subdevices = testHub.getSubdevices();
85
+ if (subdevices.length === 0) {
86
+ results.push({
87
+ name: 'should get all sensors',
88
+ passed: false,
89
+ skipped: true,
90
+ error: 'Hub has no subdevices',
91
+ device: deviceName
92
+ });
93
+ } else {
94
+ const sensorIds = subdevices.map(sub => sub.subdeviceId).slice(0, 5); // Test up to 5 sensors
95
+
96
+ const response = await testHub.getAllSensors(sensorIds);
97
+
98
+ if (!response) {
99
+ results.push({
100
+ name: 'should get all sensors',
101
+ passed: false,
102
+ skipped: false,
103
+ error: 'getAllSensors returned null or undefined',
104
+ device: deviceName
105
+ });
106
+ } else if (!Array.isArray(response.all)) {
107
+ results.push({
108
+ name: 'should get all sensors',
109
+ passed: false,
110
+ skipped: false,
111
+ error: 'Response all is not an array',
112
+ device: deviceName
113
+ });
114
+ } else {
115
+ results.push({
116
+ name: 'should get all sensors',
117
+ passed: true,
118
+ skipped: false,
119
+ error: null,
120
+ device: deviceName,
121
+ details: { sensorCount: response.all.length }
122
+ });
123
+ }
124
+ }
125
+ }
126
+ } catch (error) {
127
+ results.push({
128
+ name: 'should get all sensors',
129
+ passed: false,
130
+ skipped: false,
131
+ error: error.message,
132
+ device: deviceName
133
+ });
134
+ }
135
+
136
+ // Test 2: Get temperature and humidity sensors
137
+ try {
138
+ if (typeof testHub.getTempHumSensor !== 'function') {
139
+ results.push({
140
+ name: 'should get temperature and humidity sensors',
141
+ passed: false,
142
+ skipped: true,
143
+ error: 'Hub does not support getTempHumSensor',
144
+ device: deviceName
145
+ });
146
+ } else {
147
+ const subdevices = testHub.getSubdevices();
148
+ if (subdevices.length === 0) {
149
+ results.push({
150
+ name: 'should get temperature and humidity sensors',
151
+ passed: false,
152
+ skipped: true,
153
+ error: 'Hub has no subdevices',
154
+ device: deviceName
155
+ });
156
+ } else {
157
+ // Filter for temp/humidity sensors: ms100, ms100f, ms130
158
+ const sensorIds = subdevices
159
+ .filter(sub => {
160
+ const type = (sub.type || sub._type || '').toLowerCase();
161
+ return type === 'ms100' || type === 'ms100f' || type === 'ms130';
162
+ })
163
+ .map(sub => sub.subdeviceId)
164
+ .slice(0, 3);
165
+
166
+ if (sensorIds.length === 0) {
167
+ results.push({
168
+ name: 'should get temperature and humidity sensors',
169
+ passed: false,
170
+ skipped: true,
171
+ error: 'Hub has no temperature/humidity sensors',
172
+ device: deviceName
173
+ });
174
+ } else {
175
+ const response = await testHub.getTempHumSensor(sensorIds);
176
+
177
+ if (!response || !Array.isArray(response.tempHum)) {
178
+ results.push({
179
+ name: 'should get temperature and humidity sensors',
180
+ passed: false,
181
+ skipped: false,
182
+ error: 'Response tempHum is not an array',
183
+ device: deviceName
184
+ });
185
+ } else {
186
+ results.push({
187
+ name: 'should get temperature and humidity sensors',
188
+ passed: true,
189
+ skipped: false,
190
+ error: null,
191
+ device: deviceName,
192
+ details: { sensorCount: response.tempHum.length }
193
+ });
194
+ }
195
+ }
196
+ }
197
+ }
198
+ } catch (error) {
199
+ results.push({
200
+ name: 'should get temperature and humidity sensors',
201
+ passed: false,
202
+ skipped: false,
203
+ error: error.message,
204
+ device: deviceName
205
+ });
206
+ }
207
+
208
+ // Test 3: Get latest sensor readings
209
+ try {
210
+ if (typeof testHub.getLatestHubSensorReadings !== 'function') {
211
+ results.push({
212
+ name: 'should get latest sensor readings',
213
+ passed: false,
214
+ skipped: true,
215
+ error: 'Hub does not support getLatestHubSensorReadings',
216
+ device: deviceName
217
+ });
218
+ } else {
219
+ const subdevices = testHub.getSubdevices();
220
+ if (subdevices.length === 0) {
221
+ results.push({
222
+ name: 'should get latest sensor readings',
223
+ passed: false,
224
+ skipped: true,
225
+ error: 'Hub has no subdevices',
226
+ device: deviceName
227
+ });
228
+ } else {
229
+ const sensorIds = subdevices.map(sub => sub.subdeviceId).slice(0, 3);
230
+
231
+ const response = await testHub.getLatestHubSensorReadings(sensorIds, ['light', 'temp', 'humi']);
232
+
233
+ if (!response || !Array.isArray(response.latest)) {
234
+ results.push({
235
+ name: 'should get latest sensor readings',
236
+ passed: false,
237
+ skipped: false,
238
+ error: 'Response latest is not an array',
239
+ device: deviceName
240
+ });
241
+ } else {
242
+ results.push({
243
+ name: 'should get latest sensor readings',
244
+ passed: true,
245
+ skipped: false,
246
+ error: null,
247
+ device: deviceName,
248
+ details: { readingCount: response.latest.length }
249
+ });
250
+ }
251
+ }
252
+ }
253
+ } catch (error) {
254
+ results.push({
255
+ name: 'should get latest sensor readings',
256
+ passed: false,
257
+ skipped: false,
258
+ error: error.message,
259
+ device: deviceName
260
+ });
261
+ }
262
+
263
+ // Test 4: Get water leak sensors
264
+ try {
265
+ if (typeof testHub.getWaterLeakSensor !== 'function') {
266
+ results.push({
267
+ name: 'should get water leak sensors',
268
+ passed: false,
269
+ skipped: true,
270
+ error: 'Hub does not support getWaterLeakSensor',
271
+ device: deviceName
272
+ });
273
+ } else {
274
+ const subdevices = testHub.getSubdevices();
275
+ const waterLeakSensors = subdevices
276
+ .filter(sub => {
277
+ const type = (sub.type || sub._type || '').toLowerCase();
278
+ return type === 'ms400' || type === 'ms405';
279
+ })
280
+ .map(sub => sub.subdeviceId)
281
+ .slice(0, 3);
282
+
283
+ if (waterLeakSensors.length === 0) {
284
+ results.push({
285
+ name: 'should get water leak sensors',
286
+ passed: false,
287
+ skipped: true,
288
+ error: 'Hub has no water leak sensors',
289
+ device: deviceName
290
+ });
291
+ } else {
292
+ const response = await testHub.getWaterLeakSensor(waterLeakSensors);
293
+
294
+ if (!response || !Array.isArray(response.waterLeak)) {
295
+ results.push({
296
+ name: 'should get water leak sensors',
297
+ passed: false,
298
+ skipped: false,
299
+ error: 'Response waterLeak is not an array',
300
+ device: deviceName
301
+ });
302
+ } else {
303
+ results.push({
304
+ name: 'should get water leak sensors',
305
+ passed: true,
306
+ skipped: false,
307
+ error: null,
308
+ device: deviceName,
309
+ details: { sensorCount: response.waterLeak.length }
310
+ });
311
+ }
312
+ }
313
+ }
314
+ } catch (error) {
315
+ results.push({
316
+ name: 'should get water leak sensors',
317
+ passed: false,
318
+ skipped: false,
319
+ error: error.message,
320
+ device: deviceName
321
+ });
322
+ }
323
+
324
+ // Test 5: Get smoke alarm status
325
+ try {
326
+ if (typeof testHub.getSmokeAlarmStatus !== 'function') {
327
+ results.push({
328
+ name: 'should get smoke alarm status',
329
+ passed: false,
330
+ skipped: true,
331
+ error: 'Hub does not support getSmokeAlarmStatus',
332
+ device: deviceName
333
+ });
334
+ } else {
335
+ const subdevices = testHub.getSubdevices();
336
+ // Filter for smoke detectors: ma151
337
+ const smokeSensors = subdevices
338
+ .filter(sub => {
339
+ const type = (sub.type || sub._type || '').toLowerCase();
340
+ return type === 'ma151';
341
+ })
342
+ .map(sub => sub.subdeviceId)
343
+ .slice(0, 3);
344
+
345
+ if (smokeSensors.length === 0) {
346
+ results.push({
347
+ name: 'should get smoke alarm status',
348
+ passed: false,
349
+ skipped: true,
350
+ error: 'Hub has no smoke sensors',
351
+ device: deviceName
352
+ });
353
+ } else {
354
+ const response = await testHub.getSmokeAlarmStatus(smokeSensors);
355
+
356
+ if (!response || !Array.isArray(response.smokeAlarm)) {
357
+ results.push({
358
+ name: 'should get smoke alarm status',
359
+ passed: false,
360
+ skipped: false,
361
+ error: 'Response smokeAlarm is not an array',
362
+ device: deviceName
363
+ });
364
+ } else {
365
+ results.push({
366
+ name: 'should get smoke alarm status',
367
+ passed: true,
368
+ skipped: false,
369
+ error: null,
370
+ device: deviceName,
371
+ details: { sensorCount: response.smokeAlarm.length }
372
+ });
373
+ }
374
+ }
375
+ }
376
+ } catch (error) {
377
+ results.push({
378
+ name: 'should get smoke alarm status',
379
+ passed: false,
380
+ skipped: false,
381
+ error: error.message,
382
+ device: deviceName
383
+ });
384
+ }
385
+
386
+ // Test 6: Get hub battery status
387
+ try {
388
+ if (typeof testHub.getHubBattery !== 'function') {
389
+ results.push({
390
+ name: 'should get hub battery status',
391
+ passed: false,
392
+ skipped: true,
393
+ error: 'Hub does not support getHubBattery',
394
+ device: deviceName
395
+ });
396
+ } else {
397
+ const response = await testHub.getHubBattery();
398
+
399
+ if (!response || !Array.isArray(response.battery)) {
400
+ results.push({
401
+ name: 'should get hub battery status',
402
+ passed: false,
403
+ skipped: false,
404
+ error: 'Response battery is not an array',
405
+ device: deviceName
406
+ });
407
+ } else {
408
+ results.push({
409
+ name: 'should get hub battery status',
410
+ passed: true,
411
+ skipped: false,
412
+ error: null,
413
+ device: deviceName,
414
+ details: { batteryCount: response.battery.length }
415
+ });
416
+ }
417
+ }
418
+ } catch (error) {
419
+ results.push({
420
+ name: 'should get hub battery status',
421
+ passed: false,
422
+ skipped: false,
423
+ error: error.message,
424
+ device: deviceName
425
+ });
426
+ }
427
+
428
+ // Test 7: Get sensor adjustment settings
429
+ try {
430
+ if (typeof testHub.getHubSensorAdjust !== 'function') {
431
+ results.push({
432
+ name: 'should get sensor adjustment settings',
433
+ passed: false,
434
+ skipped: true,
435
+ error: 'Hub does not support getHubSensorAdjust',
436
+ device: deviceName
437
+ });
438
+ } else {
439
+ const subdevices = testHub.getSubdevices();
440
+ if (subdevices.length === 0) {
441
+ results.push({
442
+ name: 'should get sensor adjustment settings',
443
+ passed: false,
444
+ skipped: true,
445
+ error: 'Hub has no subdevices',
446
+ device: deviceName
447
+ });
448
+ } else {
449
+ const sensorIds = subdevices.map(sub => sub.subdeviceId).slice(0, 3);
450
+
451
+ const response = await testHub.getHubSensorAdjust(sensorIds);
452
+
453
+ if (!response || !Array.isArray(response.adjust)) {
454
+ results.push({
455
+ name: 'should get sensor adjustment settings',
456
+ passed: false,
457
+ skipped: false,
458
+ error: 'Response adjust is not an array',
459
+ device: deviceName
460
+ });
461
+ } else {
462
+ results.push({
463
+ name: 'should get sensor adjustment settings',
464
+ passed: true,
465
+ skipped: false,
466
+ error: null,
467
+ device: deviceName,
468
+ details: { adjustCount: response.adjust.length }
469
+ });
470
+ }
471
+ }
472
+ }
473
+ } catch (error) {
474
+ results.push({
475
+ name: 'should get sensor adjustment settings',
476
+ passed: false,
477
+ skipped: false,
478
+ error: error.message,
479
+ device: deviceName
480
+ });
481
+ }
482
+
483
+ return results;
484
+ }
485
+
486
+ module.exports = {
487
+ metadata,
488
+ runTests
489
+ };