meross-iot 0.3.0 → 0.4.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,6 +5,36 @@ 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.4.0] - 2026-01-16
9
+
10
+ ### Changed
11
+ - **BREAKING**: Renamed `getDevices()` to `initializeDevices()` in `ManagerMeross`
12
+ - The method name better reflects that it performs full device discovery, initialization, and connection setup, not just retrieval
13
+ - Updated `login()` and `connect()` to use `initializeDevices()`
14
+ - Updated all examples and TypeScript definitions
15
+ - **BREAKING**: Simplified device API by establishing single source of truth
16
+ - Removed `deviceDef` parameter from `deviceInitialized` event - now just `(deviceId, device)`
17
+ - Removed `cachedHttpInfo` property; all properties are now directly accessible on `MerossDevice`
18
+ - Converted simple getters to direct properties (`macAddress`, `lanIp`, `mqttHost`, etc.)
19
+ - Updated all feature files to use public properties (`abilities`, `lastFullUpdateTimestamp`)
20
+ - Removed unnecessary defensive fallback patterns (`device.dev?.uuid` → `device.uuid`)
21
+ - Fixed subdevice property consistency
22
+ - **BREAKING**: Removed snake_case handling, standardized on camelCase
23
+ - Removed snake_case property mappings from `HttpDeviceInfo`, `HttpSubdeviceInfo`, `HardwareInfo`, `FirmwareInfo`, and `TimeInfo`
24
+ - Updated filter parameters to camelCase (`deviceUuids`, `deviceType`, `onlineStatus`, etc.)
25
+ - Changed `subdevice_id` getter to `subdeviceId` in push notification classes
26
+ - Updated `TokenData` interface: `issued_on` → `issuedOn`
27
+ - All JSDoc comments now reflect direct camelCase acceptance
28
+
29
+ ### Updated
30
+ - Updated all examples to use camelCase consistently
31
+ - Updated all examples to use `initializeDevices()` instead of `getDevices()`
32
+
33
+ ## [0.3.1] - 2026-01-15
34
+
35
+ ### Fixed
36
+ - Fixed `ManagerSubscription` constructor bug: now properly calls `super()` before accessing `this` to correctly initialize EventEmitter parent class
37
+
8
38
  ## [0.3.0] - 2026-01-15
9
39
 
10
40
  ### Changed
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.3.0
29
+ npm install meross-iot@0.4.0
30
30
  ```
31
31
 
32
32
  ## Usage & Documentation
@@ -51,8 +51,8 @@ const { ManagerMeross, MerossHttpClient } = require('meross-iot');
51
51
  });
52
52
 
53
53
  // Listen for device events
54
- meross.on('deviceInitialized', (deviceId, deviceDef, device) => {
55
- console.log(`Device found: ${deviceDef.devName} (${deviceDef.deviceType})`);
54
+ meross.on('deviceInitialized', (deviceId, device) => {
55
+ console.log(`Device found: ${device.name} (${device.deviceType})`);
56
56
  });
57
57
 
58
58
  // Connect and discover devices
@@ -120,6 +120,39 @@ Please create an issue on GitHub and include:
120
120
 
121
121
  ## Changelog
122
122
 
123
+ ### [0.4.0] - 2026-01-16
124
+
125
+ #### Changed
126
+ - **BREAKING**: Renamed `getDevices()` to `initializeDevices()` in `ManagerMeross`
127
+ - The method name better reflects that it performs full device discovery, initialization, and connection setup, not just retrieval
128
+ - Updated `login()` and `connect()` to use `initializeDevices()`
129
+ - Updated all examples and TypeScript definitions
130
+ - **BREAKING**: Simplified device API by establishing single source of truth
131
+ - Removed `deviceDef` parameter from `deviceInitialized` event - now just `(deviceId, device)`
132
+ - Removed `cachedHttpInfo` property; all properties are now directly accessible on `MerossDevice`
133
+ - Converted simple getters to direct properties (`macAddress`, `lanIp`, `mqttHost`, etc.)
134
+ - Updated all feature files to use public properties (`abilities`, `lastFullUpdateTimestamp`)
135
+ - Removed unnecessary defensive fallback patterns (`device.dev?.uuid` → `device.uuid`)
136
+ - Fixed subdevice property consistency
137
+ - **BREAKING**: Removed snake_case handling, standardized on camelCase
138
+ - Removed snake_case property mappings from `HttpDeviceInfo`, `HttpSubdeviceInfo`, `HardwareInfo`, `FirmwareInfo`, and `TimeInfo`
139
+ - Updated filter parameters to camelCase (`deviceUuids`, `deviceType`, `onlineStatus`, etc.)
140
+ - Changed `subdevice_id` getter to `subdeviceId` in push notification classes
141
+ - Updated `TokenData` interface: `issued_on` → `issuedOn`
142
+ - All JSDoc comments now reflect direct camelCase acceptance
143
+
144
+ #### Updated
145
+ - Updated all examples to use camelCase consistently
146
+ - Updated all examples to use `initializeDevices()` instead of `getDevices()`
147
+
148
+ <details>
149
+ <summary>Older</summary>
150
+
151
+ ### [0.3.1] - 2026-01-15
152
+
153
+ #### Fixed
154
+ - Fixed `ManagerSubscription` constructor bug: now properly calls `super()` before accessing `this` to correctly initialize EventEmitter parent class
155
+
123
156
  ### [0.3.0] - 2026-01-15
124
157
 
125
158
  #### Changed
@@ -189,11 +222,6 @@ Please create an issue on GitHub and include:
189
222
  - This is an initial, pre-stable release. Please expect bugs.
190
223
  - Some edge cases may not be fully handled yet.
191
224
 
192
- <details>
193
- <summary>Older</summary>
194
-
195
- <!-- Older changelog entries will appear here -->
196
-
197
225
  </details>
198
226
 
199
227
  ## Disclaimer
package/index.d.ts CHANGED
@@ -164,10 +164,10 @@ declare module 'meross-iot' {
164
164
  /**
165
165
  * Creates an HttpDeviceInfo instance from a dictionary object.
166
166
  *
167
- * Normalizes incoming keys (handles camelCase and snake_case) to camelCase.
167
+ * Accepts camelCase API response format directly (no normalization needed).
168
168
  * This is the only way to create instances.
169
169
  *
170
- * @param jsonDict - Dictionary object from the API response
170
+ * @param jsonDict - Dictionary object from the API response with camelCase keys
171
171
  * @returns New HttpDeviceInfo instance
172
172
  */
173
173
  static fromDict(jsonDict: { [key: string]: any }): HttpDeviceInfo
@@ -223,7 +223,8 @@ declare module 'meross-iot' {
223
223
  readonly subDeviceIconId: string | null
224
224
  /**
225
225
  * Creates an HttpSubdeviceInfo instance from a dictionary object.
226
- * Normalizes incoming keys (handles camelCase, snake_case, and generic keys) to camelCase.
226
+ * Accepts camelCase API response format. Generic keys ('id', 'type', 'name') are supported
227
+ * as fallbacks for API variations and are normalized to camelCase properties.
227
228
  * This is the only way to create instances.
228
229
  */
229
230
  static fromDict(jsonDict: { [key: string]: any }): HttpSubdeviceInfo
@@ -252,7 +253,7 @@ declare module 'meross-iot' {
252
253
  readonly chipType: string | null
253
254
  /**
254
255
  * Creates a HardwareInfo instance from a dictionary object.
255
- * Normalizes incoming keys (handles camelCase and snake_case) to camelCase.
256
+ * Accepts camelCase API response format directly (no normalization needed).
256
257
  * This is the only way to create instances.
257
258
  */
258
259
  static fromDict(jsonDict: { [key: string]: any }): HardwareInfo | null
@@ -280,7 +281,7 @@ declare module 'meross-iot' {
280
281
  readonly compileTime: string | null
281
282
  /**
282
283
  * Creates a FirmwareInfo instance from a dictionary object.
283
- * Normalizes incoming keys (handles camelCase and snake_case) to camelCase.
284
+ * Accepts camelCase API response format directly (no normalization needed).
284
285
  * This is the only way to create instances.
285
286
  */
286
287
  static fromDict(jsonDict: { [key: string]: any }): FirmwareInfo | null
@@ -305,7 +306,7 @@ declare module 'meross-iot' {
305
306
  readonly timeRule: string | null
306
307
  /**
307
308
  * Creates a TimeInfo instance from a dictionary object.
308
- * Normalizes incoming keys (handles camelCase and snake_case) to camelCase.
309
+ * Accepts camelCase API response format directly (no normalization needed).
309
310
  * This is the only way to create instances.
310
311
  */
311
312
  static fromDict(jsonDict: { [key: string]: any }): TimeInfo | null
@@ -386,7 +387,7 @@ declare module 'meross-iot' {
386
387
  /** MQTT domain */
387
388
  mqttDomain: string;
388
389
  /** Token issue timestamp (optional) */
389
- issued_on?: string;
390
+ issuedOn?: string;
390
391
  }
391
392
 
392
393
  /**
@@ -875,7 +876,7 @@ declare module 'meross-iot' {
875
876
  readonly value?: number | string | Record<string, any>;
876
877
  readonly timestamp?: number;
877
878
  readonly channel?: number;
878
- readonly subdevice_id?: string;
879
+ readonly subdeviceId?: string;
879
880
  constructor(originatingDeviceUuid: string, rawData: any);
880
881
  }
881
882
 
@@ -894,7 +895,7 @@ declare module 'meross-iot' {
894
895
  readonly syncedTime?: number | Record<string, any>;
895
896
  readonly latestSampleTime?: number;
896
897
  readonly latestSampleIsLeak?: number;
897
- readonly subdevice_id?: string;
898
+ readonly subdeviceId?: string;
898
899
  readonly samples?: Array<{ time: number; isLeak: number; [key: string]: any }>;
899
900
  constructor(originatingDeviceUuid: string, rawData: any);
900
901
  }
@@ -965,7 +966,7 @@ declare module 'meross-iot' {
965
966
  }
966
967
 
967
968
  export class HubSensorSmokePushNotification extends GenericPushNotification {
968
- readonly subdevice_id?: string | number;
969
+ readonly subdeviceId?: string | number;
969
970
  readonly status?: number;
970
971
  readonly interConn?: number | Record<string, any>;
971
972
  readonly timestamp?: number;
@@ -1106,7 +1107,7 @@ declare module 'meross-iot' {
1106
1107
  export type ErrorCallback = (error: Error | null) => void
1107
1108
  export type DeviceInitializedEvent = 'deviceInitialized'
1108
1109
 
1109
- export type DeviceInitializedCallback = (deviceId: string, deviceDef: DeviceDefinition, device: MerossDevice) => void
1110
+ export type DeviceInitializedCallback = (deviceId: string, device: MerossDevice) => void
1110
1111
 
1111
1112
  export type PushNotificationEvent = 'pushNotification'
1112
1113
  export type PushNotificationCallback = (deviceId: string, notification: GenericPushNotification, device: MerossDevice) => void
@@ -1122,30 +1123,30 @@ declare module 'meross-iot' {
1122
1123
  * @example
1123
1124
  * ```typescript
1124
1125
  * // Find online devices
1125
- * const onlineDevices = manager.devices.find({ online_status: 1 });
1126
+ * const onlineDevices = manager.devices.find({ onlineStatus: 1 });
1126
1127
  *
1127
1128
  * // Find specific device types
1128
- * const plugs = manager.devices.find({ device_type: 'mss310' });
1129
+ * const plugs = manager.devices.find({ deviceType: 'mss310' });
1129
1130
  *
1130
1131
  * // Find by custom filter function
1131
1132
  * const customDevices = manager.devices.find({
1132
- * device_class: (device) => device.deviceType.startsWith('mss')
1133
+ * deviceClass: (device) => device.deviceType.startsWith('mss')
1133
1134
  * });
1134
1135
  * ```
1135
1136
  */
1136
1137
  export interface FindDevicesFilters {
1137
1138
  /** Array of device UUIDs to filter by */
1138
- device_uuids?: string[];
1139
+ deviceUuids?: string[];
1139
1140
  /** Array of internal device IDs to filter by */
1140
- internal_ids?: string[];
1141
+ internalIds?: string[];
1141
1142
  /** Device type to filter by */
1142
- device_type?: string;
1143
+ deviceType?: string;
1143
1144
  /** Device name to filter by */
1144
- device_name?: string;
1145
+ deviceName?: string;
1145
1146
  /** Online status to filter by (0=connecting, 1=online, 2=offline, -1=unknown, 3=upgrading) */
1146
- online_status?: number;
1147
+ onlineStatus?: number;
1147
1148
  /** Device class filter - can be a string, function, or array of filters */
1148
- device_class?: string | ((device: MerossDevice) => boolean) | Array<string | ((device: MerossDevice) => boolean)>;
1149
+ deviceClass?: string | ((device: MerossDevice) => boolean) | Array<string | ((device: MerossDevice) => boolean)>;
1149
1150
  }
1150
1151
 
1151
1152
  /**
@@ -1168,7 +1169,7 @@ declare module 'meross-iot' {
1168
1169
  * const subdevice = manager.devices.get({ hubUuid: 'hub-uuid', id: 'subdevice-id' });
1169
1170
  *
1170
1171
  * // Find devices matching filters
1171
- * const lights = manager.devices.find({ device_class: 'light' });
1172
+ * const lights = manager.devices.find({ deviceClass: 'light' });
1172
1173
  *
1173
1174
  * // Get all devices
1174
1175
  * const allDevices = manager.devices.list();
@@ -1500,8 +1501,8 @@ declare module 'meross-iot' {
1500
1501
  * const manager = new ManagerMeross({ httpClient });
1501
1502
  * await manager.connect();
1502
1503
  *
1503
- * manager.on('deviceInitialized', (deviceId, deviceDef, device) => {
1504
- * console.log(`Device ${deviceId} initialized`);
1504
+ * manager.on('deviceInitialized', (deviceId, device) => {
1505
+ * console.log(`Device ${deviceId} initialized: ${device.name}`);
1505
1506
  * });
1506
1507
  *
1507
1508
  * const devices = manager.devices.list();
@@ -1523,6 +1524,30 @@ declare module 'meross-iot' {
1523
1524
  */
1524
1525
  connect(): Promise<number>
1525
1526
 
1527
+ /**
1528
+ * Initializes all devices from the Meross cloud.
1529
+ *
1530
+ * Fetches device list from cloud API, creates device instances, and sets up
1531
+ * MQTT connections. Emits 'deviceInitialized' event for each device.
1532
+ *
1533
+ * @returns Promise resolving to the number of devices initialized
1534
+ * @throws {HttpApiError} If API request fails
1535
+ * @throws {TokenExpiredError} If authentication token has expired
1536
+ */
1537
+ initializeDevices(): Promise<number>
1538
+
1539
+ /**
1540
+ * Authenticates with Meross cloud and discovers devices.
1541
+ *
1542
+ * Alias for initializeDevices(). Retrieves device list and initializes device connections.
1543
+ * The httpClient should already be authenticated when passed to the constructor.
1544
+ *
1545
+ * @returns Promise resolving to the number of devices discovered
1546
+ * @throws {HttpApiError} If API request fails
1547
+ * @throws {TokenExpiredError} If authentication token has expired
1548
+ */
1549
+ login(): Promise<number>
1550
+
1526
1551
  /**
1527
1552
  * Registers a handler for device initialization events.
1528
1553
  *
@@ -1934,7 +1959,22 @@ declare module 'meross-iot' {
1934
1959
  readonly isOnline: boolean
1935
1960
  readonly internalId: string
1936
1961
  readonly channels: ChannelInfo[]
1937
- readonly cachedHttpInfo: HttpDeviceInfo | null
1962
+ /** Reserved MQTT domain (fallback domain) */
1963
+ readonly reservedDomain: string | null
1964
+ /** Device sub-type */
1965
+ readonly subType: string | null
1966
+ /** When the device was bound to the account */
1967
+ readonly bindTime: Date | null
1968
+ /** Skill number */
1969
+ readonly skillNumber: string | null
1970
+ /** User-defined device icon */
1971
+ readonly userDevIcon: string | null
1972
+ /** Icon type */
1973
+ readonly iconType: number | null
1974
+ /** Device region */
1975
+ readonly region: string | null
1976
+ /** Device icon ID */
1977
+ readonly devIconId: string | null
1938
1978
 
1939
1979
  /**
1940
1980
  * Validates the current device state.
@@ -96,14 +96,24 @@ class MerossDevice extends EventEmitter {
96
96
  this.onlineStatus = dev.onlineStatus !== undefined ? dev.onlineStatus : OnlineStatus.UNKNOWN;
97
97
  }
98
98
 
99
- this._abilities = null;
100
- this._macAddress = null;
101
- this._lanIp = null;
102
- this._mqttHost = null;
103
- this._mqttPort = port;
104
- this._lastFullUpdateTimestamp = null;
99
+ this.abilities = null;
100
+ this.macAddress = null;
101
+ this.lanIp = null;
102
+ this.mqttHost = null;
103
+ this.mqttPort = port;
104
+ this.lastFullUpdateTimestamp = null;
105
105
  // Lazy initialization avoids registry computation during construction
106
106
  this._internalId = null;
107
+
108
+ // Extended HTTP device info properties (populated from HttpDeviceInfo when available)
109
+ this.reservedDomain = null;
110
+ this.subType = null;
111
+ this.bindTime = null;
112
+ this.skillNumber = null;
113
+ this.userDevIcon = null;
114
+ this.iconType = null;
115
+ this.region = null;
116
+ this.devIconId = null;
107
117
  }
108
118
 
109
119
  /**
@@ -149,24 +159,34 @@ class MerossDevice extends EventEmitter {
149
159
  }
150
160
 
151
161
  /**
152
- * Initializes HTTP device info and channels.
162
+ * Initializes HTTP device info and channels from raw API data.
153
163
  *
154
- * Only creates HttpDeviceInfo when a full device object is available (not just UUID),
155
- * as subdevices may be initialized with UUID only and lack HTTP API metadata.
164
+ * Uses HttpDeviceInfo.fromDict() to normalize data types (bindTime from Unix timestamp
165
+ * to Date, onlineStatus validation) before copying properties directly to the device
166
+ * instance. The HttpDeviceInfo instance is not stored to avoid redundancy.
156
167
  *
157
168
  * @private
158
- * @param {Object} dev - Device information object
169
+ * @param {Object} dev - Device information object from API response
159
170
  */
160
171
  _initializeHttpInfo(dev) {
161
- this._cachedHttpInfo = null;
162
- this._channels = [];
172
+ this.channels = [];
163
173
 
164
174
  if (dev && dev.uuid && typeof dev === 'object' && dev.deviceType !== undefined) {
165
175
  try {
166
- this._cachedHttpInfo = HttpDeviceInfo.fromDict(dev);
167
- this._channels = MerossDevice._parseChannels(dev.channels);
176
+ const httpInfo = HttpDeviceInfo.fromDict(dev);
177
+ this.channels = MerossDevice._parseChannels(dev.channels);
178
+
179
+ // Copy extended properties directly to device instance (bindTime already normalized to Date)
180
+ this.reservedDomain = httpInfo.reservedDomain || null;
181
+ this.subType = httpInfo.subType || null;
182
+ this.bindTime = httpInfo.bindTime || null;
183
+ this.skillNumber = httpInfo.skillNumber || null;
184
+ this.userDevIcon = httpInfo.userDevIcon || null;
185
+ this.iconType = httpInfo.iconType || null;
186
+ this.region = httpInfo.region || null;
187
+ this.devIconId = httpInfo.devIconId || null;
168
188
  } catch (error) {
169
- // Continue without HttpDeviceInfo if parsing fails
189
+ // Device may be created without HTTP info (e.g., subdevices with UUID only)
170
190
  }
171
191
  }
172
192
  }
@@ -213,7 +233,7 @@ class MerossDevice extends EventEmitter {
213
233
  * @param {Object} abilities - Device abilities object
214
234
  */
215
235
  updateAbilities(abilities) {
216
- this._abilities = abilities;
236
+ this.abilities = abilities;
217
237
  if (typeof this._updateAbilitiesWithEncryption === 'function') {
218
238
  this._updateAbilitiesWithEncryption(abilities);
219
239
  }
@@ -227,59 +247,12 @@ class MerossDevice extends EventEmitter {
227
247
  * @param {string} mac - MAC address string
228
248
  */
229
249
  updateMacAddress(mac) {
230
- this._macAddress = mac;
250
+ this.macAddress = mac;
231
251
  if (typeof this._updateMacAddressWithEncryption === 'function') {
232
252
  this._updateMacAddressWithEncryption(mac);
233
253
  }
234
254
  }
235
255
 
236
- /**
237
- * Gets the device's MAC address
238
- * @returns {string|null} MAC address or null if not available
239
- */
240
- get macAddress() {
241
- return this._macAddress;
242
- }
243
-
244
- /**
245
- * Gets the device's local network IP address
246
- * @returns {string|null} LAN IP address or null if not available
247
- */
248
- get lanIp() {
249
- return this._lanIp;
250
- }
251
-
252
- /**
253
- * Gets the MQTT broker hostname
254
- * @returns {string|null} MQTT hostname or null if not available
255
- */
256
- get mqttHost() {
257
- return this._mqttHost;
258
- }
259
-
260
- /**
261
- * Gets the MQTT broker port
262
- * @returns {number|null} MQTT port or null if not available
263
- */
264
- get mqttPort() {
265
- return this._mqttPort;
266
- }
267
-
268
- /**
269
- * Gets the device's abilities dictionary
270
- * @returns {Object|null} Abilities object or null if not available
271
- */
272
- get abilities() {
273
- return this._abilities;
274
- }
275
-
276
- /**
277
- * Gets the timestamp of the last full state update
278
- * @returns {number|null} Timestamp in milliseconds or null if never updated
279
- */
280
- get lastFullUpdateTimestamp() {
281
- return this._lastFullUpdateTimestamp;
282
- }
283
256
 
284
257
  /**
285
258
  * Gets the internal ID used for device registry.
@@ -303,29 +276,6 @@ class MerossDevice extends EventEmitter {
303
276
  return this._internalId;
304
277
  }
305
278
 
306
- /**
307
- * Gets the list of channels exposed by this device
308
- *
309
- * Multi-channel devices might expose a master switch at index 0.
310
- * Channels are parsed from the HTTP device info during device initialization.
311
- *
312
- * @returns {Array<ChannelInfo>} Array of ChannelInfo objects
313
- */
314
- get channels() {
315
- return this._channels;
316
- }
317
-
318
- /**
319
- * Gets the cached HTTP device info
320
- *
321
- * Returns the original device information object from the HTTP API,
322
- * or null if not available (e.g., for subdevices created without HTTP info).
323
- *
324
- * @returns {HttpDeviceInfo|null} Cached HTTP device info or null
325
- */
326
- get cachedHttpInfo() {
327
- return this._cachedHttpInfo;
328
- }
329
279
 
330
280
  /**
331
281
  * Validates that the device state has been refreshed.
@@ -336,7 +286,7 @@ class MerossDevice extends EventEmitter {
336
286
  * @returns {boolean} True if state has been refreshed, false otherwise
337
287
  */
338
288
  validateState() {
339
- const updateDone = this._lastFullUpdateTimestamp !== null;
289
+ const updateDone = this.lastFullUpdateTimestamp !== null;
340
290
  if (!updateDone) {
341
291
  const deviceName = this.name || this.uuid || 'unknown device';
342
292
  const logger = this.cloudInst?.options?.logger || console.error;
@@ -359,7 +309,7 @@ class MerossDevice extends EventEmitter {
359
309
  await this.getSystemAllData();
360
310
 
361
311
  this.emit('stateRefreshed', {
362
- timestamp: this._lastFullUpdateTimestamp || Date.now(),
312
+ timestamp: this.lastFullUpdateTimestamp || Date.now(),
363
313
  state: this.getUnifiedState()
364
314
  });
365
315
  } else {
@@ -378,7 +328,7 @@ class MerossDevice extends EventEmitter {
378
328
  getUnifiedState() {
379
329
  const state = {
380
330
  online: this.onlineStatus,
381
- timestamp: this._lastFullUpdateTimestamp || Date.now()
331
+ timestamp: this.lastFullUpdateTimestamp || Date.now()
382
332
  };
383
333
 
384
334
  this._collectFeatureStates(state);
@@ -698,15 +648,15 @@ class MerossDevice extends EventEmitter {
698
648
  const firmware = system.firmware;
699
649
  if (firmware) {
700
650
  if (firmware.innerIp) {
701
- this._lanIp = firmware.innerIp;
651
+ this.lanIp = firmware.innerIp;
702
652
  }
703
653
  if (firmware.server) {
704
- this._mqttHost = firmware.server;
654
+ this.mqttHost = firmware.server;
705
655
  }
706
656
  if (firmware.port) {
707
- this._mqttPort = firmware.port;
657
+ this.mqttPort = firmware.port;
708
658
  }
709
- this._lastFullUpdateTimestamp = Date.now();
659
+ this.lastFullUpdateTimestamp = Date.now();
710
660
  }
711
661
 
712
662
  if (system.online) {
@@ -1092,14 +1042,14 @@ class MerossDevice extends EventEmitter {
1092
1042
  * @param {string} ip - Local IP address
1093
1043
  */
1094
1044
  setKnownLocalIp(ip) {
1095
- this._lanIp = ip;
1045
+ this.lanIp = ip;
1096
1046
  }
1097
1047
 
1098
1048
  /**
1099
1049
  * Removes the known local IP address
1100
1050
  */
1101
1051
  removeKnownLocalIp() {
1102
- this._lanIp = null;
1052
+ this.lanIp = null;
1103
1053
  }
1104
1054
 
1105
1055
  /**
@@ -1126,7 +1076,7 @@ class MerossDevice extends EventEmitter {
1126
1076
  return reject(new UnconnectedError('Device is not connected', this.uuid));
1127
1077
  }
1128
1078
 
1129
- const res = await this.cloudInst.requestMessage(this, this._lanIp, data, transportMode);
1079
+ const res = await this.cloudInst.requestMessage(this, this.lanIp, data, transportMode);
1130
1080
  if (!res) {
1131
1081
  return reject(new UnconnectedError('Device has no data connection available', this.uuid));
1132
1082
  }
@@ -1232,9 +1182,9 @@ class MerossDevice extends EventEmitter {
1232
1182
  lookupChannel(channelIdOrName) {
1233
1183
  let res = [];
1234
1184
  if (typeof channelIdOrName === 'string') {
1235
- res = this._channels.filter(c => c.name === channelIdOrName);
1185
+ res = this.channels.filter(c => c.name === channelIdOrName);
1236
1186
  } else if (typeof channelIdOrName === 'number') {
1237
- res = this._channels.filter(c => c.index === channelIdOrName);
1187
+ res = this.channels.filter(c => c.index === channelIdOrName);
1238
1188
  }
1239
1189
 
1240
1190
  if (res.length === 1) {
@@ -1245,14 +1195,16 @@ class MerossDevice extends EventEmitter {
1245
1195
  }
1246
1196
 
1247
1197
  /**
1248
- * Updates device information from HTTP device info
1198
+ * Updates device properties from HttpDeviceInfo object.
1249
1199
  *
1250
- * Updates the device's cached HTTP info, channels, and all device properties
1251
- * from an HttpDeviceInfo object (created via HttpDeviceInfo.fromDict()). Validates that the UUID matches before updating.
1200
+ * Synchronizes the device instance with updated HTTP device info, including channels,
1201
+ * core properties, and extended metadata. Validates UUID match to prevent updating
1202
+ * the wrong device. Properties are copied directly to the device instance; the
1203
+ * HttpDeviceInfo object is not stored.
1252
1204
  *
1253
- * @param {HttpDeviceInfo} deviceInfo - HttpDeviceInfo object from HTTP API
1205
+ * @param {HttpDeviceInfo} deviceInfo - HttpDeviceInfo object created via HttpDeviceInfo.fromDict()
1254
1206
  * @returns {Promise<MerossDevice>} Promise that resolves with this device instance
1255
- * @throws {Error} If device UUID doesn't match
1207
+ * @throws {Error} If device info is missing or UUID doesn't match
1256
1208
  * @example
1257
1209
  * const updatedInfo = HttpDeviceInfo.fromDict(deviceDataFromApi);
1258
1210
  * await device.updateFromHttpState(updatedInfo);
@@ -1266,13 +1218,9 @@ class MerossDevice extends EventEmitter {
1266
1218
  throw new Error(`Cannot update device (${this.uuid}) with HttpDeviceInfo for device id ${deviceInfo.uuid}`);
1267
1219
  }
1268
1220
 
1269
- // Update cached HTTP info
1270
- this._cachedHttpInfo = deviceInfo;
1221
+ this.channels = MerossDevice._parseChannels(deviceInfo.channels);
1271
1222
 
1272
- // Update channels
1273
- this._channels = MerossDevice._parseChannels(deviceInfo.channels);
1274
-
1275
- // Update device properties
1223
+ // Subdevices override name and onlineStatus as getter-only properties
1276
1224
  if (!MerossDevice._isGetterOnly(this, 'name')) {
1277
1225
  this.name = deviceInfo.devName || this.uuid || 'unknown';
1278
1226
  }
@@ -1286,6 +1234,16 @@ class MerossDevice extends EventEmitter {
1286
1234
  this.onlineStatus = deviceInfo.onlineStatus !== undefined ? deviceInfo.onlineStatus : OnlineStatus.UNKNOWN;
1287
1235
  }
1288
1236
 
1237
+ // Extended HTTP device info properties (bindTime already normalized to Date by HttpDeviceInfo)
1238
+ this.reservedDomain = deviceInfo.reservedDomain || null;
1239
+ this.subType = deviceInfo.subType || null;
1240
+ this.bindTime = deviceInfo.bindTime || null;
1241
+ this.skillNumber = deviceInfo.skillNumber || null;
1242
+ this.userDevIcon = deviceInfo.userDevIcon || null;
1243
+ this.iconType = deviceInfo.iconType || null;
1244
+ this.region = deviceInfo.region || null;
1245
+ this.devIconId = deviceInfo.devIconId || null;
1246
+
1289
1247
  return this;
1290
1248
  }
1291
1249
  }
@@ -37,10 +37,10 @@ module.exports = {
37
37
 
38
38
  if (response && response.light) {
39
39
  this._updateDiffuserLightState(response.light);
40
- this._lastFullUpdateTimestamp = Date.now();
40
+ this.lastFullUpdateTimestamp = Date.now();
41
41
  } else if (light) {
42
42
  this._updateDiffuserLightState([light]);
43
- this._lastFullUpdateTimestamp = Date.now();
43
+ this.lastFullUpdateTimestamp = Date.now();
44
44
  }
45
45
 
46
46
  return response;
@@ -69,10 +69,10 @@ module.exports = {
69
69
 
70
70
  if (response && response.spray) {
71
71
  this._updateDiffuserSprayState(response.spray, 'response');
72
- this._lastFullUpdateTimestamp = Date.now();
72
+ this.lastFullUpdateTimestamp = Date.now();
73
73
  } else {
74
74
  this._updateDiffuserSprayState([{ channel, mode: options.mode || 0 }], 'response');
75
- this._lastFullUpdateTimestamp = Date.now();
75
+ this.lastFullUpdateTimestamp = Date.now();
76
76
  }
77
77
 
78
78
  return response;
@@ -91,7 +91,7 @@ module.exports = {
91
91
  const response = await this.publishMessage('GET', 'Appliance.Control.Diffuser.Light', {});
92
92
  if (response && response.light) {
93
93
  this._updateDiffuserLightState(response.light, 'response');
94
- this._lastFullUpdateTimestamp = Date.now();
94
+ this.lastFullUpdateTimestamp = Date.now();
95
95
  }
96
96
  return response;
97
97
  },
@@ -109,7 +109,7 @@ module.exports = {
109
109
  const response = await this.publishMessage('GET', 'Appliance.Control.Diffuser.Spray', {});
110
110
  if (response && response.spray) {
111
111
  this._updateDiffuserSprayState(response.spray, 'response');
112
- this._lastFullUpdateTimestamp = Date.now();
112
+ this.lastFullUpdateTimestamp = Date.now();
113
113
  }
114
114
  return response;
115
115
  },