meross-cli 0.4.0 → 0.6.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 +19 -0
- package/README.md +24 -5
- package/cli/commands/control/execute.js +36 -6
- package/cli/commands/control/params/index.js +16 -12
- package/cli/commands/control/params/light.js +55 -25
- package/cli/commands/control/params/thermostat.js +24 -22
- package/cli/commands/control/params/timer.js +38 -43
- package/cli/commands/control/params/trigger.js +43 -40
- package/cli/commands/info.js +218 -17
- package/cli/commands/sniffer/sniffer-menu.js +2 -2
- package/cli/commands/status/device-status.js +418 -1292
- package/cli/commands/status/hub-status.js +14 -6
- package/cli/control-registry.js +211 -406
- package/cli/meross-cli.js +1 -1
- package/cli/tests/README.md +2 -0
- package/cli/tests/test-alarm.js +22 -2
- package/cli/tests/test-child-lock.js +40 -10
- package/cli/tests/test-config.js +22 -2
- package/cli/tests/test-control.js +8 -8
- package/cli/tests/test-diffuser.js +7 -7
- package/cli/tests/test-dnd.js +87 -66
- package/cli/tests/test-electricity.js +37 -33
- package/cli/tests/test-encryption.js +13 -13
- package/cli/tests/test-garage.js +12 -14
- package/cli/tests/test-helper.js +1 -1
- package/cli/tests/test-hub-sensors.js +3 -3
- package/cli/tests/test-light.js +497 -105
- package/cli/tests/test-presence.js +10 -55
- package/cli/tests/test-registry.js +7 -1
- package/cli/tests/test-roller-shutter.js +78 -90
- package/cli/tests/test-screen.js +1 -1
- package/cli/tests/test-sensor-history.js +6 -2
- package/cli/tests/test-smoke-config.js +24 -4
- package/cli/tests/test-spray.js +11 -11
- package/cli/tests/test-system.js +375 -0
- package/cli/tests/test-temp-unit.js +22 -2
- package/cli/tests/test-template.js +61 -73
- package/cli/tests/test-thermostat.js +126 -89
- package/cli/tests/test-timer.js +9 -52
- package/cli/tests/test-toggle.js +49 -173
- package/cli/tests/test-trigger.js +8 -51
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.6.0] - 2026-01-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Enhanced `info` command with normalized capabilities display
|
|
12
|
+
- Displays user-friendly device capabilities using the new `device.capabilities` map
|
|
13
|
+
- Shows channel information and supported features in an organized format
|
|
14
|
+
- Verbose mode support for displaying raw abilities (namespaces) when `MEROSS_VERBOSE=true` is set
|
|
15
|
+
|
|
16
|
+
## [0.5.0] - 2026-01-20
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **BREAKING**: Updated to use feature-based API architecture from `meross-iot` v0.6.0
|
|
20
|
+
- Updated all commands and helpers to use new feature-based API (`device.feature.method()`)
|
|
21
|
+
- Updated all test files to use new API structure
|
|
22
|
+
- Breaking changes include:
|
|
23
|
+
- `device.setLightColor()` → `device.light.set()`
|
|
24
|
+
- `device.getLightState()` → `device.light.get()`
|
|
25
|
+
- Similar changes for all features (toggle, thermostat, etc.)
|
|
26
|
+
|
|
8
27
|
## [0.4.0] - 2026-01-19
|
|
9
28
|
|
|
10
29
|
### 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.6.0
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Or use via npx:
|
|
@@ -55,7 +55,7 @@ meross-cli info <uuid> --email user@example.com --password mypass
|
|
|
55
55
|
meross-cli status --email user@example.com --password mypass
|
|
56
56
|
|
|
57
57
|
# Control a device
|
|
58
|
-
meross-cli control <uuid>
|
|
58
|
+
meross-cli control <uuid> toggle.set --channel 0 --on true
|
|
59
59
|
|
|
60
60
|
# Start interactive menu mode
|
|
61
61
|
meross-cli
|
|
@@ -77,6 +77,28 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
|
|
|
77
77
|
|
|
78
78
|
## Changelog
|
|
79
79
|
|
|
80
|
+
### [0.6.0] - 2026-01-20
|
|
81
|
+
|
|
82
|
+
#### Added
|
|
83
|
+
- Enhanced `info` command with normalized capabilities display
|
|
84
|
+
- Displays user-friendly device capabilities using the new `device.capabilities` map
|
|
85
|
+
- Shows channel information and supported features in an organized format
|
|
86
|
+
- Verbose mode support for displaying raw abilities (namespaces) when `MEROSS_VERBOSE=true` is set
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary>Older</summary>
|
|
90
|
+
|
|
91
|
+
### [0.5.0] - 2026-01-20
|
|
92
|
+
|
|
93
|
+
#### Changed
|
|
94
|
+
- **BREAKING**: Updated to use feature-based API architecture from `meross-iot` v0.6.0
|
|
95
|
+
- Updated all commands and helpers to use new feature-based API (`device.feature.method()`)
|
|
96
|
+
- Updated all test files to use new API structure
|
|
97
|
+
- Breaking changes include:
|
|
98
|
+
- `device.setLightColor()` → `device.light.set()`
|
|
99
|
+
- `device.getLightState()` → `device.light.get()`
|
|
100
|
+
- Similar changes for all features (toggle, thermostat, etc.)
|
|
101
|
+
|
|
80
102
|
### [0.4.0] - 2026-01-19
|
|
81
103
|
|
|
82
104
|
#### Changed
|
|
@@ -92,9 +114,6 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
|
|
|
92
114
|
- Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
|
|
93
115
|
- Enhanced error display with better context and user-friendly formatting
|
|
94
116
|
|
|
95
|
-
<details>
|
|
96
|
-
<summary>Older</summary>
|
|
97
|
-
|
|
98
117
|
### [0.3.0] - 2026-01-16
|
|
99
118
|
|
|
100
119
|
#### Changed
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const ManagerMeross = require('meross-iot');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Executes a control command using the feature-based API.
|
|
7
|
+
*
|
|
8
|
+
* Routes commands to device feature modules based on method name format "feature.action".
|
|
9
|
+
* This allows dynamic command execution without hardcoding device-specific logic.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} manager - ManagerMeross instance
|
|
12
|
+
* @param {string} uuid - Device UUID
|
|
13
|
+
* @param {string} methodName - Method name in format "feature.action" (e.g., "toggle.set", "light.set")
|
|
14
|
+
* @param {Object} params - Parameters to pass to the feature method
|
|
15
|
+
* @returns {Promise<*>} Result from the feature method
|
|
16
|
+
*/
|
|
5
17
|
async function executeControlCommand(manager, uuid, methodName, params) {
|
|
6
18
|
const device = manager.devices.get(uuid);
|
|
7
19
|
|
|
@@ -20,17 +32,35 @@ async function executeControlCommand(manager, uuid, methodName, params) {
|
|
|
20
32
|
);
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
const parts = methodName.split('.');
|
|
36
|
+
if (parts.length !== 2) {
|
|
24
37
|
throw new ManagerMeross.MerossErrorUnsupported(
|
|
25
|
-
`
|
|
38
|
+
`Invalid method name format: ${methodName}. Expected format: "feature.action" (e.g., "toggle.set")`,
|
|
26
39
|
methodName,
|
|
27
|
-
'Method
|
|
40
|
+
'Method name must be in format: feature.action'
|
|
28
41
|
);
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
const [featureName, action] = parts;
|
|
45
|
+
const feature = device[featureName];
|
|
46
|
+
|
|
47
|
+
if (!feature) {
|
|
48
|
+
throw new ManagerMeross.MerossErrorUnsupported(
|
|
49
|
+
`Feature '${featureName}' not available on this device`,
|
|
50
|
+
methodName,
|
|
51
|
+
`Device does not support ${featureName} feature`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof feature[action] !== 'function') {
|
|
56
|
+
throw new ManagerMeross.MerossErrorUnsupported(
|
|
57
|
+
`Action '${action}' not available on feature '${featureName}'`,
|
|
58
|
+
methodName,
|
|
59
|
+
`Feature ${featureName} does not support ${action} action`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return await feature[action](params);
|
|
33
64
|
}
|
|
34
65
|
|
|
35
66
|
module.exports = { executeControlCommand };
|
|
36
|
-
|
|
@@ -7,48 +7,52 @@ const { collectSetLightColorParams } = require('./light');
|
|
|
7
7
|
const { collectGenericParams } = require('./generic');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Main entry point for collecting control parameters.
|
|
11
|
-
*
|
|
10
|
+
* Main entry point for collecting control parameters interactively.
|
|
11
|
+
*
|
|
12
|
+
* Routes to feature-specific parameter collection handlers when available, allowing
|
|
13
|
+
* specialized prompts for complex features. Falls back to generic collection for
|
|
14
|
+
* simpler methods or when feature-specific handlers are unavailable.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} methodName - Control method name (e.g., "toggle.set", "light.set")
|
|
17
|
+
* @param {Object} methodMetadata - Method metadata from control registry
|
|
18
|
+
* @param {Object} device - Device instance
|
|
19
|
+
* @returns {Promise<Object>} Collected parameters object
|
|
12
20
|
*/
|
|
13
21
|
async function collectControlParameters(methodName, methodMetadata, device) {
|
|
14
22
|
if (!methodMetadata || !methodMetadata.params) {
|
|
15
23
|
return {};
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
// Route to feature-specific handlers
|
|
19
26
|
switch (methodName) {
|
|
20
|
-
case '
|
|
27
|
+
case 'thermostat.set':
|
|
21
28
|
return await collectThermostatModeParams(methodMetadata, device);
|
|
22
29
|
|
|
23
|
-
case '
|
|
30
|
+
case 'light.set':
|
|
24
31
|
return await collectSetLightColorParams(methodMetadata, device);
|
|
25
32
|
|
|
26
|
-
case '
|
|
33
|
+
case 'timer.set':
|
|
27
34
|
return await collectSetTimerXParams(methodMetadata, device);
|
|
28
35
|
|
|
29
|
-
case '
|
|
36
|
+
case 'timer.delete': {
|
|
30
37
|
const result = await collectDeleteTimerXParams(methodMetadata, device);
|
|
31
38
|
if (result !== null) {
|
|
32
39
|
return result;
|
|
33
40
|
}
|
|
34
|
-
// Fall through to generic collection if no timers found
|
|
35
41
|
break;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
case '
|
|
44
|
+
case 'trigger.set':
|
|
39
45
|
return await collectSetTriggerXParams(methodMetadata, device);
|
|
40
46
|
|
|
41
|
-
case '
|
|
47
|
+
case 'trigger.delete': {
|
|
42
48
|
const result = await collectDeleteTriggerXParams(methodMetadata, device);
|
|
43
49
|
if (result !== null) {
|
|
44
50
|
return result;
|
|
45
51
|
}
|
|
46
|
-
// Fall through to generic collection if no triggers found
|
|
47
52
|
break;
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
// Default to generic parameter collection
|
|
52
56
|
return await collectGenericParams(methodMetadata);
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
const inquirer = require('inquirer');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Validates
|
|
6
|
+
* Validates RGB color input format for inquirer prompts.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} value - RGB color string in format "r,g,b"
|
|
9
|
+
* @returns {boolean|string} True if valid, error message if invalid
|
|
7
10
|
*/
|
|
8
11
|
function _validateRgb(value) {
|
|
9
12
|
if (!value || value.trim() === '') {
|
|
@@ -23,7 +26,10 @@ function _validateRgb(value) {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
|
-
* Builds
|
|
29
|
+
* Builds an inquirer question for channel selection.
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} channelParam - Channel parameter metadata
|
|
32
|
+
* @returns {Object} Inquirer question configuration
|
|
27
33
|
*/
|
|
28
34
|
function _buildChannelQuestion(channelParam) {
|
|
29
35
|
return {
|
|
@@ -35,13 +41,16 @@ function _buildChannelQuestion(channelParam) {
|
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
|
-
* Builds an on/off
|
|
44
|
+
* Builds an inquirer question for on/off state selection.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} onParam - On/off parameter metadata
|
|
47
|
+
* @returns {Object} Inquirer question configuration
|
|
39
48
|
*/
|
|
40
|
-
function _buildOnOffQuestion(
|
|
49
|
+
function _buildOnOffQuestion(onParam) {
|
|
41
50
|
return {
|
|
42
51
|
type: 'list',
|
|
43
|
-
name: '
|
|
44
|
-
message:
|
|
52
|
+
name: 'on',
|
|
53
|
+
message: onParam.label || 'Turn On/Off',
|
|
45
54
|
choices: [
|
|
46
55
|
{ name: 'Skip (no change)', value: undefined },
|
|
47
56
|
{ name: 'On', value: true },
|
|
@@ -53,7 +62,10 @@ function _buildOnOffQuestion(onoffParam) {
|
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
/**
|
|
56
|
-
* Builds an
|
|
65
|
+
* Builds an inquirer question for RGB color input.
|
|
66
|
+
*
|
|
67
|
+
* @param {Object} rgbParam - RGB parameter metadata
|
|
68
|
+
* @returns {Object} Inquirer question configuration
|
|
57
69
|
*/
|
|
58
70
|
function _buildRgbQuestion(rgbParam) {
|
|
59
71
|
return {
|
|
@@ -72,7 +84,10 @@ function _buildRgbQuestion(rgbParam) {
|
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
/**
|
|
75
|
-
* Builds
|
|
87
|
+
* Builds an inquirer question for color temperature input.
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} tempParam - Temperature parameter metadata
|
|
90
|
+
* @returns {Object} Inquirer question configuration
|
|
76
91
|
*/
|
|
77
92
|
function _buildTemperatureQuestion(tempParam) {
|
|
78
93
|
return {
|
|
@@ -86,7 +101,10 @@ function _buildTemperatureQuestion(tempParam) {
|
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
/**
|
|
89
|
-
* Builds
|
|
104
|
+
* Builds an inquirer question for brightness (luminance) input.
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} luminanceParam - Luminance parameter metadata
|
|
107
|
+
* @returns {Object} Inquirer question configuration
|
|
90
108
|
*/
|
|
91
109
|
function _buildLuminanceQuestion(luminanceParam) {
|
|
92
110
|
return {
|
|
@@ -100,7 +118,10 @@ function _buildLuminanceQuestion(luminanceParam) {
|
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
/**
|
|
103
|
-
* Builds
|
|
121
|
+
* Builds an inquirer question for transition type selection.
|
|
122
|
+
*
|
|
123
|
+
* @param {Object} gradualParam - Gradual parameter metadata
|
|
124
|
+
* @returns {Object} Inquirer question configuration
|
|
104
125
|
*/
|
|
105
126
|
function _buildGradualQuestion(gradualParam) {
|
|
106
127
|
return {
|
|
@@ -118,8 +139,14 @@ function _buildGradualQuestion(gradualParam) {
|
|
|
118
139
|
}
|
|
119
140
|
|
|
120
141
|
/**
|
|
121
|
-
* Collects parameters for
|
|
122
|
-
*
|
|
142
|
+
* Collects parameters for light.set method interactively.
|
|
143
|
+
*
|
|
144
|
+
* Filters available options based on device capabilities to avoid showing unsupported
|
|
145
|
+
* features. Only displays RGB, temperature, and luminance options if the device supports them.
|
|
146
|
+
*
|
|
147
|
+
* @param {Object} methodMetadata - Method metadata from control registry
|
|
148
|
+
* @param {Object} device - Device instance
|
|
149
|
+
* @returns {Promise<Object>} Collected parameters object
|
|
123
150
|
*/
|
|
124
151
|
async function collectSetLightColorParams(methodMetadata, device) {
|
|
125
152
|
const params = {};
|
|
@@ -129,24 +156,30 @@ async function collectSetLightColorParams(methodMetadata, device) {
|
|
|
129
156
|
return params;
|
|
130
157
|
}
|
|
131
158
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
let supportsRgb = false;
|
|
160
|
+
let supportsTemperature = false;
|
|
161
|
+
let supportsLuminance = false;
|
|
162
|
+
|
|
163
|
+
if (device.light && device.abilities && device.abilities['Appliance.Control.Light']) {
|
|
164
|
+
const lightAbility = device.abilities['Appliance.Control.Light'];
|
|
165
|
+
if (lightAbility && lightAbility.capacity) {
|
|
166
|
+
const { LightMode } = require('meross-iot');
|
|
167
|
+
supportsRgb = (lightAbility.capacity & LightMode.MODE_RGB) === LightMode.MODE_RGB;
|
|
168
|
+
supportsTemperature = (lightAbility.capacity & LightMode.MODE_TEMPERATURE) === LightMode.MODE_TEMPERATURE;
|
|
169
|
+
supportsLuminance = (lightAbility.capacity & LightMode.MODE_LUMINANCE) === LightMode.MODE_LUMINANCE;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
136
172
|
|
|
137
|
-
// Channel parameter (always available)
|
|
138
173
|
const channelParam = methodMetadata.params.find(p => p.name === 'channel');
|
|
139
174
|
if (channelParam) {
|
|
140
175
|
questions.push(_buildChannelQuestion(channelParam));
|
|
141
176
|
}
|
|
142
177
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
questions.push(_buildOnOffQuestion(onoffParam));
|
|
178
|
+
const onParam = methodMetadata.params.find(p => p.name === 'on');
|
|
179
|
+
if (onParam) {
|
|
180
|
+
questions.push(_buildOnOffQuestion(onParam));
|
|
147
181
|
}
|
|
148
182
|
|
|
149
|
-
// RGB parameter (only if device supports it)
|
|
150
183
|
if (supportsRgb) {
|
|
151
184
|
const rgbParam = methodMetadata.params.find(p => p.name === 'rgb');
|
|
152
185
|
if (rgbParam) {
|
|
@@ -154,7 +187,6 @@ async function collectSetLightColorParams(methodMetadata, device) {
|
|
|
154
187
|
}
|
|
155
188
|
}
|
|
156
189
|
|
|
157
|
-
// Temperature parameter (only if device supports it)
|
|
158
190
|
if (supportsTemperature) {
|
|
159
191
|
const tempParam = methodMetadata.params.find(p => p.name === 'temperature');
|
|
160
192
|
if (tempParam) {
|
|
@@ -162,7 +194,6 @@ async function collectSetLightColorParams(methodMetadata, device) {
|
|
|
162
194
|
}
|
|
163
195
|
}
|
|
164
196
|
|
|
165
|
-
// Luminance parameter (only if device supports it)
|
|
166
197
|
if (supportsLuminance) {
|
|
167
198
|
const luminanceParam = methodMetadata.params.find(p => p.name === 'luminance');
|
|
168
199
|
if (luminanceParam) {
|
|
@@ -170,7 +201,6 @@ async function collectSetLightColorParams(methodMetadata, device) {
|
|
|
170
201
|
}
|
|
171
202
|
}
|
|
172
203
|
|
|
173
|
-
// Gradual parameter (always available as an option)
|
|
174
204
|
const gradualParam = methodMetadata.params.find(p => p.name === 'gradual');
|
|
175
205
|
if (gradualParam) {
|
|
176
206
|
questions.push(_buildGradualQuestion(gradualParam));
|
|
@@ -5,21 +5,26 @@ const inquirer = require('inquirer');
|
|
|
5
5
|
const { ThermostatMode } = require('meross-iot');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Collects parameters for
|
|
8
|
+
* Collects parameters for thermostat.set interactively.
|
|
9
|
+
*
|
|
10
|
+
* Displays current thermostat state to provide context, then prompts for which
|
|
11
|
+
* fields to update. Supports partial updates to avoid overwriting unchanged values.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} methodMetadata - Method metadata from control registry
|
|
14
|
+
* @param {Object} device - Device instance
|
|
15
|
+
* @returns {Promise<Object>} Collected parameters object
|
|
9
16
|
*/
|
|
10
17
|
async function collectThermostatModeParams(methodMetadata, device) {
|
|
11
18
|
const params = {};
|
|
12
19
|
const channel = methodMetadata.params.find(p => p.name === 'channel')?.default || 0;
|
|
13
20
|
|
|
14
|
-
// Display current state
|
|
15
21
|
try {
|
|
16
|
-
if (
|
|
22
|
+
if (device.thermostat) {
|
|
17
23
|
console.log(chalk.dim('Fetching current thermostat state...'));
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
const currentState = response.mode[0];
|
|
24
|
+
const thermostatState = await device.thermostat.get({ channel });
|
|
25
|
+
if (thermostatState) {
|
|
21
26
|
console.log(chalk.cyan('\nCurrent Thermostat State:'));
|
|
22
|
-
if (
|
|
27
|
+
if (thermostatState.mode !== undefined) {
|
|
23
28
|
const modeNames = {
|
|
24
29
|
[ThermostatMode.HEAT]: 'Heat',
|
|
25
30
|
[ThermostatMode.COOL]: 'Cool',
|
|
@@ -27,36 +32,34 @@ async function collectThermostatModeParams(methodMetadata, device) {
|
|
|
27
32
|
[ThermostatMode.AUTO]: 'Auto',
|
|
28
33
|
[ThermostatMode.MANUAL]: 'Manual'
|
|
29
34
|
};
|
|
30
|
-
const modeName = modeNames[
|
|
35
|
+
const modeName = modeNames[thermostatState.mode] || `Mode ${thermostatState.mode}`;
|
|
31
36
|
console.log(chalk.dim(` Mode: ${modeName}`));
|
|
32
37
|
}
|
|
33
|
-
if (
|
|
34
|
-
console.log(chalk.dim(` Power: ${
|
|
38
|
+
if (thermostatState.isOn !== undefined) {
|
|
39
|
+
console.log(chalk.dim(` Power: ${thermostatState.isOn ? 'On' : 'Off'}`));
|
|
35
40
|
}
|
|
36
|
-
if (
|
|
37
|
-
console.log(chalk.dim(` Heat Temp: ${
|
|
41
|
+
if (thermostatState.heatTemperatureCelsius !== undefined) {
|
|
42
|
+
console.log(chalk.dim(` Heat Temp: ${thermostatState.heatTemperatureCelsius.toFixed(1)}°C`));
|
|
38
43
|
}
|
|
39
|
-
if (
|
|
40
|
-
console.log(chalk.dim(` Cool Temp: ${
|
|
44
|
+
if (thermostatState.coolTemperatureCelsius !== undefined) {
|
|
45
|
+
console.log(chalk.dim(` Cool Temp: ${thermostatState.coolTemperatureCelsius.toFixed(1)}°C`));
|
|
41
46
|
}
|
|
42
|
-
if (
|
|
43
|
-
console.log(chalk.dim(` Eco Temp: ${
|
|
47
|
+
if (thermostatState.ecoTemperatureCelsius !== undefined) {
|
|
48
|
+
console.log(chalk.dim(` Eco Temp: ${thermostatState.ecoTemperatureCelsius.toFixed(1)}°C`));
|
|
44
49
|
}
|
|
45
|
-
if (
|
|
46
|
-
console.log(chalk.dim(` Manual Temp: ${
|
|
50
|
+
if (thermostatState.manualTemperatureCelsius !== undefined) {
|
|
51
|
+
console.log(chalk.dim(` Manual Temp: ${thermostatState.manualTemperatureCelsius.toFixed(1)}°C`));
|
|
47
52
|
}
|
|
48
53
|
console.log();
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
} catch (e) {
|
|
52
|
-
//
|
|
57
|
+
// Continue without current state if fetch fails
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
// Collect parameters interactively
|
|
56
60
|
params.channel = channel;
|
|
57
61
|
params.partialUpdate = true;
|
|
58
62
|
|
|
59
|
-
// Ask which fields to update
|
|
60
63
|
const fieldsToUpdate = await inquirer.prompt([{
|
|
61
64
|
type: 'checkbox',
|
|
62
65
|
name: 'fields',
|
|
@@ -77,7 +80,6 @@ async function collectThermostatModeParams(methodMetadata, device) {
|
|
|
77
80
|
}
|
|
78
81
|
}]);
|
|
79
82
|
|
|
80
|
-
// Prompt for each selected field
|
|
81
83
|
for (const field of fieldsToUpdate.fields) {
|
|
82
84
|
if (field === 'mode') {
|
|
83
85
|
const answer = await inquirer.prompt([{
|
|
@@ -6,18 +6,24 @@ const { TimerType, TimerUtils } = require('meross-iot');
|
|
|
6
6
|
const { timeToMinutes } = TimerUtils;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Collects parameters for setTimerX
|
|
9
|
+
* Collects parameters for setTimerX interactively.
|
|
10
|
+
*
|
|
11
|
+
* Displays existing timers for context, then prompts for timer configuration.
|
|
12
|
+
* Uses device time when available to help users set accurate trigger times.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} methodMetadata - Method metadata from control registry
|
|
15
|
+
* @param {Object} device - Device instance
|
|
16
|
+
* @returns {Promise<Object>} Collected parameters object
|
|
10
17
|
*/
|
|
11
18
|
async function collectSetTimerXParams(methodMetadata, device) {
|
|
12
19
|
const params = {};
|
|
13
20
|
const channel = methodMetadata.params.find(p => p.name === 'timerx')?.properties?.find(prop => prop.name === 'channel')?.default || 0;
|
|
14
21
|
|
|
15
|
-
// Show existing timers
|
|
16
22
|
let hasTimers = false;
|
|
17
23
|
try {
|
|
18
|
-
if (typeof device.
|
|
24
|
+
if (device.timer && typeof device.timer.get === 'function') {
|
|
19
25
|
console.log(chalk.dim('Fetching existing timers...'));
|
|
20
|
-
const response = await device.
|
|
26
|
+
const response = await device.timer.get({ channel });
|
|
21
27
|
if (response && response.timerx && Array.isArray(response.timerx) && response.timerx.length > 0) {
|
|
22
28
|
hasTimers = true;
|
|
23
29
|
console.log(chalk.cyan(`\nExisting Timers (Channel ${channel}):`));
|
|
@@ -35,7 +41,7 @@ async function collectSetTimerXParams(methodMetadata, device) {
|
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
} catch (e) {
|
|
38
|
-
//
|
|
44
|
+
// Continue without existing timers if fetch fails
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
if (!hasTimers) {
|
|
@@ -77,11 +83,10 @@ async function collectSetTimerXParams(methodMetadata, device) {
|
|
|
77
83
|
default: 0
|
|
78
84
|
}]);
|
|
79
85
|
|
|
80
|
-
// Get current time for context
|
|
81
86
|
let currentTimeStr = '';
|
|
82
87
|
try {
|
|
83
|
-
if (typeof device.
|
|
84
|
-
const timeResponse = await device.
|
|
88
|
+
if (device.system && typeof device.system.getTime === 'function') {
|
|
89
|
+
const timeResponse = await device.system.getTime();
|
|
85
90
|
if (timeResponse && timeResponse.time && timeResponse.time.timestamp) {
|
|
86
91
|
const date = new Date(timeResponse.time.timestamp * 1000);
|
|
87
92
|
const hours = date.getHours();
|
|
@@ -90,7 +95,7 @@ async function collectSetTimerXParams(methodMetadata, device) {
|
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
} catch (e) {
|
|
93
|
-
//
|
|
98
|
+
// Fall back to system time if device time unavailable
|
|
94
99
|
}
|
|
95
100
|
if (!currentTimeStr) {
|
|
96
101
|
const now = new Date();
|
|
@@ -146,7 +151,6 @@ async function collectSetTimerXParams(methodMetadata, device) {
|
|
|
146
151
|
default: 0
|
|
147
152
|
}]);
|
|
148
153
|
|
|
149
|
-
// Pass user-friendly format to API (API handles conversion)
|
|
150
154
|
params.channel = channel;
|
|
151
155
|
params.alias = aliasAnswer.alias;
|
|
152
156
|
params.time = timeAnswer.time;
|
|
@@ -159,18 +163,23 @@ async function collectSetTimerXParams(methodMetadata, device) {
|
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
/**
|
|
162
|
-
* Collects parameters for
|
|
166
|
+
* Collects parameters for timer.delete with interactive prompts.
|
|
163
167
|
*/
|
|
164
168
|
async function collectDeleteTimerXParams(methodMetadata, device) {
|
|
165
169
|
const params = {};
|
|
166
170
|
const channel = methodMetadata.params.find(p => p.name === 'channel')?.default || 0;
|
|
167
171
|
|
|
168
172
|
try {
|
|
169
|
-
if (typeof device.
|
|
173
|
+
if (device.timer && typeof device.timer.get === 'function') {
|
|
174
|
+
// Clear cache to force fresh fetch after potential deletions
|
|
175
|
+
if (device._timerxStateByChannel) {
|
|
176
|
+
device._timerxStateByChannel.delete(channel);
|
|
177
|
+
}
|
|
170
178
|
console.log(chalk.dim('Fetching existing timers...'));
|
|
171
|
-
const response = await device.
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
const response = await device.timer.get({ channel });
|
|
180
|
+
const items = response && response.timerx && Array.isArray(response.timerx) ? response.timerx : [];
|
|
181
|
+
|
|
182
|
+
if (items.length > 0) {
|
|
174
183
|
console.log(chalk.cyan(`\nExisting Timers (Channel ${channel}):`));
|
|
175
184
|
items.forEach((item, index) => {
|
|
176
185
|
const timeMinutes = item.time || 0;
|
|
@@ -184,7 +193,6 @@ async function collectDeleteTimerXParams(methodMetadata, device) {
|
|
|
184
193
|
});
|
|
185
194
|
console.log();
|
|
186
195
|
|
|
187
|
-
// Allow selection from list
|
|
188
196
|
const choices = items.map(item => {
|
|
189
197
|
const timeMinutes = item.time || 0;
|
|
190
198
|
const hours = Math.floor(timeMinutes / 60);
|
|
@@ -197,12 +205,6 @@ async function collectDeleteTimerXParams(methodMetadata, device) {
|
|
|
197
205
|
};
|
|
198
206
|
});
|
|
199
207
|
|
|
200
|
-
choices.push(new inquirer.Separator());
|
|
201
|
-
choices.push({
|
|
202
|
-
name: 'Enter ID Manually',
|
|
203
|
-
value: '__manual__'
|
|
204
|
-
});
|
|
205
|
-
|
|
206
208
|
const selected = await inquirer.prompt([{
|
|
207
209
|
type: 'list',
|
|
208
210
|
name: 'id',
|
|
@@ -210,32 +212,25 @@ async function collectDeleteTimerXParams(methodMetadata, device) {
|
|
|
210
212
|
choices
|
|
211
213
|
}]);
|
|
212
214
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
name: 'id',
|
|
217
|
-
message: 'Timer ID',
|
|
218
|
-
validate: (value) => {
|
|
219
|
-
if (!value || value.trim() === '') {
|
|
220
|
-
return 'ID is required';
|
|
221
|
-
}
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
}]);
|
|
225
|
-
params.timerId = manualAnswer.id;
|
|
226
|
-
} else {
|
|
227
|
-
params.timerId = selected.id;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
params.channel = channel;
|
|
231
|
-
return params;
|
|
215
|
+
params.timerId = selected.id;
|
|
216
|
+
} else {
|
|
217
|
+
throw new Error(`No timers found on channel ${channel}. Nothing to delete.`);
|
|
232
218
|
}
|
|
219
|
+
|
|
220
|
+
params.channel = channel;
|
|
221
|
+
return params;
|
|
233
222
|
}
|
|
234
223
|
} catch (e) {
|
|
235
|
-
//
|
|
224
|
+
// If it's our "no timers" error, re-throw it
|
|
225
|
+
if (e.message && e.message.includes('No timers found')) {
|
|
226
|
+
throw e;
|
|
227
|
+
}
|
|
228
|
+
// If fetch fails, throw error
|
|
229
|
+
throw new Error('Unable to fetch timers from device. Please try again.');
|
|
236
230
|
}
|
|
237
231
|
|
|
238
|
-
|
|
232
|
+
// Fallback if timer feature is not available
|
|
233
|
+
return null;
|
|
239
234
|
}
|
|
240
235
|
|
|
241
236
|
module.exports = { collectSetTimerXParams, collectDeleteTimerXParams };
|