ilabs-flir 2.2.3 → 2.2.5

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,7 +49,11 @@ 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
52
55
  @objc public class FlirManager: NSObject {
56
+ #endif
53
57
  @objc public static let shared = FlirManager()
54
58
 
55
59
  // MARK: - Properties
@@ -74,6 +78,9 @@ import ThermalSDK
74
78
  // Client lifecycle for discovery/connection ownership
75
79
  private var activeClients: Set<String> = []
76
80
  private var shutdownWorkItem: DispatchWorkItem? = nil
81
+ // Discovery & stream watchdogs to detect silent failures/timeouts
82
+ private var discoveryTimeoutWorkItem: DispatchWorkItem? = nil
83
+ private var streamWatchdogWorkItem: DispatchWorkItem? = nil
77
84
 
78
85
  // Battery polling timer (like Android)
79
86
  private var batteryPollingTimer: Timer?
@@ -368,7 +375,12 @@ import ThermalSDK
368
375
 
369
376
  if discovery == nil {
370
377
  discovery = FLIRDiscovery()
371
- discovery?.delegate = self
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
+ }
372
384
  FlirLogger.log(.discovery, "Created FLIRDiscovery instance")
373
385
  }
374
386
 
@@ -399,6 +411,27 @@ import ThermalSDK
399
411
  discovery?.start(interfaces)
400
412
 
401
413
  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.
417
+ 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
428
+ self.emitStateChange("no_device_found")
429
+ self.delegate?.onError("Discovery timed out")
430
+ }
431
+ }
432
+ }
433
+ discoveryTimeoutWorkItem = discWork
434
+ DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute: discWork)
402
435
  #else
403
436
  FlirLogger.logError(.discovery, "FLIR SDK not available - discovery disabled")
404
437
  delegate?.onError("FLIR SDK not available")
@@ -432,16 +465,12 @@ import ThermalSDK
432
465
  FlirLogger.log(.discovery, "Stopping discovery...")
433
466
 
434
467
  #if FLIR_ENABLED
468
+ discoveryTimeoutWorkItem?.cancel()
469
+ discoveryTimeoutWorkItem = nil
435
470
  discovery?.stop()
436
471
  isScanning = false
437
472
  FlirLogger.log(.discovery, "Discovery stopped")
438
- #endif
439
- }
440
-
441
- // MARK: - Connection
442
-
443
- @objc public func connectToDevice(_ deviceId: String) {
444
- FlirLogger.logConnectionAttempt(deviceId: deviceId)
473
+ #endif
445
474
 
446
475
  #if FLIR_ENABLED
447
476
  // Find the identity for this device
@@ -486,19 +515,29 @@ import ThermalSDK
486
515
  return
487
516
  }
488
517
 
489
- // Handle authentication for generic cameras (network cameras)
518
+ // Fail-fast authentication for generic/network cameras (prevent indefinite blocking)
490
519
  if identity.cameraType() == .generic {
491
520
  FlirLogger.log(.connection, "Generic/network camera - starting authentication...")
492
521
  let certName = getCertificateName()
493
- var status = FLIRAuthenticationStatus.pending
494
- while status == .pending {
495
- status = cam.authenticate(identity, trustedConnectionName: certName)
496
- if status == .pending {
497
- FlirLogger.log(.connection, "Waiting for camera authentication approval...")
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)")
498
529
  Thread.sleep(forTimeInterval: 1.0)
530
+ attempts += 1
499
531
  }
500
532
  }
501
- FlirLogger.log(.connection, "Authentication status: \(status.rawValue)")
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
+ }
502
541
  }
503
542
 
504
543
  do {
@@ -512,6 +551,7 @@ import ThermalSDK
512
551
  FlirLogger.log(.connection, "Step 2: Connecting to device...")
513
552
  try cam.connect()
514
553
  FlirLogger.log(.connection, "✅ Connected successfully to: \(identity.deviceId())")
554
+
515
555
 
516
556
  // Update state
517
557
  connectedIdentity = identity
@@ -609,10 +649,9 @@ import ThermalSDK
609
649
  FlirLogger.log(.streaming, "Stopping stream...")
610
650
 
611
651
  #if FLIR_ENABLED
612
- stream?.stop()
613
- stream = nil
614
- streamer = nil
615
- _isStreaming = false
652
+ // Cancel any pending watchdog to avoid false positives after an explicit stop
653
+ streamWatchdogWorkItem?.cancel()
654
+ streamWatchdogWorkItem = nil
616
655
  emitStateChange("connected")
617
656
  FlirLogger.log(.streaming, "Stream stopped")
618
657
  #endif
@@ -637,6 +676,28 @@ import ThermalSDK
637
676
  _isStreaming = true
638
677
  emitStateChange("streaming")
639
678
  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)
640
701
  } catch {
641
702
  FlirLogger.logError(.streaming, "Stream start failed", error: error)
642
703
  stream = nil
@@ -916,14 +977,32 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
916
977
  public func discoveryError(_ error: String, netServiceError nsnetserviceserror: Int32, on iface: FLIRCommunicationInterface) {
917
978
  FlirLogger.logError(.discovery, "Discovery error: \(error) (code=\(nsnetserviceserror)) on interface: \(iface)")
918
979
 
980
+ // Stop scanning and emit current device list so UI can recover
981
+ discovery?.stop()
982
+ isScanning = false
919
983
  DispatchQueue.main.async { [weak self] in
920
- self?.delegate?.onError("Discovery error: \(error)")
984
+ guard let self = self else { return }
985
+ // Re-emit device list (could be empty) so JS/UI can recover
986
+ self.delegate?.onDevicesFound(self.discoveredDevices)
987
+ self.delegate?.onError("Discovery error: \(error)")
921
988
  }
922
989
  }
923
990
 
924
991
  public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
925
992
  FlirLogger.log(.discovery, "Discovery finished on interface: \(iface)")
926
993
  isScanning = false
994
+ // Emit final list so consumers can update even if discovery ended quietly
995
+ DispatchQueue.main.async { [weak self] in
996
+ guard let self = self else { return }
997
+ self.delegate?.onDevicesFound(self.discoveredDevices)
998
+ // If no devices were found, emit an explicit no_device_found state
999
+ if self.discoveredDevices.isEmpty {
1000
+ self.emitStateChange("no_device_found")
1001
+ }
1002
+ }
1003
+ // Cancel the timeout if discovery finished normally
1004
+ discoveryTimeoutWorkItem?.cancel()
1005
+ discoveryTimeoutWorkItem = nil
927
1006
  }
928
1007
  }
929
1008
 
@@ -155,9 +155,14 @@ 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
- if (devices && devices.count > 0) {
159
- NSLog(@"[FlirModule] addListener - re-emitting %lu discovered devices", (unsigned long)devices.count);
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);
160
162
  [self onDevicesFound:devices];
163
+ } else {
164
+ NSLog(@"[FlirModule] addListener - no discovered devices available to re-emit");
165
+ [self onDevicesFound:@[]];
161
166
  }
162
167
  }
163
168
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
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",