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,281 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Encryption Device Tests
5
+ * Tests encryption support, key management, and message encryption/decryption
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'encryption',
12
+ description: 'Tests encryption support, key management, and message encryption/decryption',
13
+ requiredAbilities: ['Appliance.Encrypt.ECDHE', 'Appliance.Encrypt.Suite'],
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 encryption support
26
+ const encryptionDevices = await findDevicesByAbility(manager, 'Appliance.Encrypt.ECDHE', OnlineStatus.ONLINE);
27
+
28
+ // Also check for Appliance.Encrypt.Suite
29
+ const suiteDevices = await findDevicesByAbility(manager, 'Appliance.Encrypt.Suite', OnlineStatus.ONLINE);
30
+
31
+ // Combine and deduplicate
32
+ const allDevices = [...encryptionDevices];
33
+ for (const device of suiteDevices) {
34
+ if (!allDevices.find(d => d.dev.uuid === device.dev.uuid)) {
35
+ allDevices.push(device);
36
+ }
37
+ }
38
+
39
+ testDevices = allDevices.slice(0, 3); // Test up to 3 devices
40
+ }
41
+
42
+ // Wait for devices to be connected
43
+ for (const device of testDevices) {
44
+ await waitForDeviceConnection(device, timeout);
45
+ await new Promise(resolve => setTimeout(resolve, 1000));
46
+ }
47
+
48
+ if (testDevices.length === 0) {
49
+ results.push({
50
+ name: 'should detect encryption support',
51
+ passed: false,
52
+ skipped: true,
53
+ error: 'No device with encryption support has been found to run this test',
54
+ device: null
55
+ });
56
+ return results;
57
+ }
58
+
59
+ const testDevice = testDevices[0];
60
+ const deviceName = getDeviceName(testDevice);
61
+
62
+ // Test 1: Detect encryption support
63
+ try {
64
+ if (typeof testDevice.supportEncryption !== 'function') {
65
+ results.push({
66
+ name: 'should detect encryption support',
67
+ passed: false,
68
+ skipped: true,
69
+ error: 'Device does not have encryption feature methods',
70
+ device: deviceName
71
+ });
72
+ } else {
73
+ const supportsEncryption = testDevice.supportEncryption();
74
+
75
+ // Check if encryption key is set
76
+ let isKeySet = false;
77
+ if (supportsEncryption) {
78
+ isKeySet = testDevice.isEncryptionKeySet();
79
+ }
80
+
81
+ results.push({
82
+ name: 'should detect encryption support',
83
+ passed: true,
84
+ skipped: false,
85
+ error: null,
86
+ device: deviceName,
87
+ details: {
88
+ supportsEncryption: supportsEncryption,
89
+ isKeySet: isKeySet
90
+ }
91
+ });
92
+ }
93
+ } catch (error) {
94
+ results.push({
95
+ name: 'should detect encryption support',
96
+ passed: false,
97
+ skipped: false,
98
+ error: error.message,
99
+ device: deviceName
100
+ });
101
+ }
102
+
103
+ // Test 2: Set encryption key if supported
104
+ try {
105
+ if (typeof testDevice.supportEncryption !== 'function' || !testDevice.supportEncryption()) {
106
+ results.push({
107
+ name: 'should set encryption key if supported',
108
+ passed: false,
109
+ skipped: true,
110
+ error: 'Device does not support encryption',
111
+ device: deviceName
112
+ });
113
+ } else {
114
+ // Check if key is already set
115
+ const isKeySet = testDevice.isEncryptionKeySet();
116
+
117
+ // If key is not set and we have the required info, set it
118
+ if (!isKeySet && testDevice.dev && testDevice.dev.uuid && manager.key && testDevice._macAddress) {
119
+ testDevice.setEncryptionKey(testDevice.dev.uuid, manager.key, testDevice._macAddress);
120
+
121
+ const keySetAfter = testDevice.isEncryptionKeySet();
122
+
123
+ if (!keySetAfter) {
124
+ results.push({
125
+ name: 'should set encryption key if supported',
126
+ passed: false,
127
+ skipped: false,
128
+ error: 'Encryption key was not set successfully',
129
+ device: deviceName
130
+ });
131
+ } else {
132
+ results.push({
133
+ name: 'should set encryption key if supported',
134
+ passed: true,
135
+ skipped: false,
136
+ error: null,
137
+ device: deviceName,
138
+ details: { keySet: true }
139
+ });
140
+ }
141
+ } else if (!isKeySet) {
142
+ results.push({
143
+ name: 'should set encryption key if supported',
144
+ passed: false,
145
+ skipped: true,
146
+ error: 'Cannot set encryption key - missing required information (UUID, key, or MAC address)',
147
+ device: deviceName
148
+ });
149
+ } else {
150
+ results.push({
151
+ name: 'should set encryption key if supported',
152
+ passed: true,
153
+ skipped: false,
154
+ error: null,
155
+ device: deviceName,
156
+ details: { keySet: true, note: 'Key was already set' }
157
+ });
158
+ }
159
+ }
160
+ } catch (error) {
161
+ results.push({
162
+ name: 'should set encryption key if supported',
163
+ passed: false,
164
+ skipped: false,
165
+ error: error.message,
166
+ device: deviceName
167
+ });
168
+ }
169
+
170
+ // Test 3: Encrypt and decrypt messages if encryption key is set
171
+ try {
172
+ if (typeof testDevice.supportEncryption !== 'function' || !testDevice.supportEncryption()) {
173
+ results.push({
174
+ name: 'should encrypt and decrypt messages if encryption key is set',
175
+ passed: false,
176
+ skipped: true,
177
+ error: 'Device does not support encryption',
178
+ device: deviceName
179
+ });
180
+ } else {
181
+ // Ensure encryption key is set
182
+ if (!testDevice.isEncryptionKeySet()) {
183
+ if (testDevice.dev && testDevice.dev.uuid && manager.key && testDevice._macAddress) {
184
+ testDevice.setEncryptionKey(testDevice.dev.uuid, manager.key, testDevice._macAddress);
185
+ } else {
186
+ results.push({
187
+ name: 'should encrypt and decrypt messages if encryption key is set',
188
+ passed: false,
189
+ skipped: true,
190
+ error: 'Cannot set encryption key - missing required information',
191
+ device: deviceName
192
+ });
193
+ return results;
194
+ }
195
+ }
196
+
197
+ // Test encryption/decryption
198
+ const testMessage = { test: 'data', value: 123 };
199
+
200
+ try {
201
+ const encrypted = testDevice.encryptMessage(testMessage);
202
+
203
+ if (!encrypted) {
204
+ results.push({
205
+ name: 'should encrypt and decrypt messages if encryption key is set',
206
+ passed: false,
207
+ skipped: false,
208
+ error: 'encryptMessage returned null or undefined',
209
+ device: deviceName
210
+ });
211
+ return results;
212
+ }
213
+
214
+ // Encrypted message should be different from original
215
+ if (JSON.stringify(encrypted) === JSON.stringify(testMessage)) {
216
+ results.push({
217
+ name: 'should encrypt and decrypt messages if encryption key is set',
218
+ passed: false,
219
+ skipped: false,
220
+ error: 'Encrypted message is identical to original message',
221
+ device: deviceName
222
+ });
223
+ return results;
224
+ }
225
+
226
+ const decrypted = testDevice.decryptMessage(encrypted);
227
+
228
+ if (!decrypted) {
229
+ results.push({
230
+ name: 'should encrypt and decrypt messages if encryption key is set',
231
+ passed: false,
232
+ skipped: false,
233
+ error: 'decryptMessage returned null or undefined',
234
+ device: deviceName
235
+ });
236
+ } else if (JSON.stringify(decrypted) !== JSON.stringify(testMessage)) {
237
+ results.push({
238
+ name: 'should encrypt and decrypt messages if encryption key is set',
239
+ passed: false,
240
+ skipped: false,
241
+ error: 'Decrypted message does not match original',
242
+ device: deviceName
243
+ });
244
+ } else {
245
+ results.push({
246
+ name: 'should encrypt and decrypt messages if encryption key is set',
247
+ passed: true,
248
+ skipped: false,
249
+ error: null,
250
+ device: deviceName
251
+ });
252
+ }
253
+ } catch (error) {
254
+ // This is okay - encryption might not be fully implemented or might require specific conditions
255
+ results.push({
256
+ name: 'should encrypt and decrypt messages if encryption key is set',
257
+ passed: false,
258
+ skipped: false,
259
+ error: `Encryption/decryption test failed: ${error.message}`,
260
+ device: deviceName,
261
+ details: { note: 'Encryption might not be fully implemented or might require specific conditions' }
262
+ });
263
+ }
264
+ }
265
+ } catch (error) {
266
+ results.push({
267
+ name: 'should encrypt and decrypt messages if encryption key is set',
268
+ passed: false,
269
+ skipped: false,
270
+ error: error.message,
271
+ device: deviceName
272
+ });
273
+ }
274
+
275
+ return results;
276
+ }
277
+
278
+ module.exports = {
279
+ metadata,
280
+ runTests
281
+ };
@@ -0,0 +1,259 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Garage Door Opener Tests
5
+ * Tests open/close control for garage door openers
6
+ */
7
+
8
+ const { findDevicesByAbility, findDevicesByType, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'garage',
12
+ description: 'Tests open/close control for garage door openers',
13
+ requiredAbilities: ['Appliance.GarageDoor.State'],
14
+ minDevices: 1
15
+ };
16
+
17
+ async function runTests(context) {
18
+ const { manager, devices, options = {} } = context;
19
+ const timeout = options.timeout || 120000; // Garage doors take time
20
+ const results = [];
21
+
22
+ // If no devices provided, discover them
23
+ let garageDevices = devices || [];
24
+ if (garageDevices.length === 0) {
25
+ // Find garage door devices (try by ability first, then by type)
26
+ garageDevices = await findDevicesByAbility(manager, 'Appliance.GarageDoor.State', OnlineStatus.ONLINE);
27
+
28
+ if (garageDevices.length === 0) {
29
+ garageDevices = await findDevicesByType(manager, 'msg100', OnlineStatus.ONLINE);
30
+ }
31
+ }
32
+
33
+ // Wait for devices to be connected
34
+ for (const device of garageDevices) {
35
+ await waitForDeviceConnection(device, timeout);
36
+ await device.getGarageDoorState();
37
+ await new Promise(resolve => setTimeout(resolve, 1000));
38
+ }
39
+
40
+ if (garageDevices.length === 0) {
41
+ results.push({
42
+ name: 'should open and close garage door',
43
+ passed: false,
44
+ skipped: true,
45
+ error: 'Could not find any Garage Opener within the given set of devices',
46
+ device: null
47
+ });
48
+ return results;
49
+ }
50
+
51
+ const garage = garageDevices[0];
52
+ const deviceName = getDeviceName(garage);
53
+
54
+ // Test 1: Open and close garage door
55
+ try {
56
+ // Without a full update, the status will be undefined
57
+ let currentStatus = garage.getCachedGarageDoorState(0);
58
+
59
+ // Trigger the full update
60
+ await garage.getGarageDoorState();
61
+ await new Promise(resolve => setTimeout(resolve, 1000));
62
+ currentStatus = garage.getCachedGarageDoorState(0);
63
+
64
+ if (!currentStatus) {
65
+ results.push({
66
+ name: 'should open and close garage door',
67
+ passed: false,
68
+ skipped: false,
69
+ error: 'Could not get garage door state',
70
+ device: deviceName
71
+ });
72
+ return results;
73
+ }
74
+
75
+ // Get current state
76
+ let isOpen = garage.isGarageDoorOpened(0);
77
+ if (isOpen === undefined) {
78
+ results.push({
79
+ name: 'should open and close garage door',
80
+ passed: false,
81
+ skipped: false,
82
+ error: 'isGarageDoorOpened returned undefined',
83
+ device: deviceName
84
+ });
85
+ return results;
86
+ }
87
+
88
+ // Toggle
89
+ if (isOpen) {
90
+ await garage.closeGarageDoor({ channel: 0 });
91
+ } else {
92
+ await garage.openGarageDoor({ channel: 0 });
93
+ }
94
+
95
+ // Wait for door operation (garage doors take time)
96
+ await new Promise(resolve => setTimeout(resolve, 40000));
97
+
98
+ await garage.getGarageDoorState();
99
+ await new Promise(resolve => setTimeout(resolve, 1000));
100
+
101
+ const newIsOpen = garage.isGarageDoorOpened(0);
102
+
103
+ if (newIsOpen === undefined) {
104
+ results.push({
105
+ name: 'should open and close garage door',
106
+ passed: false,
107
+ skipped: false,
108
+ error: 'Could not verify door state after toggle',
109
+ device: deviceName
110
+ });
111
+ return results;
112
+ }
113
+
114
+ if (newIsOpen === isOpen) {
115
+ results.push({
116
+ name: 'should open and close garage door',
117
+ passed: false,
118
+ skipped: false,
119
+ error: `Door state did not change. Expected ${!isOpen}, got ${newIsOpen}`,
120
+ device: deviceName
121
+ });
122
+ return results;
123
+ }
124
+
125
+ // Toggle back
126
+ isOpen = newIsOpen;
127
+ if (isOpen) {
128
+ await garage.closeGarageDoor({ channel: 0 });
129
+ } else {
130
+ await garage.openGarageDoor({ channel: 0 });
131
+ }
132
+
133
+ await new Promise(resolve => setTimeout(resolve, 40000));
134
+
135
+ await garage.getGarageDoorState();
136
+ await new Promise(resolve => setTimeout(resolve, 1000));
137
+
138
+ const finalIsOpen = garage.isGarageDoorOpened(0);
139
+
140
+ if (finalIsOpen === undefined || finalIsOpen === isOpen) {
141
+ results.push({
142
+ name: 'should open and close garage door',
143
+ passed: false,
144
+ skipped: false,
145
+ error: `Failed to toggle back. Expected ${!isOpen}, got ${finalIsOpen}`,
146
+ device: deviceName
147
+ });
148
+ } else {
149
+ results.push({
150
+ name: 'should open and close garage door',
151
+ passed: true,
152
+ skipped: false,
153
+ error: null,
154
+ device: deviceName
155
+ });
156
+ }
157
+ } catch (error) {
158
+ results.push({
159
+ name: 'should open and close garage door',
160
+ passed: false,
161
+ skipped: false,
162
+ error: error.message,
163
+ device: deviceName
164
+ });
165
+ }
166
+
167
+ // Test 2: Get garage door multiple config
168
+ try {
169
+ if (typeof garage.getGarageDoorMultipleState !== 'function') {
170
+ results.push({
171
+ name: 'should get garage door multiple config',
172
+ passed: false,
173
+ skipped: true,
174
+ error: 'Device does not support getGarageDoorMultipleState',
175
+ device: deviceName
176
+ });
177
+ } else {
178
+ const config = await garage.getGarageDoorMultipleState();
179
+
180
+ if (!config) {
181
+ results.push({
182
+ name: 'should get garage door multiple config',
183
+ passed: false,
184
+ skipped: false,
185
+ error: 'getGarageDoorMultipleState returned null or undefined',
186
+ device: deviceName
187
+ });
188
+ } else {
189
+ results.push({
190
+ name: 'should get garage door multiple config',
191
+ passed: true,
192
+ skipped: false,
193
+ error: null,
194
+ device: deviceName,
195
+ details: { config: config.config || config }
196
+ });
197
+ }
198
+ }
199
+ } catch (error) {
200
+ results.push({
201
+ name: 'should get garage door multiple config',
202
+ passed: false,
203
+ skipped: false,
204
+ error: error.message,
205
+ device: deviceName
206
+ });
207
+ }
208
+
209
+ // Test 3: Get garage door config
210
+ try {
211
+ if (typeof garage.getGarageDoorConfig !== 'function') {
212
+ results.push({
213
+ name: 'should get garage door config',
214
+ passed: false,
215
+ skipped: true,
216
+ error: 'Device does not support getGarageDoorConfig',
217
+ device: deviceName
218
+ });
219
+ } else {
220
+ const config = await garage.getGarageDoorConfig();
221
+
222
+ if (!config) {
223
+ results.push({
224
+ name: 'should get garage door config',
225
+ passed: false,
226
+ skipped: false,
227
+ error: 'getGarageDoorConfig returned null or undefined',
228
+ device: deviceName
229
+ });
230
+ } else {
231
+ // Note: We don't test controlGarageDoorConfig as it modifies device settings
232
+ // that may affect operation. Only read operations are tested.
233
+ results.push({
234
+ name: 'should get garage door config',
235
+ passed: true,
236
+ skipped: false,
237
+ error: null,
238
+ device: deviceName,
239
+ details: { config: config }
240
+ });
241
+ }
242
+ }
243
+ } catch (error) {
244
+ results.push({
245
+ name: 'should get garage door config',
246
+ passed: false,
247
+ skipped: false,
248
+ error: error.message,
249
+ device: deviceName
250
+ });
251
+ }
252
+
253
+ return results;
254
+ }
255
+
256
+ module.exports = {
257
+ metadata,
258
+ runTests
259
+ };