react-native-ble-mesh 1.0.4 → 1.1.1

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.
@@ -0,0 +1,497 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Battery Optimizer with Adaptive Power Modes
5
+ * @module service/BatteryOptimizer
6
+ *
7
+ * Provides three power profiles (high/balanced/low) with automatic
8
+ * scanning adjustments based on battery level and network activity.
9
+ *
10
+ * Target: <5% battery drain per hour in balanced mode.
11
+ */
12
+
13
+ const EventEmitter = require('../utils/EventEmitter');
14
+
15
+ /**
16
+ * Battery mode constants
17
+ * @constant {Object}
18
+ */
19
+ const BATTERY_MODE = Object.freeze({
20
+ HIGH_PERFORMANCE: 'high',
21
+ BALANCED: 'balanced',
22
+ LOW_POWER: 'low',
23
+ AUTO: 'auto',
24
+ });
25
+
26
+ /**
27
+ * Battery profile configuration
28
+ * @typedef {Object} BatteryProfile
29
+ * @property {number} scanIntervalMs - BLE scan interval
30
+ * @property {number} scanWindowMs - BLE scan window
31
+ * @property {number} connectionIntervalMs - Connection interval
32
+ * @property {number} advertisingIntervalMs - Advertising interval
33
+ * @property {number} heartbeatIntervalMs - Heartbeat frequency
34
+ * @property {string} description - Human-readable description
35
+ */
36
+
37
+ /**
38
+ * Default battery profiles
39
+ * @constant {Object.<string, BatteryProfile>}
40
+ */
41
+ const DEFAULT_PROFILES = Object.freeze({
42
+ [BATTERY_MODE.HIGH_PERFORMANCE]: {
43
+ scanIntervalMs: 100,
44
+ scanWindowMs: 50,
45
+ connectionIntervalMs: 7.5,
46
+ advertisingIntervalMs: 100,
47
+ heartbeatIntervalMs: 10000,
48
+ description: 'Maximum performance, high battery usage',
49
+ },
50
+ [BATTERY_MODE.BALANCED]: {
51
+ scanIntervalMs: 500,
52
+ scanWindowMs: 100,
53
+ connectionIntervalMs: 30,
54
+ advertisingIntervalMs: 500,
55
+ heartbeatIntervalMs: 30000,
56
+ description: 'Balanced performance and battery, <5% drain/hour',
57
+ },
58
+ [BATTERY_MODE.LOW_POWER]: {
59
+ scanIntervalMs: 2000,
60
+ scanWindowMs: 200,
61
+ connectionIntervalMs: 100,
62
+ advertisingIntervalMs: 2000,
63
+ heartbeatIntervalMs: 60000,
64
+ description: 'Minimum battery usage, reduced responsiveness',
65
+ },
66
+ [BATTERY_MODE.AUTO]: {
67
+ // Auto mode uses balanced as base, adjusts dynamically
68
+ scanIntervalMs: 500,
69
+ scanWindowMs: 100,
70
+ connectionIntervalMs: 30,
71
+ advertisingIntervalMs: 500,
72
+ heartbeatIntervalMs: 30000,
73
+ description: 'Automatic adjustment based on battery level',
74
+ },
75
+ });
76
+
77
+ /**
78
+ * Battery level thresholds for auto mode
79
+ * @constant {Object}
80
+ */
81
+ const BATTERY_THRESHOLDS = Object.freeze({
82
+ HIGH: 50, // Above 50%: high performance
83
+ MEDIUM: 20, // 20-50%: balanced
84
+ LOW: 10, // 10-20%: low power
85
+ CRITICAL: 5, // Below 5%: ultra low power
86
+ });
87
+
88
+ /**
89
+ * Default configuration
90
+ * @constant {Object}
91
+ */
92
+ const DEFAULT_CONFIG = Object.freeze({
93
+ /** Initial battery mode */
94
+ initialMode: BATTERY_MODE.BALANCED,
95
+ /** Enable automatic mode switching */
96
+ autoAdjust: true,
97
+ /** Battery check interval (ms) */
98
+ batteryCheckIntervalMs: 60000,
99
+ /** Activity-based adjustment enabled */
100
+ activityAdjust: true,
101
+ /** Inactivity timeout for power reduction (ms) */
102
+ inactivityTimeoutMs: 5 * 60 * 1000,
103
+ });
104
+
105
+ /**
106
+ * Battery Optimizer for adaptive power management.
107
+ *
108
+ * @class BatteryOptimizer
109
+ * @extends EventEmitter
110
+ * @example
111
+ * const optimizer = new BatteryOptimizer();
112
+ *
113
+ * // Set mode manually
114
+ * await optimizer.setMode(BATTERY_MODE.BALANCED);
115
+ *
116
+ * // Enable auto-adjustment
117
+ * optimizer.setAutoAdjust(true);
118
+ *
119
+ * // Report battery level
120
+ * optimizer.updateBatteryLevel(75);
121
+ *
122
+ * // Get current profile
123
+ * const profile = optimizer.getCurrentProfile();
124
+ */
125
+ class BatteryOptimizer extends EventEmitter {
126
+ /**
127
+ * Creates a new BatteryOptimizer instance.
128
+ * @param {Object} [options={}] - Configuration options
129
+ */
130
+ constructor(options = {}) {
131
+ super();
132
+
133
+ /**
134
+ * Configuration
135
+ * @type {Object}
136
+ * @private
137
+ */
138
+ this._config = { ...DEFAULT_CONFIG, ...options };
139
+
140
+ /**
141
+ * Battery profiles
142
+ * @type {Object.<string, BatteryProfile>}
143
+ * @private
144
+ */
145
+ this._profiles = { ...DEFAULT_PROFILES };
146
+
147
+ /**
148
+ * Current battery mode
149
+ * @type {string}
150
+ * @private
151
+ */
152
+ this._currentMode = this._config.initialMode;
153
+
154
+ /**
155
+ * Current battery level (0-100)
156
+ * @type {number}
157
+ * @private
158
+ */
159
+ this._batteryLevel = 100;
160
+
161
+ /**
162
+ * Is charging
163
+ * @type {boolean}
164
+ * @private
165
+ */
166
+ this._isCharging = false;
167
+
168
+ /**
169
+ * Last activity timestamp
170
+ * @type {number}
171
+ * @private
172
+ */
173
+ this._lastActivityTime = Date.now();
174
+
175
+ /**
176
+ * Auto adjustment enabled
177
+ * @type {boolean}
178
+ * @private
179
+ */
180
+ this._autoAdjust = this._config.autoAdjust;
181
+
182
+ /**
183
+ * Battery check timer
184
+ * @type {number|null}
185
+ * @private
186
+ */
187
+ this._batteryCheckTimer = null;
188
+
189
+ /**
190
+ * Transport reference for applying settings
191
+ * @type {Object|null}
192
+ * @private
193
+ */
194
+ this._transport = null;
195
+
196
+ /**
197
+ * Statistics
198
+ * @type {Object}
199
+ * @private
200
+ */
201
+ this._stats = {
202
+ modeChanges: 0,
203
+ autoAdjustments: 0,
204
+ lastModeChange: null,
205
+ };
206
+
207
+ // Start battery monitoring if auto-adjust enabled
208
+ if (this._autoAdjust) {
209
+ this._startBatteryMonitoring();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Sets the transport to control.
215
+ * @param {Object} transport - Transport instance
216
+ */
217
+ setTransport(transport) {
218
+ this._transport = transport;
219
+ }
220
+
221
+ /**
222
+ * Sets the battery mode.
223
+ * @param {string} mode - Battery mode
224
+ * @returns {Promise<void>}
225
+ */
226
+ async setMode(mode) {
227
+ if (!Object.values(BATTERY_MODE).includes(mode)) {
228
+ throw new Error(`Invalid battery mode: ${mode}`);
229
+ }
230
+
231
+ const previousMode = this._currentMode;
232
+ this._currentMode = mode;
233
+
234
+ // If switching to AUTO, determine actual profile from battery level
235
+ const activeProfile = mode === BATTERY_MODE.AUTO
236
+ ? this._getProfileForBatteryLevel(this._batteryLevel)
237
+ : this._profiles[mode];
238
+
239
+ // Apply to transport
240
+ await this._applyProfile(activeProfile);
241
+
242
+ this._stats.modeChanges++;
243
+ this._stats.lastModeChange = Date.now();
244
+
245
+ this.emit('mode-changed', {
246
+ previous: previousMode,
247
+ current: mode,
248
+ profile: activeProfile,
249
+ });
250
+ }
251
+
252
+ /**
253
+ * Gets the current battery mode.
254
+ * @returns {string} Current mode
255
+ */
256
+ getMode() {
257
+ return this._currentMode;
258
+ }
259
+
260
+ /**
261
+ * Gets the current active profile.
262
+ * @returns {BatteryProfile} Active profile
263
+ */
264
+ getCurrentProfile() {
265
+ if (this._currentMode === BATTERY_MODE.AUTO) {
266
+ return this._getProfileForBatteryLevel(this._batteryLevel);
267
+ }
268
+ return this._profiles[this._currentMode];
269
+ }
270
+
271
+ /**
272
+ * Gets all available profiles.
273
+ * @returns {Object.<string, BatteryProfile>} Profiles
274
+ */
275
+ getProfiles() {
276
+ return { ...this._profiles };
277
+ }
278
+
279
+ /**
280
+ * Updates the battery level and triggers auto-adjustment if enabled.
281
+ * @param {number} level - Battery level (0-100)
282
+ * @param {boolean} [isCharging=false] - Whether device is charging
283
+ */
284
+ async updateBatteryLevel(level, isCharging = false) {
285
+ const previousLevel = this._batteryLevel;
286
+ this._batteryLevel = Math.max(0, Math.min(100, level));
287
+ this._isCharging = isCharging;
288
+
289
+ this.emit('battery-updated', {
290
+ level: this._batteryLevel,
291
+ isCharging,
292
+ previousLevel,
293
+ });
294
+
295
+ // Auto-adjust if enabled and in AUTO mode
296
+ if (this._autoAdjust && this._currentMode === BATTERY_MODE.AUTO) {
297
+ await this._autoAdjustMode();
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Enables or disables auto-adjustment.
303
+ * @param {boolean} enabled - Whether to enable
304
+ */
305
+ setAutoAdjust(enabled) {
306
+ this._autoAdjust = enabled;
307
+
308
+ if (enabled && !this._batteryCheckTimer) {
309
+ this._startBatteryMonitoring();
310
+ } else if (!enabled && this._batteryCheckTimer) {
311
+ this._stopBatteryMonitoring();
312
+ }
313
+
314
+ this.emit('auto-adjust-changed', { enabled });
315
+ }
316
+
317
+ /**
318
+ * Checks if auto-adjustment is enabled.
319
+ * @returns {boolean} True if enabled
320
+ */
321
+ isAutoAdjustEnabled() {
322
+ return this._autoAdjust;
323
+ }
324
+
325
+ /**
326
+ * Records user activity (for activity-based optimization).
327
+ */
328
+ recordActivity() {
329
+ this._lastActivityTime = Date.now();
330
+ }
331
+
332
+ /**
333
+ * Gets the battery level.
334
+ * @returns {number} Battery level (0-100)
335
+ */
336
+ getBatteryLevel() {
337
+ return this._batteryLevel;
338
+ }
339
+
340
+ /**
341
+ * Checks if device is charging.
342
+ * @returns {boolean} True if charging
343
+ */
344
+ isCharging() {
345
+ return this._isCharging;
346
+ }
347
+
348
+ /**
349
+ * Gets optimizer statistics.
350
+ * @returns {Object} Statistics
351
+ */
352
+ getStats() {
353
+ return {
354
+ ...this._stats,
355
+ currentMode: this._currentMode,
356
+ batteryLevel: this._batteryLevel,
357
+ isCharging: this._isCharging,
358
+ autoAdjust: this._autoAdjust,
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Destroys the optimizer.
364
+ */
365
+ destroy() {
366
+ this._stopBatteryMonitoring();
367
+ this._transport = null;
368
+ this.removeAllListeners();
369
+ }
370
+
371
+ /**
372
+ * Gets the appropriate profile for a battery level.
373
+ * @param {number} level - Battery level
374
+ * @returns {BatteryProfile} Profile
375
+ * @private
376
+ */
377
+ _getProfileForBatteryLevel(level) {
378
+ if (this._isCharging) {
379
+ return this._profiles[BATTERY_MODE.HIGH_PERFORMANCE];
380
+ }
381
+
382
+ if (level > BATTERY_THRESHOLDS.HIGH) {
383
+ return this._profiles[BATTERY_MODE.HIGH_PERFORMANCE];
384
+ } else if (level > BATTERY_THRESHOLDS.MEDIUM) {
385
+ return this._profiles[BATTERY_MODE.BALANCED];
386
+ } else {
387
+ return this._profiles[BATTERY_MODE.LOW_POWER];
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Auto-adjusts mode based on battery level.
393
+ * @returns {Promise<void>}
394
+ * @private
395
+ */
396
+ async _autoAdjustMode() {
397
+ const profile = this._getProfileForBatteryLevel(this._batteryLevel);
398
+ const currentProfile = this.getCurrentProfile();
399
+
400
+ // Only apply if profile actually changed
401
+ if (profile.scanIntervalMs !== currentProfile.scanIntervalMs) {
402
+ await this._applyProfile(profile);
403
+ this._stats.autoAdjustments++;
404
+
405
+ this.emit('auto-adjusted', {
406
+ batteryLevel: this._batteryLevel,
407
+ profile,
408
+ });
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Applies a battery profile to the transport.
414
+ * @param {BatteryProfile} profile - Profile to apply
415
+ * @returns {Promise<void>}
416
+ * @private
417
+ */
418
+ async _applyProfile(profile) {
419
+ if (!this._transport) {
420
+ return;
421
+ }
422
+
423
+ try {
424
+ // Apply scan parameters
425
+ if (typeof this._transport.setScanParameters === 'function') {
426
+ await this._transport.setScanParameters({
427
+ interval: profile.scanIntervalMs,
428
+ window: profile.scanWindowMs,
429
+ });
430
+ }
431
+
432
+ // Apply connection parameters
433
+ if (typeof this._transport.setConnectionParameters === 'function') {
434
+ await this._transport.setConnectionParameters({
435
+ interval: profile.connectionIntervalMs,
436
+ });
437
+ }
438
+
439
+ // Apply advertising parameters
440
+ if (typeof this._transport.setAdvertisingInterval === 'function') {
441
+ await this._transport.setAdvertisingInterval(profile.advertisingIntervalMs);
442
+ }
443
+
444
+ this.emit('profile-applied', { profile });
445
+ } catch (error) {
446
+ this.emit('error', {
447
+ message: 'Failed to apply battery profile',
448
+ error: error.message,
449
+ });
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Starts battery monitoring timer.
455
+ * @private
456
+ */
457
+ _startBatteryMonitoring() {
458
+ if (this._batteryCheckTimer) {
459
+ return;
460
+ }
461
+
462
+ this._batteryCheckTimer = setInterval(
463
+ async () => {
464
+ // Check for inactivity
465
+ if (this._config.activityAdjust) {
466
+ const inactiveTime = Date.now() - this._lastActivityTime;
467
+ if (inactiveTime > this._config.inactivityTimeoutMs) {
468
+ // Switch to low power if inactive
469
+ if (this._currentMode === BATTERY_MODE.AUTO) {
470
+ await this._applyProfile(this._profiles[BATTERY_MODE.LOW_POWER]);
471
+ }
472
+ }
473
+ }
474
+ },
475
+ this._config.batteryCheckIntervalMs
476
+ );
477
+ }
478
+
479
+ /**
480
+ * Stops battery monitoring timer.
481
+ * @private
482
+ */
483
+ _stopBatteryMonitoring() {
484
+ if (this._batteryCheckTimer) {
485
+ clearInterval(this._batteryCheckTimer);
486
+ this._batteryCheckTimer = null;
487
+ }
488
+ }
489
+ }
490
+
491
+ module.exports = {
492
+ BatteryOptimizer,
493
+ BATTERY_MODE,
494
+ BATTERY_THRESHOLDS,
495
+ DEFAULT_PROFILES,
496
+ DEFAULT_CONFIG,
497
+ };