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.
Files changed (42) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +24 -5
  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 +38 -43
  8. package/cli/commands/control/params/trigger.js +43 -40
  9. package/cli/commands/info.js +218 -17
  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 +9 -52
  40. package/cli/tests/test-toggle.js +49 -173
  41. package/cli/tests/test-trigger.js +8 -51
  42. 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.4.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> 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,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
- 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;
@@ -159,18 +163,23 @@ async function collectSetTimerXParams(methodMetadata, device) {
159
163
  }
160
164
 
161
165
  /**
162
- * Collects parameters for deleteTimerX with interactive prompts.
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.getTimerX === 'function') {
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.getTimerX({ channel });
172
- if (response && response.timerx && Array.isArray(response.timerx) && response.timerx.length > 0) {
173
- const items = response.timerx;
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
- if (selected.id === '__manual__') {
214
- const manualAnswer = await inquirer.prompt([{
215
- type: 'input',
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
- // Failed to fetch, continue with generic collection
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
- return null; // Return null to fall back to generic collection
232
+ // Fallback if timer feature is not available
233
+ return null;
239
234
  }
240
235
 
241
236
  module.exports = { collectSetTimerXParams, collectDeleteTimerXParams };