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 +30 -0
- package/README.md +36 -8
- package/index.d.ts +64 -24
- package/lib/controller/device.js +68 -110
- package/lib/controller/features/diffuser-feature.js +6 -6
- package/lib/controller/features/dnd-feature.js +2 -2
- package/lib/controller/features/encryption-feature.js +0 -1
- package/lib/controller/features/garage-feature.js +4 -4
- package/lib/controller/features/light-feature.js +16 -16
- package/lib/controller/features/presence-sensor-feature.js +1 -1
- package/lib/controller/features/roller-shutter-feature.js +3 -3
- package/lib/controller/features/spray-feature.js +3 -3
- package/lib/controller/features/system-feature.js +7 -7
- package/lib/controller/features/thermostat-feature.js +5 -5
- package/lib/controller/features/toggle-feature.js +9 -9
- package/lib/controller/hub-device.js +1 -1
- package/lib/controller/subdevice.js +2 -4
- package/lib/http-api.js +3 -3
- package/lib/manager.js +30 -32
- package/lib/model/http/device.js +60 -102
- package/lib/model/http/subdevice.js +37 -44
- package/lib/model/push/alarm.js +3 -4
- package/lib/model/push/bind.js +2 -2
- package/lib/model/push/common.js +55 -128
- package/lib/model/push/hub-sensor-smoke.js +2 -3
- package/lib/model/push/water-leak.js +2 -3
- package/lib/subscription.js +4 -2
- package/package.json +1 -1
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.
|
|
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,
|
|
55
|
-
console.log(`Device found: ${
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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({
|
|
1126
|
+
* const onlineDevices = manager.devices.find({ onlineStatus: 1 });
|
|
1126
1127
|
*
|
|
1127
1128
|
* // Find specific device types
|
|
1128
|
-
* const plugs = manager.devices.find({
|
|
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
|
-
*
|
|
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
|
-
|
|
1139
|
+
deviceUuids?: string[];
|
|
1139
1140
|
/** Array of internal device IDs to filter by */
|
|
1140
|
-
|
|
1141
|
+
internalIds?: string[];
|
|
1141
1142
|
/** Device type to filter by */
|
|
1142
|
-
|
|
1143
|
+
deviceType?: string;
|
|
1143
1144
|
/** Device name to filter by */
|
|
1144
|
-
|
|
1145
|
+
deviceName?: string;
|
|
1145
1146
|
/** Online status to filter by (0=connecting, 1=online, 2=offline, -1=unknown, 3=upgrading) */
|
|
1146
|
-
|
|
1147
|
+
onlineStatus?: number;
|
|
1147
1148
|
/** Device class filter - can be a string, function, or array of filters */
|
|
1148
|
-
|
|
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({
|
|
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,
|
|
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
|
-
|
|
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.
|
package/lib/controller/device.js
CHANGED
|
@@ -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.
|
|
100
|
-
this.
|
|
101
|
-
this.
|
|
102
|
-
this.
|
|
103
|
-
this.
|
|
104
|
-
this.
|
|
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
|
-
*
|
|
155
|
-
*
|
|
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.
|
|
162
|
-
this._channels = [];
|
|
172
|
+
this.channels = [];
|
|
163
173
|
|
|
164
174
|
if (dev && dev.uuid && typeof dev === 'object' && dev.deviceType !== undefined) {
|
|
165
175
|
try {
|
|
166
|
-
|
|
167
|
-
this.
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
651
|
+
this.lanIp = firmware.innerIp;
|
|
702
652
|
}
|
|
703
653
|
if (firmware.server) {
|
|
704
|
-
this.
|
|
654
|
+
this.mqttHost = firmware.server;
|
|
705
655
|
}
|
|
706
656
|
if (firmware.port) {
|
|
707
|
-
this.
|
|
657
|
+
this.mqttPort = firmware.port;
|
|
708
658
|
}
|
|
709
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1185
|
+
res = this.channels.filter(c => c.name === channelIdOrName);
|
|
1236
1186
|
} else if (typeof channelIdOrName === 'number') {
|
|
1237
|
-
res = this.
|
|
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
|
|
1198
|
+
* Updates device properties from HttpDeviceInfo object.
|
|
1249
1199
|
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
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
|
|
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
|
-
|
|
1270
|
-
this._cachedHttpInfo = deviceInfo;
|
|
1221
|
+
this.channels = MerossDevice._parseChannels(deviceInfo.channels);
|
|
1271
1222
|
|
|
1272
|
-
//
|
|
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.
|
|
40
|
+
this.lastFullUpdateTimestamp = Date.now();
|
|
41
41
|
} else if (light) {
|
|
42
42
|
this._updateDiffuserLightState([light]);
|
|
43
|
-
this.
|
|
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.
|
|
72
|
+
this.lastFullUpdateTimestamp = Date.now();
|
|
73
73
|
} else {
|
|
74
74
|
this._updateDiffuserSprayState([{ channel, mode: options.mode || 0 }], 'response');
|
|
75
|
-
this.
|
|
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.
|
|
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.
|
|
112
|
+
this.lastFullUpdateTimestamp = Date.now();
|
|
113
113
|
}
|
|
114
114
|
return response;
|
|
115
115
|
},
|