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,248 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Child Lock (Physical Lock) Tests
5
+ * Tests physical lock/child lock safety features
6
+ */
7
+
8
+ const { findDevicesByAbility, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'child-lock',
12
+ description: 'Tests physical lock/child lock safety features',
13
+ requiredAbilities: ['Appliance.Control.PhysicalLock'],
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
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.PhysicalLock', OnlineStatus.ONLINE);
26
+ }
27
+
28
+ if (testDevices.length === 0) {
29
+ results.push({
30
+ name: 'should find devices with child lock capability',
31
+ passed: false,
32
+ skipped: true,
33
+ error: 'No devices with child lock capability found',
34
+ device: null
35
+ });
36
+ return results;
37
+ }
38
+
39
+ results.push({
40
+ name: 'should find devices with child lock capability',
41
+ passed: true,
42
+ skipped: false,
43
+ error: null,
44
+ device: null
45
+ });
46
+
47
+ const testDevice = testDevices[0];
48
+ const deviceName = getDeviceName(testDevice);
49
+
50
+ // Test 1: Get child lock status
51
+ try {
52
+ const lockStatus = await testDevice.getChildLock({ channel: 0 });
53
+
54
+ if (!lockStatus) {
55
+ results.push({
56
+ name: 'should get child lock status',
57
+ passed: false,
58
+ skipped: false,
59
+ error: 'getChildLock returned null or undefined',
60
+ device: deviceName
61
+ });
62
+ } else if (!lockStatus.lock) {
63
+ results.push({
64
+ name: 'should get child lock status',
65
+ passed: false,
66
+ skipped: false,
67
+ error: 'Response does not contain lock property',
68
+ device: deviceName
69
+ });
70
+ } else {
71
+ results.push({
72
+ name: 'should get child lock status',
73
+ passed: true,
74
+ skipped: false,
75
+ error: null,
76
+ device: deviceName,
77
+ details: { lockStatus: lockStatus.lock }
78
+ });
79
+ }
80
+ } catch (error) {
81
+ results.push({
82
+ name: 'should get child lock status',
83
+ passed: false,
84
+ skipped: false,
85
+ error: error.message,
86
+ device: deviceName
87
+ });
88
+ }
89
+
90
+ // Test 2: Control child lock status
91
+ try {
92
+ // Get initial lock status
93
+ const initialStatus = await testDevice.getChildLock({ channel: 0 });
94
+
95
+ if (!initialStatus || !initialStatus.lock || !Array.isArray(initialStatus.lock) || initialStatus.lock.length === 0) {
96
+ results.push({
97
+ name: 'should control child lock status',
98
+ passed: false,
99
+ skipped: true,
100
+ error: 'Could not get initial lock status or lock array is empty',
101
+ device: deviceName
102
+ });
103
+ } else {
104
+ const initialLockState = initialStatus.lock[0].onoff;
105
+
106
+ // Toggle lock state
107
+ const newLockState = initialLockState === 1 ? 0 : 1;
108
+ const lockData = {
109
+ channel: 0,
110
+ onoff: newLockState
111
+ };
112
+
113
+ await testDevice.setChildLock(lockData);
114
+ await new Promise(resolve => setTimeout(resolve, 2000));
115
+
116
+ // Verify the change
117
+ const updatedStatus = await testDevice.getChildLock({ channel: 0 });
118
+
119
+ if (!updatedStatus || !updatedStatus.lock || !updatedStatus.lock[0]) {
120
+ results.push({
121
+ name: 'should control child lock status',
122
+ passed: false,
123
+ skipped: false,
124
+ error: 'Could not verify updated lock status',
125
+ device: deviceName
126
+ });
127
+ } else if (updatedStatus.lock[0].onoff !== newLockState) {
128
+ results.push({
129
+ name: 'should control child lock status',
130
+ passed: false,
131
+ skipped: false,
132
+ error: `Lock state did not change. Expected ${newLockState}, got ${updatedStatus.lock[0].onoff}`,
133
+ device: deviceName
134
+ });
135
+ } else {
136
+ // Restore original state
137
+ const restoreLockData = {
138
+ channel: 0,
139
+ onoff: initialLockState
140
+ };
141
+ await testDevice.setChildLock(restoreLockData);
142
+ await new Promise(resolve => setTimeout(resolve, 2000));
143
+
144
+ const restoredStatus = await testDevice.getChildLock({ channel: 0 });
145
+
146
+ if (!restoredStatus || !restoredStatus.lock || !restoredStatus.lock[0] ||
147
+ restoredStatus.lock[0].onoff !== initialLockState) {
148
+ results.push({
149
+ name: 'should control child lock status',
150
+ passed: false,
151
+ skipped: false,
152
+ error: `Failed to restore lock state. Expected ${initialLockState}, got ${restoredStatus?.lock?.[0]?.onoff}`,
153
+ device: deviceName
154
+ });
155
+ } else {
156
+ results.push({
157
+ name: 'should control child lock status',
158
+ passed: true,
159
+ skipped: false,
160
+ error: null,
161
+ device: deviceName
162
+ });
163
+ }
164
+ }
165
+ }
166
+ } catch (error) {
167
+ results.push({
168
+ name: 'should control child lock status',
169
+ passed: false,
170
+ skipped: false,
171
+ error: error.message,
172
+ device: deviceName
173
+ });
174
+ }
175
+
176
+ // Test 3: Handle multiple channel lock control
177
+ try {
178
+ // Get initial status for all channels
179
+ const initialStatus = await testDevice.getChildLock({ channel: 0 });
180
+
181
+ if (!initialStatus || !initialStatus.lock) {
182
+ results.push({
183
+ name: 'should handle multiple channel lock control',
184
+ passed: false,
185
+ skipped: true,
186
+ error: 'Could not get initial lock status',
187
+ device: deviceName
188
+ });
189
+ } else {
190
+ // Control multiple channels using array
191
+ const lockDataArray = [
192
+ {
193
+ channel: 0,
194
+ onoff: initialStatus.lock[0]?.onoff || 0
195
+ }
196
+ ];
197
+
198
+ // If device has multiple channels, add them
199
+ if (initialStatus.lock.length > 1) {
200
+ for (let i = 1; i < initialStatus.lock.length; i++) {
201
+ lockDataArray.push({
202
+ channel: initialStatus.lock[i].channel || i,
203
+ onoff: initialStatus.lock[i].onoff || 0
204
+ });
205
+ }
206
+ }
207
+
208
+ await testDevice.setChildLock({ lockData: lockDataArray });
209
+ await new Promise(resolve => setTimeout(resolve, 2000));
210
+
211
+ // Verify all channels were set
212
+ const updatedStatus = await testDevice.getChildLock({ channel: 0 });
213
+
214
+ if (!updatedStatus) {
215
+ results.push({
216
+ name: 'should handle multiple channel lock control',
217
+ passed: false,
218
+ skipped: false,
219
+ error: 'Could not verify updated lock status',
220
+ device: deviceName
221
+ });
222
+ } else {
223
+ results.push({
224
+ name: 'should handle multiple channel lock control',
225
+ passed: true,
226
+ skipped: false,
227
+ error: null,
228
+ device: deviceName
229
+ });
230
+ }
231
+ }
232
+ } catch (error) {
233
+ results.push({
234
+ name: 'should handle multiple channel lock control',
235
+ passed: false,
236
+ skipped: false,
237
+ error: error.message,
238
+ device: deviceName
239
+ });
240
+ }
241
+
242
+ return results;
243
+ }
244
+
245
+ module.exports = {
246
+ metadata,
247
+ runTests
248
+ };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Config Device Tests
5
+ * Tests device configuration and settings
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'config',
12
+ description: 'Tests device configuration and settings',
13
+ requiredAbilities: ['Appliance.Config.OverTemp'],
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
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Config.OverTemp', OnlineStatus.ONLINE);
26
+ }
27
+
28
+ // Wait for devices to be connected
29
+ for (const device of testDevices) {
30
+ await waitForDeviceConnection(device, timeout);
31
+ await new Promise(resolve => setTimeout(resolve, 2000));
32
+ }
33
+
34
+ if (testDevices.length === 0) {
35
+ results.push({
36
+ name: 'should get config over temp',
37
+ passed: false,
38
+ skipped: true,
39
+ error: 'No Config Over Temp device has been found to run this test on',
40
+ device: null
41
+ });
42
+ return results;
43
+ }
44
+
45
+ const testDevice = testDevices[0];
46
+ const deviceName = getDeviceName(testDevice);
47
+
48
+ // Test 1: Get config over temp
49
+ try {
50
+ const response = await testDevice.getConfigOverTemp();
51
+
52
+ if (!response) {
53
+ results.push({
54
+ name: 'should get config over temp',
55
+ passed: false,
56
+ skipped: false,
57
+ error: 'getConfigOverTemp returned null or undefined',
58
+ device: deviceName
59
+ });
60
+ } else if (!response.overTemp) {
61
+ results.push({
62
+ name: 'should get config over temp',
63
+ passed: false,
64
+ skipped: false,
65
+ error: 'Response does not contain overTemp property',
66
+ device: deviceName
67
+ });
68
+ } else {
69
+ results.push({
70
+ name: 'should get config over temp',
71
+ passed: true,
72
+ skipped: false,
73
+ error: null,
74
+ device: deviceName,
75
+ details: { overTemp: response.overTemp }
76
+ });
77
+ }
78
+ } catch (error) {
79
+ results.push({
80
+ name: 'should get config over temp',
81
+ passed: false,
82
+ skipped: false,
83
+ error: error.message,
84
+ device: deviceName
85
+ });
86
+ }
87
+
88
+ // Test 2: Control config over temp
89
+ try {
90
+ // Get current config first
91
+ const currentResponse = await testDevice.getConfigOverTemp();
92
+ await new Promise(resolve => setTimeout(resolve, 1000));
93
+
94
+ if (!currentResponse || !currentResponse.overTemp) {
95
+ results.push({
96
+ name: 'should control config over temp',
97
+ passed: false,
98
+ skipped: true,
99
+ error: 'Could not get current config over temp',
100
+ device: deviceName
101
+ });
102
+ } else {
103
+ // Note: We don't actually change the config to avoid disrupting the device
104
+ // We just verify the method exists and can be called
105
+ results.push({
106
+ name: 'should control config over temp',
107
+ passed: true,
108
+ skipped: false,
109
+ error: null,
110
+ device: deviceName,
111
+ details: {
112
+ note: 'Method exists, but not changing config to avoid disrupting device',
113
+ currentConfig: currentResponse.overTemp
114
+ }
115
+ });
116
+ }
117
+ } catch (error) {
118
+ results.push({
119
+ name: 'should control config over temp',
120
+ passed: false,
121
+ skipped: false,
122
+ error: error.message,
123
+ device: deviceName
124
+ });
125
+ }
126
+
127
+ return results;
128
+ }
129
+
130
+ module.exports = {
131
+ metadata,
132
+ runTests
133
+ };
@@ -0,0 +1,189 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Control Device Tests
5
+ * Tests multiple control features and device capabilities
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'control',
12
+ description: 'Tests multiple control features and device capabilities',
13
+ requiredAbilities: ['Appliance.Control.Multiple', 'Appliance.Control.Upgrade', 'Appliance.Control.OverTemp'],
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 control multiple support (most devices support this)
26
+ const multipleDevices = await findDevicesByAbility(manager, 'Appliance.Control.Multiple', OnlineStatus.ONLINE);
27
+
28
+ // Also get any online device for testing control features
29
+ const allDevices = manager.getAllDevices();
30
+ const onlineDevices = allDevices.filter(device => {
31
+ const status = device.onlineStatus !== undefined ? device.onlineStatus : (device.dev?.onlineStatus);
32
+ return status === OnlineStatus.ONLINE;
33
+ });
34
+
35
+ // Combine and deduplicate
36
+ testDevices = [...multipleDevices];
37
+ for (const device of onlineDevices.slice(0, 2)) {
38
+ const uuid = device.dev?.uuid || device.uuid;
39
+ if (!testDevices.find(d => (d.dev?.uuid || d.uuid) === uuid)) {
40
+ testDevices.push(device);
41
+ }
42
+ }
43
+ }
44
+
45
+ // Wait for devices to be connected
46
+ for (const device of testDevices) {
47
+ await waitForDeviceConnection(device, timeout);
48
+ await new Promise(resolve => setTimeout(resolve, 1000));
49
+ }
50
+
51
+ if (testDevices.length === 0) {
52
+ results.push({
53
+ name: 'should find devices with control capabilities',
54
+ passed: false,
55
+ skipped: true,
56
+ error: 'No device has been found to run this test on',
57
+ device: null
58
+ });
59
+ return results;
60
+ }
61
+
62
+ const device = testDevices[0];
63
+ const deviceName = getDeviceName(device);
64
+
65
+ // Test 1: Control Multiple
66
+ try {
67
+ if (typeof device.setMultiple !== 'function') {
68
+ results.push({
69
+ name: 'should execute multiple commands',
70
+ passed: false,
71
+ skipped: true,
72
+ error: 'Device does not support setMultiple',
73
+ device: deviceName
74
+ });
75
+ } else {
76
+ const commands = [
77
+ {
78
+ namespace: 'Appliance.System.All',
79
+ method: 'GET',
80
+ payload: {}
81
+ },
82
+ {
83
+ namespace: 'Appliance.System.Ability',
84
+ method: 'GET',
85
+ payload: {}
86
+ }
87
+ ];
88
+
89
+ const response = await device.setMultiple(commands);
90
+
91
+ if (!response) {
92
+ results.push({
93
+ name: 'should execute multiple commands',
94
+ passed: false,
95
+ skipped: false,
96
+ error: 'setMultiple returned null or undefined',
97
+ device: deviceName
98
+ });
99
+ } else {
100
+ results.push({
101
+ name: 'should execute multiple commands',
102
+ passed: true,
103
+ skipped: false,
104
+ error: null,
105
+ device: deviceName
106
+ });
107
+ }
108
+ }
109
+ } catch (error) {
110
+ results.push({
111
+ name: 'should execute multiple commands',
112
+ passed: false,
113
+ skipped: false,
114
+ error: error.message,
115
+ device: deviceName
116
+ });
117
+ }
118
+
119
+ // Test 2: Control Over Temp
120
+ try {
121
+ if (typeof device.acknowledgeControlOverTemp !== 'function') {
122
+ results.push({
123
+ name: 'should acknowledge over temp event',
124
+ passed: false,
125
+ skipped: true,
126
+ error: 'Device does not support acknowledgeControlOverTemp',
127
+ device: deviceName
128
+ });
129
+ } else {
130
+ // Note: OverTemp is typically SET by the device, and we acknowledge it
131
+ // We don't actually trigger an over temp event, just verify the method exists
132
+ results.push({
133
+ name: 'should acknowledge over temp event',
134
+ passed: true,
135
+ skipped: false,
136
+ error: null,
137
+ device: deviceName,
138
+ details: { note: 'Method exists, but not triggering actual over temp event' }
139
+ });
140
+ }
141
+ } catch (error) {
142
+ results.push({
143
+ name: 'should acknowledge over temp event',
144
+ passed: false,
145
+ skipped: false,
146
+ error: error.message,
147
+ device: deviceName
148
+ });
149
+ }
150
+
151
+ // Test 3: Control Upgrade
152
+ try {
153
+ if (typeof device.setUpgrade !== 'function') {
154
+ results.push({
155
+ name: 'should have upgrade method available',
156
+ passed: false,
157
+ skipped: true,
158
+ error: 'Device does not support setUpgrade',
159
+ device: deviceName
160
+ });
161
+ } else {
162
+ // Note: We don't actually trigger an upgrade to avoid disrupting the device
163
+ // We just verify the method exists
164
+ results.push({
165
+ name: 'should have upgrade method available',
166
+ passed: true,
167
+ skipped: false,
168
+ error: null,
169
+ device: deviceName,
170
+ details: { note: 'Method exists, but not triggering actual upgrade' }
171
+ });
172
+ }
173
+ } catch (error) {
174
+ results.push({
175
+ name: 'should have upgrade method available',
176
+ passed: false,
177
+ skipped: false,
178
+ error: error.message,
179
+ device: deviceName
180
+ });
181
+ }
182
+
183
+ return results;
184
+ }
185
+
186
+ module.exports = {
187
+ metadata,
188
+ runTests
189
+ };