meross-cli 0.4.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 (42) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +28 -2
  3. package/cli/commands/control/execute.js +36 -6
  4. package/cli/commands/control/params/index.js +16 -12
  5. package/cli/commands/control/params/light.js +55 -25
  6. package/cli/commands/control/params/thermostat.js +24 -22
  7. package/cli/commands/control/params/timer.js +18 -15
  8. package/cli/commands/control/params/trigger.js +24 -13
  9. package/cli/commands/info.js +38 -14
  10. package/cli/commands/sniffer/sniffer-menu.js +2 -2
  11. package/cli/commands/status/device-status.js +418 -1292
  12. package/cli/commands/status/hub-status.js +14 -6
  13. package/cli/control-registry.js +211 -406
  14. package/cli/meross-cli.js +1 -1
  15. package/cli/tests/README.md +2 -0
  16. package/cli/tests/test-alarm.js +22 -2
  17. package/cli/tests/test-child-lock.js +40 -10
  18. package/cli/tests/test-config.js +22 -2
  19. package/cli/tests/test-control.js +8 -8
  20. package/cli/tests/test-diffuser.js +7 -7
  21. package/cli/tests/test-dnd.js +87 -66
  22. package/cli/tests/test-electricity.js +37 -33
  23. package/cli/tests/test-encryption.js +13 -13
  24. package/cli/tests/test-garage.js +12 -14
  25. package/cli/tests/test-helper.js +1 -1
  26. package/cli/tests/test-hub-sensors.js +3 -3
  27. package/cli/tests/test-light.js +497 -105
  28. package/cli/tests/test-presence.js +10 -55
  29. package/cli/tests/test-registry.js +7 -1
  30. package/cli/tests/test-roller-shutter.js +78 -90
  31. package/cli/tests/test-screen.js +1 -1
  32. package/cli/tests/test-sensor-history.js +6 -2
  33. package/cli/tests/test-smoke-config.js +24 -4
  34. package/cli/tests/test-spray.js +11 -11
  35. package/cli/tests/test-system.js +375 -0
  36. package/cli/tests/test-temp-unit.js +22 -2
  37. package/cli/tests/test-template.js +61 -73
  38. package/cli/tests/test-thermostat.js +126 -89
  39. package/cli/tests/test-timer.js +8 -51
  40. package/cli/tests/test-toggle.js +49 -173
  41. package/cli/tests/test-trigger.js +7 -50
  42. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ 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
+
8
19
  ## [0.4.0] - 2026-01-19
9
20
 
10
21
  ### 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.4.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,17 @@ 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
+
80
91
  ### [0.4.0] - 2026-01-19
81
92
 
82
93
  #### Changed
@@ -95,6 +106,21 @@ The CLI supports all devices that are supported by the underlying `meross-iot` l
95
106
  <details>
96
107
  <summary>Older</summary>
97
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
+
98
124
  ### [0.3.0] - 2026-01-16
99
125
 
100
126
  #### 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
- if (typeof device[methodName] !== 'function') {
35
+ const parts = methodName.split('.');
36
+ if (parts.length !== 2) {
24
37
  throw new ManagerMeross.MerossErrorUnsupported(
25
- `Control method not available: ${methodName}`,
38
+ `Invalid method name format: ${methodName}. Expected format: "feature.action" (e.g., "toggle.set")`,
26
39
  methodName,
27
- 'Method not supported by this device'
40
+ 'Method name must be in format: feature.action'
28
41
  );
29
42
  }
30
43
 
31
- // All methods now use unified options pattern, so we can call directly with params
32
- return await device[methodName](params);
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
- * 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 };
@@ -6,18 +6,24 @@ const { TriggerType, TriggerUtils } = require('meross-iot');
6
6
  const { durationToSeconds, secondsToDuration } = TriggerUtils;
7
7
 
8
8
  /**
9
- * Collects parameters for setTriggerX with interactive prompts.
9
+ * Collects parameters for setTriggerX interactively.
10
+ *
11
+ * Displays existing triggers for context, then prompts for trigger configuration.
12
+ * Supports multiple trigger types and flexible duration formats.
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 collectSetTriggerXParams(methodMetadata, device) {
12
19
  const params = {};
13
20
  const channel = methodMetadata.params.find(p => p.name === 'triggerx')?.properties?.find(prop => prop.name === 'channel')?.default || 0;
14
21
 
15
- // Show existing triggers
16
22
  let hasTriggers = false;
17
23
  try {
18
- if (typeof device.getTriggerX === 'function') {
24
+ if (device.trigger && typeof device.trigger.get === 'function') {
19
25
  console.log(chalk.dim('Fetching existing triggers...'));
20
- const response = await device.getTriggerX({ channel });
26
+ const response = await device.trigger.get({ channel });
21
27
  if (response && response.triggerx && Array.isArray(response.triggerx) && response.triggerx.length > 0) {
22
28
  hasTriggers = true;
23
29
  console.log(chalk.cyan(`\nExisting Triggers (Channel ${channel}):`));
@@ -32,14 +38,13 @@ async function collectSetTriggerXParams(methodMetadata, device) {
32
38
  }
33
39
  }
34
40
  } catch (e) {
35
- // Failed to fetch, continue
41
+ // Continue without existing triggers if fetch fails
36
42
  }
37
43
 
38
44
  if (!hasTriggers) {
39
45
  console.log(chalk.yellow('No triggers currently set.\n'));
40
46
  }
41
47
 
42
- // Collect trigger configuration
43
48
  const aliasAnswer = await inquirer.prompt([{
44
49
  type: 'input',
45
50
  name: 'alias',
@@ -116,7 +121,6 @@ async function collectSetTriggerXParams(methodMetadata, device) {
116
121
  default: 0
117
122
  }]);
118
123
 
119
- // Pass user-friendly format to API (API handles conversion)
120
124
  params.channel = channel;
121
125
  params.alias = aliasAnswer.alias;
122
126
  params.duration = durationAnswer.duration;
@@ -128,16 +132,24 @@ async function collectSetTriggerXParams(methodMetadata, device) {
128
132
  }
129
133
 
130
134
  /**
131
- * Collects parameters for deleteTriggerX with interactive prompts.
135
+ * Collects parameters for deleteTriggerX interactively.
136
+ *
137
+ * Displays existing triggers and allows selection from a list, or manual ID entry
138
+ * if no triggers are found. Returns null to fall back to generic collection when
139
+ * no triggers are available.
140
+ *
141
+ * @param {Object} methodMetadata - Method metadata from control registry
142
+ * @param {Object} device - Device instance
143
+ * @returns {Promise<Object|null>} Collected parameters object, or null to use generic collection
132
144
  */
133
145
  async function collectDeleteTriggerXParams(methodMetadata, device) {
134
146
  const params = {};
135
147
  const channel = methodMetadata.params.find(p => p.name === 'channel')?.default || 0;
136
148
 
137
149
  try {
138
- if (typeof device.getTriggerX === 'function') {
150
+ if (device.trigger && typeof device.trigger.get === 'function') {
139
151
  console.log(chalk.dim('Fetching existing triggers...'));
140
- const response = await device.getTriggerX({ channel });
152
+ const response = await device.trigger.get({ channel });
141
153
  if (response && response.triggerx && Array.isArray(response.triggerx) && response.triggerx.length > 0) {
142
154
  const items = response.triggerx;
143
155
  console.log(chalk.cyan(`\nExisting Triggers (Channel ${channel}):`));
@@ -150,7 +162,6 @@ async function collectDeleteTriggerXParams(methodMetadata, device) {
150
162
  });
151
163
  console.log();
152
164
 
153
- // Allow selection from list
154
165
  const choices = items.map(item => {
155
166
  const durationSeconds = item.rule?.duration || 0;
156
167
  const durationStr = secondsToDuration(durationSeconds);
@@ -196,10 +207,10 @@ async function collectDeleteTriggerXParams(methodMetadata, device) {
196
207
  }
197
208
  }
198
209
  } catch (e) {
199
- // Failed to fetch, continue with generic collection
210
+ // Fall back to generic collection if fetch fails
200
211
  }
201
212
 
202
- return null; // Return null to fall back to generic collection
213
+ return null;
203
214
  }
204
215
 
205
216
  module.exports = { collectSetTriggerXParams, collectDeleteTriggerXParams };