meross-cli 0.3.0 → 0.5.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 (48) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +46 -5
  3. package/cli/commands/control/execute.js +50 -7
  4. package/cli/commands/control/menu.js +3 -5
  5. package/cli/commands/control/params/index.js +16 -12
  6. package/cli/commands/control/params/light.js +55 -25
  7. package/cli/commands/control/params/thermostat.js +24 -22
  8. package/cli/commands/control/params/timer.js +18 -15
  9. package/cli/commands/control/params/trigger.js +24 -13
  10. package/cli/commands/info.js +39 -15
  11. package/cli/commands/sniffer/sniffer-menu.js +2 -2
  12. package/cli/commands/status/device-status.js +418 -1292
  13. package/cli/commands/status/hub-status.js +14 -6
  14. package/cli/control-registry.js +211 -406
  15. package/cli/helpers/client.js +16 -10
  16. package/cli/helpers/meross.js +18 -14
  17. package/cli/menu/main.js +170 -13
  18. package/cli/menu/settings.js +2 -2
  19. package/cli/meross-cli.js +13 -47
  20. package/cli/tests/README.md +2 -0
  21. package/cli/tests/test-alarm.js +22 -2
  22. package/cli/tests/test-child-lock.js +40 -10
  23. package/cli/tests/test-config.js +22 -2
  24. package/cli/tests/test-control.js +8 -8
  25. package/cli/tests/test-diffuser.js +7 -7
  26. package/cli/tests/test-dnd.js +87 -66
  27. package/cli/tests/test-electricity.js +37 -33
  28. package/cli/tests/test-encryption.js +13 -13
  29. package/cli/tests/test-garage.js +12 -14
  30. package/cli/tests/test-helper.js +1 -1
  31. package/cli/tests/test-hub-sensors.js +3 -3
  32. package/cli/tests/test-light.js +497 -105
  33. package/cli/tests/test-presence.js +10 -55
  34. package/cli/tests/test-registry.js +7 -1
  35. package/cli/tests/test-roller-shutter.js +78 -90
  36. package/cli/tests/test-screen.js +1 -1
  37. package/cli/tests/test-sensor-history.js +6 -2
  38. package/cli/tests/test-smoke-config.js +24 -4
  39. package/cli/tests/test-spray.js +11 -11
  40. package/cli/tests/test-system.js +375 -0
  41. package/cli/tests/test-temp-unit.js +22 -2
  42. package/cli/tests/test-template.js +61 -73
  43. package/cli/tests/test-thermostat.js +126 -89
  44. package/cli/tests/test-timer.js +8 -51
  45. package/cli/tests/test-toggle.js +49 -173
  46. package/cli/tests/test-trigger.js +7 -50
  47. package/cli/utils/error-handler.js +257 -0
  48. package/package.json +2 -2
@@ -0,0 +1,375 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * System Device Tests
5
+ * Tests system information, hardware, firmware, abilities, and configuration
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'system',
12
+ description: 'Tests system information, hardware, firmware, abilities, and configuration',
13
+ requiredAbilities: ['Appliance.System.All', 'Appliance.System.Ability'],
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 testDevices = devices || [];
24
+ if (testDevices.length === 0) {
25
+ // Find devices with system abilities (most devices support this)
26
+ const systemDevices = await findDevicesByAbility(manager, 'Appliance.System.All', OnlineStatus.ONLINE);
27
+
28
+ // Also try System.Ability if no devices found
29
+ if (systemDevices.length === 0) {
30
+ const abilityDevices = await findDevicesByAbility(manager, 'Appliance.System.Ability', OnlineStatus.ONLINE);
31
+ testDevices = abilityDevices;
32
+ } else {
33
+ testDevices = systemDevices;
34
+ }
35
+
36
+ // Fallback: get any online device (most devices support system features)
37
+ if (testDevices.length === 0) {
38
+ const allDevices = manager.devices.list();
39
+ const onlineDevices = allDevices.filter(device => {
40
+ return device.onlineStatus === OnlineStatus.ONLINE;
41
+ });
42
+ testDevices = onlineDevices.slice(0, 1);
43
+ }
44
+ }
45
+
46
+ // Wait for devices to be connected
47
+ for (const device of testDevices) {
48
+ await waitForDeviceConnection(device, timeout);
49
+ await new Promise(resolve => setTimeout(resolve, 1000));
50
+ }
51
+
52
+ if (testDevices.length === 0) {
53
+ results.push({
54
+ name: 'should find devices with system capabilities',
55
+ passed: false,
56
+ skipped: true,
57
+ error: 'No device has been found to run this test on',
58
+ device: null
59
+ });
60
+ return results;
61
+ }
62
+
63
+ results.push({
64
+ name: 'should find devices with system capabilities',
65
+ passed: true,
66
+ skipped: false,
67
+ error: null,
68
+ device: null
69
+ });
70
+
71
+ const testDevice = testDevices[0];
72
+ const deviceName = getDeviceName(testDevice);
73
+
74
+ // Test 1: Get all system data
75
+ try {
76
+ if (!testDevice.system || typeof testDevice.system.getAllData !== 'function') {
77
+ results.push({
78
+ name: 'should get all system data',
79
+ passed: false,
80
+ skipped: true,
81
+ error: 'Device does not support system.getAllData',
82
+ device: deviceName
83
+ });
84
+ } else {
85
+ const allData = await testDevice.system.getAllData();
86
+
87
+ if (!allData || typeof allData !== 'object') {
88
+ results.push({
89
+ name: 'should get all system data',
90
+ passed: false,
91
+ skipped: false,
92
+ error: 'system.getAllData() returned null, undefined, or non-object',
93
+ device: deviceName
94
+ });
95
+ } else {
96
+ results.push({
97
+ name: 'should get all system data',
98
+ passed: true,
99
+ skipped: false,
100
+ error: null,
101
+ device: deviceName,
102
+ details: { hasAllData: !!allData.all }
103
+ });
104
+ }
105
+ }
106
+ } catch (error) {
107
+ results.push({
108
+ name: 'should get all system data',
109
+ passed: false,
110
+ skipped: false,
111
+ error: error.message,
112
+ device: deviceName
113
+ });
114
+ }
115
+
116
+ // Test 2: Get device abilities
117
+ try {
118
+ if (!testDevice.system || typeof testDevice.system.getAbilities !== 'function') {
119
+ results.push({
120
+ name: 'should get device abilities',
121
+ passed: false,
122
+ skipped: true,
123
+ error: 'Device does not support system.getAbilities',
124
+ device: deviceName
125
+ });
126
+ } else {
127
+ const abilities = await testDevice.system.getAbilities();
128
+
129
+ if (!abilities || typeof abilities !== 'object') {
130
+ results.push({
131
+ name: 'should get device abilities',
132
+ passed: false,
133
+ skipped: false,
134
+ error: 'system.getAbilities() returned null, undefined, or non-object',
135
+ device: deviceName
136
+ });
137
+ } else {
138
+ const abilityCount = abilities.ability ? Object.keys(abilities.ability).length : 0;
139
+ results.push({
140
+ name: 'should get device abilities',
141
+ passed: true,
142
+ skipped: false,
143
+ error: null,
144
+ device: deviceName,
145
+ details: { abilityCount: abilityCount }
146
+ });
147
+ }
148
+ }
149
+ } catch (error) {
150
+ results.push({
151
+ name: 'should get device abilities',
152
+ passed: false,
153
+ skipped: false,
154
+ error: error.message,
155
+ device: deviceName
156
+ });
157
+ }
158
+
159
+ // Test 3: Get hardware information
160
+ try {
161
+ if (!testDevice.system || typeof testDevice.system.getHardware !== 'function') {
162
+ results.push({
163
+ name: 'should get hardware information',
164
+ passed: false,
165
+ skipped: true,
166
+ error: 'Device does not support system.getHardware',
167
+ device: deviceName
168
+ });
169
+ } else {
170
+ const hardware = await testDevice.system.getHardware();
171
+
172
+ if (!hardware || typeof hardware !== 'object') {
173
+ results.push({
174
+ name: 'should get hardware information',
175
+ passed: false,
176
+ skipped: false,
177
+ error: 'system.getHardware() returned null, undefined, or non-object',
178
+ device: deviceName
179
+ });
180
+ } else {
181
+ results.push({
182
+ name: 'should get hardware information',
183
+ passed: true,
184
+ skipped: false,
185
+ error: null,
186
+ device: deviceName,
187
+ details: { hasHardware: !!hardware.hardware }
188
+ });
189
+ }
190
+ }
191
+ } catch (error) {
192
+ results.push({
193
+ name: 'should get hardware information',
194
+ passed: false,
195
+ skipped: false,
196
+ error: error.message,
197
+ device: deviceName
198
+ });
199
+ }
200
+
201
+ // Test 4: Get firmware information
202
+ try {
203
+ if (!testDevice.system || typeof testDevice.system.getFirmware !== 'function') {
204
+ results.push({
205
+ name: 'should get firmware information',
206
+ passed: false,
207
+ skipped: true,
208
+ error: 'Device does not support system.getFirmware',
209
+ device: deviceName
210
+ });
211
+ } else {
212
+ const firmware = await testDevice.system.getFirmware();
213
+
214
+ if (!firmware || typeof firmware !== 'object') {
215
+ results.push({
216
+ name: 'should get firmware information',
217
+ passed: false,
218
+ skipped: false,
219
+ error: 'system.getFirmware() returned null, undefined, or non-object',
220
+ device: deviceName
221
+ });
222
+ } else {
223
+ results.push({
224
+ name: 'should get firmware information',
225
+ passed: true,
226
+ skipped: false,
227
+ error: null,
228
+ device: deviceName,
229
+ details: { hasFirmware: !!firmware.firmware }
230
+ });
231
+ }
232
+ }
233
+ } catch (error) {
234
+ results.push({
235
+ name: 'should get firmware information',
236
+ passed: false,
237
+ skipped: false,
238
+ error: error.message,
239
+ device: deviceName
240
+ });
241
+ }
242
+
243
+ // Test 5: Get online status
244
+ try {
245
+ if (!testDevice.system || typeof testDevice.system.getOnlineStatus !== 'function') {
246
+ results.push({
247
+ name: 'should get online status',
248
+ passed: false,
249
+ skipped: true,
250
+ error: 'Device does not support system.getOnlineStatus',
251
+ device: deviceName
252
+ });
253
+ } else {
254
+ const onlineStatus = await testDevice.system.getOnlineStatus();
255
+
256
+ if (!onlineStatus || typeof onlineStatus !== 'object') {
257
+ results.push({
258
+ name: 'should get online status',
259
+ passed: false,
260
+ skipped: false,
261
+ error: 'system.getOnlineStatus() returned null, undefined, or non-object',
262
+ device: deviceName
263
+ });
264
+ } else {
265
+ results.push({
266
+ name: 'should get online status',
267
+ passed: true,
268
+ skipped: false,
269
+ error: null,
270
+ device: deviceName,
271
+ details: { hasOnlineStatus: !!onlineStatus.online }
272
+ });
273
+ }
274
+ }
275
+ } catch (error) {
276
+ results.push({
277
+ name: 'should get online status',
278
+ passed: false,
279
+ skipped: false,
280
+ error: error.message,
281
+ device: deviceName
282
+ });
283
+ }
284
+
285
+ // Test 6: Get time information
286
+ try {
287
+ if (!testDevice.system || typeof testDevice.system.getTime !== 'function') {
288
+ results.push({
289
+ name: 'should get time information',
290
+ passed: false,
291
+ skipped: true,
292
+ error: 'Device does not support system.getTime',
293
+ device: deviceName
294
+ });
295
+ } else {
296
+ const time = await testDevice.system.getTime();
297
+
298
+ if (!time || typeof time !== 'object') {
299
+ results.push({
300
+ name: 'should get time information',
301
+ passed: false,
302
+ skipped: false,
303
+ error: 'system.getTime() returned null, undefined, or non-object',
304
+ device: deviceName
305
+ });
306
+ } else {
307
+ results.push({
308
+ name: 'should get time information',
309
+ passed: true,
310
+ skipped: false,
311
+ error: null,
312
+ device: deviceName,
313
+ details: { hasTime: !!time.time }
314
+ });
315
+ }
316
+ }
317
+ } catch (error) {
318
+ results.push({
319
+ name: 'should get time information',
320
+ passed: false,
321
+ skipped: false,
322
+ error: error.message,
323
+ device: deviceName
324
+ });
325
+ }
326
+
327
+ // Test 7: Get LED mode (if supported)
328
+ try {
329
+ if (!testDevice.system || typeof testDevice.system.getLedMode !== 'function') {
330
+ results.push({
331
+ name: 'should get LED mode',
332
+ passed: false,
333
+ skipped: true,
334
+ error: 'Device does not support system.getLedMode',
335
+ device: deviceName
336
+ });
337
+ } else {
338
+ const ledMode = await testDevice.system.getLedMode();
339
+
340
+ if (!ledMode || typeof ledMode !== 'object') {
341
+ results.push({
342
+ name: 'should get LED mode',
343
+ passed: false,
344
+ skipped: false,
345
+ error: 'system.getLedMode() returned null, undefined, or non-object',
346
+ device: deviceName
347
+ });
348
+ } else {
349
+ results.push({
350
+ name: 'should get LED mode',
351
+ passed: true,
352
+ skipped: false,
353
+ error: null,
354
+ device: deviceName,
355
+ details: { hasLedMode: !!ledMode.ledMode }
356
+ });
357
+ }
358
+ }
359
+ } catch (error) {
360
+ results.push({
361
+ name: 'should get LED mode',
362
+ passed: false,
363
+ skipped: false,
364
+ error: error.message,
365
+ device: deviceName
366
+ });
367
+ }
368
+
369
+ return results;
370
+ }
371
+
372
+ module.exports = {
373
+ metadata,
374
+ runTests
375
+ };
@@ -47,7 +47,17 @@ async function runTests(context) {
47
47
 
48
48
  // Test 1: Get temp unit
49
49
  try {
50
- const response = await testDevice.getTempUnit(0);
50
+ if (!testDevice.tempUnit) {
51
+ results.push({
52
+ name: 'should get temp unit',
53
+ passed: false,
54
+ skipped: true,
55
+ error: 'Device does not support temp unit feature',
56
+ device: deviceName
57
+ });
58
+ return results;
59
+ }
60
+ const response = await testDevice.tempUnit.get({ channel: 0 });
51
61
 
52
62
  if (!response) {
53
63
  results.push({
@@ -87,8 +97,18 @@ async function runTests(context) {
87
97
 
88
98
  // Test 2: Control temp unit
89
99
  try {
100
+ if (!testDevice.tempUnit) {
101
+ results.push({
102
+ name: 'should control temp unit',
103
+ passed: false,
104
+ skipped: true,
105
+ error: 'Device does not support temp unit feature',
106
+ device: deviceName
107
+ });
108
+ return results;
109
+ }
90
110
  // Get current temp unit first
91
- const currentResponse = await testDevice.getTempUnit(0);
111
+ const currentResponse = await testDevice.tempUnit.get({ channel: 0 });
92
112
  await new Promise(resolve => setTimeout(resolve, 1000));
93
113
 
94
114
  if (!currentResponse || !Array.isArray(currentResponse.tempUnit) || currentResponse.tempUnit.length === 0) {
@@ -90,21 +90,20 @@ async function runTests(context) {
90
90
  const deviceName = getDeviceName(testDevice);
91
91
 
92
92
  try {
93
- if (typeof testDevice.getToggleState === 'function') {
94
- const toggleState = await testDevice.getToggleState(0);
93
+ if (testDevice.toggle) {
94
+ const toggleState = await testDevice.toggle.get({ channel: 0 });
95
95
 
96
96
  if (!toggleState) {
97
97
  results.push({
98
98
  name: 'should get toggle state',
99
99
  passed: false,
100
100
  skipped: false,
101
- error: 'getToggleState returned null or undefined',
101
+ error: 'toggle.get() returned null or undefined',
102
102
  device: deviceName
103
103
  });
104
104
  } else {
105
105
  // Check cached toggle state
106
- const cachedState = testDevice.getCachedToggleState(0);
107
- const isOn = testDevice.isOn(0);
106
+ const isOn = testDevice.toggle.isOn({ channel: 0 });
108
107
 
109
108
  results.push({
110
109
  name: 'should get toggle state',
@@ -114,7 +113,6 @@ async function runTests(context) {
114
113
  device: deviceName,
115
114
  details: {
116
115
  toggleState: toggleState,
117
- cachedState: cachedState,
118
116
  isOn: isOn
119
117
  }
120
118
  });
@@ -124,7 +122,7 @@ async function runTests(context) {
124
122
  name: 'should get toggle state',
125
123
  passed: false,
126
124
  skipped: true,
127
- error: 'Device does not support getToggleState',
125
+ error: 'Device does not support toggle feature',
128
126
  device: deviceName
129
127
  });
130
128
  }
@@ -140,81 +138,71 @@ async function runTests(context) {
140
138
 
141
139
  // Test 3: Control toggle state (turn on/off)
142
140
  try {
143
- // Get initial state
144
- let initialState = undefined;
145
- if (typeof testDevice.getToggleState === 'function') {
146
- const stateResponse = await testDevice.getToggleState(0);
147
- if (stateResponse && stateResponse.togglex) {
148
- initialState = stateResponse.togglex[0]?.onoff || stateResponse.togglex.onoff;
149
- }
150
- } else if (typeof testDevice.isOn === 'function') {
151
- initialState = testDevice.isOn(0);
152
- }
153
-
154
- // Test turnOn if available
155
- if (typeof testDevice.turnOn === 'function') {
156
- await testDevice.turnOn(0);
141
+ if (!testDevice.toggle) {
142
+ results.push({
143
+ name: 'should control toggle state',
144
+ passed: false,
145
+ skipped: true,
146
+ error: 'Device does not support toggle feature',
147
+ device: deviceName
148
+ });
149
+ } else {
150
+ // Get initial state
151
+ let initialState = testDevice.toggle.isOn({ channel: 0 });
152
+
153
+ // Test turn on
154
+ await testDevice.toggle.set({ on: true, channel: 0 });
157
155
  await new Promise(resolve => setTimeout(resolve, 2000));
158
156
 
159
157
  // Verify state
160
- if (typeof testDevice.isOn === 'function') {
161
- const isOnAfter = testDevice.isOn(0);
162
- if (!isOnAfter) {
163
- results.push({
164
- name: 'should control toggle state (turn on)',
165
- passed: false,
166
- skipped: false,
167
- error: 'Device did not turn on after turnOn() call',
168
- device: deviceName
169
- });
170
- } else {
171
- results.push({
172
- name: 'should control toggle state (turn on)',
173
- passed: true,
174
- skipped: false,
175
- error: null,
176
- device: deviceName
177
- });
178
- }
158
+ const isOnAfter = testDevice.toggle.isOn({ channel: 0 });
159
+ if (!isOnAfter) {
160
+ results.push({
161
+ name: 'should control toggle state (turn on)',
162
+ passed: false,
163
+ skipped: false,
164
+ error: 'Device did not turn on after toggle.set({ on: true, channel: 0 }) call',
165
+ device: deviceName
166
+ });
167
+ } else {
168
+ results.push({
169
+ name: 'should control toggle state (turn on)',
170
+ passed: true,
171
+ skipped: false,
172
+ error: null,
173
+ device: deviceName
174
+ });
179
175
  }
180
- }
181
-
182
- // Test turnOff if available
183
- if (typeof testDevice.turnOff === 'function') {
184
- await testDevice.turnOff(0);
176
+
177
+ // Test turn off
178
+ await testDevice.toggle.set({ on: false, channel: 0 });
185
179
  await new Promise(resolve => setTimeout(resolve, 2000));
186
180
 
187
181
  // Verify state
188
- if (typeof testDevice.isOn === 'function') {
189
- const isOnAfter = testDevice.isOn(0);
190
- if (isOnAfter) {
191
- results.push({
192
- name: 'should control toggle state (turn off)',
193
- passed: false,
194
- skipped: false,
195
- error: 'Device did not turn off after turnOff() call',
196
- device: deviceName
197
- });
198
- } else {
199
- results.push({
200
- name: 'should control toggle state (turn off)',
201
- passed: true,
202
- skipped: false,
203
- error: null,
204
- device: deviceName
205
- });
206
- }
207
- }
208
- }
209
-
210
- // Restore initial state if we changed it
211
- if (initialState !== undefined && typeof testDevice.turnOn === 'function' && typeof testDevice.turnOff === 'function') {
212
- if (initialState === 1 || initialState === true) {
213
- await testDevice.turnOn(0);
182
+ const isOffAfter = testDevice.toggle.isOn({ channel: 0 });
183
+ if (isOffAfter) {
184
+ results.push({
185
+ name: 'should control toggle state (turn off)',
186
+ passed: false,
187
+ skipped: false,
188
+ error: 'Device did not turn off after toggle.set({ on: false, channel: 0 }) call',
189
+ device: deviceName
190
+ });
214
191
  } else {
215
- await testDevice.turnOff(0);
192
+ results.push({
193
+ name: 'should control toggle state (turn off)',
194
+ passed: true,
195
+ skipped: false,
196
+ error: null,
197
+ device: deviceName
198
+ });
199
+ }
200
+
201
+ // Restore initial state if we changed it
202
+ if (initialState !== undefined) {
203
+ await testDevice.toggle.set({ on: initialState, channel: 0 });
204
+ await new Promise(resolve => setTimeout(resolve, 2000));
216
205
  }
217
- await new Promise(resolve => setTimeout(resolve, 2000));
218
206
  }
219
207
 
220
208
  } catch (error) {