meross-iot 0.7.0 → 0.7.2
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,29 @@ 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.7.2] - 2026-01-21
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Optimized ManagerSubscription polling behavior for better efficiency
|
|
12
|
+
- Changed default `deviceStateInterval` from 30000ms to 0 (push-only by default after initial state)
|
|
13
|
+
- Device state is now polled once on initial subscription to establish baseline, then relies on push notifications
|
|
14
|
+
- Implemented per-namespace push tracking instead of global push active state for more granular control
|
|
15
|
+
- Removed unnecessary push-active checks from electricity/consumption polling (these features don't support push notifications)
|
|
16
|
+
- Polling now skips when recent push notifications were received for the specific namespace being polled
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Added `pushNotificationReceived` event to MerossDevice that emits the namespace for push activity tracking
|
|
20
|
+
- Allows subscription manager to track push notifications per-namespace for selective polling optimization
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Removed default channel initialization for subdevices that could cause incorrect channel setup
|
|
24
|
+
|
|
25
|
+
## [0.7.1] - 2026-01-21
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Prefer Consumption/ConsumptionX/ConsumptionH in the right order and fallback sequence when fetching usage history
|
|
29
|
+
- Poll electricity via the feature-based API and honor channel cache data in ManagerSubscription
|
|
30
|
+
|
|
8
31
|
## [0.7.0] - 2026-01-20
|
|
9
32
|
|
|
10
33
|
### Added
|
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.7.
|
|
29
|
+
npm install meross-iot@0.7.1
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Usage & Documentation
|
|
@@ -177,6 +177,32 @@ Please create an issue on GitHub and include:
|
|
|
177
177
|
|
|
178
178
|
## Changelog
|
|
179
179
|
|
|
180
|
+
### [0.7.2] - 2026-01-21
|
|
181
|
+
|
|
182
|
+
#### Changed
|
|
183
|
+
- Optimized ManagerSubscription polling behavior for better efficiency
|
|
184
|
+
- Changed default `deviceStateInterval` from 30000ms to 0 (push-only by default after initial state)
|
|
185
|
+
- Device state is now polled once on initial subscription to establish baseline, then relies on push notifications
|
|
186
|
+
- Implemented per-namespace push tracking instead of global push active state for more granular control
|
|
187
|
+
- Removed unnecessary push-active checks from electricity/consumption polling (these features don't support push notifications)
|
|
188
|
+
- Polling now skips when recent push notifications were received for the specific namespace being polled
|
|
189
|
+
|
|
190
|
+
#### Added
|
|
191
|
+
- Added `pushNotificationReceived` event to MerossDevice that emits the namespace for push activity tracking
|
|
192
|
+
- Allows subscription manager to track push notifications per-namespace for selective polling optimization
|
|
193
|
+
|
|
194
|
+
#### Fixed
|
|
195
|
+
- Removed default channel initialization for subdevices that could cause incorrect channel setup
|
|
196
|
+
|
|
197
|
+
<details>
|
|
198
|
+
<summary>Older</summary>
|
|
199
|
+
|
|
200
|
+
### [0.7.1] - 2026-01-21
|
|
201
|
+
|
|
202
|
+
#### Fixed
|
|
203
|
+
- Prefer Consumption/ConsumptionX/ConsumptionH in the right order and fallback sequence when fetching usage history
|
|
204
|
+
- Poll electricity via the feature-based API and honor channel cache data in ManagerSubscription
|
|
205
|
+
|
|
180
206
|
### [0.7.0] - 2026-01-20
|
|
181
207
|
|
|
182
208
|
#### Added
|
|
@@ -187,9 +213,6 @@ Please create an issue on GitHub and include:
|
|
|
187
213
|
- Capabilities are automatically built when device abilities are updated
|
|
188
214
|
- TypeScript definitions updated with `DeviceCapabilities` interface
|
|
189
215
|
|
|
190
|
-
<details>
|
|
191
|
-
<summary>Older</summary>
|
|
192
|
-
|
|
193
216
|
### [0.6.0] - 2026-01-20
|
|
194
217
|
|
|
195
218
|
#### Changed
|
package/lib/controller/device.js
CHANGED
|
@@ -1042,7 +1042,9 @@ class MerossDevice extends EventEmitter {
|
|
|
1042
1042
|
* Handles push notification messages.
|
|
1043
1043
|
*
|
|
1044
1044
|
* Tracks notification activity, parses into typed notification objects, routes to
|
|
1045
|
-
* feature modules, and emits unified state events.
|
|
1045
|
+
* feature modules, and emits unified state events. Also emits a lightweight
|
|
1046
|
+
* `pushNotificationReceived` event so subscription/polling managers can track
|
|
1047
|
+
* push activity by namespace without depending on payload parsing details.
|
|
1046
1048
|
*
|
|
1047
1049
|
* @private
|
|
1048
1050
|
* @param {Object} message - The push notification message object
|
|
@@ -1053,6 +1055,8 @@ class MerossDevice extends EventEmitter {
|
|
|
1053
1055
|
const namespace = message.header?.namespace || '';
|
|
1054
1056
|
const payload = message.payload || message;
|
|
1055
1057
|
|
|
1058
|
+
this.emit('pushNotificationReceived', namespace);
|
|
1059
|
+
|
|
1056
1060
|
parsePushNotification(namespace, payload, this.uuid);
|
|
1057
1061
|
|
|
1058
1062
|
this._routePushNotificationToFeatures(namespace, payload);
|
|
@@ -1406,6 +1410,7 @@ class MerossDevice extends EventEmitter {
|
|
|
1406
1410
|
/**
|
|
1407
1411
|
* @typedef {Object} MerossDeviceEvents
|
|
1408
1412
|
* @property {Function} state - Emitted when device state changes (unified event for all state changes)
|
|
1413
|
+
* @property {Function} pushNotificationReceived - Emitted on any push notification to track push activity by namespace
|
|
1409
1414
|
* @property {Function} error - Emitted when an error occurs
|
|
1410
1415
|
* @property {Function} connected - Emitted when device connects
|
|
1411
1416
|
* @property {Function} disconnected - Emitted when device disconnects
|
|
@@ -46,12 +46,12 @@ function createConsumptionFeature(device) {
|
|
|
46
46
|
const hasConsumptionX = device.abilities?.['Appliance.Control.ConsumptionX'];
|
|
47
47
|
const hasConsumption = device.abilities?.['Appliance.Control.Consumption'];
|
|
48
48
|
|
|
49
|
-
if (
|
|
50
|
-
return await this.
|
|
49
|
+
if (hasConsumption) {
|
|
50
|
+
return await this._getConsumption(channel);
|
|
51
51
|
} else if (hasConsumptionX) {
|
|
52
52
|
return await this._getConsumptionX(channel);
|
|
53
|
-
} else if (
|
|
54
|
-
return await this.
|
|
53
|
+
} else if (hasConsumptionH) {
|
|
54
|
+
return await this._getConsumptionH(channel);
|
|
55
55
|
} else {
|
|
56
56
|
return await this._getConsumptionWithFallback(channel);
|
|
57
57
|
}
|
|
@@ -161,9 +161,9 @@ function createConsumptionFeature(device) {
|
|
|
161
161
|
* @private
|
|
162
162
|
*/
|
|
163
163
|
async _getConsumptionWithFallback(channel) {
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
return
|
|
164
|
+
const result = await this._getConsumption(channel);
|
|
165
|
+
if (result) {
|
|
166
|
+
return result;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
const resultX = await this._getConsumptionX(channel);
|
|
@@ -171,7 +171,7 @@ function createConsumptionFeature(device) {
|
|
|
171
171
|
return resultX;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
return await this.
|
|
174
|
+
return await this._getConsumptionH(channel);
|
|
175
175
|
},
|
|
176
176
|
|
|
177
177
|
/**
|
|
@@ -61,13 +61,6 @@ class MerossSubDevice extends MerossDevice {
|
|
|
61
61
|
this.lanIp = hub.lanIp;
|
|
62
62
|
|
|
63
63
|
this._onlineStatus = OnlineStatus.UNKNOWN;
|
|
64
|
-
|
|
65
|
-
// Initialize channels for subdevices (default to channel 0)
|
|
66
|
-
// This ensures capabilities can be built even if HTTP info isn't available
|
|
67
|
-
if (!this.channels || this.channels.length === 0) {
|
|
68
|
-
const ChannelInfo = require('../model/channel-info');
|
|
69
|
-
this.channels = [new ChannelInfo(0, 'Main channel', null, true)];
|
|
70
|
-
}
|
|
71
64
|
}
|
|
72
65
|
|
|
73
66
|
/**
|
|
@@ -6,12 +6,15 @@ const { MerossErrorValidation } = require('../model/exception');
|
|
|
6
6
|
/**
|
|
7
7
|
* Manages automatic polling and unified update streams for Meross devices.
|
|
8
8
|
*
|
|
9
|
-
* Coordinates push notifications and
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Coordinates push notifications and targeted polling to provide a single event stream for
|
|
10
|
+
* device state changes. Device state is polled once on initial subscription to establish a
|
|
11
|
+
* baseline for listeners, then typically relies on push notifications for ongoing updates.
|
|
12
|
+
* Some features (electricity, consumption) require periodic polling because they do not
|
|
13
|
+
* emit push notifications.
|
|
12
14
|
*
|
|
13
|
-
* Push notifications
|
|
14
|
-
*
|
|
15
|
+
* Push notifications reduce latency and network traffic compared to frequent polling.
|
|
16
|
+
* Periodic polling is intended for features without push support (electricity, consumption)
|
|
17
|
+
* or as an explicit fallback when a device is not producing push updates.
|
|
15
18
|
*
|
|
16
19
|
* @class
|
|
17
20
|
* @extends EventEmitter
|
|
@@ -23,13 +26,13 @@ class ManagerSubscription extends EventEmitter {
|
|
|
23
26
|
* @param {Object} manager - MerossManager instance that provides device access
|
|
24
27
|
* @param {Object} [options={}] - Configuration options
|
|
25
28
|
* @param {Function} [options.logger] - Logger function for debug output
|
|
26
|
-
* @param {number} [options.deviceStateInterval=
|
|
29
|
+
* @param {number} [options.deviceStateInterval=0] - Device state polling interval in milliseconds (0 to disable periodic polling, rely on push only after initial state)
|
|
27
30
|
* @param {number} [options.electricityInterval=30000] - Electricity metrics polling interval in milliseconds (0 to disable)
|
|
28
31
|
* @param {number} [options.consumptionInterval=60000] - Power consumption polling interval in milliseconds (0 to disable)
|
|
29
32
|
* @param {number} [options.httpDeviceListInterval=120000] - HTTP device list polling interval in milliseconds
|
|
30
33
|
* @param {boolean} [options.smartCaching=true] - Skip polling when cached data is fresh to reduce network traffic
|
|
31
34
|
* @param {number} [options.cacheMaxAge=10000] - Maximum cache age in milliseconds before considering data stale
|
|
32
|
-
* @param {boolean} [options.pushOnly=false] - If true,
|
|
35
|
+
* @param {boolean} [options.pushOnly=false] - If true, treat push as the primary update mechanism (a baseline state is still emitted on subscribe; metrics polling still depends on intervals)
|
|
33
36
|
*/
|
|
34
37
|
constructor(manager, options = {}) {
|
|
35
38
|
super();
|
|
@@ -42,7 +45,7 @@ class ManagerSubscription extends EventEmitter {
|
|
|
42
45
|
this.logger = options.logger || (() => {});
|
|
43
46
|
|
|
44
47
|
this.defaultConfig = {
|
|
45
|
-
deviceStateInterval: options.
|
|
48
|
+
deviceStateInterval: options.deviceStateInterval !== undefined ? options.deviceStateInterval : 0,
|
|
46
49
|
electricityInterval: options.electricityInterval !== undefined ? options.electricityInterval : 30000,
|
|
47
50
|
consumptionInterval: options.consumptionInterval !== undefined ? options.consumptionInterval : 60000,
|
|
48
51
|
httpDeviceListInterval: options.httpDeviceListInterval || 120000,
|
|
@@ -60,17 +63,19 @@ class ManagerSubscription extends EventEmitter {
|
|
|
60
63
|
* Subscribe to device updates.
|
|
61
64
|
*
|
|
62
65
|
* Registers event listeners for push notifications and starts periodic polling
|
|
63
|
-
* based on configuration.
|
|
64
|
-
* the shortest interval
|
|
66
|
+
* based on configuration. For metrics (electricity/consumption), multiple calls merge by
|
|
67
|
+
* selecting the shortest interval so all listeners receive updates at the required frequency.
|
|
68
|
+
* Device state polling is configured explicitly via `deviceStateInterval` (with a baseline
|
|
69
|
+
* state poll on initial subscription).
|
|
65
70
|
*
|
|
66
71
|
* @param {MerossDevice} device - Device to subscribe to
|
|
67
72
|
* @param {Object} [config={}] - Subscription configuration
|
|
68
|
-
* @param {number} [config.deviceStateInterval] - Device state polling interval in milliseconds (0 to disable)
|
|
73
|
+
* @param {number} [config.deviceStateInterval] - Device state polling interval in milliseconds (0 to disable periodic polling, rely on push only after initial state)
|
|
69
74
|
* @param {number} [config.electricityInterval] - Electricity metrics polling interval in milliseconds (0 to disable)
|
|
70
75
|
* @param {number} [config.consumptionInterval] - Power consumption polling interval in milliseconds (0 to disable)
|
|
71
76
|
* @param {boolean} [config.smartCaching] - Skip polling when cached data is fresh
|
|
72
77
|
* @param {number} [config.cacheMaxAge] - Maximum cache age in milliseconds before refresh
|
|
73
|
-
* @param {boolean} [config.pushOnly=false] -
|
|
78
|
+
* @param {boolean} [config.pushOnly=false] - Prefer push-driven updates and emit cached state immediately when available
|
|
74
79
|
* @example
|
|
75
80
|
* subscription.subscribe(device, { pushOnly: true });
|
|
76
81
|
* subscription.on(`deviceUpdate:${device.uuid}`, (update) => {
|
|
@@ -93,10 +98,7 @@ class ManagerSubscription extends EventEmitter {
|
|
|
93
98
|
const pushOnly = config.pushOnly !== undefined ? config.pushOnly : (existingConfig.pushOnly || this.defaultConfig.pushOnly);
|
|
94
99
|
|
|
95
100
|
subscription.config = {
|
|
96
|
-
deviceStateInterval:
|
|
97
|
-
existingConfig.deviceStateInterval || this.defaultConfig.deviceStateInterval,
|
|
98
|
-
config.deviceStateInterval !== undefined ? config.deviceStateInterval : (existingConfig.deviceStateInterval || this.defaultConfig.deviceStateInterval)
|
|
99
|
-
),
|
|
101
|
+
deviceStateInterval: config.deviceStateInterval !== undefined ? config.deviceStateInterval : (existingConfig.deviceStateInterval !== undefined ? existingConfig.deviceStateInterval : this.defaultConfig.deviceStateInterval),
|
|
100
102
|
electricityInterval: Math.min(
|
|
101
103
|
existingConfig.electricityInterval || this.defaultConfig.electricityInterval,
|
|
102
104
|
config.electricityInterval !== undefined ? config.electricityInterval : (existingConfig.electricityInterval || this.defaultConfig.electricityInterval)
|
|
@@ -215,26 +217,27 @@ class ManagerSubscription extends EventEmitter {
|
|
|
215
217
|
pollingIntervals: new Map(),
|
|
216
218
|
lastPollTimes: new Map(),
|
|
217
219
|
lastUpdate: null,
|
|
218
|
-
|
|
219
|
-
pushLastSeen: null
|
|
220
|
+
pushActiveNamespaces: new Map()
|
|
220
221
|
};
|
|
221
222
|
|
|
222
223
|
this.subscriptions.set(device.uuid, subscription);
|
|
223
224
|
|
|
224
|
-
device.on('pushNotificationReceived', () => {
|
|
225
|
-
this.
|
|
225
|
+
device.on('pushNotificationReceived', (namespace) => {
|
|
226
|
+
this._setPushActive(device.uuid, namespace);
|
|
226
227
|
});
|
|
227
228
|
|
|
228
229
|
device.on('state', (event) => {
|
|
229
|
-
this.
|
|
230
|
+
this._onStateEvent(device.uuid, event);
|
|
230
231
|
});
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
/**
|
|
234
|
-
* Start
|
|
235
|
+
* Start polling for device state, electricity, and consumption data.
|
|
235
236
|
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
237
|
+
* Performs a one-time device state poll on initial subscription so consumers
|
|
238
|
+
* can receive a current baseline state without waiting for the next push event.
|
|
239
|
+
* Periodic polling is reserved for features that do not support push notifications
|
|
240
|
+
* (electricity, consumption) and for the optional device-state fallback interval.
|
|
238
241
|
*
|
|
239
242
|
* @private
|
|
240
243
|
* @param {MerossDevice} device - Device to poll
|
|
@@ -243,16 +246,17 @@ class ManagerSubscription extends EventEmitter {
|
|
|
243
246
|
_startPolling(device, subscription) {
|
|
244
247
|
const config = subscription.config || this.defaultConfig;
|
|
245
248
|
|
|
249
|
+
this._pollDeviceState(device, subscription);
|
|
250
|
+
|
|
246
251
|
if (config.deviceStateInterval > 0) {
|
|
247
252
|
const interval = setInterval(async () => {
|
|
248
253
|
await this._pollDeviceState(device, subscription);
|
|
249
254
|
}, config.deviceStateInterval);
|
|
250
255
|
|
|
251
256
|
subscription.pollingIntervals.set('deviceState', interval);
|
|
252
|
-
this._pollDeviceState(device, subscription);
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
if (config.electricityInterval > 0 && typeof device.
|
|
259
|
+
if (config.electricityInterval > 0 && device.electricity && typeof device.electricity.get === 'function') {
|
|
256
260
|
const interval = setInterval(async () => {
|
|
257
261
|
await this._pollElectricity(device, subscription, config);
|
|
258
262
|
}, config.electricityInterval);
|
|
@@ -291,19 +295,19 @@ class ManagerSubscription extends EventEmitter {
|
|
|
291
295
|
/**
|
|
292
296
|
* Poll device state via System.All namespace.
|
|
293
297
|
*
|
|
298
|
+
* Called once on initial subscription to provide current state to listeners.
|
|
294
299
|
* Skips polling when push notifications were received recently or cached data
|
|
295
|
-
* is fresh to avoid redundant network requests.
|
|
300
|
+
* is fresh to avoid redundant network requests. After initial poll, ongoing
|
|
301
|
+
* updates rely on push notifications.
|
|
296
302
|
*
|
|
297
303
|
* @private
|
|
298
304
|
* @param {MerossDevice} device - Device to poll
|
|
299
305
|
* @param {Object} subscription - Subscription state object
|
|
300
306
|
*/
|
|
301
307
|
async _pollDeviceState(device, subscription) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
308
|
+
const namespace = 'Appliance.System.All';
|
|
309
|
+
if (this._hasRecentPush(subscription, namespace, 5000)) {
|
|
310
|
+
return;
|
|
307
311
|
}
|
|
308
312
|
|
|
309
313
|
const config = subscription.config || this.defaultConfig;
|
|
@@ -326,8 +330,9 @@ class ManagerSubscription extends EventEmitter {
|
|
|
326
330
|
/**
|
|
327
331
|
* Poll electricity metrics for all device channels.
|
|
328
332
|
*
|
|
329
|
-
*
|
|
330
|
-
* cached data
|
|
333
|
+
* Electricity metrics require polling as they don't support push notifications.
|
|
334
|
+
* Skips polling when all channels have fresh cached data (if smartCaching enabled)
|
|
335
|
+
* to reduce network traffic.
|
|
331
336
|
*
|
|
332
337
|
* @private
|
|
333
338
|
* @param {MerossDevice} device - Device to poll
|
|
@@ -335,17 +340,13 @@ class ManagerSubscription extends EventEmitter {
|
|
|
335
340
|
* @param {Object} config - Configuration object with caching settings
|
|
336
341
|
*/
|
|
337
342
|
async _pollElectricity(device, subscription, config) {
|
|
338
|
-
if (subscription.pushActive) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
343
|
try {
|
|
343
|
-
if (config.smartCaching &&
|
|
344
|
+
if (config.smartCaching && device._channelCachedSamples) {
|
|
344
345
|
const channels = device.channels || [{ index: 0 }];
|
|
345
346
|
let allCached = true;
|
|
346
347
|
|
|
347
348
|
for (const channel of channels) {
|
|
348
|
-
const cached = device.
|
|
349
|
+
const cached = device._channelCachedSamples.get(channel.index);
|
|
349
350
|
if (!cached || !cached.sampleTimestamp) {
|
|
350
351
|
allCached = false;
|
|
351
352
|
break;
|
|
@@ -364,7 +365,7 @@ class ManagerSubscription extends EventEmitter {
|
|
|
364
365
|
|
|
365
366
|
const channels = device.channels || [{ index: 0 }];
|
|
366
367
|
for (const channel of channels) {
|
|
367
|
-
await device.
|
|
368
|
+
await device.electricity.get({ channel: channel.index });
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
subscription.lastPollTimes.set('electricity', Date.now());
|
|
@@ -376,8 +377,9 @@ class ManagerSubscription extends EventEmitter {
|
|
|
376
377
|
/**
|
|
377
378
|
* Poll power consumption data for all device channels.
|
|
378
379
|
*
|
|
379
|
-
*
|
|
380
|
-
* consumption data
|
|
380
|
+
* Consumption data requires polling as it doesn't support push notifications.
|
|
381
|
+
* Skips polling when all channels have cached consumption data (if smartCaching enabled)
|
|
382
|
+
* to reduce network traffic.
|
|
381
383
|
*
|
|
382
384
|
* @private
|
|
383
385
|
* @param {MerossDevice} device - Device to poll
|
|
@@ -385,10 +387,6 @@ class ManagerSubscription extends EventEmitter {
|
|
|
385
387
|
* @param {Object} config - Configuration object with caching settings
|
|
386
388
|
*/
|
|
387
389
|
async _pollConsumption(device, subscription, config) {
|
|
388
|
-
if (subscription.pushActive) {
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
390
|
try {
|
|
393
391
|
if (config.smartCaching && device._channelCachedConsumption) {
|
|
394
392
|
const channels = device.channels || [{ index: 0 }];
|
|
@@ -419,29 +417,54 @@ class ManagerSubscription extends EventEmitter {
|
|
|
419
417
|
}
|
|
420
418
|
|
|
421
419
|
/**
|
|
422
|
-
* Mark push notifications as active
|
|
420
|
+
* Mark push notifications as active for a specific namespace.
|
|
423
421
|
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
422
|
+
* Tracks push notification activity per namespace to allow selective polling
|
|
423
|
+
* skipping. Only namespaces that receive push notifications will skip polling.
|
|
426
424
|
*
|
|
427
425
|
* @private
|
|
428
426
|
* @param {string} deviceUuid - Device UUID that received push notification
|
|
427
|
+
* @param {string} namespace - Namespace that received the push notification
|
|
429
428
|
*/
|
|
430
|
-
|
|
429
|
+
_setPushActive(deviceUuid, namespace) {
|
|
431
430
|
const subscription = this.subscriptions.get(deviceUuid);
|
|
432
431
|
if (!subscription) {
|
|
433
432
|
return;
|
|
434
433
|
}
|
|
435
434
|
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
if (!namespace) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const timestamp = Date.now();
|
|
440
|
+
subscription.pushActiveNamespaces.set(namespace, timestamp);
|
|
438
441
|
|
|
439
|
-
|
|
440
|
-
subscription
|
|
441
|
-
|
|
442
|
+
const timerKey = `pushInactivityTimer_${namespace}`;
|
|
443
|
+
clearTimeout(subscription[timerKey]);
|
|
444
|
+
subscription[timerKey] = setTimeout(() => {
|
|
445
|
+
subscription.pushActiveNamespaces.delete(namespace);
|
|
442
446
|
}, 60000);
|
|
443
447
|
}
|
|
444
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Check if a namespace received a recent push notification.
|
|
451
|
+
*
|
|
452
|
+
* @private
|
|
453
|
+
* @param {Object} subscription - Subscription state object
|
|
454
|
+
* @param {string} namespace - Namespace to check
|
|
455
|
+
* @param {number} maxAge - Maximum age in milliseconds to consider recent
|
|
456
|
+
* @returns {boolean} True if namespace received push within maxAge
|
|
457
|
+
*/
|
|
458
|
+
_hasRecentPush(subscription, namespace, maxAge) {
|
|
459
|
+
const pushTimestamp = subscription.pushActiveNamespaces.get(namespace);
|
|
460
|
+
if (!pushTimestamp) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const timeSincePush = Date.now() - pushTimestamp;
|
|
465
|
+
return timeSincePush < maxAge;
|
|
466
|
+
}
|
|
467
|
+
|
|
445
468
|
/**
|
|
446
469
|
* Handle unified state events from push notifications or polling.
|
|
447
470
|
*
|
|
@@ -453,13 +476,13 @@ class ManagerSubscription extends EventEmitter {
|
|
|
453
476
|
* @param {string} deviceUuid - Device UUID that changed state
|
|
454
477
|
* @param {Object} event - Unified state event from device
|
|
455
478
|
*/
|
|
456
|
-
|
|
479
|
+
_onStateEvent(deviceUuid, event) {
|
|
457
480
|
const subscription = this.subscriptions.get(deviceUuid);
|
|
458
481
|
if (!subscription) {
|
|
459
482
|
return;
|
|
460
483
|
}
|
|
461
484
|
|
|
462
|
-
//
|
|
485
|
+
// Refresh events already represent a full snapshot; distribute without diffing.
|
|
463
486
|
if (event.type === 'refresh') {
|
|
464
487
|
const update = {
|
|
465
488
|
source: event.source || 'poll',
|
|
@@ -473,17 +496,18 @@ class ManagerSubscription extends EventEmitter {
|
|
|
473
496
|
return;
|
|
474
497
|
}
|
|
475
498
|
|
|
476
|
-
// For
|
|
499
|
+
// For non-refresh events, compute a minimal changeset so listeners can avoid
|
|
500
|
+
// expensive full-state comparisons when they only care about deltas.
|
|
477
501
|
const changes = {};
|
|
478
502
|
if (event.type && event.value !== undefined) {
|
|
479
|
-
//
|
|
503
|
+
// Keep per-channel features grouped under their event type.
|
|
480
504
|
if (event.channel !== undefined) {
|
|
481
505
|
if (!changes[event.type]) {
|
|
482
506
|
changes[event.type] = {};
|
|
483
507
|
}
|
|
484
508
|
changes[event.type][event.channel] = event.value;
|
|
485
509
|
} else {
|
|
486
|
-
//
|
|
510
|
+
// Device-level updates (e.g., online status, properties) are not channel-scoped.
|
|
487
511
|
changes[event.type] = event.value;
|
|
488
512
|
}
|
|
489
513
|
}
|