ilabs-flir 2.2.5 → 2.2.7
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
419
|
-
guard let self = self else { return }
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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("
|
|
419
|
+
self.delegate?.onError("No FLIR devices found")
|
|
430
420
|
}
|
|
431
421
|
}
|
|
432
422
|
}
|
|
433
|
-
discoveryTimeoutWorkItem =
|
|
434
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute:
|
|
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,28 +460,38 @@ import ThermalSDK
|
|
|
470
460
|
discovery?.stop()
|
|
471
461
|
isScanning = false
|
|
472
462
|
FlirLogger.log(.discovery, "Discovery stopped")
|
|
473
|
-
|
|
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
|
|
477
473
|
guard let identity = findIdentity(for: deviceId) else {
|
|
478
474
|
FlirLogger.logError(.connection, "Device not found in identity map: \(deviceId)")
|
|
479
|
-
|
|
475
|
+
DispatchQueue.main.async { [weak self] in
|
|
476
|
+
self?.emitStateChange("connection_failed")
|
|
477
|
+
self?.delegate?.onError("Device not found: \(deviceId)")
|
|
478
|
+
}
|
|
480
479
|
return
|
|
481
480
|
}
|
|
482
481
|
|
|
482
|
+
// Run connection on background thread with timeout monitoring
|
|
483
483
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
484
484
|
self?.performConnection(identity: identity)
|
|
485
485
|
}
|
|
486
486
|
#else
|
|
487
487
|
FlirLogger.logError(.connection, "FLIR SDK not available")
|
|
488
|
-
|
|
488
|
+
DispatchQueue.main.async { [weak self] in
|
|
489
|
+
self?.delegate?.onError("FLIR SDK not available")
|
|
490
|
+
}
|
|
489
491
|
#endif
|
|
490
492
|
}
|
|
491
493
|
|
|
492
494
|
#if FLIR_ENABLED
|
|
493
|
-
private var identityMap: [String: FLIRIdentity] = [:]
|
|
494
|
-
|
|
495
495
|
private func findIdentity(for deviceId: String) -> FLIRIdentity? {
|
|
496
496
|
return identityMap[deviceId]
|
|
497
497
|
}
|
|
@@ -515,26 +515,37 @@ import ThermalSDK
|
|
|
515
515
|
return
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
//
|
|
518
|
+
// Handle authentication for generic cameras (network cameras)
|
|
519
519
|
if identity.cameraType() == .generic {
|
|
520
520
|
FlirLogger.log(.connection, "Generic/network camera - starting authentication...")
|
|
521
521
|
let certName = getCertificateName()
|
|
522
|
-
var
|
|
522
|
+
var status = FLIRAuthenticationStatus.pending
|
|
523
523
|
var attempts = 0
|
|
524
|
-
let maxAttempts = 10
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
524
|
+
let maxAttempts = 10 // 10 seconds max
|
|
525
|
+
|
|
526
|
+
while status == .pending && attempts < maxAttempts {
|
|
527
|
+
status = cam.authenticate(identity, trustedConnectionName: certName)
|
|
528
|
+
if status == .pending {
|
|
529
|
+
FlirLogger.log(.connection, "Waiting for camera authentication approval... (\(attempts + 1)/\(maxAttempts))")
|
|
529
530
|
Thread.sleep(forTimeInterval: 1.0)
|
|
530
531
|
attempts += 1
|
|
531
532
|
}
|
|
532
533
|
}
|
|
533
|
-
|
|
534
|
-
if
|
|
535
|
-
FlirLogger.logError(.connection, "Authentication
|
|
534
|
+
|
|
535
|
+
if status == .pending {
|
|
536
|
+
FlirLogger.logError(.connection, "Authentication timeout after \(maxAttempts) seconds")
|
|
537
|
+
DispatchQueue.main.async { [weak self] in
|
|
538
|
+
self?.delegate?.onError("Camera authentication timeout - device may require approval")
|
|
539
|
+
}
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
FlirLogger.log(.connection, "Authentication status: \(status.rawValue)")
|
|
544
|
+
|
|
545
|
+
if status != .authenticated {
|
|
546
|
+
FlirLogger.logError(.connection, "Authentication failed with status: \(status.rawValue)")
|
|
536
547
|
DispatchQueue.main.async { [weak self] in
|
|
537
|
-
self?.delegate?.onError("
|
|
548
|
+
self?.delegate?.onError("Camera authentication failed")
|
|
538
549
|
}
|
|
539
550
|
return
|
|
540
551
|
}
|
|
@@ -548,10 +559,10 @@ import ThermalSDK
|
|
|
548
559
|
FlirLogger.log(.connection, "✅ Paired successfully with: \(identity.deviceId())")
|
|
549
560
|
|
|
550
561
|
// Step 2: Connect (no identity parameter - uses paired identity)
|
|
562
|
+
// Note: This can hang on some devices - ensure we have timeout in place
|
|
551
563
|
FlirLogger.log(.connection, "Step 2: Connecting to device...")
|
|
552
564
|
try cam.connect()
|
|
553
565
|
FlirLogger.log(.connection, "✅ Connected successfully to: \(identity.deviceId())")
|
|
554
|
-
|
|
555
566
|
|
|
556
567
|
// Update state
|
|
557
568
|
connectedIdentity = identity
|
|
@@ -603,8 +614,14 @@ import ThermalSDK
|
|
|
603
614
|
} catch {
|
|
604
615
|
FlirLogger.logError(.connection, "Connection failed", error: error)
|
|
605
616
|
_isConnected = false
|
|
617
|
+
_isStreaming = false
|
|
618
|
+
connectedDeviceId = nil
|
|
619
|
+
connectedDeviceName = nil
|
|
606
620
|
camera = nil
|
|
621
|
+
|
|
622
|
+
// CRITICAL: Always emit error state to RN so UI doesn't hang waiting
|
|
607
623
|
DispatchQueue.main.async { [weak self] in
|
|
624
|
+
self?.emitStateChange("connection_failed")
|
|
608
625
|
self?.delegate?.onError("Connection failed: \(error.localizedDescription)")
|
|
609
626
|
}
|
|
610
627
|
}
|
|
@@ -649,9 +666,10 @@ import ThermalSDK
|
|
|
649
666
|
FlirLogger.log(.streaming, "Stopping stream...")
|
|
650
667
|
|
|
651
668
|
#if FLIR_ENABLED
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
669
|
+
stream?.stop()
|
|
670
|
+
stream = nil
|
|
671
|
+
streamer = nil
|
|
672
|
+
_isStreaming = false
|
|
655
673
|
emitStateChange("connected")
|
|
656
674
|
FlirLogger.log(.streaming, "Stream stopped")
|
|
657
675
|
#endif
|
|
@@ -676,28 +694,6 @@ import ThermalSDK
|
|
|
676
694
|
_isStreaming = true
|
|
677
695
|
emitStateChange("streaming")
|
|
678
696
|
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
697
|
} catch {
|
|
702
698
|
FlirLogger.logError(.streaming, "Stream start failed", error: error)
|
|
703
699
|
stream = nil
|
|
@@ -977,12 +973,15 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
|
977
973
|
public func discoveryError(_ error: String, netServiceError nsnetserviceserror: Int32, on iface: FLIRCommunicationInterface) {
|
|
978
974
|
FlirLogger.logError(.discovery, "Discovery error: \(error) (code=\(nsnetserviceserror)) on interface: \(iface)")
|
|
979
975
|
|
|
980
|
-
// Stop scanning and
|
|
976
|
+
// Stop scanning and cancel timeout on error
|
|
977
|
+
discoveryTimeoutWorkItem?.cancel()
|
|
978
|
+
discoveryTimeoutWorkItem = nil
|
|
981
979
|
discovery?.stop()
|
|
982
980
|
isScanning = false
|
|
981
|
+
|
|
982
|
+
// Emit current device list (could be empty) so RN/UI can recover
|
|
983
983
|
DispatchQueue.main.async { [weak self] in
|
|
984
984
|
guard let self = self else { return }
|
|
985
|
-
// Re-emit device list (could be empty) so JS/UI can recover
|
|
986
985
|
self.delegate?.onDevicesFound(self.discoveredDevices)
|
|
987
986
|
self.delegate?.onError("Discovery error: \(error)")
|
|
988
987
|
}
|
|
@@ -991,23 +990,27 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
|
991
990
|
public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
|
|
992
991
|
FlirLogger.log(.discovery, "Discovery finished on interface: \(iface)")
|
|
993
992
|
isScanning = false
|
|
994
|
-
|
|
993
|
+
|
|
994
|
+
// Cancel timeout since discovery finished normally
|
|
995
|
+
discoveryTimeoutWorkItem?.cancel()
|
|
996
|
+
discoveryTimeoutWorkItem = nil
|
|
997
|
+
|
|
998
|
+
// CRITICAL: Emit final device list so RN layer doesn't hang waiting for results
|
|
995
999
|
DispatchQueue.main.async { [weak self] in
|
|
996
1000
|
guard let self = self else { return }
|
|
997
1001
|
self.delegate?.onDevicesFound(self.discoveredDevices)
|
|
998
|
-
// If no devices were found, emit
|
|
1002
|
+
// If no devices were found, emit explicit state so UI can show "no devices"
|
|
999
1003
|
if self.discoveredDevices.isEmpty {
|
|
1000
1004
|
self.emitStateChange("no_device_found")
|
|
1001
1005
|
}
|
|
1002
1006
|
}
|
|
1003
|
-
// Cancel the timeout if discovery finished normally
|
|
1004
|
-
discoveryTimeoutWorkItem?.cancel()
|
|
1005
|
-
discoveryTimeoutWorkItem = nil
|
|
1006
1007
|
}
|
|
1007
1008
|
}
|
|
1009
|
+
#endif
|
|
1008
1010
|
|
|
1009
1011
|
// MARK: - FLIRDataReceivedDelegate
|
|
1010
1012
|
|
|
1013
|
+
#if FLIR_ENABLED
|
|
1011
1014
|
extension FlirManager: FLIRDataReceivedDelegate {
|
|
1012
1015
|
public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
|
|
1013
1016
|
FlirLogger.logError(.disconnect, "Camera disconnected callback", error: error)
|
|
@@ -1029,9 +1032,12 @@ extension FlirManager: FLIRDataReceivedDelegate {
|
|
|
1029
1032
|
}
|
|
1030
1033
|
}
|
|
1031
1034
|
}
|
|
1035
|
+
#endif
|
|
1032
1036
|
|
|
1033
1037
|
// MARK: - FLIRStreamDelegate
|
|
1034
1038
|
|
|
1039
|
+
#if FLIR_ENABLED
|
|
1040
|
+
|
|
1035
1041
|
extension FlirManager: FLIRStreamDelegate {
|
|
1036
1042
|
public func onError(_ error: Error) {
|
|
1037
1043
|
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
|
-
|
|
159
|
-
|
|
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
|
});
|