ilabs-flir 2.4.1 → 2.4.3

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.
@@ -44,6 +44,9 @@ object FlirManager {
44
44
  private var isStreaming = false
45
45
  private var connectedDeviceId: String? = null
46
46
  private var connectedDeviceName: String? = null
47
+
48
+ // Manual gating
49
+ var manualOnly: Boolean = true
47
50
 
48
51
  // Concurrency control
49
52
  private val shouldProcessFrames = java.util.concurrent.atomic.AtomicBoolean(false)
@@ -124,17 +127,33 @@ object FlirManager {
124
127
  */
125
128
  @Synchronized
126
129
  fun startDiscovery(retry: Boolean = false) {
130
+ if (manualOnly && !retry) {
131
+ Log.w(TAG, "🔭 [FLIR] Discovery blocked: manualOnly is enabled. Use startManualDiscovery() or disable the gate.")
132
+ return
133
+ }
134
+
127
135
  if (!isInitialized && reactContext != null) {
128
136
  init(reactContext!!)
129
137
  }
130
138
 
131
139
  if (isScanning && !retry) return
132
140
 
133
- Log.i(TAG, "Starting FlirManager discovery...")
141
+ Log.i(TAG, "🔭 [FLIR] Starting discovery (retry=$retry)...")
134
142
  isScanning = true
135
143
  emitDeviceState("discovering")
136
144
  sdkManager?.scan()
137
145
  }
146
+
147
+ /**
148
+ * Explicitly starts discovery regardless of manualOnly flag.
149
+ * Use this for JS-triggered scans.
150
+ */
151
+ @Synchronized
152
+ fun startManualDiscovery() {
153
+ Log.i(TAG, "🔭 [FLIR] Manual discovery requested - opening gate.")
154
+ manualOnly = false
155
+ startDiscovery(true)
156
+ }
138
157
 
139
158
  /**
140
159
  * Start discovery with React context
@@ -145,7 +145,9 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
145
145
  palettes.forEach { result.pushString(it) }
146
146
  promise?.resolve(result)
147
147
  } catch (e: Throwable) {
148
- promise?.reject("ERR_FLIR_PALETTES", e)
148
+ try {
149
+ promise?.reject("ERR_FLIR_PALETTES", e)
150
+ } catch (ignored: Exception) {}
149
151
  }
150
152
  }
151
153
 
@@ -163,7 +165,9 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
163
165
  }
164
166
  promise?.resolve(result)
165
167
  } catch (e: Throwable) {
166
- promise?.reject("ERR_FLIR_PALETTE_ICONS", e)
168
+ try {
169
+ promise?.reject("ERR_FLIR_PALETTE_ICONS", e)
170
+ } catch (ignored: Exception) {}
167
171
  }
168
172
  }
169
173
 
@@ -234,7 +238,7 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
234
238
  // Ensure SDK is initialized with context before starting discovery
235
239
  FlirManager.init(reactContext)
236
240
  // With simplified API, just start discovery - emulators are discovered like any device
237
- FlirManager.startDiscovery(true)
241
+ FlirManager.startManualDiscovery()
238
242
  promise?.resolve(true)
239
243
  } catch (e: Exception) {
240
244
  promise?.reject("ERR_FLIR_EMULATOR", e)
@@ -281,7 +285,7 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
281
285
  try {
282
286
  // Ensure SDK is initialized with context before starting discovery
283
287
  FlirManager.init(reactContext)
284
- FlirManager.startDiscovery(true)
288
+ FlirManager.startManualDiscovery()
285
289
  promise?.resolve(true)
286
290
  } catch (e: Exception) {
287
291
  promise?.reject("ERR_FLIR_DISCOVERY", e)
@@ -336,7 +340,7 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
336
340
  @ReactMethod
337
341
  fun resumeFlirAfterPreview(promise: Promise?) {
338
342
  try {
339
- FlirManager.startDiscovery(true)
343
+ FlirManager.startManualDiscovery()
340
344
  promise?.resolve(true)
341
345
  } catch (e: Exception) {
342
346
  promise?.reject("ERR_RESUME_FLIR", e)
@@ -345,17 +349,26 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
345
349
 
346
350
  @ReactMethod
347
351
  fun captureRadiometricSnapshot(path: String, promise: Promise?) {
352
+ val snapshotPromise = promise
353
+ var settled = java.util.concurrent.atomic.AtomicBoolean(false)
354
+
348
355
  try {
349
356
  FlirManager.captureRadiometricSnapshot(path, object : FlirSdkManager.SnapshotCallback {
350
357
  override fun onSnapshotSaved(savedPath: String) {
351
- promise?.resolve(savedPath)
358
+ if (settled.compareAndSet(false, true)) {
359
+ snapshotPromise?.resolve(savedPath)
360
+ }
352
361
  }
353
362
  override fun onSnapshotError(message: String) {
354
- promise?.reject("ERR_RADIOMETRIC_SAVE", message)
363
+ if (settled.compareAndSet(false, true)) {
364
+ snapshotPromise?.reject("ERR_RADIOMETRIC_SAVE", message)
365
+ }
355
366
  }
356
367
  })
357
368
  } catch (e: Exception) {
358
- promise?.reject("ERR_RADIOMETRIC_TRIGGER", e.message)
369
+ if (settled.compareAndSet(false, true)) {
370
+ promise?.reject("ERR_RADIOMETRIC_TRIGGER", e.message)
371
+ }
359
372
  }
360
373
  }
361
374
 
@@ -395,6 +408,16 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
395
408
  }
396
409
  }
397
410
 
411
+ @ReactMethod
412
+ fun setManualDiscoveryOnly(enabled: Boolean, promise: Promise?) {
413
+ try {
414
+ FlirManager.manualOnly = enabled
415
+ promise?.resolve(true)
416
+ } catch (e: Exception) {
417
+ promise?.reject("ERR_FLIR_GATE", e)
418
+ }
419
+ }
420
+
398
421
  @ReactMethod
399
422
  fun initializeSDK(promise: Promise?) {
400
423
  try {
@@ -409,7 +432,9 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
409
432
  result.putBoolean("initialized", false)
410
433
  result.putString("error", e.message ?: "Unknown error")
411
434
  result.putString("errorType", e.javaClass.simpleName)
412
- promise?.resolve(result)
435
+ try {
436
+ promise?.resolve(result)
437
+ } catch (ignored: Exception) {}
413
438
  }
414
439
  }
415
440
 
@@ -83,7 +83,7 @@ public class FlirSdkManager {
83
83
  void onSnapshotError(String message);
84
84
  }
85
85
 
86
- private SnapshotCallback snapshotCallback;
86
+ private volatile SnapshotCallback snapshotCallback;
87
87
 
88
88
  private FlirSdkManager(Context context) {
89
89
  this.context = context.getApplicationContext();
@@ -470,11 +470,13 @@ public class FlirSdkManager {
470
470
  Log.i(TAG, "[SNAPSHOT] ✅ Success: Radiometric snapshot saved");
471
471
  if (snapshotCallback != null) {
472
472
  snapshotCallback.onSnapshotSaved(snapshotPath);
473
+ snapshotCallback = null;
473
474
  }
474
475
  } catch (java.io.IOException e) {
475
476
  Log.e(TAG, "Failed to save radiometric snapshot", e);
476
477
  if (snapshotCallback != null) {
477
478
  snapshotCallback.onSnapshotError(e.getMessage());
479
+ snapshotCallback = null;
478
480
  }
479
481
  }
480
482
  }
@@ -28,10 +28,8 @@ class FlirView(context: ThemedReactContext) : FrameLayout(context) {
28
28
 
29
29
  override fun onAttachedToWindow() {
30
30
  super.onAttachedToWindow()
31
- // Let the centralized manager handle discovery and streaming and emit events
32
- try {
33
- FlirManager.startDiscoveryAndConnect(context as ThemedReactContext)
34
- } catch (ignored: Throwable) {}
31
+ // GATING: Automatic discovery on attach is disabled to enforce manual discovery
32
+ // FlirManager.startDiscoveryAndConnect(context as ThemedReactContext)
35
33
  }
36
34
 
37
35
  override fun onDetachedFromWindow() {
@@ -66,6 +66,9 @@ import ThermalSDK
66
66
  // Internal synchronization
67
67
  private let stateLock = NSObject()
68
68
 
69
+ // Manual gating
70
+ @objc public var manualOnly: Bool = true
71
+
69
72
  // Palette and Snapshot state
70
73
  private var currentPaletteName: String = "WhiteHot"
71
74
  private var pendingSnapshotPath: String?
@@ -113,7 +116,12 @@ import ThermalSDK
113
116
  // MARK: - Discovery
114
117
 
115
118
  @objc public func startDiscovery() {
116
- NSLog("[FlirManager] startDiscovery")
119
+ if manualOnly {
120
+ NSLog("[FlirManager] 🔭 Discovery blocked: manualOnly is enabled. Use startManualDiscovery() or disable the gate.")
121
+ return
122
+ }
123
+
124
+ NSLog("[FlirManager] 🔭 startDiscovery (manualOnly=false)")
117
125
 
118
126
  #if FLIR_ENABLED
119
127
  discoveredDevices.removeAll()
@@ -133,6 +141,14 @@ import ThermalSDK
133
141
  #endif
134
142
  }
135
143
 
144
+ /// Explicitly starts discovery regardless of manualOnly flag.
145
+ /// Use this for JS-triggered scans.
146
+ @objc public func startManualDiscovery() {
147
+ NSLog("[FlirManager] 🔭 Manual discovery requested - opening gate.")
148
+ manualOnly = false
149
+ startDiscovery()
150
+ }
151
+
136
152
  @objc public func stopDiscovery() {
137
153
  objc_sync_enter(stateLock); defer { objc_sync_exit(stateLock) }
138
154
  NSLog("[FlirManager] stopDiscovery")
@@ -151,13 +151,15 @@ RCT_EXPORT_METHOD(startDiscovery : (RCTPromiseResolveBlock)
151
151
  dispatch_async(dispatch_get_main_queue(), ^{
152
152
  id manager = flir_manager_shared();
153
153
  if (manager &&
154
- [manager respondsToSelector:sel_registerName("startDiscovery")]) {
155
- NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.startDiscovery",
154
+ [manager respondsToSelector:sel_registerName("startManualDiscovery")]) {
155
+ NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.startManualDiscovery",
156
156
  [NSDate date]);
157
+ ((void (*)(id, SEL))objc_msgSend)(manager,
158
+ sel_registerName("startManualDiscovery"));
159
+ } else if (manager && [manager respondsToSelector:sel_registerName("startDiscovery")]) {
160
+ // Fallback for older manager versions
157
161
  ((void (*)(id, SEL))objc_msgSend)(manager,
158
162
  sel_registerName("startDiscovery"));
159
- NSLog(@"[FlirModule] [%@] ⏱ FlirManager.startDiscovery returned",
160
- [NSDate date]);
161
163
  }
162
164
  if (resolve) resolve(@(YES));
163
165
  });
@@ -291,6 +293,11 @@ RCT_EXPORT_METHOD(startEmulator : (NSString *)emulatorType resolver : (
291
293
  ((void (*)(id, SEL, id))objc_msgSend)(
292
294
  manager, sel_registerName("startEmulatorWithType:"), emulatorType ?: @"FLIR_ONE_EDGE");
293
295
 
296
+ // Opening the gate for emulator discovery too
297
+ if ([manager respondsToSelector:sel_registerName("setManualOnly:")]) {
298
+ ((void (*)(id, SEL, BOOL))objc_msgSend)(manager, sel_registerName("setManualOnly:"), NO);
299
+ }
300
+
294
301
  // Resolve immediately - connection status will come via events
295
302
  if (resolve) resolve(@(YES));
296
303
  } else {
@@ -445,7 +452,10 @@ RCT_EXPORT_METHOD(resumeFlirAfterPreview : (RCTPromiseResolveBlock)
445
452
  resolve rejecter : (RCTPromiseRejectBlock)reject) {
446
453
  dispatch_async(dispatch_get_main_queue(), ^{
447
454
  id manager = flir_manager_shared();
448
- if (manager && [manager respondsToSelector:sel_registerName("startDiscovery")]) {
455
+ if (manager && [manager respondsToSelector:sel_registerName("startManualDiscovery")]) {
456
+ atomic_store(&_isCapturing, true);
457
+ ((void (*)(id, SEL))objc_msgSend)(manager, sel_registerName("startManualDiscovery"));
458
+ } else if (manager && [manager respondsToSelector:sel_registerName("startDiscovery")]) {
449
459
  atomic_store(&_isCapturing, true);
450
460
  ((void (*)(id, SEL))objc_msgSend)(manager, sel_registerName("startDiscovery"));
451
461
  }
@@ -455,6 +465,17 @@ RCT_EXPORT_METHOD(resumeFlirAfterPreview : (RCTPromiseResolveBlock)
455
465
 
456
466
 
457
467
 
468
+ RCT_EXPORT_METHOD(setManualDiscoveryOnly : (BOOL)enabled resolver : (
469
+ RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
470
+ dispatch_async(dispatch_get_main_queue(), ^{
471
+ id manager = flir_manager_shared();
472
+ if (manager && [manager respondsToSelector:sel_registerName("setManualOnly:")]) {
473
+ ((void (*)(id, SEL, BOOL))objc_msgSend)(manager, sel_registerName("setManualOnly:"), enabled);
474
+ }
475
+ if (resolve) resolve(@(YES));
476
+ });
477
+ }
478
+
458
479
  RCT_EXPORT_METHOD(getSDKStatus : (RCTPromiseResolveBlock)
459
480
  resolve rejecter : (RCTPromiseRejectBlock)reject) {
460
481
  if (resolve) resolve(@{@"available" : @(YES), @"arch" : @"arm64", @"platform" : @"iOS"});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",