meross-cli 0.3.0 → 0.5.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 (48) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +46 -5
  3. package/cli/commands/control/execute.js +50 -7
  4. package/cli/commands/control/menu.js +3 -5
  5. package/cli/commands/control/params/index.js +16 -12
  6. package/cli/commands/control/params/light.js +55 -25
  7. package/cli/commands/control/params/thermostat.js +24 -22
  8. package/cli/commands/control/params/timer.js +18 -15
  9. package/cli/commands/control/params/trigger.js +24 -13
  10. package/cli/commands/info.js +39 -15
  11. package/cli/commands/sniffer/sniffer-menu.js +2 -2
  12. package/cli/commands/status/device-status.js +418 -1292
  13. package/cli/commands/status/hub-status.js +14 -6
  14. package/cli/control-registry.js +211 -406
  15. package/cli/helpers/client.js +16 -10
  16. package/cli/helpers/meross.js +18 -14
  17. package/cli/menu/main.js +170 -13
  18. package/cli/menu/settings.js +2 -2
  19. package/cli/meross-cli.js +13 -47
  20. package/cli/tests/README.md +2 -0
  21. package/cli/tests/test-alarm.js +22 -2
  22. package/cli/tests/test-child-lock.js +40 -10
  23. package/cli/tests/test-config.js +22 -2
  24. package/cli/tests/test-control.js +8 -8
  25. package/cli/tests/test-diffuser.js +7 -7
  26. package/cli/tests/test-dnd.js +87 -66
  27. package/cli/tests/test-electricity.js +37 -33
  28. package/cli/tests/test-encryption.js +13 -13
  29. package/cli/tests/test-garage.js +12 -14
  30. package/cli/tests/test-helper.js +1 -1
  31. package/cli/tests/test-hub-sensors.js +3 -3
  32. package/cli/tests/test-light.js +497 -105
  33. package/cli/tests/test-presence.js +10 -55
  34. package/cli/tests/test-registry.js +7 -1
  35. package/cli/tests/test-roller-shutter.js +78 -90
  36. package/cli/tests/test-screen.js +1 -1
  37. package/cli/tests/test-sensor-history.js +6 -2
  38. package/cli/tests/test-smoke-config.js +24 -4
  39. package/cli/tests/test-spray.js +11 -11
  40. package/cli/tests/test-system.js +375 -0
  41. package/cli/tests/test-temp-unit.js +22 -2
  42. package/cli/tests/test-template.js +61 -73
  43. package/cli/tests/test-thermostat.js +126 -89
  44. package/cli/tests/test-timer.js +8 -51
  45. package/cli/tests/test-toggle.js +49 -173
  46. package/cli/tests/test-trigger.js +7 -50
  47. package/cli/utils/error-handler.js +257 -0
  48. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ 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.5.0] - 2026-01-20
9
+
10
+ ### Changed
11
+ - **BREAKING**: Updated to use feature-based API architecture from `meross-iot` v0.6.0
12
+ - Updated all commands and helpers to use new feature-based API (`device.feature.method()`)
13
+ - Updated all test files to use new API structure
14
+ - Breaking changes include:
15
+ - `device.setLightColor()` → `device.light.set()`
16
+ - `device.getLightState()` → `device.light.get()`
17
+ - Similar changes for all features (toggle, thermostat, etc.)
18
+
19
+ ## [0.4.0] - 2026-01-19
20
+
21
+ ### Changed
22
+ - **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
23
+ - Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
24
+ - Updated all commands and helpers to use new property-based access patterns
25
+ - **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
26
+ - Updated to use new `MerossError*` error class names
27
+ - Replaced inline error handling with centralized `handleError()` function
28
+ - All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
29
+
30
+ ### Added
31
+ - Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
32
+ - Enhanced error display with better context and user-friendly formatting
33
+
8
34
  ## [0.3.0] - 2026-01-16
9
35
 
10
36
  ### 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.3.0
26
+ npm install -g meross-cli@0.5.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> setToggleX --channel 0 --on
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,50 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
77
77
 
78
78
  ## Changelog
79
79
 
80
+ ### [0.5.0] - 2026-01-20
81
+
82
+ #### Changed
83
+ - **BREAKING**: Updated to use feature-based API architecture from `meross-iot` v0.6.0
84
+ - Updated all commands and helpers to use new feature-based API (`device.feature.method()`)
85
+ - Updated all test files to use new API structure
86
+ - Breaking changes include:
87
+ - `device.setLightColor()` → `device.light.set()`
88
+ - `device.getLightState()` → `device.light.get()`
89
+ - Similar changes for all features (toggle, thermostat, etc.)
90
+
91
+ ### [0.4.0] - 2026-01-19
92
+
93
+ #### Changed
94
+ - **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
95
+ - Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
96
+ - Updated all commands and helpers to use new property-based access patterns
97
+ - **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
98
+ - Updated to use new `MerossError*` error class names
99
+ - Replaced inline error handling with centralized `handleError()` function
100
+ - All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
101
+
102
+ #### Added
103
+ - Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
104
+ - Enhanced error display with better context and user-friendly formatting
105
+
106
+ <details>
107
+ <summary>Older</summary>
108
+
109
+ ### [0.4.0] - 2026-01-19
110
+
111
+ #### Changed
112
+ - **BREAKING**: Updated to use new manager module structure from `meross-iot` v0.5.0
113
+ - Updated to use manager properties (`manager.devices`, `manager.mqtt`, `manager.http`, etc.) instead of direct methods
114
+ - Updated all commands and helpers to use new property-based access patterns
115
+ - **BREAKING**: Updated to use standardized error handling from `meross-iot` v0.5.0
116
+ - Updated to use new `MerossError*` error class names
117
+ - Replaced inline error handling with centralized `handleError()` function
118
+ - All error handling now uses the new error handler utility for consistent, user-friendly formatted messages
119
+
120
+ #### Added
121
+ - Centralized error handler utility (`cli/utils/error-handler.js`) with formatted error messages
122
+ - Enhanced error display with better context and user-friendly formatting
123
+
80
124
  ### [0.3.0] - 2026-01-16
81
125
 
82
126
  #### Changed
@@ -86,9 +130,6 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
86
130
  - Updated to use camelCase property names consistently
87
131
  - Updated all tests and commands to use new API patterns
88
132
 
89
- <details>
90
- <summary>Older</summary>
91
-
92
133
  ### [0.2.0] - 2026-01-15
93
134
 
94
135
  #### Changed
@@ -1,23 +1,66 @@
1
1
  'use strict';
2
2
 
3
+ const ManagerMeross = require('meross-iot');
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
+ */
3
17
  async function executeControlCommand(manager, uuid, methodName, params) {
4
18
  const device = manager.devices.get(uuid);
5
19
 
6
20
  if (!device) {
7
- throw new Error(`Device not found: ${uuid}`);
21
+ throw new ManagerMeross.MerossErrorNotFound(
22
+ `Device not found: ${uuid}`,
23
+ 'device',
24
+ uuid
25
+ );
8
26
  }
9
27
 
10
28
  if (!device.deviceConnected) {
11
- throw new Error('Device is not connected. Please wait for device to connect.');
29
+ throw new ManagerMeross.MerossErrorUnconnected(
30
+ 'Device is not connected. Please wait for device to connect.',
31
+ uuid
32
+ );
33
+ }
34
+
35
+ const parts = methodName.split('.');
36
+ if (parts.length !== 2) {
37
+ throw new ManagerMeross.MerossErrorUnsupported(
38
+ `Invalid method name format: ${methodName}. Expected format: "feature.action" (e.g., "toggle.set")`,
39
+ methodName,
40
+ 'Method name must be in format: feature.action'
41
+ );
12
42
  }
13
43
 
14
- if (typeof device[methodName] !== 'function') {
15
- throw new Error(`Control method not available: ${methodName}`);
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
+ );
16
53
  }
17
54
 
18
- // All methods now use unified options pattern, so we can call directly with params
19
- return await device[methodName](params);
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);
20
64
  }
21
65
 
22
66
  module.exports = { executeControlCommand };
23
-
@@ -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.defaultTransportMode;
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
- console.log(chalk.red(`\n✗ Error: ${error.message}`));
174
- if (error.stack && process.env.MEROSS_VERBOSE) {
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([{
@@ -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
- * Routes to feature-specific handlers or falls back to generic collection.
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 'setThermostatMode':
27
+ case 'thermostat.set':
21
28
  return await collectThermostatModeParams(methodMetadata, device);
22
29
 
23
- case 'setLightColor':
30
+ case 'light.set':
24
31
  return await collectSetLightColorParams(methodMetadata, device);
25
32
 
26
- case 'setTimerX':
33
+ case 'timer.set':
27
34
  return await collectSetTimerXParams(methodMetadata, device);
28
35
 
29
- case 'deleteTimerX': {
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 'setTriggerX':
44
+ case 'trigger.set':
39
45
  return await collectSetTriggerXParams(methodMetadata, device);
40
46
 
41
- case 'deleteTriggerX': {
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 an RGB color value.
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 a channel question for inquirer.
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 question for inquirer.
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(onoffParam) {
49
+ function _buildOnOffQuestion(onParam) {
41
50
  return {
42
51
  type: 'list',
43
- name: 'onoff',
44
- message: onoffParam.label || 'Turn On/Off',
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 RGB question for inquirer.
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 a temperature question for inquirer.
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 a luminance question for inquirer.
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 a gradual transition question for inquirer.
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 setLightColor method, filtering options based on device capabilities.
122
- * Only shows RGB, temperature, and luminance options if the device actually supports them.
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
- // Check device capabilities
133
- const supportsRgb = typeof device.getSupportsRgb === 'function' && device.getSupportsRgb(0);
134
- const supportsTemperature = typeof device.getSupportsTemperature === 'function' && device.getSupportsTemperature(0);
135
- const supportsLuminance = typeof device.getSupportsLuminance === 'function' && device.getSupportsLuminance(0);
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
- // On/Off parameter (always available, but optional)
144
- const onoffParam = methodMetadata.params.find(p => p.name === 'onoff');
145
- if (onoffParam) {
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 setThermostatMode with interactive prompts.
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 (typeof device.getThermostatMode === 'function') {
22
+ if (device.thermostat) {
17
23
  console.log(chalk.dim('Fetching current thermostat state...'));
18
- const response = await device.getThermostatMode({ channel });
19
- if (response && response.mode && Array.isArray(response.mode) && response.mode.length > 0) {
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 (currentState.mode !== undefined) {
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[currentState.mode] || `Mode ${currentState.mode}`;
35
+ const modeName = modeNames[thermostatState.mode] || `Mode ${thermostatState.mode}`;
31
36
  console.log(chalk.dim(` Mode: ${modeName}`));
32
37
  }
33
- if (currentState.onoff !== undefined) {
34
- console.log(chalk.dim(` Power: ${currentState.onoff ? 'On' : 'Off'}`));
38
+ if (thermostatState.isOn !== undefined) {
39
+ console.log(chalk.dim(` Power: ${thermostatState.isOn ? 'On' : 'Off'}`));
35
40
  }
36
- if (currentState.heatTemp !== undefined) {
37
- console.log(chalk.dim(` Heat Temp: ${currentState.heatTemp / 10}°C`));
41
+ if (thermostatState.heatTemperatureCelsius !== undefined) {
42
+ console.log(chalk.dim(` Heat Temp: ${thermostatState.heatTemperatureCelsius.toFixed(1)}°C`));
38
43
  }
39
- if (currentState.coolTemp !== undefined) {
40
- console.log(chalk.dim(` Cool Temp: ${currentState.coolTemp / 10}°C`));
44
+ if (thermostatState.coolTemperatureCelsius !== undefined) {
45
+ console.log(chalk.dim(` Cool Temp: ${thermostatState.coolTemperatureCelsius.toFixed(1)}°C`));
41
46
  }
42
- if (currentState.ecoTemp !== undefined) {
43
- console.log(chalk.dim(` Eco Temp: ${currentState.ecoTemp / 10}°C`));
47
+ if (thermostatState.ecoTemperatureCelsius !== undefined) {
48
+ console.log(chalk.dim(` Eco Temp: ${thermostatState.ecoTemperatureCelsius.toFixed(1)}°C`));
44
49
  }
45
- if (currentState.manualTemp !== undefined) {
46
- console.log(chalk.dim(` Manual Temp: ${currentState.manualTemp / 10}°C`));
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
- // Failed to fetch, continue without current state
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 with interactive prompts.
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.getTimerX === 'function') {
24
+ if (device.timer && typeof device.timer.get === 'function') {
19
25
  console.log(chalk.dim('Fetching existing timers...'));
20
- const response = await device.getTimerX({ channel });
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
- // Failed to fetch, continue
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.getSystemTime === 'function') {
84
- const timeResponse = await device.getSystemTime();
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
- // Failed to get device time
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;
@@ -166,9 +170,9 @@ async function collectDeleteTimerXParams(methodMetadata, device) {
166
170
  const channel = methodMetadata.params.find(p => p.name === 'channel')?.default || 0;
167
171
 
168
172
  try {
169
- if (typeof device.getTimerX === 'function') {
173
+ if (device.timer && typeof device.timer.get === 'function') {
170
174
  console.log(chalk.dim('Fetching existing timers...'));
171
- const response = await device.getTimerX({ channel });
175
+ const response = await device.timer.get({ channel });
172
176
  if (response && response.timerx && Array.isArray(response.timerx) && response.timerx.length > 0) {
173
177
  const items = response.timerx;
174
178
  console.log(chalk.cyan(`\nExisting Timers (Channel ${channel}):`));
@@ -184,7 +188,6 @@ async function collectDeleteTimerXParams(methodMetadata, device) {
184
188
  });
185
189
  console.log();
186
190
 
187
- // Allow selection from list
188
191
  const choices = items.map(item => {
189
192
  const timeMinutes = item.time || 0;
190
193
  const hours = Math.floor(timeMinutes / 60);
@@ -232,10 +235,10 @@ async function collectDeleteTimerXParams(methodMetadata, device) {
232
235
  }
233
236
  }
234
237
  } catch (e) {
235
- // Failed to fetch, continue with generic collection
238
+ // Fall back to generic collection if fetch fails
236
239
  }
237
240
 
238
- return null; // Return null to fall back to generic collection
241
+ return null;
239
242
  }
240
243
 
241
244
  module.exports = { collectSetTimerXParams, collectDeleteTimerXParams };