meross-cli 0.2.0 → 0.4.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 +24 -0
- package/README.md +28 -6
- package/cli/commands/control/execute.js +16 -3
- package/cli/commands/control/menu.js +3 -5
- package/cli/commands/info.js +30 -27
- package/cli/control-registry.js +2 -2
- package/cli/helpers/client.js +16 -10
- package/cli/helpers/meross.js +18 -14
- package/cli/menu/main.js +170 -13
- package/cli/menu/settings.js +2 -2
- package/cli/meross-cli.js +12 -46
- package/cli/tests/test-control.js +3 -4
- package/cli/tests/test-diffuser.js +2 -2
- package/cli/tests/test-dnd.js +1 -2
- package/cli/tests/test-encryption.js +5 -5
- package/cli/tests/test-helper.js +24 -34
- package/cli/tests/test-hub-sensors.js +1 -1
- package/cli/tests/test-runner.js +7 -10
- package/cli/tests/test-runtime.js +3 -4
- package/cli/tests/test-template.js +2 -2
- package/cli/tests/test-toggle.js +2 -2
- package/cli/utils/error-handler.js +257 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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.4.0] - 2026-01-19
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
|
|
12
|
+
- Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
|
|
13
|
+
- Updated all commands and helpers to use new property-based access patterns
|
|
14
|
+
- **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
|
|
15
|
+
- Updated to use new `MerossError*` error class names
|
|
16
|
+
- Replaced inline error handling with centralized `handleError()` function
|
|
17
|
+
- All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
|
|
21
|
+
- Enhanced error display with better context and user-friendly formatting
|
|
22
|
+
|
|
23
|
+
## [0.3.0] - 2026-01-16
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **BREAKING**: Updated to use simplified device API from `meross-iot` v0.4.0
|
|
27
|
+
- Updated to use `initializeDevices()` instead of `getDevices()`
|
|
28
|
+
- Updated to use direct device properties instead of `cachedHttpInfo`
|
|
29
|
+
- Updated to use camelCase property names consistently
|
|
30
|
+
- Updated all tests and commands to use new API patterns
|
|
31
|
+
|
|
8
32
|
## [0.2.0] - 2026-01-15
|
|
9
33
|
|
|
10
34
|
### 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.
|
|
26
|
+
npm install -g meross-cli@0.4.0
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Or use via npx:
|
|
@@ -77,6 +77,33 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
|
|
|
77
77
|
|
|
78
78
|
## Changelog
|
|
79
79
|
|
|
80
|
+
### [0.4.0] - 2026-01-19
|
|
81
|
+
|
|
82
|
+
#### Changed
|
|
83
|
+
- **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
|
|
84
|
+
- Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
|
|
85
|
+
- Updated all commands and helpers to use new property-based access patterns
|
|
86
|
+
- **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
|
|
87
|
+
- Updated to use new `MerossError*` error class names
|
|
88
|
+
- Replaced inline error handling with centralized `handleError()` function
|
|
89
|
+
- All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
|
|
90
|
+
|
|
91
|
+
#### Added
|
|
92
|
+
- Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
|
|
93
|
+
- Enhanced error display with better context and user-friendly formatting
|
|
94
|
+
|
|
95
|
+
<details>
|
|
96
|
+
<summary>Older</summary>
|
|
97
|
+
|
|
98
|
+
### [0.3.0] - 2026-01-16
|
|
99
|
+
|
|
100
|
+
#### Changed
|
|
101
|
+
- **BREAKING**: Updated to use simplified device API from `meross-iot` v0.4.0
|
|
102
|
+
- Updated to use `initializeDevices()` instead of `getDevices()`
|
|
103
|
+
- Updated to use direct device properties instead of `cachedHttpInfo`
|
|
104
|
+
- Updated to use camelCase property names consistently
|
|
105
|
+
- Updated all tests and commands to use new API patterns
|
|
106
|
+
|
|
80
107
|
### [0.2.0] - 2026-01-15
|
|
81
108
|
|
|
82
109
|
#### Changed
|
|
@@ -107,11 +134,6 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
|
|
|
107
134
|
- This is an initial, pre-stable release. Please expect bugs.
|
|
108
135
|
- Some edge cases may not be fully handled yet.
|
|
109
136
|
|
|
110
|
-
<details>
|
|
111
|
-
<summary>Older</summary>
|
|
112
|
-
|
|
113
|
-
<!-- Older changelog entries will appear here -->
|
|
114
|
-
|
|
115
137
|
</details>
|
|
116
138
|
|
|
117
139
|
## Disclaimer
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const ManagerMeross = require('meross-iot');
|
|
4
|
+
|
|
3
5
|
async function executeControlCommand(manager, uuid, methodName, params) {
|
|
4
6
|
const device = manager.devices.get(uuid);
|
|
5
7
|
|
|
6
8
|
if (!device) {
|
|
7
|
-
throw new
|
|
9
|
+
throw new ManagerMeross.MerossErrorNotFound(
|
|
10
|
+
`Device not found: ${uuid}`,
|
|
11
|
+
'device',
|
|
12
|
+
uuid
|
|
13
|
+
);
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
if (!device.deviceConnected) {
|
|
11
|
-
throw new
|
|
17
|
+
throw new ManagerMeross.MerossErrorUnconnected(
|
|
18
|
+
'Device is not connected. Please wait for device to connect.',
|
|
19
|
+
uuid
|
|
20
|
+
);
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
if (typeof device[methodName] !== 'function') {
|
|
15
|
-
throw new
|
|
24
|
+
throw new ManagerMeross.MerossErrorUnsupported(
|
|
25
|
+
`Control method not available: ${methodName}`,
|
|
26
|
+
methodName,
|
|
27
|
+
'Method not supported by this device'
|
|
28
|
+
);
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
// All methods now use unified options pattern, so we can call directly with params
|
|
@@ -143,7 +143,7 @@ async function controlDeviceMenu(manager, rl, currentUser = null) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// Check error budget if using LAN HTTP transport modes
|
|
146
|
-
const transportMode = manager.
|
|
146
|
+
const transportMode = manager.transport.defaultMode;
|
|
147
147
|
const usesLanHttp = transportMode === TransportMode.LAN_HTTP_FIRST ||
|
|
148
148
|
transportMode === TransportMode.LAN_HTTP_FIRST_ONLY_GET;
|
|
149
149
|
if (usesLanHttp) {
|
|
@@ -170,10 +170,8 @@ async function controlDeviceMenu(manager, rl, currentUser = null) {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
} catch (error) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
console.error(error.stack);
|
|
176
|
-
}
|
|
173
|
+
const { handleError } = require('../../utils/error-handler');
|
|
174
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
177
175
|
}
|
|
178
176
|
|
|
179
177
|
const { continueControl } = await inquirer.prompt([{
|
package/cli/commands/info.js
CHANGED
|
@@ -34,7 +34,7 @@ function _buildBasicDeviceInfo(device, manager) {
|
|
|
34
34
|
|
|
35
35
|
info.push(['Connected', status]);
|
|
36
36
|
info.push(['Online', onlineStatus]);
|
|
37
|
-
info.push(['Transport', getTransportModeName(manager.
|
|
37
|
+
info.push(['Transport', getTransportModeName(manager.transport.defaultMode)]);
|
|
38
38
|
|
|
39
39
|
return info;
|
|
40
40
|
}
|
|
@@ -84,35 +84,41 @@ function _displayChannels(device) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
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(
|
|
95
|
+
function _buildHttpInfoData(device) {
|
|
90
96
|
const httpInfoData = [];
|
|
91
97
|
|
|
92
|
-
if (
|
|
93
|
-
httpInfoData.push(['MQTT Domain',
|
|
98
|
+
if (device.domain) {
|
|
99
|
+
httpInfoData.push(['MQTT Domain', device.domain]);
|
|
94
100
|
}
|
|
95
|
-
if (
|
|
96
|
-
httpInfoData.push(['Reserved Domain',
|
|
101
|
+
if (device.reservedDomain) {
|
|
102
|
+
httpInfoData.push(['Reserved Domain', device.reservedDomain]);
|
|
97
103
|
}
|
|
98
|
-
if (
|
|
99
|
-
httpInfoData.push(['Sub Type',
|
|
104
|
+
if (device.subType) {
|
|
105
|
+
httpInfoData.push(['Sub Type', device.subType]);
|
|
100
106
|
}
|
|
101
|
-
if (
|
|
102
|
-
httpInfoData.push(['Region',
|
|
107
|
+
if (device.region) {
|
|
108
|
+
httpInfoData.push(['Region', device.region]);
|
|
103
109
|
}
|
|
104
|
-
if (
|
|
105
|
-
httpInfoData.push(['Skill Number',
|
|
110
|
+
if (device.skillNumber) {
|
|
111
|
+
httpInfoData.push(['Skill Number', device.skillNumber]);
|
|
106
112
|
}
|
|
107
|
-
if (
|
|
108
|
-
httpInfoData.push(['Icon ID',
|
|
113
|
+
if (device.devIconId) {
|
|
114
|
+
httpInfoData.push(['Icon ID', device.devIconId]);
|
|
109
115
|
}
|
|
110
|
-
if (
|
|
111
|
-
httpInfoData.push(['Bind Time',
|
|
116
|
+
if (device.bindTime) {
|
|
117
|
+
httpInfoData.push(['Bind Time', device.bindTime.toLocaleString()]);
|
|
112
118
|
}
|
|
113
|
-
if (
|
|
114
|
-
const onlineStatusText =
|
|
115
|
-
|
|
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
|
-
|
|
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;
|
package/cli/control-registry.js
CHANGED
|
@@ -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.
|
|
765
|
+
if (!device.abilities || typeof device.abilities !== 'object') {
|
|
766
766
|
return false;
|
|
767
767
|
}
|
|
768
|
-
return !!device.
|
|
768
|
+
return !!device.abilities[namespace];
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
/**
|
package/cli/helpers/client.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { MerossHttpClient, TransportMode } = require('meross-iot');
|
|
5
|
+
const { handleError } = require('../utils/error-handler');
|
|
5
6
|
|
|
6
7
|
async function processOptionsAndCreateHttpClient(opts) {
|
|
7
8
|
const email = opts.email || process.env.MEROSS_EMAIL || null;
|
|
@@ -33,16 +34,21 @@ async function processOptionsAndCreateHttpClient(opts) {
|
|
|
33
34
|
maxStatsSamples: 1000
|
|
34
35
|
});
|
|
35
36
|
} else if (email && password) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
try {
|
|
38
|
+
httpClient = await MerossHttpClient.fromUserPassword({
|
|
39
|
+
email,
|
|
40
|
+
password,
|
|
41
|
+
mfaCode,
|
|
42
|
+
logger: verbose ? console.log : null,
|
|
43
|
+
timeout,
|
|
44
|
+
autoRetryOnBadDomain: true,
|
|
45
|
+
enableStats,
|
|
46
|
+
maxStatsSamples: 1000
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// Re-throw with better context for MFA/auth errors
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
46
52
|
} else {
|
|
47
53
|
throw new Error('Email and password are required (or provide token data).\nUse --email and --password options or set MEROSS_EMAIL and MEROSS_PASSWORD environment variables.');
|
|
48
54
|
}
|
package/cli/helpers/meross.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const ManagerMeross = require('meross-iot');
|
|
4
4
|
const { MerossHttpClient, TransportMode } = require('meross-iot');
|
|
5
5
|
const ora = require('ora');
|
|
6
|
+
const { handleError } = require('../utils/error-handler');
|
|
6
7
|
|
|
7
8
|
async function createMerossInstance(optionsOrEmail, password, mfaCode, transportMode, timeout, enableStats, verbose) {
|
|
8
9
|
let httpClient;
|
|
@@ -29,16 +30,21 @@ async function createMerossInstance(optionsOrEmail, password, mfaCode, transport
|
|
|
29
30
|
finalVerbose = verbose || false;
|
|
30
31
|
|
|
31
32
|
// Create HTTP client
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
try {
|
|
34
|
+
httpClient = await MerossHttpClient.fromUserPassword({
|
|
35
|
+
email,
|
|
36
|
+
password,
|
|
37
|
+
mfaCode,
|
|
38
|
+
logger: finalVerbose ? console.log : null,
|
|
39
|
+
timeout: finalTimeout,
|
|
40
|
+
autoRetryOnBadDomain: true,
|
|
41
|
+
enableStats: finalEnableStats,
|
|
42
|
+
maxStatsSamples: 1000
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Re-throw to let caller handle with proper error formatting
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
const instance = new ManagerMeross({
|
|
@@ -79,10 +85,8 @@ async function connectMeross(manager) {
|
|
|
79
85
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
80
86
|
return true;
|
|
81
87
|
} catch (error) {
|
|
82
|
-
spinner.
|
|
83
|
-
|
|
84
|
-
console.error(error.stack);
|
|
85
|
-
}
|
|
88
|
+
spinner.stop();
|
|
89
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
86
90
|
return false;
|
|
87
91
|
}
|
|
88
92
|
}
|
package/cli/menu/main.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
+
const ora = require('ora');
|
|
5
6
|
const { MerossHubDevice, MerossSubDevice, createDebugUtils, TransportMode } = require('meross-iot');
|
|
6
7
|
const testRunner = require('../tests/test-runner');
|
|
7
8
|
const { clearScreen, renderLogoAtTop, renderSimpleHeader, clearMenuArea, CONTENT_START_LINE, SIMPLE_CONTENT_START_LINE, createRL, question, promptForPassword } = require('../utils/terminal');
|
|
8
9
|
const { formatDevice } = require('../utils/display');
|
|
9
10
|
const { listDevices, showStats, dumpRegistry, listMqttConnections, getDeviceStatus, showDeviceInfo, controlDeviceMenu, runTestCommand, snifferMenu } = require('../commands');
|
|
10
11
|
const { addUser, getUser, listUsers } = require('../config/users');
|
|
11
|
-
const { createMerossInstance,
|
|
12
|
+
const { createMerossInstance, disconnectMeross } = require('../helpers/meross');
|
|
12
13
|
const { showSettingsMenu } = require('./settings');
|
|
13
14
|
|
|
14
15
|
// Helper functions
|
|
@@ -169,6 +170,164 @@ async function _saveCredentialsPrompt(rl, currentCredentials) {
|
|
|
169
170
|
return null;
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Prompts user to select devices and subdevices to initialize.
|
|
175
|
+
*
|
|
176
|
+
* Discovers available devices and subdevices, presents them in a hierarchical
|
|
177
|
+
* selection UI with subdevices indented under their hubs, and initializes
|
|
178
|
+
* only the selected items.
|
|
179
|
+
*
|
|
180
|
+
* @param {ManagerMeross} manager - Meross manager instance
|
|
181
|
+
* @returns {Promise<boolean>} True if initialization succeeded, false otherwise
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
async function _selectDevicesToInitialize(manager) {
|
|
185
|
+
const spinner = ora('Discovering available devices...').start();
|
|
186
|
+
try {
|
|
187
|
+
const [baseDevices, subdevices] = await Promise.all([
|
|
188
|
+
manager.devices.discover({ onlineOnly: true }),
|
|
189
|
+
manager.devices.discoverSubdevices({ onlineOnly: true })
|
|
190
|
+
]);
|
|
191
|
+
spinner.stop();
|
|
192
|
+
|
|
193
|
+
if ((!baseDevices || baseDevices.length === 0) && (!subdevices || subdevices.length === 0)) {
|
|
194
|
+
console.log(chalk.yellow('\nNo online devices or subdevices found.'));
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Group subdevices by hub UUID to display them hierarchically
|
|
199
|
+
const subdevicesByHub = new Map();
|
|
200
|
+
if (subdevices && subdevices.length > 0) {
|
|
201
|
+
for (const subdevice of subdevices) {
|
|
202
|
+
const hubUuid = subdevice.hubUuid;
|
|
203
|
+
if (!subdevicesByHub.has(hubUuid)) {
|
|
204
|
+
subdevicesByHub.set(hubUuid, []);
|
|
205
|
+
}
|
|
206
|
+
subdevicesByHub.get(hubUuid).push(subdevice);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const deviceChoices = [];
|
|
211
|
+
const deviceUuids = new Set();
|
|
212
|
+
|
|
213
|
+
if (baseDevices && baseDevices.length > 0) {
|
|
214
|
+
for (const device of baseDevices) {
|
|
215
|
+
const hasSubdevices = subdevicesByHub.has(device.uuid);
|
|
216
|
+
if (hasSubdevices) {
|
|
217
|
+
deviceUuids.add(device.uuid);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
deviceChoices.push({
|
|
221
|
+
name: `${device.devName || 'Unknown'} (${device.deviceType}) - ${chalk.grey(device.uuid)}`,
|
|
222
|
+
value: `device:${device.uuid}`,
|
|
223
|
+
checked: true
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Display subdevices indented under their hub for visual hierarchy
|
|
227
|
+
if (hasSubdevices) {
|
|
228
|
+
const hubSubdevices = subdevicesByHub.get(device.uuid);
|
|
229
|
+
for (const subdevice of hubSubdevices) {
|
|
230
|
+
deviceChoices.push({
|
|
231
|
+
name: ` └─ ${subdevice.subdeviceName || 'Unknown'} (${subdevice.subdeviceType}) - ${chalk.grey(subdevice.subdeviceId)}`,
|
|
232
|
+
value: `subdevice:${subdevice.hubUuid}:${subdevice.subdeviceId}`,
|
|
233
|
+
checked: true
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Handle edge case where subdevices exist but their hub isn't in base device list
|
|
241
|
+
for (const [hubUuid, hubSubdevices] of subdevicesByHub) {
|
|
242
|
+
if (!deviceUuids.has(hubUuid)) {
|
|
243
|
+
for (const subdevice of hubSubdevices) {
|
|
244
|
+
deviceChoices.push({
|
|
245
|
+
name: ` └─ ${subdevice.subdeviceName || 'Unknown'} (${subdevice.subdeviceType}) - ${chalk.grey(subdevice.subdeviceId)} [Hub: ${chalk.grey(subdevice.hubUuid)}]`,
|
|
246
|
+
value: `subdevice:${subdevice.hubUuid}:${subdevice.subdeviceId}`,
|
|
247
|
+
checked: true
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (deviceChoices.length === 0) {
|
|
254
|
+
console.log(chalk.yellow('\nNo devices or subdevices found.'));
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const { selectedItems } = await inquirer.prompt([{
|
|
259
|
+
type: 'checkbox',
|
|
260
|
+
name: 'selectedItems',
|
|
261
|
+
message: 'Select devices/subdevices to initialize (use space to toggle, enter to confirm):',
|
|
262
|
+
choices: deviceChoices,
|
|
263
|
+
pageSize: 20
|
|
264
|
+
}]);
|
|
265
|
+
|
|
266
|
+
if (!selectedItems || selectedItems.length === 0) {
|
|
267
|
+
console.log(chalk.yellow('\nNo devices selected. Skipping initialization.'));
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Parse selection into base devices and subdevices for different initialization paths
|
|
272
|
+
const baseDeviceUuids = [];
|
|
273
|
+
const subdeviceIdentifiers = [];
|
|
274
|
+
|
|
275
|
+
for (const item of selectedItems) {
|
|
276
|
+
if (item.startsWith('device:')) {
|
|
277
|
+
baseDeviceUuids.push(item.replace('device:', ''));
|
|
278
|
+
} else if (item.startsWith('subdevice:')) {
|
|
279
|
+
const parts = item.replace('subdevice:', '').split(':');
|
|
280
|
+
if (parts.length === 2) {
|
|
281
|
+
subdeviceIdentifiers.push({ hubUuid: parts[0], id: parts[1] });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
spinner.start('Initializing selected devices...');
|
|
287
|
+
try {
|
|
288
|
+
let totalInitialized = 0;
|
|
289
|
+
|
|
290
|
+
if (baseDeviceUuids.length > 0) {
|
|
291
|
+
const deviceCount = await manager.devices.initialize({ uuids: baseDeviceUuids });
|
|
292
|
+
totalInitialized += deviceCount;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Initialize subdevices individually since they require hub context
|
|
296
|
+
for (const subdeviceId of subdeviceIdentifiers) {
|
|
297
|
+
try {
|
|
298
|
+
const subdevice = await manager.devices.initializeDevice(subdeviceId);
|
|
299
|
+
if (subdevice) {
|
|
300
|
+
totalInitialized++;
|
|
301
|
+
}
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (manager.options && manager.options.logger) {
|
|
304
|
+
manager.options.logger(`Failed to initialize subdevice ${subdeviceId.id}: ${error.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
spinner.succeed(`Initialized ${totalInitialized} device(s)`);
|
|
310
|
+
|
|
311
|
+
// Allow time for MQTT connections to establish
|
|
312
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
313
|
+
|
|
314
|
+
manager.authenticated = true;
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
spinner.stop();
|
|
319
|
+
const { handleError } = require('../utils/error-handler');
|
|
320
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
spinner.stop();
|
|
325
|
+
const { handleError } = require('../utils/error-handler');
|
|
326
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
172
331
|
async function _selectDevice(manager, message = 'Select device:') {
|
|
173
332
|
const devices = manager.devices.list().filter(d => !(d instanceof MerossSubDevice));
|
|
174
333
|
if (devices.length === 0) {
|
|
@@ -384,12 +543,12 @@ function _buildSettingsCallbacks(
|
|
|
384
543
|
enableStatsRef.current,
|
|
385
544
|
verboseRef.current
|
|
386
545
|
);
|
|
387
|
-
const connected = await
|
|
546
|
+
const connected = await _selectDevicesToInitialize(managerRef.current);
|
|
388
547
|
if (connected) {
|
|
389
548
|
currentUserRef.current = userName;
|
|
390
549
|
return { success: true };
|
|
391
550
|
}
|
|
392
|
-
return { success: false, error: 'Failed to
|
|
551
|
+
return { success: false, error: 'Failed to initialize devices with new user' };
|
|
393
552
|
},
|
|
394
553
|
onSaveCredentials: async (name) => {
|
|
395
554
|
if (!currentCredentials) {
|
|
@@ -408,15 +567,15 @@ function _buildSettingsCallbacks(
|
|
|
408
567
|
}
|
|
409
568
|
|
|
410
569
|
async function _handleSettingsCommand(manager, rl, currentUserRef, currentCredentials) {
|
|
411
|
-
const transportModeRef = { current: manager.
|
|
570
|
+
const transportModeRef = { current: manager.transport.defaultMode || TransportMode.MQTT_ONLY };
|
|
412
571
|
const timeoutRef = { current: 10000 };
|
|
413
|
-
const enableStatsRef = { current: manager.
|
|
572
|
+
const enableStatsRef = { current: manager.statistics.isEnabled() };
|
|
414
573
|
const verboseRef = { current: manager.options && manager.options.logger !== null };
|
|
415
574
|
const managerRef = { current: manager };
|
|
416
575
|
|
|
417
576
|
const setTransportMode = (mode) => {
|
|
418
577
|
transportModeRef.current = mode;
|
|
419
|
-
managerRef.current.
|
|
578
|
+
managerRef.current.transport.defaultMode = mode;
|
|
420
579
|
};
|
|
421
580
|
const setTimeout = (newTimeout) => {
|
|
422
581
|
timeoutRef.current = newTimeout;
|
|
@@ -540,10 +699,10 @@ async function menuMode() {
|
|
|
540
699
|
currentCredentials = result.credentials;
|
|
541
700
|
}
|
|
542
701
|
|
|
543
|
-
//
|
|
544
|
-
const connected = await
|
|
702
|
+
// Discover and select devices to initialize
|
|
703
|
+
const connected = await _selectDevicesToInitialize(currentManager);
|
|
545
704
|
if (!connected) {
|
|
546
|
-
console.error('Failed to
|
|
705
|
+
console.error('Failed to initialize devices. Exiting.');
|
|
547
706
|
rl.close();
|
|
548
707
|
return;
|
|
549
708
|
}
|
|
@@ -622,10 +781,8 @@ async function menuMode() {
|
|
|
622
781
|
}
|
|
623
782
|
}
|
|
624
783
|
} catch (error) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
console.error(error.stack);
|
|
628
|
-
}
|
|
784
|
+
const { handleError } = require('../utils/error-handler');
|
|
785
|
+
handleError(error, { verbose: process.env.MEROSS_VERBOSE === 'true' });
|
|
629
786
|
await question(rl, '\nPress Enter to return to menu...');
|
|
630
787
|
}
|
|
631
788
|
}
|
package/cli/menu/settings.js
CHANGED
|
@@ -32,7 +32,7 @@ async function showSettingsMenu(rl, currentManager, currentUser, timeout, enable
|
|
|
32
32
|
const debug = currentManager ? createDebugUtils(currentManager) : null;
|
|
33
33
|
const currentStatsEnabled = debug ? debug.isStatsEnabled() : enableStats;
|
|
34
34
|
const currentTransportMode = currentManager
|
|
35
|
-
? getTransportModeName(currentManager.
|
|
35
|
+
? getTransportModeName(currentManager.transport.defaultMode)
|
|
36
36
|
: getTransportModeName(TransportMode.MQTT_ONLY);
|
|
37
37
|
const currentVerboseState = currentManager && currentManager.options ? (currentManager.options.logger !== null) : verbose;
|
|
38
38
|
|
|
@@ -111,7 +111,7 @@ async function showTransportModeSettings(rl, currentManager, currentUser, setTra
|
|
|
111
111
|
type: 'list',
|
|
112
112
|
name: 'mode',
|
|
113
113
|
message: 'Transport Mode',
|
|
114
|
-
default: currentManager.
|
|
114
|
+
default: currentManager.transport.defaultMode,
|
|
115
115
|
choices: [
|
|
116
116
|
{
|
|
117
117
|
name: 'MQTT Only (default, works remotely)',
|