meross-iot 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,37 @@ 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
- ## [Unreleased]
8
+ ## [0.3.0] - 2026-01-15
9
+
10
+ ### Changed
11
+ - **BREAKING**: Renamed core classes to follow Manager-prefix naming pattern
12
+ - `MerossManager` → `ManagerMeross` (all imports/exports)
13
+ - `SubscriptionManager` → `ManagerSubscription` (all imports/exports)
14
+ - **BREAKING**: Replaced method-based access with property-based access patterns
15
+ - Removed `getSubscriptionManager()` method - use `meross.subscription` property instead
16
+ - Removed wrapper methods - use `meross.devices.*` instead:
17
+ - `getDevice(uuid)` → `meross.devices.get(uuid)`
18
+ - `findDevices(filters)` → `meross.devices.find(filters)`
19
+ - `getAllDevices()` → `meross.devices.list()`
20
+ - **BREAKING**: Unified device lookup API in DeviceRegistry
21
+ - Removed `lookupByUuid()` and `lookupByInternalId()` from public API
22
+ - Added unified `get(identifier)` method that handles both base devices and subdevices:
23
+ - Base devices: `meross.devices.get('device-uuid')`
24
+ - Subdevices: `meross.devices.get({ hubUuid: 'hub-uuid', id: 'subdevice-id' })`
25
+ - **BREAKING**: Renamed DeviceRegistry methods for cleaner API
26
+ - `getAllDevices()` → `list()` (returns all devices)
27
+ - `findDevices(filters)` → `find(filters)` (search/filter devices)
28
+
29
+ ### Added
30
+ - Property access to subscription manager: `meross.subscription` returns `ManagerSubscription` instance
31
+ - Property access to device registry: `meross.devices` returns `DeviceRegistry` instance with full API access
32
+ - Unified `get()` method in DeviceRegistry supporting both base devices and subdevices
33
+ - Constructor option `subscription` for configuring subscription manager during initialization
34
+
35
+ ## [0.2.1] - 2026-01-14
36
+
37
+ ### Fixed
38
+ - Fixed syntax error in `example/device-control.js` - missing closing brace for `deviceInitialized` event handler
9
39
 
10
40
  ## [0.2.0] - 2026-01-14
11
41
 
package/README.md CHANGED
@@ -26,7 +26,7 @@ The library can control devices locally via HTTP or via cloud MQTT server.
26
26
  npm install meross-iot@alpha
27
27
 
28
28
  # Or install specific version
29
- npm install meross-iot@0.1.0
29
+ npm install meross-iot@0.3.0
30
30
  ```
31
31
 
32
32
  ## Usage & Documentation
@@ -36,7 +36,7 @@ Refer to the [example/README.md](example/README.md) for detailed usage instructi
36
36
  If you are really impatient to use this library, refer to the following snippet of code that looks for a device and turns it on/off.
37
37
 
38
38
  ```javascript
39
- const { MerossManager, MerossHttpClient } = require('meross-iot');
39
+ const { ManagerMeross, MerossHttpClient } = require('meross-iot');
40
40
 
41
41
  (async () => {
42
42
  // Create HTTP client using factory method
@@ -46,7 +46,7 @@ const { MerossManager, MerossHttpClient } = require('meross-iot');
46
46
  });
47
47
 
48
48
  // Create manager with HTTP client
49
- const meross = new MerossManager({
49
+ const meross = new ManagerMeross({
50
50
  httpClient: httpClient
51
51
  });
52
52
 
@@ -59,7 +59,7 @@ const { MerossManager, MerossHttpClient } = require('meross-iot');
59
59
  await meross.connect();
60
60
 
61
61
  // Find a device and control it
62
- const devices = meross.getAllDevices();
62
+ const devices = meross.devices.list();
63
63
  if (devices.length > 0) {
64
64
  const device = devices[0];
65
65
 
@@ -75,7 +75,7 @@ The `example/` directory contains focused examples for different use cases:
75
75
  - **`basic-usage.js`** - Simple connection and device discovery
76
76
  - **`device-control.js`** - Controlling switches, lights, and monitoring devices
77
77
  - **`event-handling.js`** - Handling events from devices and the manager
78
- - **`subscription-manager.js`** - Automatic polling and unified update streams with SubscriptionManager
78
+ - **`subscription-manager.js`** - Automatic polling and unified update streams with ManagerSubscription
79
79
  - **`token-reuse.js`** - Saving and reusing authentication tokens
80
80
  - **`statistics.js`** - Enabling and viewing API call statistics
81
81
  - **`error-handling.js`** - Comprehensive error handling and MFA
@@ -120,6 +120,54 @@ Please create an issue on GitHub and include:
120
120
 
121
121
  ## Changelog
122
122
 
123
+ ### [0.3.0] - 2026-01-15
124
+
125
+ #### Changed
126
+ - **BREAKING**: Renamed core classes to follow Manager-prefix naming pattern
127
+ - `MerossManager` → `ManagerMeross` (all imports/exports)
128
+ - `SubscriptionManager` → `ManagerSubscription` (all imports/exports)
129
+ - **BREAKING**: Replaced method-based access with property-based access patterns
130
+ - Removed `getSubscriptionManager()` method - use `meross.subscription` property instead
131
+ - Removed wrapper methods - use `meross.devices.*` instead:
132
+ - `getDevice(uuid)` → `meross.devices.get(uuid)`
133
+ - `findDevices(filters)` → `meross.devices.find(filters)`
134
+ - `getAllDevices()` → `meross.devices.list()`
135
+ - **BREAKING**: Unified device lookup API in DeviceRegistry
136
+ - Removed `lookupByUuid()` and `lookupByInternalId()` from public API
137
+ - Added unified `get(identifier)` method that handles both base devices and subdevices:
138
+ - Base devices: `meross.devices.get('device-uuid')`
139
+ - Subdevices: `meross.devices.get({ hubUuid: 'hub-uuid', id: 'subdevice-id' })`
140
+ - **BREAKING**: Renamed DeviceRegistry methods for cleaner API
141
+ - `getAllDevices()` → `list()` (returns all devices)
142
+ - `findDevices(filters)` → `find(filters)` (search/filter devices)
143
+
144
+ #### Added
145
+ - Property access to subscription manager: `meross.subscription` returns `ManagerSubscription` instance
146
+ - Property access to device registry: `meross.devices` returns `DeviceRegistry` instance with full API access
147
+ - Unified `get()` method in DeviceRegistry supporting both base devices and subdevices
148
+ - Constructor option `subscription` for configuring subscription manager during initialization
149
+
150
+ ### [0.2.1] - 2026-01-14
151
+
152
+ #### Fixed
153
+ - Fixed syntax error in `device-control.js` example - missing closing brace for `deviceInitialized` event handler
154
+
155
+ ### [0.2.0] - 2026-01-14
156
+
157
+ #### Changed
158
+ - **BREAKING**: `SubscriptionManager` now uses EventEmitter pattern instead of callbacks
159
+ - `subscribe(device, config, onUpdate)` → `subscribe(device, config)` (no callback, no return value)
160
+ - `unsubscribe(deviceUuid, subscriptionId)` → `unsubscribe(deviceUuid)` (no subscription ID needed)
161
+ - `subscribeToDeviceList(onUpdate)` → `subscribeToDeviceList()` (no callback, no return value)
162
+ - `unsubscribeFromDeviceList(subscriptionId)` → `unsubscribeFromDeviceList()` (no subscription ID needed)
163
+ - Listen for updates using: `on('deviceUpdate:${deviceUuid}', handler)` and `on('deviceListUpdate', handler)`
164
+ - Use standard EventEmitter methods: `on()`, `once()`, `off()`, `removeAllListeners()`
165
+ - Configuration is now per-device subscription (merged aggressively) rather than per-listener
166
+
167
+ #### Added
168
+ - `subscription-manager.js` example demonstrating EventEmitter-based SubscriptionManager usage
169
+ - Enhanced documentation for SubscriptionManager with JSDoc comments explaining implementation rationale
170
+
123
171
  ### [0.1.0] - 2026-01-10
124
172
 
125
173
  #### Added
package/index.d.ts CHANGED
@@ -436,7 +436,7 @@ declare module 'meross-iot' {
436
436
  }
437
437
 
438
438
  /**
439
- * Configuration options for MerossManager cloud manager.
439
+ * Configuration options for ManagerMeross cloud manager.
440
440
  *
441
441
  * @example
442
442
  * ```typescript
@@ -445,7 +445,7 @@ declare module 'meross-iot' {
445
445
  * password: 'password'
446
446
  * });
447
447
  *
448
- * const manager = new MerossManager({
448
+ * const manager = new ManagerMeross({
449
449
  * httpClient,
450
450
  * transportMode: TransportMode.LAN_HTTP_FIRST,
451
451
  * logger: console.log
@@ -476,7 +476,9 @@ declare module 'meross-iot' {
476
476
  /** Delay in milliseconds between batches (default: 200) */
477
477
  requestBatchDelay?: number,
478
478
  /** Enable/disable request throttling (default: true) */
479
- enableRequestThrottling?: boolean
479
+ enableRequestThrottling?: boolean,
480
+ /** Subscription manager options for automatic polling and data provisioning */
481
+ subscription?: ManagerSubscriptionOptions
480
482
  }
481
483
 
482
484
  export interface LightData {
@@ -1115,18 +1117,18 @@ declare module 'meross-iot' {
1115
1117
  /**
1116
1118
  * Filter options for finding devices.
1117
1119
  *
1118
- * Used with MerossManager.findDevices() to filter the device list.
1120
+ * Used with DeviceRegistry.find() to filter the device list.
1119
1121
  *
1120
1122
  * @example
1121
1123
  * ```typescript
1122
1124
  * // Find online devices
1123
- * const onlineDevices = manager.findDevices({ online_status: 1 });
1125
+ * const onlineDevices = manager.devices.find({ online_status: 1 });
1124
1126
  *
1125
1127
  * // Find specific device types
1126
- * const plugs = manager.findDevices({ device_type: 'mss310' });
1128
+ * const plugs = manager.devices.find({ device_type: 'mss310' });
1127
1129
  *
1128
1130
  * // Find by custom filter function
1129
- * const customDevices = manager.findDevices({
1131
+ * const customDevices = manager.devices.find({
1130
1132
  * device_class: (device) => device.deviceType.startsWith('mss')
1131
1133
  * });
1132
1134
  * ```
@@ -1146,6 +1148,102 @@ declare module 'meross-iot' {
1146
1148
  device_class?: string | ((device: MerossDevice) => boolean) | Array<string | ((device: MerossDevice) => boolean)>;
1147
1149
  }
1148
1150
 
1151
+ /**
1152
+ * Registry for managing Meross devices and subdevices.
1153
+ *
1154
+ * Maintains indexes for efficient device lookups across base devices and subdevices.
1155
+ * Base devices can be looked up by UUID, while internal IDs enable unified lookup
1156
+ * for both base devices and subdevices.
1157
+ *
1158
+ * Internal IDs unify device identification:
1159
+ * - Base devices: `#BASE:{uuid}`
1160
+ * - Subdevices: `#SUB:{hubUuid}:{subdeviceId}`
1161
+ *
1162
+ * @example
1163
+ * ```typescript
1164
+ * // Look up device by UUID
1165
+ * const device = manager.devices.get('device-uuid');
1166
+ *
1167
+ * // Look up subdevice by hub UUID and subdevice ID
1168
+ * const subdevice = manager.devices.get({ hubUuid: 'hub-uuid', id: 'subdevice-id' });
1169
+ *
1170
+ * // Find devices matching filters
1171
+ * const lights = manager.devices.find({ device_class: 'light' });
1172
+ *
1173
+ * // Get all devices
1174
+ * const allDevices = manager.devices.list();
1175
+ * ```
1176
+ */
1177
+ export class DeviceRegistry {
1178
+ /**
1179
+ * Generates an internal ID for a device or subdevice.
1180
+ *
1181
+ * @param uuid - Device UUID (for base devices) or hub UUID (for subdevices)
1182
+ * @param isSubdevice - Whether this is a subdevice
1183
+ * @param hubUuid - Hub UUID (required if isSubdevice is true)
1184
+ * @param subdeviceId - Subdevice ID (required if isSubdevice is true)
1185
+ * @returns Internal ID string
1186
+ */
1187
+ static generateInternalId(uuid: string, isSubdevice?: boolean, hubUuid?: string | null, subdeviceId?: string | null): string
1188
+
1189
+ /**
1190
+ * Registers a device in the registry.
1191
+ *
1192
+ * @param device - Device instance to register
1193
+ */
1194
+ registerDevice(device: MerossDevice | MerossHubDevice | MerossSubDevice): void
1195
+
1196
+ /**
1197
+ * Removes a device from the registry.
1198
+ *
1199
+ * @param device - Device instance to remove
1200
+ */
1201
+ removeDevice(device: MerossDevice | MerossHubDevice | MerossSubDevice): void
1202
+
1203
+ /**
1204
+ * Unified method to get a device by identifier.
1205
+ *
1206
+ * Supports both base devices (by UUID string) and subdevices (by object with hubUuid and id).
1207
+ * Internally converts the identifier to an internal ID format and performs the lookup.
1208
+ *
1209
+ * @param identifier - Device identifier
1210
+ * @returns Device instance, or null if not found
1211
+ * @example
1212
+ * // Get base device by UUID
1213
+ * const device = registry.get('device-uuid');
1214
+ *
1215
+ * @example
1216
+ * // Get subdevice by hub UUID and subdevice ID
1217
+ * const subdevice = registry.get({ hubUuid: 'hub-uuid', id: 'subdevice-id' });
1218
+ */
1219
+ get(identifier: string | { hubUuid: string; id: string }): MerossDevice | MerossHubDevice | MerossSubDevice | null
1220
+
1221
+ /**
1222
+ * Gets all registered devices.
1223
+ *
1224
+ * @returns Array of all registered devices
1225
+ */
1226
+ list(): Array<MerossDevice | MerossHubDevice | MerossSubDevice>
1227
+
1228
+ /**
1229
+ * Finds devices matching the specified filters.
1230
+ *
1231
+ * @param filters - Optional filter criteria
1232
+ * @returns Array of matching devices
1233
+ */
1234
+ find(filters?: FindDevicesFilters): Array<MerossDevice | MerossHubDevice | MerossSubDevice>
1235
+
1236
+ /**
1237
+ * Clears all devices from the registry.
1238
+ */
1239
+ clear(): void
1240
+
1241
+ /**
1242
+ * Gets the total number of devices registered (including subdevices).
1243
+ */
1244
+ readonly size: number
1245
+ }
1246
+
1149
1247
  // Statistics types
1150
1248
  export class HttpRequestSample {
1151
1249
  readonly url: string;
@@ -1335,9 +1433,9 @@ declare module 'meross-iot' {
1335
1433
  isStatsEnabled(): boolean;
1336
1434
  }
1337
1435
 
1338
- export function createDebugUtils(manager: MerossManager): DebugUtils;
1436
+ export function createDebugUtils(manager: ManagerMeross): DebugUtils;
1339
1437
 
1340
- export interface SubscriptionManagerOptions {
1438
+ export interface ManagerSubscriptionOptions {
1341
1439
  /** Logger function for debug output */
1342
1440
  logger?: Logger;
1343
1441
  deviceStateInterval?: number;
@@ -1372,9 +1470,9 @@ declare module 'meross-iot' {
1372
1470
  timestamp: number
1373
1471
  }
1374
1472
 
1375
- export class SubscriptionManager extends EventEmitter {
1376
- constructor(manager: MerossManager, options?: SubscriptionManagerOptions);
1377
- subscribe(device: MerossDevice, config?: SubscriptionManagerOptions): void;
1473
+ export class ManagerSubscription extends EventEmitter {
1474
+ constructor(manager: ManagerMeross, options?: ManagerSubscriptionOptions);
1475
+ subscribe(device: MerossDevice, config?: ManagerSubscriptionOptions): void;
1378
1476
  unsubscribe(deviceUuid: string): void;
1379
1477
  subscribeToDeviceList(): void;
1380
1478
  unsubscribeFromDeviceList(): void;
@@ -1399,19 +1497,19 @@ declare module 'meross-iot' {
1399
1497
  * password: 'password'
1400
1498
  * });
1401
1499
  *
1402
- * const manager = new MerossManager({ httpClient });
1500
+ * const manager = new ManagerMeross({ httpClient });
1403
1501
  * await manager.connect();
1404
1502
  *
1405
1503
  * manager.on('deviceInitialized', (deviceId, deviceDef, device) => {
1406
1504
  * console.log(`Device ${deviceId} initialized`);
1407
1505
  * });
1408
1506
  *
1409
- * const devices = manager.getAllDevices();
1507
+ * const devices = manager.devices.list();
1410
1508
  * ```
1411
1509
  */
1412
- export class MerossManager extends EventEmitter {
1510
+ export class ManagerMeross extends EventEmitter {
1413
1511
  /**
1414
- * Creates a new MerossManager instance.
1512
+ * Creates a new ManagerMeross instance.
1415
1513
  *
1416
1514
  * @param options - Configuration options
1417
1515
  */
@@ -1466,35 +1564,6 @@ declare module 'meross-iot' {
1466
1564
  */
1467
1565
  disconnectAll(force: boolean): void
1468
1566
 
1469
- /**
1470
- * Gets a device by UUID.
1471
- *
1472
- * @param uuid - Device UUID
1473
- * @returns Device instance or null if not found
1474
- */
1475
- getDevice(uuid: string): MerossDevice | null
1476
-
1477
- /**
1478
- * Finds devices matching the specified filters.
1479
- *
1480
- * @param filters - Optional filter criteria
1481
- * @returns Array of matching device instances
1482
- *
1483
- * @example
1484
- * ```typescript
1485
- * const onlineDevices = manager.findDevices({ online_status: 1 });
1486
- * const plugs = manager.findDevices({ device_type: 'mss310' });
1487
- * ```
1488
- */
1489
- findDevices(filters?: FindDevicesFilters): MerossDevice[]
1490
-
1491
- /**
1492
- * Gets all initialized devices.
1493
- *
1494
- * @returns Array of all device instances
1495
- */
1496
- getAllDevices(): MerossDevice[]
1497
-
1498
1567
  /**
1499
1568
  * Gets the current token data.
1500
1569
  *
@@ -1504,6 +1573,12 @@ declare module 'meross-iot' {
1504
1573
 
1505
1574
  /** HTTP client instance */
1506
1575
  readonly httpClient: MerossHttpClient
1576
+
1577
+ /** Subscription manager instance for automatic polling and data provisioning */
1578
+ readonly subscription: ManagerSubscription
1579
+
1580
+ /** Device registry instance for device lookups and queries */
1581
+ readonly devices: DeviceRegistry
1507
1582
  }
1508
1583
 
1509
1584
 
@@ -1684,7 +1759,7 @@ declare module 'meross-iot' {
1684
1759
  *
1685
1760
  * @example
1686
1761
  * ```typescript
1687
- * const device = manager.getDevice('device-uuid');
1762
+ * const device = manager.devices.get('device-uuid');
1688
1763
  *
1689
1764
  * device.on('connected', () => {
1690
1765
  * console.log('Device connected');
@@ -2359,5 +2434,5 @@ declare module 'meross-iot' {
2359
2434
  mqttDomain?: string
2360
2435
  }): MerossError
2361
2436
 
2362
- export default MerossManager
2437
+ export default ManagerMeross
2363
2438
  }
package/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  // Main entry point - re-export everything from new modular structure
4
- const MerossManager = require('./lib/manager');
4
+ const ManagerMeross = require('./lib/manager');
5
5
  const enums = require('./lib/model/enums');
6
6
  const errors = require('./lib/model/exception');
7
7
  const states = require('./lib/model/states');
8
8
  const push = require('./lib/model/push');
9
9
 
10
10
  // Export main class
11
- module.exports = MerossManager;
11
+ module.exports = ManagerMeross;
12
12
 
13
13
  // Export error classes
14
14
  const httpExceptions = require('./lib/model/http/exception');
@@ -126,6 +126,6 @@ module.exports.HubSmokeDetector = subdevice.HubSmokeDetector;
126
126
  const MerossHttpClient = require('./lib/http-api');
127
127
  module.exports.MerossHttpClient = MerossHttpClient;
128
128
 
129
- // Export SubscriptionManager class
130
- const SubscriptionManager = require('./lib/subscription');
131
- module.exports.SubscriptionManager = SubscriptionManager;
129
+ // Export ManagerSubscription class
130
+ const ManagerSubscription = require('./lib/subscription');
131
+ module.exports.ManagerSubscription = ManagerSubscription;
@@ -3,8 +3,8 @@
3
3
  const EventEmitter = require('events');
4
4
  const { OnlineStatus } = require('../model/enums');
5
5
  const { parsePushNotification } = require('../model/push');
6
- // DeviceRegistry is nested in MerossManager but exported separately to avoid circular dependencies
7
- const { MerossManager } = require('../manager');
6
+ // DeviceRegistry is nested in ManagerMeross but exported separately to avoid circular dependencies
7
+ const { ManagerMeross } = require('../manager');
8
8
  const ChannelInfo = require('../model/channel-info');
9
9
  const HttpDeviceInfo = require('../model/http/device');
10
10
  const {
@@ -299,7 +299,7 @@ class MerossDevice extends EventEmitter {
299
299
  throw new UnknownDeviceTypeError('Cannot generate internal ID: device missing UUID');
300
300
  }
301
301
 
302
- this._internalId = MerossManager.DeviceRegistry.generateInternalId(this.uuid);
302
+ this._internalId = ManagerMeross.DeviceRegistry.generateInternalId(this.uuid);
303
303
  return this._internalId;
304
304
  }
305
305
 
@@ -307,7 +307,7 @@ module.exports = {
307
307
  }
308
308
 
309
309
  lightPayload.channel = channel;
310
-
310
+
311
311
  // Handle gradual transition parameter
312
312
  if (gradual !== undefined) {
313
313
  // Allow both boolean and number (0/1) for convenience
@@ -2,8 +2,8 @@
2
2
 
3
3
  const { MerossDevice } = require('./device');
4
4
  const { OnlineStatus, SmokeAlarmStatus } = require('../model/enums');
5
- // DeviceRegistry is nested in MerossManager but exported separately to avoid circular dependencies
6
- const { MerossManager } = require('../manager');
5
+ // DeviceRegistry is nested in ManagerMeross but exported separately to avoid circular dependencies
6
+ const { ManagerMeross } = require('../manager');
7
7
  const { UnknownDeviceTypeError, CommandError } = require('../model/exception');
8
8
 
9
9
  /**
@@ -42,7 +42,7 @@ class MerossSubDevice extends MerossDevice {
42
42
  */
43
43
  constructor(hubDeviceUuid, subdeviceId, manager, kwargs = {}) {
44
44
  // eslint-disable-next-line camelcase
45
- const hubs = manager.findDevices({ device_uuids: [hubDeviceUuid] });
45
+ const hubs = manager.devices.find({ device_uuids: [hubDeviceUuid] });
46
46
  if (!hubs || hubs.length < 1) {
47
47
  throw new UnknownDeviceTypeError(`Specified hub device ${hubDeviceUuid} is not present`);
48
48
  }
@@ -137,7 +137,7 @@ class MerossSubDevice extends MerossDevice {
137
137
  throw new UnknownDeviceTypeError('Cannot generate internal ID: hub missing UUID');
138
138
  }
139
139
 
140
- this._internalId = MerossManager.DeviceRegistry.generateInternalId(hubUuid, true, hubUuid, this._subdeviceId);
140
+ this._internalId = ManagerMeross.DeviceRegistry.generateInternalId(hubUuid, true, hubUuid, this._subdeviceId);
141
141
  return this._internalId;
142
142
  }
143
143
 
package/lib/manager.js CHANGED
@@ -34,9 +34,9 @@ const { HttpApiError } = require('./model/http/exception');
34
34
  * @class
35
35
  * @extends EventEmitter
36
36
  */
37
- class MerossManager extends EventEmitter {
37
+ class ManagerMeross extends EventEmitter {
38
38
  /**
39
- * Creates a new MerossManager instance
39
+ * Creates a new ManagerMeross instance
40
40
  *
41
41
  * @param {Object} options - Configuration options
42
42
  * @param {MerossHttpClient} options.httpClient - HTTP client instance (required)
@@ -94,7 +94,7 @@ class MerossManager extends EventEmitter {
94
94
 
95
95
  this.mqttConnections = {};
96
96
  this._mqttConnectionPromises = new Map();
97
- this._deviceRegistry = new MerossManager.DeviceRegistry();
97
+ this._deviceRegistry = new ManagerMeross.DeviceRegistry();
98
98
  this._appId = null;
99
99
  this.clientResponseTopic = null;
100
100
  this._pendingMessagesFutures = new Map();
@@ -104,6 +104,9 @@ class MerossManager extends EventEmitter {
104
104
 
105
105
  this.httpClient = options.httpClient;
106
106
 
107
+ // Store subscription options from constructor for lazy initialization
108
+ this._subscriptionOptions = options.subscription || {};
109
+
107
110
  // Reuse authentication from HTTP client if already authenticated to avoid
108
111
  // redundant authentication and share credentials between HTTP and MQTT connections
109
112
  if (this.httpClient.token) {
@@ -490,51 +493,6 @@ class MerossManager extends EventEmitter {
490
493
  return response;
491
494
  }
492
495
 
493
- /**
494
- * Gets a device by UUID
495
- *
496
- * @param {string} uuid - Device UUID
497
- * @returns {MerossDevice|MerossHubDevice|undefined} Device instance or undefined if not found
498
- */
499
- getDevice(uuid) {
500
- return this._deviceRegistry.lookupByUuid(uuid);
501
- }
502
-
503
- /**
504
- * Finds devices matching the specified filters
505
- *
506
- * @param {Object} [filters={}] - Filter criteria
507
- * @param {string[]} [filters.device_uuids] - Array of device UUIDs to match (snake_case to match API)
508
- * @param {string[]} [filters.internal_ids] - Array of internal IDs to match
509
- * @param {string} [filters.device_type] - Device type to match (e.g., 'mss310')
510
- * @param {string} [filters.device_name] - Device name to match
511
- * @param {number} [filters.online_status] - Online status to match (from OnlineStatus enum)
512
- * @param {string|Function|Array} [filters.device_class] - Device class filter (string, function, or array)
513
- * @returns {Array<MerossDevice|MerossHubDevice>} Array of matching devices
514
- */
515
- findDevices(filters = {}) {
516
- return this._deviceRegistry.findDevices(filters);
517
- }
518
-
519
- /**
520
- * Gets all registered devices
521
- *
522
- * @returns {Array<MerossDevice|MerossHubDevice>} Array of all devices
523
- */
524
- getAllDevices() {
525
- return this._deviceRegistry.getAllDevices();
526
- }
527
-
528
- /**
529
- * Gets a device by UUID (internal helper method)
530
- *
531
- * @param {string} uuid - Device UUID
532
- * @returns {MerossDevice|MerossHubDevice|undefined} Device instance or undefined if not found
533
- * @private
534
- */
535
- _getDeviceByUuid(uuid) {
536
- return this._deviceRegistry.lookupByUuid(uuid);
537
- }
538
496
 
539
497
  /**
540
498
  * Disconnects all devices and closes all MQTT connections
@@ -547,7 +505,7 @@ class MerossManager extends EventEmitter {
547
505
  */
548
506
  disconnectAll(force) {
549
507
  // Retrieve devices before clearing registry to allow queue cleanup
550
- const devices = this._deviceRegistry.getAllDevices();
508
+ const devices = this._deviceRegistry.list();
551
509
  this._deviceRegistry.clear();
552
510
 
553
511
  if (this._requestQueue) {
@@ -650,7 +608,7 @@ class MerossManager extends EventEmitter {
650
608
  }
651
609
 
652
610
  this.mqttConnections[domain].deviceList.forEach(devId => {
653
- const device = this._getDeviceByUuid(devId);
611
+ const device = this._deviceRegistry._devicesByUuid.get(devId) || null;
654
612
  if (device) {
655
613
  device.emit('connected');
656
614
  }
@@ -660,7 +618,7 @@ class MerossManager extends EventEmitter {
660
618
  client.on('error', (error) => {
661
619
  // MQTT client handles reconnection automatically via reconnectPeriod option
662
620
  this.mqttConnections[domain].deviceList.forEach(devId => {
663
- const device = this._getDeviceByUuid(devId);
621
+ const device = this._deviceRegistry._devicesByUuid.get(devId) || null;
664
622
  if (device) {
665
623
  device.emit('error', error ? error.toString() : null);
666
624
  }
@@ -669,7 +627,7 @@ class MerossManager extends EventEmitter {
669
627
 
670
628
  client.on('close', (error) => {
671
629
  this.mqttConnections[domain].deviceList.forEach(devId => {
672
- const device = this._getDeviceByUuid(devId);
630
+ const device = this._deviceRegistry._devicesByUuid.get(devId) || null;
673
631
  if (device) {
674
632
  device.emit('close', error ? error.toString() : null);
675
633
  }
@@ -678,7 +636,7 @@ class MerossManager extends EventEmitter {
678
636
 
679
637
  client.on('reconnect', () => {
680
638
  this.mqttConnections[domain].deviceList.forEach(devId => {
681
- const device = this._getDeviceByUuid(devId);
639
+ const device = this._deviceRegistry._devicesByUuid.get(devId) || null;
682
640
  if (device) {
683
641
  device.emit('reconnect');
684
642
  }
@@ -734,7 +692,7 @@ class MerossManager extends EventEmitter {
734
692
 
735
693
  if (!message.header.from) {return;}
736
694
  const deviceUuid = deviceUuidFromPushNotification(message.header.from);
737
- const device = this._getDeviceByUuid(deviceUuid);
695
+ const device = this._deviceRegistry._devicesByUuid.get(deviceUuid) || null;
738
696
  if (device) {
739
697
  device.handleMessage(message);
740
698
  }
@@ -831,7 +789,7 @@ class MerossManager extends EventEmitter {
831
789
  const topic = buildDeviceRequestTopic(device.uuid);
832
790
  this.mqttConnections[domain].client.publish(topic, JSON.stringify(data), undefined, err => {
833
791
  if (err) {
834
- const deviceObj = this._getDeviceByUuid(device.uuid);
792
+ const deviceObj = this._deviceRegistry._devicesByUuid.get(device.uuid) || null;
835
793
  if (deviceObj) {
836
794
  deviceObj.emit('error', err);
837
795
  }
@@ -1230,25 +1188,40 @@ class MerossManager extends EventEmitter {
1230
1188
  }
1231
1189
 
1232
1190
  /**
1233
- * Gets or creates the SubscriptionManager instance
1191
+ * Gets or creates the ManagerSubscription instance
1234
1192
  *
1235
1193
  * Provides automatic polling and data provisioning for devices.
1236
1194
  * Uses lazy initialization to create the manager only when needed.
1195
+ * Merges constructor subscription options with any additional options.
1237
1196
  *
1238
- * @param {Object} [options={}] - Configuration options for SubscriptionManager
1239
- * @returns {SubscriptionManager} SubscriptionManager instance
1197
+ * @returns {ManagerSubscription} ManagerSubscription instance
1240
1198
  */
1241
- getSubscriptionManager(options = {}) {
1199
+ get subscription() {
1242
1200
  if (!this._subscriptionManager) {
1243
- const SubscriptionManager = require('./subscription');
1244
- this._subscriptionManager = new SubscriptionManager(this, {
1201
+ const ManagerSubscription = require('./subscription');
1202
+ this._subscriptionManager = new ManagerSubscription(this, {
1245
1203
  logger: this.options?.logger,
1246
- ...options
1204
+ ...this._subscriptionOptions
1247
1205
  });
1248
1206
  }
1249
1207
  return this._subscriptionManager;
1250
1208
  }
1251
1209
 
1210
+ /**
1211
+ * Gets the DeviceRegistry instance for device management
1212
+ *
1213
+ * Provides direct access to DeviceRegistry methods for device lookups and queries.
1214
+ * Use this property to access device registry functionality:
1215
+ * - `meross.devices.get(identifier)` - Get a device by UUID or subdevice identifier
1216
+ * - `meross.devices.find(filters)` - Find devices matching filters
1217
+ * - `meross.devices.list()` - Get all registered devices
1218
+ *
1219
+ * @returns {DeviceRegistry} DeviceRegistry instance
1220
+ */
1221
+ get devices() {
1222
+ return this._deviceRegistry;
1223
+ }
1224
+
1252
1225
  }
1253
1226
 
1254
1227
  /**
@@ -1342,29 +1315,38 @@ class DeviceRegistry {
1342
1315
  }
1343
1316
 
1344
1317
  /**
1345
- * Looks up a device by its internal ID.
1318
+ * Unified method to get a device by identifier.
1346
1319
  *
1347
- * Uses the unified internal ID format to support lookups for both base devices and subdevices
1348
- * through a single method, eliminating the need for type-specific lookup logic.
1320
+ * Supports both base devices (by UUID string) and subdevices (by object with hubUuid and id).
1321
+ * Internally converts the identifier to an internal ID format and performs the lookup.
1349
1322
  *
1350
- * @param {string} internalId - Internal ID (e.g., "#BASE:{uuid}" or "#SUB:{hubUuid}:{subdeviceId}")
1323
+ * @param {string|Object} identifier - Device identifier
1324
+ * @param {string} identifier - For base devices: device UUID string (e.g., 'device-uuid')
1325
+ * @param {Object} identifier - For subdevices: object with hubUuid and id properties
1326
+ * @param {string} identifier.hubUuid - Hub UUID that the subdevice belongs to
1327
+ * @param {string} identifier.id - Subdevice ID
1351
1328
  * @returns {MerossDevice|MerossHubDevice|MerossSubDevice|null} Device instance, or null if not found
1352
- */
1353
- lookupByInternalId(internalId) {
1354
- return this._devicesByInternalId.get(internalId) || null;
1355
- }
1356
-
1357
- /**
1358
- * Looks up a base device by its UUID.
1359
- *
1360
- * Provides O(1) lookup for base devices using the UUID index. Subdevices do not have
1361
- * unique UUIDs and must be looked up by internal ID instead.
1329
+ * @example
1330
+ * // Get base device by UUID
1331
+ * const device = registry.get('device-uuid');
1362
1332
  *
1363
- * @param {string} uuid - Device UUID
1364
- * @returns {MerossDevice|MerossHubDevice|null} Device instance, or null if not found
1333
+ * @example
1334
+ * // Get subdevice by hub UUID and subdevice ID
1335
+ * const subdevice = registry.get({ hubUuid: 'hub-uuid', id: 'subdevice-id' });
1365
1336
  */
1366
- lookupByUuid(uuid) {
1367
- return this._devicesByUuid.get(uuid) || null;
1337
+ get(identifier) {
1338
+ if (typeof identifier === 'string') {
1339
+ // Base device lookup by UUID
1340
+ return this._devicesByUuid.get(identifier) || null;
1341
+ } else if (identifier && typeof identifier === 'object') {
1342
+ // Subdevice lookup by hubUuid and id
1343
+ const { hubUuid, id } = identifier;
1344
+ if (hubUuid && id) {
1345
+ const internalId = ManagerMeross.DeviceRegistry.generateInternalId(hubUuid, true, hubUuid, id);
1346
+ return this._devicesByInternalId.get(internalId) || null;
1347
+ }
1348
+ }
1349
+ return null;
1368
1350
  }
1369
1351
 
1370
1352
  /**
@@ -1375,7 +1357,7 @@ class DeviceRegistry {
1375
1357
  *
1376
1358
  * @returns {Array<MerossDevice|MerossHubDevice|MerossSubDevice>} Array of all registered devices
1377
1359
  */
1378
- getAllDevices() {
1360
+ list() {
1379
1361
  return Array.from(this._devicesByInternalId.values());
1380
1362
  }
1381
1363
 
@@ -1398,8 +1380,8 @@ class DeviceRegistry {
1398
1380
  * - Function: custom filter function that receives device and returns boolean
1399
1381
  * @returns {Array<MerossDevice|MerossHubDevice|MerossSubDevice>} Array of matching devices
1400
1382
  */
1401
- findDevices(filters = {}) {
1402
- let devices = this.getAllDevices();
1383
+ find(filters = {}) {
1384
+ let devices = this.list();
1403
1385
 
1404
1386
  if (filters.device_uuids && Array.isArray(filters.device_uuids) && filters.device_uuids.length > 0) {
1405
1387
  const uuidSet = new Set(filters.device_uuids);
@@ -1513,7 +1495,7 @@ class DeviceRegistry {
1513
1495
  throw new UnknownDeviceTypeError('Cannot generate internal ID for subdevice: missing hub UUID or subdevice ID');
1514
1496
  }
1515
1497
 
1516
- const internalId = MerossManager.DeviceRegistry.generateInternalId(hubUuid, true, hubUuid, subdeviceId);
1498
+ const internalId = ManagerMeross.DeviceRegistry.generateInternalId(hubUuid, true, hubUuid, subdeviceId);
1517
1499
  device._internalId = internalId;
1518
1500
  return internalId;
1519
1501
  }
@@ -1523,7 +1505,7 @@ class DeviceRegistry {
1523
1505
  throw new UnknownDeviceTypeError('Cannot generate internal ID: device missing UUID');
1524
1506
  }
1525
1507
 
1526
- const internalId = MerossManager.DeviceRegistry.generateInternalId(uuid);
1508
+ const internalId = ManagerMeross.DeviceRegistry.generateInternalId(uuid);
1527
1509
  device._internalId = internalId;
1528
1510
  return internalId;
1529
1511
  }
@@ -1551,7 +1533,7 @@ class DeviceRegistry {
1551
1533
  * @returns {void}
1552
1534
  */
1553
1535
  clear() {
1554
- const devices = this.getAllDevices();
1536
+ const devices = this.list();
1555
1537
  devices.forEach(device => {
1556
1538
  if (device.disconnect) {
1557
1539
  device.disconnect();
@@ -1571,11 +1553,11 @@ class DeviceRegistry {
1571
1553
  }
1572
1554
  }
1573
1555
 
1574
- // Attach DeviceRegistry as nested class to MerossManager
1575
- MerossManager.DeviceRegistry = DeviceRegistry;
1556
+ // Attach DeviceRegistry as nested class to ManagerMeross
1557
+ ManagerMeross.DeviceRegistry = DeviceRegistry;
1576
1558
 
1577
1559
  /**
1578
- * Events emitted by MerossManager instance
1560
+ * Events emitted by ManagerMeross instance
1579
1561
  *
1580
1562
  * @typedef {Object} MerossCloudEvents
1581
1563
  * @property {Function} deviceInitialized - Emitted when a device is initialized
@@ -1604,6 +1586,6 @@ MerossManager.DeviceRegistry = DeviceRegistry;
1604
1586
  * @param {Object} message - Raw message object
1605
1587
  */
1606
1588
 
1607
- module.exports = MerossManager;
1589
+ module.exports = ManagerMeross;
1608
1590
  module.exports.DeviceRegistry = DeviceRegistry;
1609
1591
 
@@ -12,9 +12,9 @@ const EventEmitter = require('events');
12
12
  * @class
13
13
  * @extends EventEmitter
14
14
  */
15
- class SubscriptionManager extends EventEmitter {
15
+ class ManagerSubscription extends EventEmitter {
16
16
  /**
17
- * Creates a new SubscriptionManager instance.
17
+ * Creates a new ManagerSubscription instance.
18
18
  *
19
19
  * @param {Object} manager - MerossManager instance that provides device access
20
20
  * @param {Object} [options={}] - Configuration options
@@ -184,7 +184,7 @@ class SubscriptionManager extends EventEmitter {
184
184
  * Initialize subscription state and register device event handlers.
185
185
  *
186
186
  * Sets up event listeners on the device to capture push notifications and state
187
- * changes, which are then distributed to SubscriptionManager listeners via events.
187
+ * changes, which are then distributed to ManagerSubscription listeners via events.
188
188
  *
189
189
  * @private
190
190
  * @param {MerossDevice} device - Device to create subscription for
@@ -612,5 +612,5 @@ class SubscriptionManager extends EventEmitter {
612
612
 
613
613
  }
614
614
 
615
- module.exports = SubscriptionManager;
615
+ module.exports = ManagerSubscription;
616
616
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meross-iot",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Control Meross cloud devices using nodejs",
5
5
  "author": "Abe Haverkamp",
6
6
  "contributors": [