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
@@ -1,102 +1,67 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Control method registry for device control testing.
5
- * Maps control method names to metadata including parameter schemas and categories.
4
+ * Control method registry for device control.
5
+ * Uses feature-based API structure: "feature.action" (e.g., "toggle.set", "light.set")
6
6
  */
7
7
 
8
8
  /**
9
9
  * Mapping from control method names to required ability namespaces.
10
- * This is used to filter methods based on what the device actually supports.
10
+ * Method names are in format: "feature.action"
11
11
  */
12
12
  const METHOD_TO_NAMESPACE_MAP = {
13
13
  // Power Control
14
- setToggle: ['Appliance.Control.Toggle'],
15
- setToggleX: ['Appliance.Control.ToggleX'],
14
+ 'toggle.set': ['Appliance.Control.ToggleX', 'Appliance.Control.Toggle'],
16
15
 
17
16
  // Light Control
18
- setLight: ['Appliance.Control.Light'],
19
- setLightColor: ['Appliance.Control.Light'],
20
- setDiffuserLight: ['Appliance.Control.Diffuser.Light'],
17
+ 'light.set': ['Appliance.Control.Light'],
18
+ 'diffuser.setLight': ['Appliance.Control.Diffuser.Light'],
21
19
 
22
20
  // Climate Control
23
- setThermostatMode: ['Appliance.Control.Thermostat.Mode'],
24
- setThermostatModeB: ['Appliance.Control.Thermostat.ModeB'],
25
- setSpray: ['Appliance.Control.Spray'],
26
- setDiffuserSpray: ['Appliance.Control.Diffuser.Spray'],
21
+ 'thermostat.set': ['Appliance.Control.Thermostat.Mode', 'Appliance.Control.Thermostat.ModeB'],
22
+ 'spray.set': ['Appliance.Control.Spray'],
23
+ 'diffuser.setSpray': ['Appliance.Control.Diffuser.Spray'],
27
24
 
28
25
  // Cover Control
29
- setGarageDoor: ['Appliance.GarageDoor.State'],
30
- openGarageDoor: ['Appliance.GarageDoor.State'],
31
- closeGarageDoor: ['Appliance.GarageDoor.State'],
32
- setGarageDoorConfig: ['Appliance.GarageDoor.Config', 'Appliance.GarageDoor.MultipleConfig'],
33
- setRollerShutterPosition: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
34
- setRollerShutterUp: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
35
- setRollerShutterDown: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
36
- setRollerShutterStop: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
37
- openRollerShutter: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
38
- closeRollerShutter: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
39
- stopRollerShutter: ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
40
- setRollerShutterConfig: ['Appliance.RollerShutter.Config'],
41
-
42
- // Configuration
43
- setChildLock: ['Appliance.Control.PhysicalLock'],
44
- setSystemLedMode: ['Appliance.System.LedMode'],
45
- setScreenBrightness: ['Appliance.Control.Screen.Brightness'],
46
- setTempUnit: ['Appliance.Control.TempUnit'],
47
- setDNDMode: ['Appliance.System.DNDMode'],
48
- setConfigOverTemp: ['Appliance.Config.OverTemp'],
49
- setPresenceConfig: ['Appliance.Control.Presence.Config'],
50
- setPresenceStudy: ['Appliance.Control.Presence.Study'],
26
+ 'garage.set': ['Appliance.GarageDoor.State'],
27
+ 'rollerShutter.setPosition': ['Appliance.RollerShutter.Position', 'Appliance.RollerShutter.State'],
51
28
 
52
29
  // Timer and Trigger
53
- setTimerX: ['Appliance.Control.TimerX'],
54
- deleteTimerX: ['Appliance.Control.TimerX'],
55
- setTriggerX: ['Appliance.Control.TriggerX'],
56
- deleteTriggerX: ['Appliance.Control.TriggerX']
30
+ 'timer.set': ['Appliance.Control.TimerX'],
31
+ 'timer.delete': ['Appliance.Control.TimerX'],
32
+ 'trigger.set': ['Appliance.Control.TriggerX'],
33
+ 'trigger.delete': ['Appliance.Control.TriggerX'],
34
+
35
+ // Configuration
36
+ 'childLock.set': ['Appliance.Control.PhysicalLock'],
37
+ 'system.setLedMode': ['Appliance.System.LedMode'],
38
+ 'screen.setBrightness': ['Appliance.Control.Screen.Brightness'],
39
+ 'tempUnit.set': ['Appliance.Control.TempUnit'],
40
+ 'dnd.set': ['Appliance.System.DNDMode'],
41
+ 'config.setOverTemp': ['Appliance.Config.OverTemp'],
42
+ 'presence.setConfig': ['Appliance.Control.Presence.Config'],
43
+ 'presence.setStudy': ['Appliance.Control.Presence.Study']
57
44
  };
58
45
 
59
46
  /**
60
47
  * Control method metadata registry.
61
- * Each entry defines:
62
- * - name: Human-readable name
63
- * - category: Feature category for grouping
64
- * - params: Parameter schema for input collection
65
- * - description: Brief description
48
+ * Method names use feature-based format: "feature.action"
66
49
  */
67
50
  const CONTROL_METHOD_REGISTRY = {
68
51
  // Power Control
69
- setToggle: {
52
+ 'toggle.set': {
70
53
  name: 'Toggle (On/Off)',
71
54
  category: 'Power Control',
72
- description: 'Turn device on or off',
73
- params: [
74
- {
75
- name: 'onoff',
76
- type: 'boolean',
77
- label: 'State',
78
- choices: [
79
- { name: 'On', value: true },
80
- { name: 'Off', value: false }
81
- ],
82
- required: true
83
- }
84
- ]
85
- },
86
- setToggleX: {
87
- name: 'Toggle Channel (On/Off)',
88
- category: 'Power Control',
89
55
  description: 'Turn device channel on or off',
90
56
  params: [
91
57
  {
92
58
  name: 'channel',
93
59
  type: 'number',
94
60
  label: 'Channel',
95
- required: true,
96
61
  default: 0
97
62
  },
98
63
  {
99
- name: 'onoff',
64
+ name: 'on',
100
65
  type: 'boolean',
101
66
  label: 'State',
102
67
  choices: [
@@ -109,30 +74,10 @@ const CONTROL_METHOD_REGISTRY = {
109
74
  },
110
75
 
111
76
  // Light Control
112
- setLight: {
77
+ 'light.set': {
113
78
  name: 'Light Control',
114
79
  category: 'Light Control',
115
- description: 'Control light settings (advanced)',
116
- params: [
117
- {
118
- name: 'light',
119
- type: 'object',
120
- label: 'Light Configuration',
121
- required: true,
122
- properties: [
123
- { name: 'channel', type: 'number', default: 0 },
124
- { name: 'onoff', type: 'number', default: 1 },
125
- { name: 'rgb', type: 'number' },
126
- { name: 'luminance', type: 'number', min: 0, max: 100 },
127
- { name: 'temperature', type: 'number', min: 0, max: 100 }
128
- ]
129
- }
130
- ]
131
- },
132
- setLightColor: {
133
- name: 'Light Color & Brightness',
134
- category: 'Light Control',
135
- description: 'Set light color, brightness, and temperature',
80
+ description: 'Set light color, brightness, temperature, and on/off state',
136
81
  params: [
137
82
  {
138
83
  name: 'channel',
@@ -141,7 +86,7 @@ const CONTROL_METHOD_REGISTRY = {
141
86
  default: 0
142
87
  },
143
88
  {
144
- name: 'onoff',
89
+ name: 'on',
145
90
  type: 'boolean',
146
91
  label: 'Turn On/Off',
147
92
  required: false
@@ -176,31 +121,45 @@ const CONTROL_METHOD_REGISTRY = {
176
121
  }
177
122
  ]
178
123
  },
179
- setDiffuserLight: {
124
+ 'diffuser.setLight': {
180
125
  name: 'Diffuser Light',
181
126
  category: 'Light Control',
182
127
  description: 'Control diffuser light settings',
183
128
  params: [
184
129
  {
185
- name: 'light',
186
- type: 'object',
187
- label: 'Light Configuration',
188
- required: true,
189
- properties: [
190
- { name: 'channel', type: 'number', default: 0 },
191
- { name: 'onoff', type: 'number', default: 1 },
192
- { name: 'rgb', type: 'number' },
193
- { name: 'luminance', type: 'number', min: 0, max: 100 }
194
- ]
130
+ name: 'channel',
131
+ type: 'number',
132
+ label: 'Channel',
133
+ default: 0
134
+ },
135
+ {
136
+ name: 'on',
137
+ type: 'boolean',
138
+ label: 'State',
139
+ required: false
140
+ },
141
+ {
142
+ name: 'rgb',
143
+ type: 'rgb',
144
+ label: 'RGB Color (r,g,b)',
145
+ required: false
146
+ },
147
+ {
148
+ name: 'luminance',
149
+ type: 'number',
150
+ label: 'Brightness (0-100)',
151
+ min: 0,
152
+ max: 100,
153
+ required: false
195
154
  }
196
155
  ]
197
156
  },
198
157
 
199
158
  // Climate Control
200
- setThermostatMode: {
201
- name: 'Thermostat Mode',
159
+ 'thermostat.set': {
160
+ name: 'Thermostat Control',
202
161
  category: 'Climate Control',
203
- description: 'Set thermostat mode and temperature',
162
+ description: 'Set thermostat mode, temperature, and on/off state',
204
163
  params: [
205
164
  {
206
165
  name: 'channel',
@@ -250,29 +209,17 @@ const CONTROL_METHOD_REGISTRY = {
250
209
  type: 'number',
251
210
  label: 'Manual Temperature (°C)',
252
211
  required: false
253
- }
254
- ]
255
- },
256
- setThermostatModeB: {
257
- name: 'Thermostat Mode B',
258
- category: 'Climate Control',
259
- description: 'Set thermostat mode B (advanced)',
260
- params: [
261
- {
262
- name: 'channel',
263
- type: 'number',
264
- label: 'Channel',
265
- default: 0
266
212
  },
267
213
  {
268
- name: 'modeData',
269
- type: 'object',
270
- label: 'Mode Data',
271
- required: true
214
+ name: 'partialUpdate',
215
+ type: 'boolean',
216
+ label: 'Partial Update',
217
+ required: false,
218
+ default: false
272
219
  }
273
220
  ]
274
221
  },
275
- setSpray: {
222
+ 'spray.set': {
276
223
  name: 'Spray/Humidifier',
277
224
  category: 'Climate Control',
278
225
  description: 'Control spray/humidifier mode',
@@ -296,7 +243,7 @@ const CONTROL_METHOD_REGISTRY = {
296
243
  }
297
244
  ]
298
245
  },
299
- setDiffuserSpray: {
246
+ 'diffuser.setSpray': {
300
247
  name: 'Diffuser Spray',
301
248
  category: 'Climate Control',
302
249
  description: 'Control diffuser spray mode',
@@ -322,7 +269,7 @@ const CONTROL_METHOD_REGISTRY = {
322
269
  },
323
270
 
324
271
  // Cover Control
325
- setGarageDoor: {
272
+ 'garage.set': {
326
273
  name: 'Garage Door',
327
274
  category: 'Cover Control',
328
275
  description: 'Open or close garage door',
@@ -334,18 +281,14 @@ const CONTROL_METHOD_REGISTRY = {
334
281
  default: 0
335
282
  },
336
283
  {
337
- name: 'open',
338
- type: 'boolean',
339
- label: 'Action',
340
- choices: [
341
- { name: 'Open', value: true },
342
- { name: 'Close', value: false }
343
- ],
284
+ name: 'onoff',
285
+ type: 'number',
286
+ label: 'Action (0=close, 1=open)',
344
287
  required: true
345
288
  }
346
289
  ]
347
290
  },
348
- setRollerShutterPosition: {
291
+ 'rollerShutter.setPosition': {
349
292
  name: 'Roller Shutter Position',
350
293
  category: 'Cover Control',
351
294
  description: 'Set roller shutter position (0-100, -1 to stop)',
@@ -366,63 +309,56 @@ const CONTROL_METHOD_REGISTRY = {
366
309
  }
367
310
  ]
368
311
  },
369
- setRollerShutterUp: {
370
- name: 'Roller Shutter Open',
371
- category: 'Cover Control',
372
- description: 'Open roller shutter (position 100)',
312
+
313
+ // Timer and Trigger
314
+ 'timer.set': {
315
+ name: 'Timer Control',
316
+ category: 'Automation',
317
+ description: 'Create or update a timer',
373
318
  params: [
374
319
  {
375
320
  name: 'channel',
376
321
  type: 'number',
377
322
  label: 'Channel',
378
323
  default: 0
379
- }
380
- ]
381
- },
382
- setRollerShutterDown: {
383
- name: 'Roller Shutter Close',
384
- category: 'Cover Control',
385
- description: 'Close roller shutter (position 0)',
386
- params: [
324
+ },
387
325
  {
388
- name: 'channel',
326
+ name: 'id',
327
+ type: 'string',
328
+ label: 'Timer ID (for updates)',
329
+ required: false
330
+ },
331
+ {
332
+ name: 'onoff',
389
333
  type: 'number',
390
- label: 'Channel',
391
- default: 0
392
- }
393
- ]
394
- },
395
- setRollerShutterStop: {
396
- name: 'Roller Shutter Stop',
397
- category: 'Cover Control',
398
- description: 'Stop roller shutter movement',
399
- params: [
334
+ label: 'Action (0=off, 1=on)',
335
+ required: true
336
+ },
400
337
  {
401
- name: 'channel',
338
+ name: 'type',
402
339
  type: 'number',
403
- label: 'Channel',
404
- default: 0
405
- }
406
- ]
407
- },
408
- openRollerShutter: {
409
- name: 'Roller Shutter Open',
410
- category: 'Cover Control',
411
- description: 'Open roller shutter (convenience method)',
412
- params: [
340
+ label: 'Timer Type',
341
+ required: true
342
+ },
413
343
  {
414
- name: 'channel',
344
+ name: 'time',
415
345
  type: 'number',
416
- label: 'Channel',
417
- default: 0
346
+ label: 'Time Value',
347
+ required: true
418
348
  }
419
349
  ]
420
350
  },
421
- closeRollerShutter: {
422
- name: 'Roller Shutter Close',
423
- category: 'Cover Control',
424
- description: 'Close roller shutter (convenience method)',
351
+ 'timer.delete': {
352
+ name: 'Delete Timer',
353
+ category: 'Automation',
354
+ description: 'Delete a timer',
425
355
  params: [
356
+ {
357
+ name: 'timerId',
358
+ type: 'string',
359
+ label: 'Timer ID',
360
+ required: true
361
+ },
426
362
  {
427
363
  name: 'channel',
428
364
  type: 'number',
@@ -431,30 +367,34 @@ const CONTROL_METHOD_REGISTRY = {
431
367
  }
432
368
  ]
433
369
  },
434
- stopRollerShutter: {
435
- name: 'Roller Shutter Stop',
436
- category: 'Cover Control',
437
- description: 'Stop roller shutter (convenience method)',
370
+ 'trigger.set': {
371
+ name: 'Trigger Control',
372
+ category: 'Automation',
373
+ description: 'Create or update a trigger',
438
374
  params: [
439
375
  {
440
376
  name: 'channel',
441
377
  type: 'number',
442
378
  label: 'Channel',
443
379
  default: 0
380
+ },
381
+ {
382
+ name: 'triggerx',
383
+ type: 'object',
384
+ label: 'Trigger Configuration',
385
+ required: true
444
386
  }
445
387
  ]
446
388
  },
447
- setRollerShutterPosition: {
448
- name: 'Roller Shutter Set Position',
449
- category: 'Cover Control',
450
- description: 'Set roller shutter to specific position (convenience method)',
389
+ 'trigger.delete': {
390
+ name: 'Delete Trigger',
391
+ category: 'Automation',
392
+ description: 'Delete a trigger',
451
393
  params: [
452
394
  {
453
- name: 'position',
454
- type: 'number',
455
- label: 'Position (0-100)',
456
- min: 0,
457
- max: 100,
395
+ name: 'triggerId',
396
+ type: 'string',
397
+ label: 'Trigger ID',
458
398
  required: true
459
399
  },
460
400
  {
@@ -465,77 +405,28 @@ const CONTROL_METHOD_REGISTRY = {
465
405
  }
466
406
  ]
467
407
  },
468
- setRollerShutterConfig: {
469
- name: 'Roller Shutter Configuration',
408
+
409
+ // Configuration
410
+ 'childLock.set': {
411
+ name: 'Child Lock',
470
412
  category: 'Configuration',
471
- description: 'Configure roller shutter settings',
472
- params: [
473
- {
474
- name: 'config',
475
- type: 'object',
476
- label: 'Configuration Object',
477
- required: true
478
- }
479
- ]
480
- },
481
- openGarageDoor: {
482
- name: 'Garage Door Open',
483
- category: 'Cover Control',
484
- description: 'Open garage door (convenience method)',
413
+ description: 'Enable or disable child lock',
485
414
  params: [
486
415
  {
487
416
  name: 'channel',
488
417
  type: 'number',
489
418
  label: 'Channel',
490
419
  default: 0
491
- }
492
- ]
493
- },
494
- closeGarageDoor: {
495
- name: 'Garage Door Close',
496
- category: 'Cover Control',
497
- description: 'Close garage door (convenience method)',
498
- params: [
420
+ },
499
421
  {
500
- name: 'channel',
422
+ name: 'lock',
501
423
  type: 'number',
502
- label: 'Channel',
503
- default: 0
504
- }
505
- ]
506
- },
507
- setGarageDoorConfig: {
508
- name: 'Garage Door Configuration',
509
- category: 'Configuration',
510
- description: 'Configure garage door settings',
511
- params: [
512
- {
513
- name: 'configData',
514
- type: 'object',
515
- label: 'Configuration Object',
424
+ label: 'Lock (0=unlock, 1=lock)',
516
425
  required: true
517
426
  }
518
427
  ]
519
428
  },
520
-
521
- // Configuration
522
- setChildLock: {
523
- name: 'Child Lock',
524
- category: 'Configuration',
525
- description: 'Enable or disable child lock',
526
- params: [
527
- {
528
- name: 'lockData',
529
- type: 'object',
530
- label: 'Lock Configuration',
531
- required: true,
532
- properties: [
533
- { name: 'lock', type: 'number', label: 'Lock (0=unlock, 1=lock)', required: true }
534
- ]
535
- }
536
- ]
537
- },
538
- setSystemLedMode: {
429
+ 'system.setLedMode': {
539
430
  name: 'LED Indicator Mode',
540
431
  category: 'Configuration',
541
432
  description: 'Control LED indicator mode',
@@ -548,56 +439,60 @@ const CONTROL_METHOD_REGISTRY = {
548
439
  }
549
440
  ]
550
441
  },
551
- setScreenBrightness: {
442
+ 'screen.setBrightness': {
552
443
  name: 'Screen Brightness',
553
444
  category: 'Configuration',
554
445
  description: 'Set device screen brightness',
555
446
  params: [
556
447
  {
557
- name: 'brightnessData',
558
- type: 'object',
559
- label: 'Brightness Configuration',
560
- required: true,
561
- properties: [
562
- { name: 'brightness', type: 'number', min: 0, max: 100, required: true }
563
- ]
448
+ name: 'channel',
449
+ type: 'number',
450
+ label: 'Channel',
451
+ default: 0
452
+ },
453
+ {
454
+ name: 'brightness',
455
+ type: 'number',
456
+ label: 'Brightness (0-100)',
457
+ min: 0,
458
+ max: 100,
459
+ required: true
564
460
  }
565
461
  ]
566
462
  },
567
- setTempUnit: {
463
+ 'tempUnit.set': {
568
464
  name: 'Temperature Unit',
569
465
  category: 'Configuration',
570
466
  description: 'Set temperature unit (Celsius/Fahrenheit)',
571
467
  params: [
572
468
  {
573
- name: 'tempUnitData',
574
- type: 'object',
575
- label: 'Temperature Unit Configuration',
576
- required: true,
577
- properties: [
578
- { name: 'unit', type: 'number', label: 'Unit (0=Celsius, 1=Fahrenheit)', required: true }
579
- ]
469
+ name: 'channel',
470
+ type: 'number',
471
+ label: 'Channel',
472
+ default: 0
473
+ },
474
+ {
475
+ name: 'unit',
476
+ type: 'number',
477
+ label: 'Unit (0=Celsius, 1=Fahrenheit)',
478
+ required: true
580
479
  }
581
480
  ]
582
481
  },
583
- setDNDMode: {
482
+ 'dnd.set': {
584
483
  name: 'Do Not Disturb Mode',
585
484
  category: 'Configuration',
586
- description: 'Enable or disable Do Not Disturb mode (turns off LED indicator)',
485
+ description: 'Enable or disable Do Not Disturb mode',
587
486
  params: [
588
487
  {
589
488
  name: 'mode',
590
- type: 'boolean',
591
- label: 'DND Mode',
592
- choices: [
593
- { name: 'Enable DND', value: true },
594
- { name: 'Disable DND', value: false }
595
- ],
489
+ type: 'number',
490
+ label: 'DND Mode (0=disabled, 1=enabled)',
596
491
  required: true
597
492
  }
598
493
  ]
599
494
  },
600
- setConfigOverTemp: {
495
+ 'config.setOverTemp': {
601
496
  name: 'Over-Temperature Protection',
602
497
  category: 'Configuration',
603
498
  description: 'Enable or disable over-temperature protection',
@@ -614,122 +509,56 @@ const CONTROL_METHOD_REGISTRY = {
614
509
  }
615
510
  ]
616
511
  },
617
- setPresenceConfig: {
512
+ 'presence.setConfig': {
618
513
  name: 'Presence Sensor Configuration',
619
514
  category: 'Configuration',
620
- description: 'Configure presence sensor settings (mode, sensitivity, distance, etc.)',
515
+ description: 'Configure presence sensor settings',
621
516
  params: [
517
+ {
518
+ name: 'channel',
519
+ type: 'number',
520
+ label: 'Channel',
521
+ default: 0
522
+ },
622
523
  {
623
524
  name: 'configData',
624
525
  type: 'object',
625
526
  label: 'Configuration Object',
626
- required: true,
627
- properties: [
628
- { name: 'channel', type: 'number', label: 'Channel', default: 0 },
629
- { name: 'mode', type: 'object', label: 'Mode Configuration' },
630
- { name: 'noBodyTime', type: 'object', label: 'No Body Time Configuration' },
631
- { name: 'distance', type: 'object', label: 'Distance Configuration' },
632
- { name: 'sensitivity', type: 'object', label: 'Sensitivity Configuration' },
633
- { name: 'mthx', type: 'object', label: 'Motion Threshold Configuration' }
634
- ]
527
+ required: true
635
528
  }
636
529
  ]
637
530
  },
638
- setPresenceStudy: {
531
+ 'presence.setStudy': {
639
532
  name: 'Presence Sensor Study/Calibration',
640
533
  category: 'Configuration',
641
534
  description: 'Start or stop presence sensor study/calibration mode',
642
535
  params: [
643
- {
644
- name: 'studyData',
645
- type: 'object',
646
- label: 'Study Data Object',
647
- required: true,
648
- properties: [
649
- { name: 'channel', type: 'number', label: 'Channel', default: 0 },
650
- { name: 'value', type: 'number', label: 'Study Mode Value (typically 1-3)' },
651
- { name: 'status', type: 'number', label: 'Status (0=stop, 1=start)', required: true }
652
- ]
653
- }
654
- ]
655
- },
656
- setTimerX: {
657
- name: 'Timer Control',
658
- category: 'Automation',
659
- description: 'Create or update a timer',
660
- params: [
661
- {
662
- name: 'timerx',
663
- type: 'object',
664
- label: 'Timer Configuration',
665
- required: true,
666
- properties: [
667
- { name: 'id', type: 'string', label: 'Timer ID (for updates)' },
668
- { name: 'channel', type: 'number', default: 0 },
669
- { name: 'onoff', type: 'number', label: 'Action (0=off, 1=on)', required: true },
670
- { name: 'type', type: 'number', label: 'Timer Type', required: true },
671
- { name: 'time', type: 'number', label: 'Time Value', required: true }
672
- ]
673
- }
674
- ]
675
- },
676
- deleteTimerX: {
677
- name: 'Delete Timer',
678
- category: 'Automation',
679
- description: 'Delete a timer',
680
- params: [
681
- {
682
- name: 'timerId',
683
- type: 'string',
684
- label: 'Timer ID',
685
- required: true
686
- },
687
536
  {
688
537
  name: 'channel',
689
538
  type: 'number',
690
539
  label: 'Channel',
691
540
  default: 0
692
- }
693
- ]
694
- },
695
- setTriggerX: {
696
- name: 'Trigger Control',
697
- category: 'Automation',
698
- description: 'Create or update a trigger',
699
- params: [
700
- {
701
- name: 'triggerx',
702
- type: 'object',
703
- label: 'Trigger Configuration',
704
- required: true
705
- }
706
- ]
707
- },
708
- deleteTriggerX: {
709
- name: 'Delete Trigger',
710
- category: 'Automation',
711
- description: 'Delete a trigger',
712
- params: [
541
+ },
713
542
  {
714
- name: 'triggerId',
715
- type: 'string',
716
- label: 'Trigger ID',
717
- required: true
543
+ name: 'value',
544
+ type: 'number',
545
+ label: 'Study Mode Value',
546
+ required: false
718
547
  },
719
548
  {
720
- name: 'channel',
549
+ name: 'status',
721
550
  type: 'number',
722
- label: 'Channel',
723
- required: false,
724
- default: 0
551
+ label: 'Status (0=stop, 1=start)',
552
+ required: true
725
553
  }
726
554
  ]
727
555
  }
728
556
  };
729
557
 
730
558
  /**
731
- * Get metadata for a control method.
732
- * @param {string} methodName - Name of the control method
559
+ * Gets metadata for a control method from the registry.
560
+ *
561
+ * @param {string} methodName - Name of the control method (format: "feature.action")
733
562
  * @returns {Object|null} Method metadata or null if not found
734
563
  */
735
564
  function getMethodMetadata(methodName) {
@@ -737,7 +566,8 @@ function getMethodMetadata(methodName) {
737
566
  }
738
567
 
739
568
  /**
740
- * Get all methods grouped by category.
569
+ * Gets all control methods grouped by category for organized display.
570
+ *
741
571
  * @returns {Object} Methods grouped by category
742
572
  */
743
573
  function getMethodsByCategory() {
@@ -756,7 +586,8 @@ function getMethodsByCategory() {
756
586
  }
757
587
 
758
588
  /**
759
- * Check if device supports a required namespace.
589
+ * Checks if a device supports a specific ability namespace.
590
+ *
760
591
  * @param {Object} device - Device instance
761
592
  * @param {string} namespace - Namespace to check
762
593
  * @returns {boolean} True if device supports the namespace
@@ -769,90 +600,65 @@ function deviceSupportsNamespace(device, namespace) {
769
600
  }
770
601
 
771
602
  /**
772
- * Check if device supports any of the required namespaces for a method.
603
+ * Checks if a device supports any of the required namespaces for a method.
604
+ *
605
+ * Some methods accept multiple namespace alternatives (e.g., ToggleX or Toggle),
606
+ * so any match satisfies the requirement.
607
+ *
773
608
  * @param {Object} device - Device instance
774
609
  * @param {Array<string>} namespaces - Array of namespace strings (any match is sufficient)
775
610
  * @returns {boolean} True if device supports at least one namespace
776
611
  */
777
612
  function deviceSupportsAnyNamespace(device, namespaces) {
778
613
  if (!namespaces || namespaces.length === 0) {
779
- return true; // No namespace requirement means always available
614
+ return true;
780
615
  }
781
616
  return namespaces.some(namespace => deviceSupportsNamespace(device, namespace));
782
617
  }
783
618
 
784
619
  /**
785
- * Detect available control methods on a device based on device abilities.
786
- * Only shows methods that are in the registry AND the device supports the required namespaces.
620
+ * Detects available control methods on a device based on device abilities.
621
+ *
622
+ * Validates both namespace requirements and feature availability to determine
623
+ * which methods can be used with a specific device. Uses feature-based API structure.
624
+ *
787
625
  * @param {Object} device - Device instance
788
626
  * @returns {Array} Array of available control methods with metadata
789
627
  */
790
628
  function detectControlMethods(device) {
791
629
  const availableMethods = [];
792
630
 
793
- // Only show methods that are in our registry and device supports
794
- // Iterate through registry instead of device methods to ensure we only show known methods
795
631
  for (const [methodName, metadata] of Object.entries(CONTROL_METHOD_REGISTRY)) {
796
- // Check if method exists on device (check both exact name and common variations)
797
- const deviceMethod = device[methodName];
798
- if (typeof deviceMethod !== 'function') {
799
- // Try alternative naming (e.g., setDNDMode might be on device)
800
- continue; // Method doesn't exist on device, skip
801
- }
802
-
803
- // Check if this method requires specific namespaces
804
632
  const requiredNamespaces = METHOD_TO_NAMESPACE_MAP[methodName];
805
633
 
806
- // If method has namespace requirements, check if device supports them
807
634
  if (requiredNamespaces && requiredNamespaces.length > 0) {
808
635
  if (!deviceSupportsAnyNamespace(device, requiredNamespaces)) {
809
- // Device doesn't support required namespaces, skip this method
810
636
  continue;
811
637
  }
812
- } else {
813
- // Method has no namespace requirement - this shouldn't happen for control methods
814
- // Skip it to be safe, or we could add it but it's better to be strict
638
+ }
639
+
640
+ const parts = methodName.split('.');
641
+ if (parts.length !== 2) {
642
+ continue;
643
+ }
644
+
645
+ const [featureName, action] = parts;
646
+
647
+ const feature = device[featureName];
648
+ if (!feature) {
649
+ continue;
650
+ }
651
+
652
+ if (typeof feature[action] !== 'function') {
815
653
  continue;
816
654
  }
817
655
 
818
- // Method exists, has namespace requirements, and device supports them - add it
819
656
  availableMethods.push({
820
657
  methodName,
821
658
  ...metadata
822
659
  });
823
660
  }
824
661
 
825
- // Also check for any control/set methods on device that might not be in registry
826
- // but have namespace requirements (for future extensibility)
827
- for (const prop in device) {
828
- if ((prop.startsWith('control') || prop.startsWith('set')) && typeof device[prop] === 'function') {
829
- // Skip if already in our list
830
- if (availableMethods.some(m => m.methodName === prop)) {
831
- continue;
832
- }
833
-
834
- // Check if this method has namespace requirements
835
- const requiredNamespaces = METHOD_TO_NAMESPACE_MAP[prop];
836
-
837
- // Only include if it has namespace requirements AND device supports them
838
- if (requiredNamespaces && requiredNamespaces.length > 0) {
839
- if (deviceSupportsAnyNamespace(device, requiredNamespaces)) {
840
- // Unknown method but device supports it - add with basic info
841
- const displayName = prop.replace(/^(control|set)/, '').replace(/([A-Z])/g, ' $1').trim();
842
- availableMethods.push({
843
- methodName: prop,
844
- name: displayName,
845
- category: 'Other',
846
- description: 'Control method',
847
- params: []
848
- });
849
- }
850
- }
851
- // If no namespace requirement, skip it (don't show unknown methods without namespace checks)
852
- }
853
- }
854
-
855
- // Sort by category, then by name
856
662
  availableMethods.sort((a, b) => {
857
663
  if (a.category !== b.category) {
858
664
  return a.category.localeCompare(b.category);
@@ -872,4 +678,3 @@ module.exports = {
872
678
  deviceSupportsNamespace,
873
679
  deviceSupportsAnyNamespace
874
680
  };
875
-