meross-cli 0.2.0 → 0.3.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 CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-01-16
9
+
10
+ ### Changed
11
+ - **BREAKING**: Updated to use simplified device API from `meross-iot` v0.4.0
12
+ - Updated to use `initializeDevices()` instead of `getDevices()`
13
+ - Updated to use direct device properties instead of `cachedHttpInfo`
14
+ - Updated to use camelCase property names consistently
15
+ - Updated all tests and commands to use new API patterns
16
+
8
17
  ## [0.2.0] - 2026-01-15
9
18
 
10
19
  ### Changed
package/README.md CHANGED
@@ -23,7 +23,7 @@ Command-line interface for controlling and managing Meross smart home devices.
23
23
  npm install -g meross-cli@alpha
24
24
 
25
25
  # Or install specific version
26
- npm install -g meross-cli@0.2.0
26
+ npm install -g meross-cli@0.3.0
27
27
  ```
28
28
 
29
29
  Or use via npx:
@@ -77,6 +77,18 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
77
77
 
78
78
  ## Changelog
79
79
 
80
+ ### [0.3.0] - 2026-01-16
81
+
82
+ #### Changed
83
+ - **BREAKING**: Updated to use simplified device API from `meross-iot` v0.4.0
84
+ - Updated to use `initializeDevices()` instead of `getDevices()`
85
+ - Updated to use direct device properties instead of `cachedHttpInfo`
86
+ - Updated to use camelCase property names consistently
87
+ - Updated all tests and commands to use new API patterns
88
+
89
+ <details>
90
+ <summary>Older</summary>
91
+
80
92
  ### [0.2.0] - 2026-01-15
81
93
 
82
94
  #### Changed
@@ -107,11 +119,6 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
107
119
  - This is an initial, pre-stable release. Please expect bugs.
108
120
  - Some edge cases may not be fully handled yet.
109
121
 
110
- <details>
111
- <summary>Older</summary>
112
-
113
- <!-- Older changelog entries will appear here -->
114
-
115
122
  </details>
116
123
 
117
124
  ## Disclaimer
@@ -84,35 +84,41 @@ function _displayChannels(device) {
84
84
  }
85
85
 
86
86
  /**
87
- * Builds HTTP device info data array.
87
+ * Extracts HTTP device info properties for display.
88
+ *
89
+ * Collects HTTP metadata properties (domain, reservedDomain, subType, region, etc.)
90
+ * that are now directly accessible on the device instance for formatting in the CLI output.
91
+ *
92
+ * @param {Object} device - MerossDevice instance
93
+ * @returns {Array<Array<string>>} Array of [label, value] pairs for display
88
94
  */
89
- function _buildHttpInfoData(httpInfo) {
95
+ function _buildHttpInfoData(device) {
90
96
  const httpInfoData = [];
91
97
 
92
- if (httpInfo.domain) {
93
- httpInfoData.push(['MQTT Domain', httpInfo.domain]);
98
+ if (device.domain) {
99
+ httpInfoData.push(['MQTT Domain', device.domain]);
94
100
  }
95
- if (httpInfo.reservedDomain) {
96
- httpInfoData.push(['Reserved Domain', httpInfo.reservedDomain]);
101
+ if (device.reservedDomain) {
102
+ httpInfoData.push(['Reserved Domain', device.reservedDomain]);
97
103
  }
98
- if (httpInfo.subType) {
99
- httpInfoData.push(['Sub Type', httpInfo.subType]);
104
+ if (device.subType) {
105
+ httpInfoData.push(['Sub Type', device.subType]);
100
106
  }
101
- if (httpInfo.region) {
102
- httpInfoData.push(['Region', httpInfo.region]);
107
+ if (device.region) {
108
+ httpInfoData.push(['Region', device.region]);
103
109
  }
104
- if (httpInfo.skillNumber) {
105
- httpInfoData.push(['Skill Number', httpInfo.skillNumber]);
110
+ if (device.skillNumber) {
111
+ httpInfoData.push(['Skill Number', device.skillNumber]);
106
112
  }
107
- if (httpInfo.devIconId) {
108
- httpInfoData.push(['Icon ID', httpInfo.devIconId]);
113
+ if (device.devIconId) {
114
+ httpInfoData.push(['Icon ID', device.devIconId]);
109
115
  }
110
- if (httpInfo.bindTime) {
111
- httpInfoData.push(['Bind Time', httpInfo.bindTime.toLocaleString()]);
116
+ if (device.bindTime) {
117
+ httpInfoData.push(['Bind Time', device.bindTime.toLocaleString()]);
112
118
  }
113
- if (httpInfo.onlineStatus !== undefined) {
114
- const onlineStatusText = httpInfo.onlineStatus === OnlineStatus.ONLINE ? chalk.green('Online') :
115
- httpInfo.onlineStatus === OnlineStatus.OFFLINE ? chalk.red('Offline') :
119
+ if (device.onlineStatus !== undefined) {
120
+ const onlineStatusText = device.onlineStatus === OnlineStatus.ONLINE ? chalk.green('Online') :
121
+ device.onlineStatus === OnlineStatus.OFFLINE ? chalk.red('Offline') :
116
122
  chalk.yellow('Unknown');
117
123
  httpInfoData.push(['Online Status', onlineStatusText]);
118
124
  }
@@ -121,15 +127,12 @@ function _buildHttpInfoData(httpInfo) {
121
127
  }
122
128
 
123
129
  /**
124
- * Displays HTTP device info if available.
130
+ * Displays HTTP device info section if properties are available.
131
+ *
132
+ * @param {Object} device - MerossDevice instance
125
133
  */
126
134
  function _displayHttpInfo(device) {
127
- if (!device.cachedHttpInfo) {
128
- return;
129
- }
130
-
131
- const httpInfo = device.cachedHttpInfo;
132
- const httpInfoData = _buildHttpInfoData(httpInfo);
135
+ const httpInfoData = _buildHttpInfoData(device);
133
136
 
134
137
  if (httpInfoData.length === 0) {
135
138
  return;
@@ -762,10 +762,10 @@ function getMethodsByCategory() {
762
762
  * @returns {boolean} True if device supports the namespace
763
763
  */
764
764
  function deviceSupportsNamespace(device, namespace) {
765
- if (!device._abilities || typeof device._abilities !== 'object') {
765
+ if (!device.abilities || typeof device.abilities !== 'object') {
766
766
  return false;
767
767
  }
768
- return !!device._abilities[namespace];
768
+ return !!device.abilities[namespace];
769
769
  }
770
770
 
771
771
  /**
@@ -28,15 +28,14 @@ async function runTests(context) {
28
28
  // Also get any online device for testing control features
29
29
  const allDevices = manager.devices.list();
30
30
  const onlineDevices = allDevices.filter(device => {
31
- const status = device.onlineStatus !== undefined ? device.onlineStatus : (device.dev?.onlineStatus);
32
- return status === OnlineStatus.ONLINE;
31
+ return device.onlineStatus === OnlineStatus.ONLINE;
33
32
  });
34
33
 
35
34
  // Combine and deduplicate
36
35
  testDevices = [...multipleDevices];
37
36
  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)) {
37
+ const uuid = device.uuid;
38
+ if (!testDevices.find(d => d.uuid === uuid)) {
40
39
  testDevices.push(device);
41
40
  }
42
41
  }
@@ -27,10 +27,10 @@ async function runTests(context) {
27
27
  if (devices && devices.length > 0) {
28
28
  // Use provided devices, filter by capabilities
29
29
  lightDevices = devices.filter(d =>
30
- d._abilities && d._abilities['Appliance.Control.Diffuser.Light']
30
+ d.abilities && d.abilities['Appliance.Control.Diffuser.Light']
31
31
  );
32
32
  sprayDevices = devices.filter(d =>
33
- d._abilities && d._abilities['Appliance.Control.Diffuser.Spray']
33
+ d.abilities && d.abilities['Appliance.Control.Diffuser.Spray']
34
34
  );
35
35
  } else {
36
36
  // Find diffuser light devices
@@ -26,8 +26,7 @@ async function runTests(context) {
26
26
  // Find devices that support DND mode (most devices support this)
27
27
  const allDevices = manager.devices.list();
28
28
  testDevices = allDevices.filter(d => {
29
- const status = d.onlineStatus !== undefined ? d.onlineStatus : (d.dev?.onlineStatus);
30
- return status === OnlineStatus.ONLINE &&
29
+ return d.onlineStatus === OnlineStatus.ONLINE &&
31
30
  typeof d.getDNDMode === 'function' &&
32
31
  typeof d.setDNDMode === 'function';
33
32
  });
@@ -31,7 +31,7 @@ async function runTests(context) {
31
31
  // Combine and deduplicate
32
32
  const allDevices = [...encryptionDevices];
33
33
  for (const device of suiteDevices) {
34
- if (!allDevices.find(d => d.dev.uuid === device.dev.uuid)) {
34
+ if (!allDevices.find(d => d.uuid === device.uuid)) {
35
35
  allDevices.push(device);
36
36
  }
37
37
  }
@@ -115,8 +115,8 @@ async function runTests(context) {
115
115
  const isKeySet = testDevice.isEncryptionKeySet();
116
116
 
117
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);
118
+ if (!isKeySet && testDevice.uuid && manager.key && testDevice.macAddress) {
119
+ testDevice.setEncryptionKey(testDevice.uuid, manager.key, testDevice.macAddress);
120
120
 
121
121
  const keySetAfter = testDevice.isEncryptionKeySet();
122
122
 
@@ -180,8 +180,8 @@ async function runTests(context) {
180
180
  } else {
181
181
  // Ensure encryption key is set
182
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);
183
+ if (testDevice.uuid && manager.key && testDevice.macAddress) {
184
+ testDevice.setEncryptionKey(testDevice.uuid, manager.key, testDevice.macAddress);
185
185
  } else {
186
186
  results.push({
187
187
  name: 'should encrypt and decrypt messages if encryption key is set',
@@ -10,10 +10,15 @@ const { OnlineStatus } = require('meross-iot');
10
10
  */
11
11
 
12
12
  /**
13
- * Waits for devices to be discovered
13
+ * Waits for devices to be discovered.
14
+ *
15
+ * Returns an array of MerossDevice instances once they are initialized. Uses
16
+ * device UUID (or subdevice ID) for deduplication to avoid returning the same
17
+ * device multiple times.
18
+ *
14
19
  * @param {Object} manager - ManagerMeross instance
15
20
  * @param {number} timeout - Timeout in milliseconds (default: 5000)
16
- * @returns {Promise<Array>} Array of device objects with structure: { deviceId, deviceDef, device }
21
+ * @returns {Promise<Array<MerossDevice>>} Array of device instances
17
22
  */
18
23
  function waitForDevices(manager, timeout = 5000) {
19
24
  return new Promise((resolve) => {
@@ -24,33 +29,28 @@ function waitForDevices(manager, timeout = 5000) {
24
29
  const deviceIds = new Set();
25
30
 
26
31
  for (const device of existingDevices) {
27
- // Use unique identifier for subdevices (internalId) or UUID for base devices
32
+ // Use unique identifier for subdevices (parentUUID:subdeviceId) or UUID for base devices
28
33
  const deviceId = device.subdeviceId
29
- ? `${device.dev?.uuid || device.uuid}:${device.subdeviceId}`
30
- : (device.dev?.uuid || device.uuid);
34
+ ? `${device.uuid}:${device.subdeviceId}`
35
+ : device.uuid;
31
36
 
32
37
  if (!deviceIds.has(deviceId)) {
33
38
  deviceIds.add(deviceId);
34
- devices.push({
35
- deviceId: deviceId,
36
- deviceDef: device.dev || { uuid: device.uuid },
37
- device
38
- });
39
+ devices.push(device);
39
40
  }
40
41
  }
41
42
  resolve(devices);
42
43
  return;
43
44
  }
44
45
 
45
- // Wait for devices to be initialized
46
46
  const devices = [];
47
47
  const deviceIds = new Set();
48
48
  let timeoutId = null;
49
49
 
50
- const onDeviceInitialized = (deviceId, deviceDef, device) => {
50
+ const onDeviceInitialized = (deviceId, device) => {
51
51
  if (!deviceIds.has(deviceId)) {
52
52
  deviceIds.add(deviceId);
53
- devices.push({ deviceId, deviceDef, device });
53
+ devices.push(device);
54
54
  }
55
55
  };
56
56
 
@@ -61,7 +61,8 @@ function waitForDevices(manager, timeout = 5000) {
61
61
  resolve(devices);
62
62
  }, timeout);
63
63
 
64
- // Also check periodically if devices were added (in case event was missed)
64
+ // Periodic check handles cases where deviceInitialized event was missed
65
+ // (e.g., devices initialized before event handler was attached)
65
66
  const checkInterval = setInterval(() => {
66
67
  const currentDevices = manager.devices.list();
67
68
  if (currentDevices && currentDevices.length > 0) {
@@ -71,19 +72,14 @@ function waitForDevices(manager, timeout = 5000) {
71
72
  }
72
73
  manager.removeListener('deviceInitialized', onDeviceInitialized);
73
74
 
74
- // Add any new devices
75
75
  for (const device of currentDevices) {
76
76
  const deviceId = device.subdeviceId
77
- ? `${device.dev?.uuid || device.uuid}:${device.subdeviceId}`
78
- : (device.dev?.uuid || device.uuid);
77
+ ? `${device.uuid}:${device.subdeviceId}`
78
+ : device.uuid;
79
79
 
80
80
  if (!deviceIds.has(deviceId)) {
81
81
  deviceIds.add(deviceId);
82
- devices.push({
83
- deviceId: deviceId,
84
- deviceDef: device.dev || { uuid: device.uuid },
85
- device
86
- });
82
+ devices.push(device);
87
83
  }
88
84
  }
89
85
  resolve(devices);
@@ -98,13 +94,7 @@ function waitForDevices(manager, timeout = 5000) {
98
94
  * @returns {number|null} OnlineStatus value or null if not available
99
95
  */
100
96
  function getDeviceOnlineStatus(device) {
101
- if (device.onlineStatus !== undefined) {
102
- return device.onlineStatus;
103
- }
104
- if (device.dev && device.dev.onlineStatus !== undefined) {
105
- return device.dev.onlineStatus;
106
- }
107
- return null;
97
+ return device.onlineStatus !== undefined ? device.onlineStatus : null;
108
98
  }
109
99
 
110
100
  /**
@@ -114,7 +104,7 @@ function getDeviceOnlineStatus(device) {
114
104
  * @returns {boolean} True if device has the ability
115
105
  */
116
106
  function deviceHasAbility(device, namespace) {
117
- return !!(device._abilities && device._abilities[namespace]);
107
+ return !!(device.abilities && device.abilities[namespace]);
118
108
  }
119
109
 
120
110
  /**
@@ -146,7 +136,7 @@ async function findDevicesByAbility(manager, namespace, onlineStatus = null, dev
146
136
  const devices = await waitForDevices(manager, 2000);
147
137
  const filteredDevices = [];
148
138
 
149
- for (const { device } of devices) {
139
+ for (const device of devices) {
150
140
  // Check if device has the ability
151
141
  if (deviceHasAbility(device, namespace)) {
152
142
  // Filter by online status if specified
@@ -176,7 +166,7 @@ async function findDevicesByType(manager, deviceType, onlineStatus = null, devic
176
166
  // If device filter is provided, use it instead of discovering devices
177
167
  if (deviceFilter && Array.isArray(deviceFilter) && deviceFilter.length > 0) {
178
168
  return deviceFilter.filter(device => {
179
- const baseDeviceType = device.dev?.deviceType;
169
+ const baseDeviceType = device.deviceType;
180
170
  const subdeviceType = device.type || device._type;
181
171
  const matchesType = baseDeviceType === deviceType || subdeviceType === deviceType;
182
172
 
@@ -197,9 +187,9 @@ async function findDevicesByType(manager, deviceType, onlineStatus = null, devic
197
187
  const devices = await waitForDevices(manager, 2000);
198
188
  const filteredDevices = [];
199
189
 
200
- for (const { device } of devices) {
190
+ for (const device of devices) {
201
191
  // Check both base device type and subdevice type
202
- const baseDeviceType = device.dev?.deviceType;
192
+ const baseDeviceType = device.deviceType;
203
193
  const subdeviceType = device.type || device._type;
204
194
  const matchesType = baseDeviceType === deviceType || subdeviceType === deviceType;
205
195
 
@@ -36,7 +36,7 @@ async function runTests(context) {
36
36
  // Also try finding by hub sensor abilities
37
37
  const sensorHubs = await findDevicesByAbility(manager, 'Appliance.Hub.Sensor.All', OnlineStatus.ONLINE);
38
38
  for (const device of sensorHubs) {
39
- if (!hubDevices.find(d => (d.dev?.uuid || d.uuid) === (device.dev?.uuid || device.uuid))) {
39
+ if (!hubDevices.find(d => d.uuid === device.uuid)) {
40
40
  hubDevices.push(device);
41
41
  }
42
42
  }
@@ -325,7 +325,7 @@ async function findDevicesForTestType(testType, manager) {
325
325
  if (!allDevices || allDevices.length === 0) {
326
326
  const { waitForDevices } = require('./test-helper');
327
327
  const deviceList = await waitForDevices(manager, 1000);
328
- return deviceList.map(({ device }) => device).filter(d => d);
328
+ return deviceList.filter(d => d);
329
329
  }
330
330
 
331
331
  const matchingDevices = [];
@@ -340,13 +340,12 @@ async function findDevicesForTestType(testType, manager) {
340
340
  }
341
341
 
342
342
  for (const device of allDevices) {
343
- const uuid = device.dev?.uuid || device.uuid;
343
+ const uuid = device.uuid;
344
344
  if (seenUuids.has(uuid)) {
345
345
  continue;
346
346
  }
347
347
 
348
- if (device.onlineStatus !== OnlineStatus.ONLINE &&
349
- device.dev?.onlineStatus !== OnlineStatus.ONLINE) {
348
+ if (device.onlineStatus !== OnlineStatus.ONLINE) {
350
349
  continue;
351
350
  }
352
351
 
@@ -371,7 +370,7 @@ async function findDevicesForTestType(testType, manager) {
371
370
  }
372
371
 
373
372
  const hasAbility = abilities.some(ability =>
374
- device._abilities && device._abilities[ability]
373
+ device.abilities && device.abilities[ability]
375
374
  );
376
375
 
377
376
  if (hasAbility) {
@@ -383,17 +382,15 @@ async function findDevicesForTestType(testType, manager) {
383
382
  // For garage, also try by device type if no matches found
384
383
  if (testType.toLowerCase() === 'garage' && matchingDevices.length === 0) {
385
384
  for (const device of allDevices) {
386
- const uuid = device.dev?.uuid || device.uuid;
385
+ const uuid = device.uuid;
387
386
  if (seenUuids.has(uuid)) {
388
387
  continue;
389
388
  }
390
389
 
391
- const baseDeviceType = device.dev?.deviceType;
392
390
  const subdeviceType = device.type || device._type;
393
- const matchesType = baseDeviceType === 'msg100' || subdeviceType === 'msg100';
391
+ const matchesType = device.deviceType === 'msg100' || subdeviceType === 'msg100';
394
392
 
395
- if (matchesType && (device.onlineStatus === OnlineStatus.ONLINE ||
396
- device.dev?.onlineStatus === OnlineStatus.ONLINE)) {
393
+ if (matchesType && device.onlineStatus === OnlineStatus.ONLINE) {
397
394
  seenUuids.add(uuid);
398
395
  matchingDevices.push(device);
399
396
  }
@@ -25,11 +25,10 @@ async function runTests(context) {
25
25
  // Runtime is typically available on most devices, so we'll test with any online device
26
26
  const allDevices = manager.devices.list();
27
27
  testDevices = allDevices.filter(device => {
28
- const status = device.onlineStatus !== undefined ? device.onlineStatus : (device.dev?.onlineStatus);
29
- if (status !== OnlineStatus.ONLINE) return false;
28
+ if (device.onlineStatus !== OnlineStatus.ONLINE) return false;
30
29
  // Check if device has runtime ability or if it's a common device type
31
- return device._abilities && (
32
- device._abilities['Appliance.System.Runtime'] ||
30
+ return device.abilities && (
31
+ device.abilities['Appliance.System.Runtime'] ||
33
32
  // Most devices support runtime, so we'll test with any online device
34
33
  true
35
34
  );
@@ -52,8 +52,8 @@ async function runTests(context) {
52
52
  // Combine and deduplicate
53
53
  testDevices = [...toggleXDevices];
54
54
  for (const device of toggleDevices) {
55
- const uuid = device.dev?.uuid || device.uuid;
56
- if (!testDevices.find(d => (d.dev?.uuid || d.uuid) === uuid)) {
55
+ const uuid = device.uuid;
56
+ if (!testDevices.find(d => d.uuid === uuid)) {
57
57
  testDevices.push(device);
58
58
  }
59
59
  }
@@ -29,8 +29,8 @@ async function findAllToggleDevices(manager) {
29
29
  // Combine and deduplicate
30
30
  const allToggleDevices = [...toggleXDevices];
31
31
  for (const device of toggleDevices) {
32
- const uuid = device.dev?.uuid || device.uuid;
33
- if (!allToggleDevices.find(d => (d.dev?.uuid || d.uuid) === uuid)) {
32
+ const uuid = device.uuid;
33
+ if (!allToggleDevices.find(d => d.uuid === uuid)) {
34
34
  allToggleDevices.push(device);
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meross-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Command-line interface for controlling Meross smart home devices",
5
5
  "author": "Abe Haverkamp",
6
6
  "homepage": "https://github.com/Doekse/merossiot#readme",
@@ -24,7 +24,7 @@
24
24
  "chalk": "^4.1.2",
25
25
  "commander": "^12.1.0",
26
26
  "inquirer": "^8.2.6",
27
- "meross-iot": "^0.2.1",
27
+ "meross-iot": "^0.4.0",
28
28
  "ora": "^5.4.1"
29
29
  },
30
30
  "devDependencies": {