ilabs-flir 2.2.5 → 2.2.6

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.
@@ -49,11 +49,7 @@ import ThermalSDK
49
49
  }
50
50
 
51
51
  /// Main FLIR Manager - Singleton that manages all FLIR camera operations
52
- #if FLIR_ENABLED
53
- @objc public class FlirManager: NSObject, FLIRDiscoveryEventDelegate, FLIRDataReceivedDelegate, FLIRStreamDelegate {
54
- #else
55
52
  @objc public class FlirManager: NSObject {
56
- #endif
57
53
  @objc public static let shared = FlirManager()
58
54
 
59
55
  // MARK: - Properties
@@ -78,9 +74,8 @@ import ThermalSDK
78
74
  // Client lifecycle for discovery/connection ownership
79
75
  private var activeClients: Set<String> = []
80
76
  private var shutdownWorkItem: DispatchWorkItem? = nil
81
- // Discovery & stream watchdogs to detect silent failures/timeouts
77
+ // Discovery timeout to prevent infinite scanning
82
78
  private var discoveryTimeoutWorkItem: DispatchWorkItem? = nil
83
- private var streamWatchdogWorkItem: DispatchWorkItem? = nil
84
79
 
85
80
  // Battery polling timer (like Android)
86
81
  private var batteryPollingTimer: Timer?
@@ -93,6 +88,7 @@ import ThermalSDK
93
88
  private var stream: FLIRStream?
94
89
  private var streamer: FLIRThermalStreamer?
95
90
  private var connectedIdentity: FLIRIdentity?
91
+ private var identityMap: [String: FLIRIdentity] = [:]
96
92
  #endif
97
93
 
98
94
  private override init() {
@@ -375,12 +371,7 @@ import ThermalSDK
375
371
 
376
372
  if discovery == nil {
377
373
  discovery = FLIRDiscovery()
378
- // Assign delegate only if we conform to the required discovery delegate protocol
379
- if let dd = self as? FLIRDiscoveryEventDelegate {
380
- discovery?.delegate = dd
381
- } else {
382
- FlirLogger.log(.discovery, "Warning: FlirManager does not conform to FLIRDiscoveryEventDelegate at runtime; delegate not assigned")
383
- }
374
+ discovery?.delegate = self
384
375
  FlirLogger.log(.discovery, "Created FLIRDiscovery instance")
385
376
  }
386
377
 
@@ -411,27 +402,26 @@ import ThermalSDK
411
402
  discovery?.start(interfaces)
412
403
 
413
404
  emitStateChange("discovering")
414
-
415
- // Cancel any previous discovery timeout and schedule a new one so
416
- // the UI doesn't remain stuck when discovery yields no devices.
405
+
406
+ // Set timeout to prevent infinite scanning (matches Android's 8-second timeout)
417
407
  discoveryTimeoutWorkItem?.cancel()
418
- let discWork = DispatchWorkItem { [weak self] in
419
- guard let self = self else { return }
420
- if self.discoveredDevices.isEmpty && self.isScanning {
421
- FlirLogger.logError(.discovery, "Discovery timed out with no devices")
422
- self.discovery?.stop()
423
- self.isScanning = false
424
- DispatchQueue.main.async {
425
- // Notify native listeners that discovery finished with no devices
426
- self.delegate?.onDevicesFound(self.discoveredDevices)
427
- // Emit a state change so JS can set noDeviceFound = true
408
+ let timeoutWork = DispatchWorkItem { [weak self] in
409
+ guard let self = self, self.isScanning else { return }
410
+ FlirLogger.log(.discovery, "⏱ Discovery timeout reached - stopping scan")
411
+ self.discovery?.stop()
412
+ self.isScanning = false
413
+
414
+ // Emit final device list and state
415
+ DispatchQueue.main.async {
416
+ self.delegate?.onDevicesFound(self.discoveredDevices)
417
+ if self.discoveredDevices.isEmpty {
428
418
  self.emitStateChange("no_device_found")
429
- self.delegate?.onError("Discovery timed out")
419
+ self.delegate?.onError("No FLIR devices found")
430
420
  }
431
421
  }
432
422
  }
433
- discoveryTimeoutWorkItem = discWork
434
- DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute: discWork)
423
+ discoveryTimeoutWorkItem = timeoutWork
424
+ DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute: timeoutWork)
435
425
  #else
436
426
  FlirLogger.logError(.discovery, "FLIR SDK not available - discovery disabled")
437
427
  delegate?.onError("FLIR SDK not available")
@@ -470,7 +460,13 @@ import ThermalSDK
470
460
  discovery?.stop()
471
461
  isScanning = false
472
462
  FlirLogger.log(.discovery, "Discovery stopped")
473
- #endif
463
+ #endif
464
+ }
465
+
466
+ // MARK: - Connection
467
+
468
+ @objc public func connectToDevice(_ deviceId: String) {
469
+ FlirLogger.logConnectionAttempt(deviceId: deviceId)
474
470
 
475
471
  #if FLIR_ENABLED
476
472
  // Find the identity for this device
@@ -490,8 +486,6 @@ import ThermalSDK
490
486
  }
491
487
 
492
488
  #if FLIR_ENABLED
493
- private var identityMap: [String: FLIRIdentity] = [:]
494
-
495
489
  private func findIdentity(for deviceId: String) -> FLIRIdentity? {
496
490
  return identityMap[deviceId]
497
491
  }
@@ -515,29 +509,19 @@ import ThermalSDK
515
509
  return
516
510
  }
517
511
 
518
- // Fail-fast authentication for generic/network cameras (prevent indefinite blocking)
512
+ // Handle authentication for generic cameras (network cameras)
519
513
  if identity.cameraType() == .generic {
520
514
  FlirLogger.log(.connection, "Generic/network camera - starting authentication...")
521
515
  let certName = getCertificateName()
522
- var authStatus = FLIRAuthenticationStatus.pending
523
- var attempts = 0
524
- let maxAttempts = 10
525
- while authStatus == .pending && attempts < maxAttempts {
526
- authStatus = cam.authenticate(identity, trustedConnectionName: certName)
527
- if authStatus == .pending {
528
- FlirLogger.log(.connection, "Authentication pending... attempt \(attempts + 1)")
516
+ var status = FLIRAuthenticationStatus.pending
517
+ while status == .pending {
518
+ status = cam.authenticate(identity, trustedConnectionName: certName)
519
+ if status == .pending {
520
+ FlirLogger.log(.connection, "Waiting for camera authentication approval...")
529
521
  Thread.sleep(forTimeInterval: 1.0)
530
- attempts += 1
531
522
  }
532
523
  }
533
- FlirLogger.log(.connection, "Authentication status: \(authStatus.rawValue)")
534
- if authStatus == .pending {
535
- FlirLogger.logError(.connection, "Authentication timed out for device: \(identity.deviceId())")
536
- DispatchQueue.main.async { [weak self] in
537
- self?.delegate?.onError("Authentication timed out")
538
- }
539
- return
540
- }
524
+ FlirLogger.log(.connection, "Authentication status: \(status.rawValue)")
541
525
  }
542
526
 
543
527
  do {
@@ -551,7 +535,6 @@ import ThermalSDK
551
535
  FlirLogger.log(.connection, "Step 2: Connecting to device...")
552
536
  try cam.connect()
553
537
  FlirLogger.log(.connection, "✅ Connected successfully to: \(identity.deviceId())")
554
-
555
538
 
556
539
  // Update state
557
540
  connectedIdentity = identity
@@ -649,9 +632,10 @@ import ThermalSDK
649
632
  FlirLogger.log(.streaming, "Stopping stream...")
650
633
 
651
634
  #if FLIR_ENABLED
652
- // Cancel any pending watchdog to avoid false positives after an explicit stop
653
- streamWatchdogWorkItem?.cancel()
654
- streamWatchdogWorkItem = nil
635
+ stream?.stop()
636
+ stream = nil
637
+ streamer = nil
638
+ _isStreaming = false
655
639
  emitStateChange("connected")
656
640
  FlirLogger.log(.streaming, "Stream stopped")
657
641
  #endif
@@ -676,28 +660,6 @@ import ThermalSDK
676
660
  _isStreaming = true
677
661
  emitStateChange("streaming")
678
662
  FlirLogger.log(.streaming, "✅ Stream started successfully (thermal=\(newStream.isThermal))")
679
-
680
- // Schedule a short watchdog to detect silent no-frame scenarios.
681
- streamWatchdogWorkItem?.cancel()
682
- let watch = DispatchWorkItem { [weak self] in
683
- guard let self = self else { return }
684
- if self._latestImage == nil {
685
- FlirLogger.logError(.streaming, "No frames received within watchdog period after stream start")
686
- // Attempt a soft restart
687
- do {
688
- self.stream?.stop()
689
- try self.stream?.start()
690
- FlirLogger.log(.streaming, "Attempted stream restart")
691
- } catch {
692
- FlirLogger.logError(.streaming, "Stream restart failed", error: error)
693
- DispatchQueue.main.async {
694
- self.delegate?.onError("No frames received after starting stream")
695
- }
696
- }
697
- }
698
- }
699
- streamWatchdogWorkItem = watch
700
- DispatchQueue.main.asyncAfter(deadline: .now() + 3.0, execute: watch)
701
663
  } catch {
702
664
  FlirLogger.logError(.streaming, "Stream start failed", error: error)
703
665
  stream = nil
@@ -977,12 +939,15 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
977
939
  public func discoveryError(_ error: String, netServiceError nsnetserviceserror: Int32, on iface: FLIRCommunicationInterface) {
978
940
  FlirLogger.logError(.discovery, "Discovery error: \(error) (code=\(nsnetserviceserror)) on interface: \(iface)")
979
941
 
980
- // Stop scanning and emit current device list so UI can recover
942
+ // Stop scanning and cancel timeout on error
943
+ discoveryTimeoutWorkItem?.cancel()
944
+ discoveryTimeoutWorkItem = nil
981
945
  discovery?.stop()
982
946
  isScanning = false
947
+
948
+ // Emit current device list (could be empty) so RN/UI can recover
983
949
  DispatchQueue.main.async { [weak self] in
984
950
  guard let self = self else { return }
985
- // Re-emit device list (could be empty) so JS/UI can recover
986
951
  self.delegate?.onDevicesFound(self.discoveredDevices)
987
952
  self.delegate?.onError("Discovery error: \(error)")
988
953
  }
@@ -991,23 +956,27 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
991
956
  public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
992
957
  FlirLogger.log(.discovery, "Discovery finished on interface: \(iface)")
993
958
  isScanning = false
994
- // Emit final list so consumers can update even if discovery ended quietly
959
+
960
+ // Cancel timeout since discovery finished normally
961
+ discoveryTimeoutWorkItem?.cancel()
962
+ discoveryTimeoutWorkItem = nil
963
+
964
+ // CRITICAL: Emit final device list so RN layer doesn't hang waiting for results
995
965
  DispatchQueue.main.async { [weak self] in
996
966
  guard let self = self else { return }
997
967
  self.delegate?.onDevicesFound(self.discoveredDevices)
998
- // If no devices were found, emit an explicit no_device_found state
968
+ // If no devices were found, emit explicit state so UI can show "no devices"
999
969
  if self.discoveredDevices.isEmpty {
1000
970
  self.emitStateChange("no_device_found")
1001
971
  }
1002
972
  }
1003
- // Cancel the timeout if discovery finished normally
1004
- discoveryTimeoutWorkItem?.cancel()
1005
- discoveryTimeoutWorkItem = nil
1006
973
  }
1007
974
  }
975
+ #endif
1008
976
 
1009
977
  // MARK: - FLIRDataReceivedDelegate
1010
978
 
979
+ #if FLIR_ENABLED
1011
980
  extension FlirManager: FLIRDataReceivedDelegate {
1012
981
  public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
1013
982
  FlirLogger.logError(.disconnect, "Camera disconnected callback", error: error)
@@ -1029,9 +998,12 @@ extension FlirManager: FLIRDataReceivedDelegate {
1029
998
  }
1030
999
  }
1031
1000
  }
1001
+ #endif
1032
1002
 
1033
1003
  // MARK: - FLIRStreamDelegate
1034
1004
 
1005
+ #if FLIR_ENABLED
1006
+
1035
1007
  extension FlirManager: FLIRStreamDelegate {
1036
1008
  public func onError(_ error: Error) {
1037
1009
  FlirLogger.logError(.streaming, "Stream error", error: error)
@@ -155,14 +155,9 @@ RCT_EXPORT_METHOD(addListener : (NSString *)eventName) {
155
155
  if (manager) {
156
156
  NSArray *devices = ((NSArray * (*)(id, SEL))
157
157
  objc_msgSend)(manager, sel_registerName("getDiscoveredDevices"));
158
- // Re-emit even if the device list is empty so JS listeners can
159
- // observe a 'no devices found' state and avoid hanging while waiting.
160
- if (devices) {
161
- NSLog(@"[FlirModule] addListener - re-emitting discovered devices (count=%lu)", (unsigned long)devices.count);
158
+ if (devices && devices.count > 0) {
159
+ NSLog(@"[FlirModule] addListener - re-emitting %lu discovered devices", (unsigned long)devices.count);
162
160
  [self onDevicesFound:devices];
163
- } else {
164
- NSLog(@"[FlirModule] addListener - no discovered devices available to re-emit");
165
- [self onDevicesFound:@[]];
166
161
  }
167
162
  }
168
163
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
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",