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
|
|
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
|
-
//
|
|
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
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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: \(
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
|
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
|
|
159
|
-
|
|
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
|
});
|