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.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/cli/commands/control/execute.js +23 -0
- package/cli/commands/control/index.js +12 -0
- package/cli/commands/control/menu.js +193 -0
- package/cli/commands/control/params/generic.js +229 -0
- package/cli/commands/control/params/index.js +56 -0
- package/cli/commands/control/params/light.js +188 -0
- package/cli/commands/control/params/thermostat.js +166 -0
- package/cli/commands/control/params/timer.js +242 -0
- package/cli/commands/control/params/trigger.js +206 -0
- package/cli/commands/dump.js +35 -0
- package/cli/commands/index.js +34 -0
- package/cli/commands/info.js +221 -0
- package/cli/commands/list.js +112 -0
- package/cli/commands/mqtt.js +187 -0
- package/cli/commands/sniffer/device-sniffer.js +217 -0
- package/cli/commands/sniffer/fake-app.js +233 -0
- package/cli/commands/sniffer/index.js +7 -0
- package/cli/commands/sniffer/message-queue.js +65 -0
- package/cli/commands/sniffer/sniffer-menu.js +676 -0
- package/cli/commands/stats.js +90 -0
- package/cli/commands/status/device-status.js +1403 -0
- package/cli/commands/status/hub-status.js +72 -0
- package/cli/commands/status/index.js +50 -0
- package/cli/commands/status/subdevices/hub-smoke-detector.js +82 -0
- package/cli/commands/status/subdevices/hub-temp-hum-sensor.js +43 -0
- package/cli/commands/status/subdevices/hub-thermostat-valve.js +83 -0
- package/cli/commands/status/subdevices/hub-water-leak-sensor.js +27 -0
- package/cli/commands/status/subdevices/index.js +23 -0
- package/cli/commands/test/index.js +185 -0
- package/cli/config/users.js +108 -0
- package/cli/control-registry.js +875 -0
- package/cli/helpers/client.js +89 -0
- package/cli/helpers/meross.js +106 -0
- package/cli/menu/index.js +10 -0
- package/cli/menu/main.js +648 -0
- package/cli/menu/settings.js +789 -0
- package/cli/meross-cli.js +547 -0
- package/cli/tests/README.md +365 -0
- package/cli/tests/test-alarm.js +144 -0
- package/cli/tests/test-child-lock.js +248 -0
- package/cli/tests/test-config.js +133 -0
- package/cli/tests/test-control.js +189 -0
- package/cli/tests/test-diffuser.js +505 -0
- package/cli/tests/test-dnd.js +246 -0
- package/cli/tests/test-electricity.js +209 -0
- package/cli/tests/test-encryption.js +281 -0
- package/cli/tests/test-garage.js +259 -0
- package/cli/tests/test-helper.js +313 -0
- package/cli/tests/test-hub-mts100.js +355 -0
- package/cli/tests/test-hub-sensors.js +489 -0
- package/cli/tests/test-light.js +253 -0
- package/cli/tests/test-presence.js +497 -0
- package/cli/tests/test-registry.js +419 -0
- package/cli/tests/test-roller-shutter.js +628 -0
- package/cli/tests/test-runner.js +415 -0
- package/cli/tests/test-runtime.js +234 -0
- package/cli/tests/test-screen.js +133 -0
- package/cli/tests/test-sensor-history.js +146 -0
- package/cli/tests/test-smoke-config.js +138 -0
- package/cli/tests/test-spray.js +131 -0
- package/cli/tests/test-temp-unit.js +133 -0
- package/cli/tests/test-template.js +238 -0
- package/cli/tests/test-thermostat.js +919 -0
- package/cli/tests/test-timer.js +372 -0
- package/cli/tests/test-toggle.js +342 -0
- package/cli/tests/test-trigger.js +279 -0
- package/cli/utils/display.js +86 -0
- package/cli/utils/terminal.js +137 -0
- 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
|
+
};
|