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,246 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * DND Mode Tests
5
+ * Tests do-not-disturb mode functionality
6
+ */
7
+
8
+ const { findDevicesByAbility, getDeviceName, OnlineStatus } = require('./test-helper');
9
+ const { DNDMode } = require('meross-iot');
10
+
11
+ const metadata = {
12
+ name: 'dnd',
13
+ description: 'Tests do-not-disturb mode functionality',
14
+ requiredAbilities: ['Appliance.System.DNDMode'],
15
+ minDevices: 1
16
+ };
17
+
18
+ async function runTests(context) {
19
+ const { manager, devices, options = {} } = context;
20
+ const timeout = options.timeout || 30000;
21
+ const results = [];
22
+
23
+ // If no devices provided, discover them
24
+ let testDevices = devices || [];
25
+ if (testDevices.length === 0) {
26
+ // Find devices that support DND mode (most devices support this)
27
+ const allDevices = manager.getAllDevices();
28
+ testDevices = allDevices.filter(d => {
29
+ const status = d.onlineStatus !== undefined ? d.onlineStatus : (d.dev?.onlineStatus);
30
+ return status === OnlineStatus.ONLINE &&
31
+ typeof d.getDNDMode === 'function' &&
32
+ typeof d.setDNDMode === 'function';
33
+ });
34
+ }
35
+
36
+ if (testDevices.length === 0) {
37
+ results.push({
38
+ name: 'should find devices with DND mode capability',
39
+ passed: false,
40
+ skipped: true,
41
+ error: 'No online devices found to test DND mode',
42
+ device: null
43
+ });
44
+ return results;
45
+ }
46
+
47
+ results.push({
48
+ name: 'should find devices with DND mode capability',
49
+ passed: true,
50
+ skipped: false,
51
+ error: null,
52
+ device: null
53
+ });
54
+
55
+ const testDevice = testDevices[0];
56
+ const deviceName = getDeviceName(testDevice);
57
+
58
+ // Test 1: Get and set DND mode
59
+ try {
60
+ // Get current DND mode
61
+ const initialMode = await testDevice.getDNDMode();
62
+
63
+ if (initialMode === null || initialMode === undefined) {
64
+ results.push({
65
+ name: 'should get and set DND mode',
66
+ passed: false,
67
+ skipped: false,
68
+ error: 'getDNDMode returned null or undefined',
69
+ device: deviceName
70
+ });
71
+ } else if (initialMode !== DNDMode.DND_DISABLED && initialMode !== DNDMode.DND_ENABLED) {
72
+ results.push({
73
+ name: 'should get and set DND mode',
74
+ passed: false,
75
+ skipped: false,
76
+ error: `Invalid DND mode value: ${initialMode}`,
77
+ device: deviceName
78
+ });
79
+ } else {
80
+ // Toggle DND mode
81
+ const newMode = initialMode === DNDMode.DND_ENABLED ? DNDMode.DND_DISABLED : DNDMode.DND_ENABLED;
82
+ await testDevice.setDNDMode({ mode: newMode });
83
+
84
+ // Wait for the change to take effect
85
+ await new Promise(resolve => setTimeout(resolve, 2000));
86
+
87
+ // Verify the change
88
+ const updatedMode = await testDevice.getDNDMode();
89
+
90
+ if (updatedMode !== newMode) {
91
+ results.push({
92
+ name: 'should get and set DND mode',
93
+ passed: false,
94
+ skipped: false,
95
+ error: `DND mode did not change. Expected ${newMode}, got ${updatedMode}`,
96
+ device: deviceName
97
+ });
98
+ } else {
99
+ // Restore original mode
100
+ await testDevice.setDNDMode({ mode: initialMode });
101
+ await new Promise(resolve => setTimeout(resolve, 2000));
102
+
103
+ const restoredMode = await testDevice.getDNDMode();
104
+
105
+ if (restoredMode !== initialMode) {
106
+ results.push({
107
+ name: 'should get and set DND mode',
108
+ passed: false,
109
+ skipped: false,
110
+ error: `Failed to restore DND mode. Expected ${initialMode}, got ${restoredMode}`,
111
+ device: deviceName
112
+ });
113
+ } else {
114
+ results.push({
115
+ name: 'should get and set DND mode',
116
+ passed: true,
117
+ skipped: false,
118
+ error: null,
119
+ device: deviceName
120
+ });
121
+ }
122
+ }
123
+ }
124
+ } catch (error) {
125
+ results.push({
126
+ name: 'should get and set DND mode',
127
+ passed: false,
128
+ skipped: false,
129
+ error: error.message,
130
+ device: deviceName
131
+ });
132
+ }
133
+
134
+ // Test 2: Accept boolean values for DND mode
135
+ try {
136
+ // Get current mode
137
+ const initialMode = await testDevice.getDNDMode();
138
+ const initialBoolean = initialMode === DNDMode.DND_ENABLED;
139
+
140
+ // Set using boolean (true = enabled)
141
+ await testDevice.setDNDMode({ mode: !initialBoolean });
142
+ await new Promise(resolve => setTimeout(resolve, 2000));
143
+
144
+ const updatedMode = await testDevice.getDNDMode();
145
+ const expectedMode = !initialBoolean ? DNDMode.DND_ENABLED : DNDMode.DND_DISABLED;
146
+
147
+ if (updatedMode !== expectedMode) {
148
+ results.push({
149
+ name: 'should accept boolean values for DND mode',
150
+ passed: false,
151
+ skipped: false,
152
+ error: `Boolean set failed. Expected ${expectedMode}, got ${updatedMode}`,
153
+ device: deviceName
154
+ });
155
+ } else {
156
+ // Restore original mode
157
+ await testDevice.setDNDMode({ mode: initialBoolean });
158
+ await new Promise(resolve => setTimeout(resolve, 2000));
159
+
160
+ results.push({
161
+ name: 'should accept boolean values for DND mode',
162
+ passed: true,
163
+ skipped: false,
164
+ error: null,
165
+ device: deviceName
166
+ });
167
+ }
168
+ } catch (error) {
169
+ results.push({
170
+ name: 'should accept boolean values for DND mode',
171
+ passed: false,
172
+ skipped: false,
173
+ error: error.message,
174
+ device: deviceName
175
+ });
176
+ }
177
+
178
+ // Test 3: Get raw DND mode value
179
+ try {
180
+ if (typeof testDevice.getRawDNDMode !== 'function') {
181
+ results.push({
182
+ name: 'should get raw DND mode value',
183
+ passed: false,
184
+ skipped: true,
185
+ error: 'Device does not support getRawDNDMode',
186
+ device: deviceName
187
+ });
188
+ } else {
189
+ const rawMode = await testDevice.getRawDNDMode();
190
+
191
+ if (typeof rawMode !== 'number') {
192
+ results.push({
193
+ name: 'should get raw DND mode value',
194
+ passed: false,
195
+ skipped: false,
196
+ error: `Raw mode is not a number: ${typeof rawMode}`,
197
+ device: deviceName
198
+ });
199
+ } else if (rawMode !== 0 && rawMode !== 1) {
200
+ results.push({
201
+ name: 'should get raw DND mode value',
202
+ passed: false,
203
+ skipped: false,
204
+ error: `Invalid raw mode value: ${rawMode} (expected 0 or 1)`,
205
+ device: deviceName
206
+ });
207
+ } else {
208
+ // Verify it matches enum value
209
+ const enumMode = await testDevice.getDNDMode();
210
+
211
+ if (rawMode !== enumMode) {
212
+ results.push({
213
+ name: 'should get raw DND mode value',
214
+ passed: false,
215
+ skipped: false,
216
+ error: `Raw mode (${rawMode}) does not match enum mode (${enumMode})`,
217
+ device: deviceName
218
+ });
219
+ } else {
220
+ results.push({
221
+ name: 'should get raw DND mode value',
222
+ passed: true,
223
+ skipped: false,
224
+ error: null,
225
+ device: deviceName
226
+ });
227
+ }
228
+ }
229
+ }
230
+ } catch (error) {
231
+ results.push({
232
+ name: 'should get raw DND mode value',
233
+ passed: false,
234
+ skipped: false,
235
+ error: error.message,
236
+ device: deviceName
237
+ });
238
+ }
239
+
240
+ return results;
241
+ }
242
+
243
+ module.exports = {
244
+ metadata,
245
+ runTests
246
+ };
@@ -0,0 +1,209 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Electricity/Consumption Device Tests
5
+ * Tests electricity metrics and power consumption tracking
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ const metadata = {
11
+ name: 'electricity',
12
+ description: 'Tests electricity metrics and power consumption tracking',
13
+ requiredAbilities: ['Appliance.Control.ConsumptionX', 'Appliance.Control.Consumption', 'Appliance.Control.Electricity'],
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 consumption/electricity devices (try ConsumptionX first, then Consumption, then Electricity)
26
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.ConsumptionX', OnlineStatus.ONLINE);
27
+
28
+ if (testDevices.length === 0) {
29
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.Consumption', OnlineStatus.ONLINE);
30
+ }
31
+
32
+ if (testDevices.length === 0) {
33
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.Electricity', OnlineStatus.ONLINE);
34
+ }
35
+ }
36
+
37
+ if (testDevices.length === 0) {
38
+ results.push({
39
+ name: 'should get instant electricity metrics',
40
+ passed: false,
41
+ skipped: true,
42
+ error: 'No ConsumptionX/Electricity device has been found to run this test on',
43
+ device: null
44
+ });
45
+ return results;
46
+ }
47
+
48
+ const testDevice = testDevices[0];
49
+ const deviceName = getDeviceName(testDevice);
50
+
51
+ await waitForDeviceConnection(testDevice, timeout);
52
+ await new Promise(resolve => setTimeout(resolve, 1000));
53
+
54
+ // Test 1: Get instant electricity metrics
55
+ try {
56
+ // Try to get electricity data
57
+ let metrics = null;
58
+
59
+ // Check which method is available
60
+ if (typeof testDevice.getElectricity === 'function') {
61
+ metrics = await testDevice.getElectricity();
62
+ } else if (typeof testDevice.getPowerConsumptionX === 'function') {
63
+ metrics = await testDevice.getPowerConsumptionX();
64
+ } else if (typeof testDevice.getPowerConsumption === 'function') {
65
+ metrics = await testDevice.getPowerConsumption();
66
+ }
67
+
68
+ if (!metrics || typeof metrics !== 'object') {
69
+ results.push({
70
+ name: 'should get instant electricity metrics',
71
+ passed: false,
72
+ skipped: false,
73
+ error: 'Metrics is not an object or is null',
74
+ device: deviceName
75
+ });
76
+ } else {
77
+ results.push({
78
+ name: 'should get instant electricity metrics',
79
+ passed: true,
80
+ skipped: false,
81
+ error: null,
82
+ device: deviceName,
83
+ details: { metrics: metrics }
84
+ });
85
+ }
86
+ } catch (error) {
87
+ results.push({
88
+ name: 'should get instant electricity metrics',
89
+ passed: false,
90
+ skipped: false,
91
+ error: error.message,
92
+ device: deviceName
93
+ });
94
+ }
95
+
96
+ // Test 2: Get daily power consumption
97
+ try {
98
+ if (typeof testDevice.getPowerConsumptionX !== 'function') {
99
+ results.push({
100
+ name: 'should get daily power consumption',
101
+ passed: false,
102
+ skipped: true,
103
+ error: 'Device does not support daily power consumption tracking',
104
+ device: deviceName
105
+ });
106
+ } else {
107
+ const consumption = await testDevice.getPowerConsumptionX();
108
+
109
+ if (!Array.isArray(consumption)) {
110
+ results.push({
111
+ name: 'should get daily power consumption',
112
+ passed: false,
113
+ skipped: false,
114
+ error: 'Consumption is not an array',
115
+ device: deviceName
116
+ });
117
+ } else {
118
+ // If we got data, verify structure
119
+ if (consumption.length > 0) {
120
+ const first = consumption[0];
121
+ if (!first.date || first.totalConsumptionKwh === undefined) {
122
+ results.push({
123
+ name: 'should get daily power consumption',
124
+ passed: false,
125
+ skipped: false,
126
+ error: 'Consumption entries missing required properties (date or totalConsumptionKwh)',
127
+ device: deviceName
128
+ });
129
+ } else {
130
+ results.push({
131
+ name: 'should get daily power consumption',
132
+ passed: true,
133
+ skipped: false,
134
+ error: null,
135
+ device: deviceName,
136
+ details: { entryCount: consumption.length }
137
+ });
138
+ }
139
+ } else {
140
+ results.push({
141
+ name: 'should get daily power consumption',
142
+ passed: true,
143
+ skipped: false,
144
+ error: null,
145
+ device: deviceName,
146
+ details: { entryCount: 0, note: 'No consumption data available' }
147
+ });
148
+ }
149
+ }
150
+ }
151
+ } catch (error) {
152
+ results.push({
153
+ name: 'should get daily power consumption',
154
+ passed: false,
155
+ skipped: false,
156
+ error: error.message,
157
+ device: deviceName
158
+ });
159
+ }
160
+
161
+ // Test 3: Get consumption config
162
+ try {
163
+ if (typeof testDevice.getConsumptionConfig !== 'function') {
164
+ results.push({
165
+ name: 'should get consumption config',
166
+ passed: false,
167
+ skipped: true,
168
+ error: 'Device does not support getConsumptionConfig',
169
+ device: deviceName
170
+ });
171
+ } else {
172
+ const response = await testDevice.getConsumptionConfig();
173
+
174
+ if (!response) {
175
+ results.push({
176
+ name: 'should get consumption config',
177
+ passed: false,
178
+ skipped: false,
179
+ error: 'getConsumptionConfig returned null or undefined',
180
+ device: deviceName
181
+ });
182
+ } else {
183
+ results.push({
184
+ name: 'should get consumption config',
185
+ passed: true,
186
+ skipped: false,
187
+ error: null,
188
+ device: deviceName,
189
+ details: { config: response }
190
+ });
191
+ }
192
+ }
193
+ } catch (error) {
194
+ results.push({
195
+ name: 'should get consumption config',
196
+ passed: false,
197
+ skipped: false,
198
+ error: error.message,
199
+ device: deviceName
200
+ });
201
+ }
202
+
203
+ return results;
204
+ }
205
+
206
+ module.exports = {
207
+ metadata,
208
+ runTests
209
+ };