ilabs-flir 2.2.8 → 2.2.10

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.
@@ -413,7 +413,7 @@ import ThermalSDK
413
413
  discoveryTimeoutWorkItem?.cancel()
414
414
  let timeoutWork = DispatchWorkItem { [weak self] in
415
415
  guard let self = self, self.isScanning else { return }
416
- FlirLogger.log(.discovery, "⏱ Discovery timeout reached - stopping scan")
416
+ FlirLogger.log(.discovery, "DISCOVERY TIMEOUT triggered (8s) - stopping scan")
417
417
  self.discovery?.stop()
418
418
  self.isScanning = false
419
419
 
@@ -481,16 +481,31 @@ import ThermalSDK
481
481
 
482
482
  // MARK: - Connection
483
483
 
484
+ // Flag to prevent re-entrant connections
485
+ private var isConnecting = false
486
+
484
487
  @objc public func connectToDevice(_ deviceId: String) {
485
488
  FlirLogger.logConnectionAttempt(deviceId: deviceId)
486
489
 
487
490
  #if FLIR_ENABLED
488
- // Guard: if already connected, disconnect first
489
- if _isConnected {
490
- FlirLogger.log(.connection, "⚠️ Already connected - disconnecting first...")
491
+ // Guard: if already connecting, skip (like demo app's guard pattern)
492
+ guard !isConnecting else {
493
+ FlirLogger.log(.connection, "⚠️ Connection already in progress - skipping")
494
+ return
495
+ }
496
+
497
+ // Guard: if already connected to this device, skip (matches demo app)
498
+ if _isConnected && connectedDeviceId == deviceId {
499
+ FlirLogger.log(.connection, "⚠️ Already connected to this device - skipping")
500
+ return
501
+ }
502
+
503
+ // If connected to a different device, disconnect first but DON'T block
504
+ if _isConnected && connectedDeviceId != deviceId {
505
+ FlirLogger.log(.connection, "⚠️ Connected to different device - disconnecting first...")
491
506
  disconnect()
492
- // Give disconnect a moment to complete
493
- Thread.sleep(forTimeInterval: 0.5)
507
+ // Don't block! The new connection will proceed immediately
508
+ // The disconnect is async and will complete in the background
494
509
  }
495
510
 
496
511
  // Find the identity for this device
@@ -503,32 +518,14 @@ import ThermalSDK
503
518
  return
504
519
  }
505
520
 
506
- // Run connection on background thread with overall timeout
507
- let timeoutSeconds: Double = 15.0
508
- var connectionCompleted = false
509
- let connectionQueue = DispatchQueue.global(qos: .userInitiated)
510
-
511
- // Start connection attempt
512
- connectionQueue.async { [weak self] in
513
- self?.performConnection(identity: identity)
514
- connectionCompleted = true
515
- }
521
+ isConnecting = true
516
522
 
517
- // Monitor for timeout
518
- DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + timeoutSeconds) { [weak self] in
519
- guard let self = self, !connectionCompleted else { return }
520
-
521
- FlirLogger.logError(.connection, "⏱ Connection timeout after \(timeoutSeconds) seconds - aborting")
522
-
523
- // Force cleanup
524
- self.camera = nil
525
- self._isConnected = false
526
- self.connectedDeviceId = nil
527
-
528
- DispatchQueue.main.async {
529
- self.emitStateChange("connection_failed")
530
- self.delegate?.onError("Connection timeout - device not responding")
531
- }
523
+ // Run connection on background thread - simple pattern matching demo app
524
+ // Demo app uses: DispatchQueue.global().async { try pair(); try connect(); try stream.start() }
525
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
526
+ guard let self = self else { return }
527
+ self.performConnection(identity: identity)
528
+ self.isConnecting = false
532
529
  }
533
530
  #else
534
531
  FlirLogger.logError(.connection, "FLIR SDK not available")
@@ -537,6 +534,7 @@ import ThermalSDK
537
534
  }
538
535
  #endif
539
536
  }
537
+
540
538
 
541
539
  #if FLIR_ENABLED
542
540
  private func findIdentity(for deviceId: String) -> FLIRIdentity? {
@@ -546,6 +544,8 @@ import ThermalSDK
546
544
  private func performConnection(identity: FLIRIdentity) {
547
545
  // Use the proven connection pattern from FLIR SDK samples:
548
546
  // FLIROneCameraSwift uses: pair(identity, code:) then connect()
547
+ let startTime = Date()
548
+ FlirLogger.log(.connection, "⏱ performConnection STARTED for: \(identity.deviceId())")
549
549
  FlirLogger.log(.connection, "performConnection starting for: \(identity.deviceId())")
550
550
 
551
551
  if camera == nil {
@@ -563,45 +563,42 @@ import ThermalSDK
563
563
  }
564
564
 
565
565
  // Handle authentication for generic cameras (network cameras)
566
+ // Note: This uses a polling approach since the SDK doesn't provide async callback
567
+ // Reduced polling frequency to minimize blocking
566
568
  if identity.cameraType() == .generic {
567
- FlirLogger.log(.connection, "Generic/network camera - starting authentication...")
569
+ FlirLogger.log(.connection, "Generic/network camera - attempting authentication...")
568
570
  let certName = getCertificateName()
569
- var status = FLIRAuthenticationStatus.pending
570
- var attempts = 0
571
- let maxAttempts = 10 // 10 seconds max
572
-
573
- while status == .pending && attempts < maxAttempts {
574
- status = cam.authenticate(identity, trustedConnectionName: certName)
575
- if status == .pending {
576
- FlirLogger.log(.connection, "Waiting for camera authentication approval... (\(attempts + 1)/\(maxAttempts))")
577
- Thread.sleep(forTimeInterval: 1.0)
578
- attempts += 1
579
- }
580
- }
571
+ let status = cam.authenticate(identity, trustedConnectionName: certName)
581
572
 
582
573
  if status == .pending {
583
- FlirLogger.logError(.connection, "Authentication timeout after \(maxAttempts) seconds")
574
+ // For pending auth, just log and continue - user needs to approve on camera
575
+ FlirLogger.log(.connection, "Authentication pending - camera may require approval")
576
+ // Don't block here - proceed with connection attempt
577
+ // If auth is required, the connect() call will fail appropriately
578
+ } else if status == .notUsed || status == .trusted {
579
+ FlirLogger.log(.connection, "✅ Authentication status: \(status.rawValue)")
580
+ } else {
581
+ FlirLogger.logError(.connection, "Authentication failed with status: \(status.rawValue)")
584
582
  DispatchQueue.main.async { [weak self] in
585
- self?.delegate?.onError("Camera authentication timeout - device may require approval")
583
+ self?.emitStateChange("connection_failed")
584
+ self?.delegate?.onError("Camera authentication failed")
586
585
  }
587
586
  return
588
587
  }
589
-
590
- FlirLogger.log(.connection, "Authentication status: \(status.rawValue)")
591
588
  }
592
589
 
593
590
  do {
594
591
  // Step 1: Pair with identity (required for FLIR One devices)
595
592
  // The code parameter is for BLE pairing, 0 for direct connection
596
- FlirLogger.log(.connection, "Step 1: Pairing with device...")
593
+ FlirLogger.log(.connection, "Step 1: PAIRING starting... [time since start: \(Date().timeIntervalSince(startTime))s]")
597
594
  try cam.pair(identity, code: 0)
598
- FlirLogger.log(.connection, "✅ Paired successfully with: \(identity.deviceId())")
595
+ FlirLogger.log(.connection, "✅ Step 1: PAIRED successfully [time: \(Date().timeIntervalSince(startTime))s]")
599
596
 
600
597
  // Step 2: Connect (no identity parameter - uses paired identity)
601
598
  // Note: This can hang on some devices - ensure we have timeout in place
602
- FlirLogger.log(.connection, "Step 2: Connecting to device...")
599
+ FlirLogger.log(.connection, "Step 2: CONNECTING starting... [time: \(Date().timeIntervalSince(startTime))s]")
603
600
  try cam.connect()
604
- FlirLogger.log(.connection, "✅ Connected successfully to: \(identity.deviceId())")
601
+ FlirLogger.log(.connection, "✅ Step 2: CONNECTED successfully [time: \(Date().timeIntervalSince(startTime))s]")
605
602
 
606
603
  // Update state
607
604
  connectedIdentity = identity
@@ -702,9 +699,15 @@ import ThermalSDK
702
699
  }
703
700
 
704
701
  @objc public func stopStream() {
702
+ #if FLIR_ENABLED
703
+ // Guard: if not streaming, skip to avoid redundant operations
704
+ guard _isStreaming || stream != nil else {
705
+ FlirLogger.log(.streaming, "Not streaming - skipping stopStream")
706
+ return
707
+ }
708
+
705
709
  FlirLogger.log(.streaming, "Stopping stream...")
706
710
 
707
- #if FLIR_ENABLED
708
711
  stream?.stop()
709
712
  stream = nil
710
713
  streamer = nil
@@ -737,6 +740,9 @@ import ThermalSDK
737
740
  FlirLogger.logError(.streaming, "Stream start failed", error: error)
738
741
  stream = nil
739
742
  streamer = nil
743
+ _isStreaming = false
744
+ // Emit state change so RN knows streaming failed
745
+ emitStateChange("connected") // Fall back to connected (not streaming)
740
746
  delegate?.onError("Stream start failed: \(error.localizedDescription)")
741
747
  }
742
748
  }
@@ -745,9 +751,15 @@ import ThermalSDK
745
751
  // MARK: - Disconnect
746
752
 
747
753
  @objc public func disconnect() {
754
+ #if FLIR_ENABLED
755
+ // Guard: if already disconnected, do nothing (prevents re-entrant issues)
756
+ guard _isConnected || stream != nil || camera != nil else {
757
+ FlirLogger.log(.disconnect, "Already disconnected - skipping")
758
+ return
759
+ }
760
+
748
761
  FlirLogger.log(.disconnect, "Disconnecting...")
749
762
 
750
- #if FLIR_ENABLED
751
763
  // Stop battery polling
752
764
  stopBatteryPolling()
753
765
 
@@ -887,6 +899,12 @@ import ThermalSDK
887
899
  // MARK: - State Emission
888
900
 
889
901
  private func emitStateChange(_ state: String) {
902
+ let timestamp = Date()
903
+ let formatter = DateFormatter()
904
+ formatter.dateFormat = "HH:mm:ss.SSS"
905
+ let timeStr = formatter.string(from: timestamp)
906
+ FlirLogger.log(.connection, "⏱ [\(timeStr)] EMITTING STATE to RN: '\(state)'")
907
+
890
908
  DispatchQueue.main.async { [weak self] in
891
909
  guard let self = self else { return }
892
910
  self.delegate?.onStateChanged(
@@ -895,6 +913,7 @@ import ThermalSDK
895
913
  isStreaming: self._isStreaming,
896
914
  isEmulator: self.isEmulator
897
915
  )
916
+ FlirLogger.log(.connection, "✅ [\(timeStr)] State '\(state)' emitted to RN")
898
917
  }
899
918
  }
900
919
 
@@ -204,12 +204,15 @@ RCT_EXPORT_METHOD(setNetworkDiscoveryEnabled : (BOOL)enabled resolver : (
204
204
 
205
205
  RCT_EXPORT_METHOD(startDiscovery : (RCTPromiseResolveBlock)
206
206
  resolve rejecter : (RCTPromiseRejectBlock)reject) {
207
+ NSLog(@"[FlirModule] [%@] ⏱ RN->startDiscovery called", [NSDate date]);
207
208
  dispatch_async(dispatch_get_main_queue(), ^{
208
209
  id manager = flir_manager_shared();
209
210
  if (manager &&
210
211
  [manager respondsToSelector:sel_registerName("startDiscovery")]) {
212
+ NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.startDiscovery", [NSDate date]);
211
213
  ((void (*)(id, SEL))objc_msgSend)(manager,
212
214
  sel_registerName("startDiscovery"));
215
+ NSLog(@"[FlirModule] [%@] ⏱ FlirManager.startDiscovery returned", [NSDate date]);
213
216
  }
214
217
  resolve(@(YES));
215
218
  });
@@ -250,13 +253,12 @@ RCT_EXPORT_METHOD(getDiscoveredDevices : (RCTPromiseResolveBlock)
250
253
 
251
254
  RCT_EXPORT_METHOD(connectToDevice : (NSString *)deviceId resolver : (
252
255
  RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
256
+ NSLog(@"[FlirModule] [%@] ⏱ RN->connectToDevice called for: %@", [NSDate date], deviceId);
253
257
  dispatch_async(dispatch_get_main_queue(), ^{
254
- NSLog(@"[FlirModule] connectToDevice called for: %@", deviceId);
255
-
256
258
  id manager = flir_manager_shared();
257
259
  if (manager &&
258
260
  [manager respondsToSelector:sel_registerName("connectToDevice:")]) {
259
- NSLog(@"[FlirModule] Calling FlirManager.connectToDevice");
261
+ NSLog(@"[FlirModule] [%@] ⏱ Calling FlirManager.connectToDevice", [NSDate date]);
260
262
 
261
263
  // Store callbacks for event-driven updates (but don't block on them)
262
264
  self.connectResolve = nil; // Don't use promise for blocking
@@ -266,10 +268,12 @@ RCT_EXPORT_METHOD(connectToDevice : (NSString *)deviceId resolver : (
266
268
  ((void (*)(id, SEL, id))objc_msgSend)(
267
269
  manager, sel_registerName("connectToDevice:"), deviceId);
268
270
 
271
+ NSLog(@"[FlirModule] [%@] ⏱ FlirManager.connectToDevice returned (async started)", [NSDate date]);
272
+
269
273
  // Resolve immediately - connection status will come via events
270
274
  resolve(@(YES));
271
275
  } else {
272
- NSLog(@"[FlirModule] FlirManager not found");
276
+ NSLog(@"[FlirModule] [%@] ❌ FlirManager not found", [NSDate date]);
273
277
  reject(@"ERR_NO_MANAGER", @"FlirManager not found", nil);
274
278
  }
275
279
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.8",
3
+ "version": "2.2.10",
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",