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,372 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Timer Tests
5
+ * Tests timer creation, management, and push notifications
6
+ */
7
+
8
+ const { findDevicesByAbility, getDeviceName, OnlineStatus } = require('./test-helper');
9
+ const { TimerType, TimerUtils } = require('meross-iot');
10
+ const { createTimer } = TimerUtils;
11
+
12
+ const metadata = {
13
+ name: 'timer',
14
+ description: 'Tests timer creation, management, and push notifications',
15
+ requiredAbilities: ['Appliance.Control.TimerX'],
16
+ minDevices: 1
17
+ };
18
+
19
+ async function runTests(context) {
20
+ const { manager, devices, options = {} } = context;
21
+ const timeout = options.timeout || 30000;
22
+ const results = [];
23
+
24
+ // If no devices provided, discover them
25
+ let testDevices = devices || [];
26
+ if (testDevices.length === 0) {
27
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.TimerX', OnlineStatus.ONLINE);
28
+ }
29
+
30
+ if (testDevices.length === 0) {
31
+ results.push({
32
+ name: 'should find devices with timer capability',
33
+ passed: false,
34
+ skipped: true,
35
+ error: 'No devices with timer capability found',
36
+ device: null
37
+ });
38
+ return results;
39
+ }
40
+
41
+ results.push({
42
+ name: 'should find devices with timer capability',
43
+ passed: true,
44
+ skipped: false,
45
+ error: null,
46
+ device: null
47
+ });
48
+
49
+ const testDevice = testDevices[0];
50
+ const deviceName = getDeviceName(testDevice);
51
+
52
+ let createdTimerId = null;
53
+
54
+ // Test 1: Create a timer and verify it exists
55
+ try {
56
+ // Create a test timer using the utility function
57
+ const testTimer = createTimer({
58
+ alias: 'Test Timer - CLI Test',
59
+ time: '12:00',
60
+ days: ['weekday'],
61
+ on: true,
62
+ type: TimerType.SINGLE_POINT_WEEKLY_CYCLE,
63
+ channel: 0,
64
+ enabled: true
65
+ });
66
+
67
+ // Wait for push notification after SET (as per API spec: "PUSH after SET")
68
+ let timerIdFromPush = null;
69
+ const pushNotificationPromise = new Promise((resolve) => {
70
+ const handler = (notification) => {
71
+ if (notification.namespace === 'Appliance.Control.TimerX' && notification.timerxData) {
72
+ const timerData = Array.isArray(notification.timerxData) ? notification.timerxData : [notification.timerxData];
73
+ const createdTimer = timerData.find(t =>
74
+ t.channel === 0 &&
75
+ t.alias === 'Test Timer - CLI Test' &&
76
+ t.id
77
+ );
78
+ if (createdTimer && createdTimer.id) {
79
+ timerIdFromPush = createdTimer.id;
80
+ testDevice.removeListener('pushNotification', handler);
81
+ resolve();
82
+ }
83
+ }
84
+ };
85
+ testDevice.on('pushNotification', handler);
86
+
87
+ // Timeout after 5 seconds if no push notification arrives
88
+ setTimeout(() => {
89
+ testDevice.removeListener('pushNotification', handler);
90
+ resolve();
91
+ }, 5000);
92
+ });
93
+
94
+ const createResult = await testDevice.setTimerX(testTimer);
95
+
96
+ if (!createResult) {
97
+ results.push({
98
+ name: 'should create timer',
99
+ passed: false,
100
+ skipped: false,
101
+ error: 'setTimerX returned null or undefined',
102
+ device: deviceName
103
+ });
104
+ return results;
105
+ }
106
+
107
+ // Wait for push notification (contains timer with ID)
108
+ await pushNotificationPromise;
109
+
110
+ // Get the timer ID from push notification, or fallback to generated ID
111
+ if (timerIdFromPush) {
112
+ createdTimerId = timerIdFromPush;
113
+ } else if (testTimer.id) {
114
+ createdTimerId = testTimer.id;
115
+ } else {
116
+ // Wait a bit and query by alias to find it
117
+ await new Promise(resolve => setTimeout(resolve, 2000));
118
+ const timers = await testDevice.getTimerX({ channel: 0 });
119
+ const foundTimer = timers?.timerx?.find(t => t.alias === 'Test Timer - CLI Test');
120
+ if (foundTimer && foundTimer.id) {
121
+ createdTimerId = foundTimer.id;
122
+ }
123
+ }
124
+
125
+ if (!createdTimerId) {
126
+ results.push({
127
+ name: 'should create timer',
128
+ passed: false,
129
+ skipped: false,
130
+ error: 'Could not get timer ID after creation',
131
+ device: deviceName
132
+ });
133
+ return results;
134
+ }
135
+
136
+ // Verify timer exists by querying it
137
+ const timerInfo = await testDevice.getTimerX({ timerId: createdTimerId });
138
+
139
+ if (!timerInfo || !timerInfo.timerx) {
140
+ results.push({
141
+ name: 'should create timer',
142
+ passed: false,
143
+ skipped: false,
144
+ error: 'getTimerX returned null or undefined for created timer',
145
+ device: deviceName
146
+ });
147
+ return results;
148
+ }
149
+
150
+ // Verify cached timer state
151
+ const cachedTimers = testDevice.getCachedTimerX(0);
152
+
153
+ results.push({
154
+ name: 'should create timer',
155
+ passed: true,
156
+ skipped: false,
157
+ error: null,
158
+ device: deviceName,
159
+ details: {
160
+ timerId: createdTimerId,
161
+ cachedTimerCount: cachedTimers ? cachedTimers.length : 0
162
+ }
163
+ });
164
+ } catch (error) {
165
+ results.push({
166
+ name: 'should create timer',
167
+ passed: false,
168
+ skipped: false,
169
+ error: error.message,
170
+ device: deviceName
171
+ });
172
+ return results;
173
+ }
174
+
175
+ // Test 2: Delete the timer
176
+ if (createdTimerId) {
177
+ try {
178
+ // Wait a bit after creation before deleting
179
+ await new Promise(resolve => setTimeout(resolve, 1000));
180
+
181
+ let deleteResponse;
182
+ try {
183
+ deleteResponse = await testDevice.deleteTimerX({ timerId: createdTimerId, channel: 0 });
184
+ } catch (deleteError) {
185
+ results.push({
186
+ name: 'should delete timer',
187
+ passed: false,
188
+ skipped: false,
189
+ error: `DELETE command threw error: ${deleteError.message}`,
190
+ device: deviceName,
191
+ details: {
192
+ timerId: createdTimerId,
193
+ error: deleteError.message,
194
+ stack: deleteError.stack
195
+ }
196
+ });
197
+ return results;
198
+ }
199
+
200
+ // Log the full response for debugging
201
+ const deleteResponseString = JSON.stringify(deleteResponse, null, 2);
202
+
203
+ // Check if response is null/undefined (might indicate timeout or no response)
204
+ if (!deleteResponse) {
205
+ results.push({
206
+ name: 'should delete timer',
207
+ passed: false,
208
+ skipped: false,
209
+ error: 'DELETE command returned null or undefined response',
210
+ device: deviceName,
211
+ details: {
212
+ timerId: createdTimerId,
213
+ note: 'DELETE command may have timed out or not received DELETEACK'
214
+ }
215
+ });
216
+ return results;
217
+ }
218
+
219
+ // Check what the DELETE response contains
220
+ // Error 5050 "id not found" is returned even when deletion succeeds
221
+ // Empty DELETEACK (no error) also means deletion succeeded
222
+ const deleteResponseError = deleteResponse?.error;
223
+ const hasError = deleteResponseError !== null && deleteResponseError !== undefined;
224
+ const isIdNotFound = deleteResponseError?.code === 5050 ||
225
+ deleteResponseError?.detail === 'id not found';
226
+
227
+ // If DELETE returned an error other than "id not found", this is a real failure
228
+ if (hasError && !isIdNotFound) {
229
+ results.push({
230
+ name: 'should delete timer',
231
+ passed: false,
232
+ skipped: false,
233
+ error: `DELETE command failed: ${JSON.stringify(deleteResponseError)}`,
234
+ device: deviceName,
235
+ details: {
236
+ timerId: createdTimerId,
237
+ deleteResponse: deleteResponseString
238
+ }
239
+ });
240
+ return results;
241
+ }
242
+
243
+ // DELETE command was accepted (no error, or error 5050)
244
+ // Error 5050 means the timer is deleted (success)
245
+ // Wait longer for device to process deletion (some devices need more time)
246
+ await new Promise(resolve => setTimeout(resolve, 3000));
247
+
248
+ // Clear cached state and query fresh from device
249
+ try {
250
+ // Force a refresh by querying the device directly
251
+ const timersResponse = await testDevice.getTimerX({ channel: 0 });
252
+
253
+ let timerStillExists = false;
254
+ let allTimerIds = [];
255
+
256
+ if (timersResponse && timersResponse.timerx) {
257
+ const timers = Array.isArray(timersResponse.timerx) ? timersResponse.timerx : [timersResponse.timerx];
258
+ allTimerIds = timers.map(t => t.id);
259
+ timerStillExists = timers.some(t => t.id === createdTimerId);
260
+ }
261
+
262
+ if (timerStillExists) {
263
+ results.push({
264
+ name: 'should delete timer',
265
+ passed: false,
266
+ skipped: false,
267
+ error: `Timer with ID ${createdTimerId} still exists after deletion`,
268
+ device: deviceName,
269
+ details: {
270
+ timerId: createdTimerId,
271
+ deleteResponse: deleteResponseString,
272
+ allTimerIds: allTimerIds,
273
+ note: 'DELETE command was sent and acknowledged, but timer still exists on device after 3 seconds'
274
+ }
275
+ });
276
+ } else {
277
+ results.push({
278
+ name: 'should delete timer',
279
+ passed: true,
280
+ skipped: false,
281
+ error: null,
282
+ device: deviceName,
283
+ details: {
284
+ timerId: createdTimerId,
285
+ deleteResponse: isIdNotFound ? 'Deleted (error 5050 - id not found)' : 'Deleted (empty DELETEACK)',
286
+ verified: 'Timer confirmed removed from device',
287
+ allTimerIds: allTimerIds
288
+ }
289
+ });
290
+ }
291
+ } catch (queryError) {
292
+ // If query fails, still consider deletion successful since DELETEACK was received
293
+ results.push({
294
+ name: 'should delete timer',
295
+ passed: true,
296
+ skipped: false,
297
+ error: null,
298
+ device: deviceName,
299
+ details: {
300
+ timerId: createdTimerId,
301
+ deleteResponse: isIdNotFound ? 'Deleted (error 5050 - id not found)' : 'Deleted (empty DELETEACK)',
302
+ verificationQueryFailed: queryError.message,
303
+ note: 'DELETE command acknowledged, but verification query failed'
304
+ }
305
+ });
306
+ }
307
+ } catch (error) {
308
+ results.push({
309
+ name: 'should delete timer',
310
+ passed: false,
311
+ skipped: false,
312
+ error: error.message,
313
+ device: deviceName,
314
+ details: {
315
+ timerId: createdTimerId,
316
+ stack: error.stack
317
+ }
318
+ });
319
+ }
320
+ }
321
+
322
+ // Test 3: Handle timer push notifications
323
+ try {
324
+ // Set up listener for timer push notifications
325
+ let receivedNotification = false;
326
+ const notificationHandler = (notification) => {
327
+ if (notification.namespace === 'Appliance.Control.TimerX') {
328
+ receivedNotification = true;
329
+ }
330
+ };
331
+
332
+ testDevice.on('pushNotification', notificationHandler);
333
+
334
+ // Get timer info (may trigger a push notification)
335
+ await testDevice.getTimerX({ channel: 0 });
336
+
337
+ // Wait a bit for potential push notifications
338
+ await new Promise(resolve => setTimeout(resolve, 2000));
339
+
340
+ // Remove listener
341
+ testDevice.removeListener('pushNotification', notificationHandler);
342
+
343
+ // Note: We don't assert on receivedNotification since push notifications
344
+ // are device-initiated and may not occur during testing
345
+ results.push({
346
+ name: 'should handle timer push notifications',
347
+ passed: true,
348
+ skipped: false,
349
+ error: null,
350
+ device: deviceName,
351
+ details: {
352
+ notificationReceived: receivedNotification,
353
+ note: 'Push notifications are device-initiated and may not occur during testing'
354
+ }
355
+ });
356
+ } catch (error) {
357
+ results.push({
358
+ name: 'should handle timer push notifications',
359
+ passed: false,
360
+ skipped: false,
361
+ error: error.message,
362
+ device: deviceName
363
+ });
364
+ }
365
+
366
+ return results;
367
+ }
368
+
369
+ module.exports = {
370
+ metadata,
371
+ runTests
372
+ };