ilabs-flir 2.1.37 → 2.1.39

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.
@@ -38,7 +38,7 @@ android {
38
38
 
39
39
  dependencies {
40
40
  // React Native
41
- implementation("com.facebook.react:react-native:+")66a6f83912ba50ae5553aff1f5b0
41
+ implementation("com.facebook.react:react-native:+")
42
42
 
43
43
  // Kotlin coroutines
44
44
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
@@ -88,14 +88,14 @@ object FlirManager {
88
88
  // Store react context for event emission if it's a React context
89
89
  // Always update if we get a valid ReactContext (in case previous was stale)
90
90
  if (context is ReactContext) {
91
- Log.d(TAG, "Storing ReactContext for event emission: ${context.javaClass.simpleName}")
91
+ Log.d(TAG, "[Flir-BRIDGE-LOAD] Storing ReactContext for event emission: ${context.javaClass.simpleName}")
92
92
  reactContext = context
93
93
  } else {
94
- Log.d(TAG, "Context is not ReactContext: ${context.javaClass.simpleName}")
94
+ Log.d(TAG, "[Flir-BRIDGE-LOAD] Context is not ReactContext: ${context.javaClass.simpleName}")
95
95
  }
96
96
 
97
97
  if (isInitialized) {
98
- Log.d(TAG, "Already initialized")
98
+ Log.d(TAG, "[Flir-BRIDGE-LOAD] Already initialized")
99
99
  return
100
100
  }
101
101
 
@@ -106,14 +106,14 @@ object FlirManager {
106
106
  sdkManager?.initialize()
107
107
 
108
108
  isInitialized = true
109
- Log.i(TAG, "FlirManager initialized")
109
+ Log.i(TAG, "[Flir-BRIDGE-LOAD] FlirManager initialized")
110
110
  }
111
111
 
112
112
  /**
113
113
  * Start scanning for devices (USB, Network, Emulator - ALL types)
114
114
  */
115
115
  fun startDiscovery(retry: Boolean = false) {
116
- Log.i(TAG, "startDiscovery(retry=$retry)")
116
+ Log.i(TAG, "[Flir-BRIDGE-DISCOVERY] startDiscovery(retry=$retry)")
117
117
 
118
118
  if (!isInitialized && appContext != null) {
119
119
  init(appContext!!)
@@ -141,7 +141,7 @@ object FlirManager {
141
141
  * Stop scanning
142
142
  */
143
143
  fun stopDiscovery() {
144
- Log.i(TAG, "stopDiscovery")
144
+ Log.i(TAG, "[Flir-BRIDGE-DISCOVERY] stopDiscovery")
145
145
  sdkManager?.stop()
146
146
  isScanning = false
147
147
  }
@@ -156,9 +156,10 @@ object FlirManager {
156
156
  val identity = devices.find { it.deviceId == deviceId }
157
157
 
158
158
  if (identity != null) {
159
+ Log.i(TAG, "[Flir-BRIDGE-CONNECTION] Connecting to found device: $deviceId")
159
160
  sdkManager?.connect(identity)
160
161
  } else {
161
- Log.e(TAG, "Device not found: $deviceId")
162
+ Log.e(TAG, "[Flir-BRIDGE-ERROR] Device not found: $deviceId")
162
163
  emitError("Device not found: $deviceId")
163
164
  }
164
165
  }
@@ -191,7 +192,7 @@ object FlirManager {
191
192
  * Stop streaming
192
193
  */
193
194
  fun stopStream() {
194
- Log.i(TAG, "stopStream")
195
+ Log.i(TAG, "[Flir-BRIDGE-STREAMING] stopStream")
195
196
  sdkManager?.stopStream()
196
197
  isStreaming = false
197
198
  }
@@ -200,7 +201,7 @@ object FlirManager {
200
201
  * Disconnect from current device
201
202
  */
202
203
  fun disconnect() {
203
- Log.i(TAG, "disconnect")
204
+ Log.i(TAG, "[Flir-BRIDGE-DISCONNECT] disconnect")
204
205
  sdkManager?.disconnect()
205
206
  isConnected = false
206
207
  isStreaming = false
@@ -318,7 +319,7 @@ object FlirManager {
318
319
  }
319
320
 
320
321
  override fun onDisconnected() {
321
- Log.i(TAG, "Disconnected")
322
+ Log.i(TAG, "[Flir-BRIDGE-DISCONNECT] Disconnected callback")
322
323
  isConnected = false
323
324
  isStreaming = false
324
325
  connectedDeviceId = null
@@ -354,7 +355,7 @@ object FlirManager {
354
355
  }
355
356
 
356
357
  override fun onBatteryUpdated(level: Int, isCharging: Boolean) {
357
- Log.d(TAG, "onBatteryUpdated: level=$level charging=$isCharging")
358
+ Log.d(TAG, "[Flir-BRIDGE-BATTERY] onBatteryUpdated: level=$level charging=$isCharging")
358
359
  emitBatteryState(level, isCharging)
359
360
  }
360
361
  }
@@ -382,10 +383,10 @@ object FlirManager {
382
383
  private fun emitDeviceState(state: String) {
383
384
  val ctx = reactContext
384
385
  if (ctx == null) {
385
- Log.e(TAG, "Cannot emit FlirDeviceConnected($state) - reactContext is null!")
386
+ Log.e(TAG, "[Flir-BRIDGE-ERROR] Cannot emit FlirDeviceConnected($state) - reactContext is null!")
386
387
  return
387
388
  }
388
- Log.d(TAG, "Emitting FlirDeviceConnected: $state")
389
+ Log.d(TAG, "[Flir-BRIDGE-CONNECTION] Emitting FlirDeviceConnected: $state")
389
390
  try {
390
391
  val params = Arguments.createMap().apply {
391
392
  putString("state", state)
@@ -428,9 +429,9 @@ object FlirManager {
428
429
 
429
430
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
430
431
  .emit("FlirDevicesFound", params)
431
- Log.d(TAG, "Successfully emitted FlirDevicesFound")
432
+ Log.d(TAG, "[Flir-BRIDGE-DISCOVERY] Successfully emitted FlirDevicesFound (${devices.size} devices)")
432
433
  } catch (e: Exception) {
433
- Log.e(TAG, "Failed to emit devices found", e)
434
+ Log.e(TAG, "[Flir-BRIDGE-ERROR] Failed to emit devices found", e)
434
435
  }
435
436
  }
436
437
 
@@ -175,9 +175,9 @@ public class FlirSdkManager {
175
175
 
176
176
  ThermalSdkAndroid.init(context);
177
177
  isInitialized = true;
178
- Log.d(TAG, "SDK initialized successfully");
178
+ Log.d(TAG, "[Flir-LOAD] SDK initialized successfully");
179
179
  } catch (Exception e) {
180
- Log.e(TAG, "Failed to initialize SDK", e);
180
+ Log.e(TAG, "[Flir-ERROR] Failed to initialize SDK", e);
181
181
  notifyError("SDK initialization failed: " + e.getMessage());
182
182
  }
183
183
  }
@@ -208,7 +208,7 @@ public class FlirSdkManager {
208
208
  isScanning = true;
209
209
  discoveredDevices.clear();
210
210
 
211
- Log.d(TAG, "Starting discovery for EMULATOR, NETWORK, USB...");
211
+ Log.d(TAG, "[Flir-DISCOVERY] Starting discovery for EMULATOR, NETWORK, USB...");
212
212
 
213
213
  try {
214
214
  DiscoveryFactory.getInstance().scan(
@@ -241,7 +241,7 @@ public class FlirSdkManager {
241
241
  }
242
242
 
243
243
  isScanning = false;
244
- Log.d(TAG, "Discovery stopped");
244
+ Log.d(TAG, "[Flir-DISCOVERY] Discovery stopped");
245
245
  }
246
246
 
247
247
  /**
@@ -273,7 +273,7 @@ public class FlirSdkManager {
273
273
  camera = new Camera();
274
274
  camera.connect(identity, connectionStatusListener, new ConnectParameters());
275
275
 
276
- Log.d(TAG, "Connected to camera");
276
+ Log.d(TAG, "[Flir-CONNECTION] Connected to camera: " + identity.deviceId);
277
277
 
278
278
  if (listener != null) {
279
279
  listener.onConnected(identity);
@@ -309,7 +309,7 @@ public class FlirSdkManager {
309
309
  listener.onDisconnected();
310
310
  }
311
311
 
312
- Log.d(TAG, "Disconnected");
312
+ Log.d(TAG, "[Flir-DISCONNECT] Disconnected");
313
313
  }
314
314
 
315
315
  /**
@@ -397,10 +397,10 @@ public class FlirSdkManager {
397
397
  notifyError("Stream error: " + error);
398
398
  });
399
399
 
400
- Log.d(TAG, "Streaming started");
400
+ Log.d(TAG, "[Flir-STREAMING] Streaming started");
401
401
 
402
402
  } catch (Exception e) {
403
- Log.e(TAG, "Failed to start stream", e);
403
+ Log.e(TAG, "[Flir-ERROR] Failed to start stream", e);
404
404
  notifyError("Stream failed: " + e.getMessage());
405
405
  }
406
406
  });
@@ -425,7 +425,7 @@ public class FlirSdkManager {
425
425
  isProcessingFrame = false;
426
426
  lastFrameProcessedMs = 0;
427
427
 
428
- Log.d(TAG, "Streaming stopped");
428
+ Log.d(TAG, "[Flir-STREAMING] Streaming stopped");
429
429
  }
430
430
 
431
431
  /**
@@ -522,12 +522,13 @@ public class FlirSdkManager {
522
522
  streamer.withThermalImage(thermalImage -> {
523
523
  thermalImage.setPalette(palette);
524
524
  });
525
- Log.d(TAG, "Palette set to: " + paletteName);
525
+ Log.d(TAG, "[Flir-STREAMING] Palette set to: " + paletteName);
526
526
  }
527
527
  } catch (Exception e) {
528
528
  Log.e(TAG, "Error setting palette", e);
529
529
  }
530
530
  });
531
+
531
532
  }
532
533
 
533
534
  /**
@@ -644,8 +645,8 @@ public class FlirSdkManager {
644
645
  @Override
645
646
  public void onCameraFound(DiscoveredCamera discoveredCamera) {
646
647
  Identity identity = discoveredCamera.getIdentity();
647
- Log.d(TAG, "Device found: " + identity.deviceId +
648
- " type=" + identity.communicationInterface);
648
+ Log.d(TAG,
649
+ "[Flir-DISCOVERY] Device found: " + identity.deviceId + " type=" + identity.communicationInterface);
649
650
 
650
651
  // Add to list if not already present
651
652
  synchronized (discoveredDevices) {
@@ -669,7 +670,7 @@ public class FlirSdkManager {
669
670
 
670
671
  @Override
671
672
  public void onCameraLost(Identity identity) {
672
- Log.d(TAG, "Device lost: " + identity.deviceId);
673
+ Log.d(TAG, "[Flir-DISCOVERY] Device lost: " + identity.deviceId);
673
674
 
674
675
  synchronized (discoveredDevices) {
675
676
  discoveredDevices.removeIf(d -> d.deviceId.equals(identity.deviceId));
@@ -682,13 +683,13 @@ public class FlirSdkManager {
682
683
 
683
684
  @Override
684
685
  public void onDiscoveryError(CommunicationInterface iface, ErrorCode error) {
685
- Log.e(TAG, "Discovery error: " + iface + " - " + error);
686
+ Log.e(TAG, "[Flir-ERROR] Discovery error: " + iface + " - " + error);
686
687
  notifyError("Discovery error: " + error);
687
688
  }
688
689
 
689
690
  @Override
690
691
  public void onDiscoveryFinished(CommunicationInterface iface) {
691
- Log.d(TAG, "Discovery finished for: " + iface);
692
+ Log.d(TAG, "[Flir-DISCOVERY] Discovery finished for: " + iface);
692
693
  }
693
694
  };
694
695
 
@@ -739,6 +740,9 @@ public class FlirSdkManager {
739
740
  if (level != lastPolledBatteryLevel || charging != lastPolledCharging) {
740
741
  lastPolledBatteryLevel = level;
741
742
  lastPolledCharging = charging;
743
+
744
+ Log.d(TAG, String.format("[Flir-BATTERY] Level: %d%%, Charging: %b", level, charging));
745
+
742
746
  if (listener != null) {
743
747
  try {
744
748
  listener.onBatteryUpdated(level, charging);
@@ -1,43 +1,67 @@
1
1
  import Foundation
2
2
  import UIKit
3
3
 
4
+ /// ObjC-compatible shim that forwards to FlirManager
5
+ /// This provides a clean API for native consumers (FilterDataProvider, etc.)
4
6
  @objc public class FLIRManager: NSObject {
5
7
  @objc public static let shared = FLIRManager()
8
+
9
+ private override init() {
10
+ super.init()
11
+ }
6
12
 
13
+ // MARK: - SDK Availability
14
+
7
15
  @objc public func isAvailable() -> Bool {
8
16
  return FlirManager.isSDKAvailable
9
17
  }
18
+
19
+ @objc public static var isSDKAvailable: Bool {
20
+ return FlirManager.isSDKAvailable
21
+ }
10
22
 
23
+ // MARK: - Temperature APIs
24
+
11
25
  @objc public func getTemperatureAtPoint(x: Int, y: Int) -> Double {
12
- // FlirManager currently doesn't expose a direct getTemperatureAtPoint API.
13
- // Return NaN for now (consumers should handle NaN) until full parity is implemented.
14
- return Double.nan
26
+ return FlirManager.shared.getTemperatureAtPoint(x, y: y)
15
27
  }
16
28
 
17
29
  @objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
18
- return Double.nan
30
+ return FlirManager.shared.getTemperatureAtNormalized(nx, y: y)
19
31
  }
20
32
 
33
+ // MARK: - Battery APIs
34
+
21
35
  @objc public func getBatteryLevel() -> Int {
22
- return -1
36
+ return FlirManager.shared.getBatteryLevel()
23
37
  }
24
38
 
25
39
  @objc public func isBatteryCharging() -> Bool {
26
- return false
40
+ return FlirManager.shared.isBatteryCharging()
27
41
  }
28
42
 
43
+ // MARK: - Rotation Preference
44
+
29
45
  @objc public func setPreferSdkRotation(_ prefer: Bool) {
30
- // FlirManager doesn't currently support rotation preference; no-op
46
+ FlirManager.shared.setPreferSdkRotation(prefer)
31
47
  }
32
48
 
33
49
  @objc public func isPreferSdkRotation() -> Bool {
34
- return false
50
+ return FlirManager.shared.isPreferSdkRotation()
35
51
  }
36
52
 
53
+ // MARK: - Frame Access
54
+
37
55
  @objc public func latestFrameImage() -> UIImage? {
38
56
  return FlirManager.shared.latestImage
39
57
  }
58
+
59
+ @objc public var latestImage: UIImage? {
60
+ return FlirManager.shared.latestImage
61
+ }
40
62
 
63
+ // MARK: - Discovery & Connection
64
+
41
65
  @objc public func startDiscovery() {
42
66
  FlirManager.shared.startDiscovery()
43
67
  }
@@ -45,4 +69,36 @@ import UIKit
45
69
  @objc public func stopDiscovery() {
46
70
  FlirManager.shared.stopDiscovery()
47
71
  }
72
+
73
+ @objc public var isConnected: Bool {
74
+ return FlirManager.shared.isConnected
75
+ }
76
+
77
+ @objc public var isStreaming: Bool {
78
+ return FlirManager.shared.isStreaming
79
+ }
80
+
81
+ @objc public var isEmulator: Bool {
82
+ return FlirManager.shared.isEmulator
83
+ }
84
+
85
+ // MARK: - Palette Control
86
+
87
+ @objc public func setPalette(_ name: String) {
88
+ FlirManager.shared.setPalette(name)
89
+ }
90
+
91
+ @objc public func setPaletteFromAcol(_ acol: Float) {
92
+ FlirManager.shared.setPaletteFromAcol(acol)
93
+ }
94
+
95
+ // MARK: - Client Lifecycle
96
+
97
+ @objc public func retainClient(_ clientId: String) {
98
+ FlirManager.shared.retainClient(clientId)
99
+ }
100
+
101
+ @objc public func releaseClient(_ clientId: String) {
102
+ FlirManager.shared.releaseClient(clientId)
103
+ }
48
104
  }
@@ -0,0 +1,118 @@
1
+ //
2
+ // FlirLogger.swift
3
+ // Flir
4
+ //
5
+ // Comprehensive lifecycle logging for FLIR camera operations
6
+ // Helps debug: load → discovery → connection → streaming → frame → battery
7
+ //
8
+
9
+ import Foundation
10
+
11
+ /// Lifecycle stages for FLIR operations
12
+ @objc public enum FlirLifecycleStage: Int {
13
+ case load // SDK initialization
14
+ case discovery // Device scanning
15
+ case connection // Device pairing/connecting
16
+ case streaming // Stream start/stop
17
+ case frame // Frame receiving
18
+ case battery // Battery polling
19
+ case error // Error states
20
+ case disconnect // Disconnection events
21
+
22
+ var tag: String {
23
+ switch self {
24
+ case .load: return "LOAD"
25
+ case .discovery: return "DISCOVERY"
26
+ case .connection: return "CONNECTION"
27
+ case .streaming: return "STREAMING"
28
+ case .frame: return "FRAME"
29
+ case .battery: return "BATTERY"
30
+ case .error: return "ERROR"
31
+ case .disconnect: return "DISCONNECT"
32
+ }
33
+ }
34
+ }
35
+
36
+ /// Centralized logger for FLIR operations with consistent formatting
37
+ @objc public class FlirLogger: NSObject {
38
+
39
+ /// Frame counter for periodic frame logging
40
+ private static var frameCount: Int = 0
41
+ private static let frameLogInterval: Int = 100 // Log every 100 frames
42
+
43
+ /// Log a message at a specific lifecycle stage
44
+ @objc public static func log(_ stage: FlirLifecycleStage, _ message: String) {
45
+ let timestamp = Date()
46
+ let formatter = DateFormatter()
47
+ formatter.dateFormat = "HH:mm:ss.SSS"
48
+ let timeStr = formatter.string(from: timestamp)
49
+ NSLog("[Flir-\(stage.tag)] [\(timeStr)] \(message)")
50
+ }
51
+
52
+ /// Log an error with optional Error object
53
+ @objc public static func logError(_ stage: FlirLifecycleStage, _ message: String, error: Error? = nil) {
54
+ let errorDesc = error?.localizedDescription ?? "no error details"
55
+ log(stage, "❌ \(message) - \(errorDesc)")
56
+ }
57
+
58
+ /// Log frame received (rate-limited to avoid log spam)
59
+ @objc public static func logFrame(width: Int, height: Int, temperature: Double? = nil) {
60
+ frameCount += 1
61
+ if frameCount % frameLogInterval == 0 {
62
+ var msg = "Frame #\(frameCount) received (\(width)x\(height))"
63
+ if let temp = temperature, !temp.isNaN {
64
+ msg += " temp=\(String(format: "%.1f", temp))°C"
65
+ }
66
+ log(.frame, msg)
67
+ }
68
+ }
69
+
70
+ /// Reset frame counter (call on disconnect)
71
+ @objc public static func resetFrameCounter() {
72
+ frameCount = 0
73
+ }
74
+
75
+ /// Log discovery interface status (important for debugging network vs lightning)
76
+ @objc public static func logDiscoveryInterfaces(lightning: Bool, network: Bool, wireless: Bool, emulator: Bool) {
77
+ var interfaces: [String] = []
78
+ if lightning { interfaces.append("Lightning") }
79
+ if network { interfaces.append("Network") }
80
+ if wireless { interfaces.append("FlirOneWireless") }
81
+ if emulator { interfaces.append("Emulator") }
82
+
83
+ log(.discovery, "Starting discovery on interfaces: \(interfaces.joined(separator: ", "))")
84
+
85
+ if !network {
86
+ log(.discovery, "⚠️ Network discovery DISABLED - requires paid iOS Developer License with NSLocalNetworkUsageDescription")
87
+ }
88
+ }
89
+
90
+ /// Log device found with details
91
+ @objc public static func logDeviceFound(deviceId: String, name: String, type: String, isEmulator: Bool) {
92
+ var msg = "Device found: '\(name)' (id=\(deviceId), type=\(type))"
93
+ if isEmulator {
94
+ msg += " [EMULATOR]"
95
+ }
96
+ log(.discovery, msg)
97
+ }
98
+
99
+ /// Log connection attempt
100
+ @objc public static func logConnectionAttempt(deviceId: String) {
101
+ log(.connection, "Attempting connection to: \(deviceId)")
102
+ }
103
+
104
+ /// Log connection success with stream info
105
+ @objc public static func logConnectionSuccess(deviceId: String, streamCount: Int, hasThermal: Bool) {
106
+ log(.connection, "✅ Connected to: \(deviceId) - \(streamCount) stream(s), thermal=\(hasThermal)")
107
+ }
108
+
109
+ /// Log battery state
110
+ @objc public static func logBattery(level: Int, isCharging: Bool) {
111
+ if level >= 0 {
112
+ let chargingStr = isCharging ? "charging" : "not charging"
113
+ log(.battery, "Level: \(level)%, \(chargingStr)")
114
+ } else {
115
+ log(.battery, "Battery info unavailable")
116
+ }
117
+ }
118
+ }
@@ -75,6 +75,11 @@ import ThermalSDK
75
75
  private var activeClients: Set<String> = []
76
76
  private var shutdownWorkItem: DispatchWorkItem? = nil
77
77
 
78
+ // Battery polling timer (like Android)
79
+ private var batteryPollingTimer: Timer?
80
+ private var lastPolledBatteryLevel: Int = -1
81
+ private var lastPolledCharging: Bool = false
82
+
78
83
  #if FLIR_ENABLED
79
84
  private var discovery: FLIRDiscovery?
80
85
  private var camera: FLIRCamera?
@@ -85,7 +90,12 @@ import ThermalSDK
85
90
 
86
91
  private override init() {
87
92
  super.init()
88
- NSLog("[FlirManager] Initialized")
93
+ FlirLogger.log(.load, "FlirManager singleton initialized")
94
+ #if FLIR_ENABLED
95
+ FlirLogger.log(.load, "✅ FLIR SDK is ENABLED (ThermalSDK available)")
96
+ #else
97
+ FlirLogger.log(.load, "⚠️ FLIR SDK is DISABLED (FLIR_ENABLED not defined)")
98
+ #endif
89
99
  }
90
100
 
91
101
  // MARK: - Public State Accessors
@@ -97,6 +107,17 @@ import ThermalSDK
97
107
  connectedDeviceName?.lowercased().contains("emulat") == true
98
108
  }
99
109
 
110
+ // Preference: ask SDK to deliver oriented/rotated frames (if SDK supports it)
111
+ private var _preferSdkRotation: Bool = false
112
+
113
+ @objc public func setPreferSdkRotation(_ prefer: Bool) {
114
+ _preferSdkRotation = prefer
115
+ }
116
+
117
+ @objc public func isPreferSdkRotation() -> Bool {
118
+ return _preferSdkRotation
119
+ }
120
+
100
121
  @objc public func getConnectedDeviceInfo() -> String {
101
122
  return connectedDeviceName ?? "Not connected"
102
123
  }
@@ -324,11 +345,11 @@ import ThermalSDK
324
345
  // MARK: - Discovery
325
346
 
326
347
  @objc public func startDiscovery() {
327
- NSLog("[FlirManager] Starting discovery...")
348
+ FlirLogger.log(.discovery, "Starting discovery...")
328
349
 
329
350
  #if FLIR_ENABLED
330
351
  if isScanning {
331
- NSLog("[FlirManager] Already scanning")
352
+ FlirLogger.log(.discovery, "Already scanning - skipping")
332
353
  return
333
354
  }
334
355
 
@@ -338,6 +359,7 @@ import ThermalSDK
338
359
  if discovery == nil {
339
360
  discovery = FLIRDiscovery()
340
361
  discovery?.delegate = self
362
+ FlirLogger.log(.discovery, "Created FLIRDiscovery instance")
341
363
  }
342
364
 
343
365
  // Build interfaces based on available permissions
@@ -351,19 +373,24 @@ import ThermalSDK
351
373
  // Only add network discovery if NSLocalNetworkUsageDescription is present
352
374
  // This prevents crashes/errors when user doesn't have iOS developer registration
353
375
  // or hasn't declared network permission
354
- if shouldEnableNetworkDiscovery() {
376
+ let networkEnabled = shouldEnableNetworkDiscovery()
377
+ if networkEnabled {
355
378
  interfaces.insert(.network)
356
- NSLog("[FlirManager] Network discovery enabled (NSLocalNetworkUsageDescription present)")
357
- } else {
358
- NSLog("[FlirManager] Network discovery disabled (no NSLocalNetworkUsageDescription)")
359
379
  }
360
380
 
381
+ // Log which interfaces are enabled (important for debugging)
382
+ FlirLogger.logDiscoveryInterfaces(
383
+ lightning: true,
384
+ network: networkEnabled,
385
+ wireless: true,
386
+ emulator: true
387
+ )
388
+
361
389
  discovery?.start(interfaces)
362
390
 
363
391
  emitStateChange("discovering")
364
- NSLog("[FlirManager] Discovery started on interfaces: Lightning, \(interfaces.contains(.network) ? "Network, " : "")FlirOneWireless, Emulator")
365
392
  #else
366
- NSLog("[FlirManager] FLIR SDK not available - discovery disabled")
393
+ FlirLogger.logError(.discovery, "FLIR SDK not available - discovery disabled")
367
394
  delegate?.onError("FLIR SDK not available")
368
395
  #endif
369
396
  }
@@ -388,28 +415,28 @@ import ThermalSDK
388
415
  /// Allow explicit override of network discovery (called from React Native)
389
416
  @objc public func setNetworkDiscoveryEnabled(_ enabled: Bool) {
390
417
  UserDefaults.standard.set(enabled, forKey: "ilabsFlir.networkDiscoveryEnabled")
391
- NSLog("[FlirManager] Network discovery override set to: \(enabled)")
418
+ FlirLogger.log(.discovery, "Network discovery override set to: \(enabled)")
392
419
  }
393
420
 
394
421
  @objc public func stopDiscovery() {
395
- NSLog("[FlirManager] Stopping discovery...")
422
+ FlirLogger.log(.discovery, "Stopping discovery...")
396
423
 
397
424
  #if FLIR_ENABLED
398
425
  discovery?.stop()
399
426
  isScanning = false
400
- NSLog("[FlirManager] Discovery stopped")
427
+ FlirLogger.log(.discovery, "Discovery stopped")
401
428
  #endif
402
429
  }
403
430
 
404
431
  // MARK: - Connection
405
432
 
406
433
  @objc public func connectToDevice(_ deviceId: String) {
407
- NSLog("[FlirManager] Connecting to device: \(deviceId)")
434
+ FlirLogger.logConnectionAttempt(deviceId: deviceId)
408
435
 
409
436
  #if FLIR_ENABLED
410
437
  // Find the identity for this device
411
438
  guard let identity = findIdentity(for: deviceId) else {
412
- NSLog("[FlirManager] Device not found: \(deviceId)")
439
+ FlirLogger.logError(.connection, "Device not found in identity map: \(deviceId)")
413
440
  delegate?.onError("Device not found: \(deviceId)")
414
441
  return
415
442
  }
@@ -418,6 +445,7 @@ import ThermalSDK
418
445
  self?.performConnection(identity: identity)
419
446
  }
420
447
  #else
448
+ FlirLogger.logError(.connection, "FLIR SDK not available")
421
449
  delegate?.onError("FLIR SDK not available")
422
450
  #endif
423
451
  }
@@ -432,14 +460,16 @@ import ThermalSDK
432
460
  private func performConnection(identity: FLIRIdentity) {
433
461
  // Use the proven connection pattern from FLIR SDK samples:
434
462
  // FLIROneCameraSwift uses: pair(identity, code:) then connect()
463
+ FlirLogger.log(.connection, "performConnection starting for: \(identity.deviceId())")
435
464
 
436
465
  if camera == nil {
437
466
  camera = FLIRCamera()
438
467
  camera?.delegate = self
468
+ FlirLogger.log(.connection, "Created new FLIRCamera instance")
439
469
  }
440
470
 
441
471
  guard let cam = camera else {
442
- NSLog("[FlirManager] Failed to create FLIRCamera")
472
+ FlirLogger.logError(.connection, "Failed to create FLIRCamera instance")
443
473
  DispatchQueue.main.async { [weak self] in
444
474
  self?.delegate?.onError("Failed to create camera instance")
445
475
  }
@@ -448,27 +478,30 @@ import ThermalSDK
448
478
 
449
479
  // Handle authentication for generic cameras (network cameras)
450
480
  if identity.cameraType() == .generic {
481
+ FlirLogger.log(.connection, "Generic/network camera - starting authentication...")
451
482
  let certName = getCertificateName()
452
483
  var status = FLIRAuthenticationStatus.pending
453
484
  while status == .pending {
454
485
  status = cam.authenticate(identity, trustedConnectionName: certName)
455
486
  if status == .pending {
456
- NSLog("[FlirManager] Waiting for camera authentication approval...")
487
+ FlirLogger.log(.connection, "Waiting for camera authentication approval...")
457
488
  Thread.sleep(forTimeInterval: 1.0)
458
489
  }
459
490
  }
460
- NSLog("[FlirManager] Authentication status: \(status.rawValue)")
491
+ FlirLogger.log(.connection, "Authentication status: \(status.rawValue)")
461
492
  }
462
493
 
463
494
  do {
464
495
  // Step 1: Pair with identity (required for FLIR One devices)
465
496
  // The code parameter is for BLE pairing, 0 for direct connection
497
+ FlirLogger.log(.connection, "Step 1: Pairing with device...")
466
498
  try cam.pair(identity, code: 0)
467
- NSLog("[FlirManager] Paired with: \(identity.deviceId())")
499
+ FlirLogger.log(.connection, " Paired successfully with: \(identity.deviceId())")
468
500
 
469
501
  // Step 2: Connect (no identity parameter - uses paired identity)
502
+ FlirLogger.log(.connection, "Step 2: Connecting to device...")
470
503
  try cam.connect()
471
- NSLog("[FlirManager] Connected to: \(identity.deviceId())")
504
+ FlirLogger.log(.connection, " Connected successfully to: \(identity.deviceId())")
472
505
 
473
506
  // Update state
474
507
  connectedIdentity = identity
@@ -479,23 +512,27 @@ import ThermalSDK
479
512
  // Get camera info if available
480
513
  if let remoteControl = cam.getRemoteControl(),
481
514
  let cameraInfo = try? remoteControl.getCameraInformation() {
482
- NSLog("[FlirManager] Camera info: \(cameraInfo)")
515
+ FlirLogger.log(.connection, "Camera info: \(cameraInfo)")
483
516
  }
484
517
 
485
518
  // Get streams
486
519
  let streams = cam.getStreams()
520
+ let thermalCount = streams.filter { $0.isThermal }.count
521
+ FlirLogger.logConnectionSuccess(deviceId: identity.deviceId(), streamCount: streams.count, hasThermal: thermalCount > 0)
522
+
487
523
  if !streams.isEmpty {
488
- NSLog("[FlirManager] Found \(streams.count) streams")
489
-
490
524
  // Find and start the first thermal stream (preferred) or any stream
491
525
  let thermalStream = streams.first { $0.isThermal } ?? streams.first
492
526
  if let streamToStart = thermalStream {
493
527
  startStreamInternal(streamToStart)
494
528
  }
495
529
  } else {
496
- NSLog("[FlirManager] No streams available on camera")
530
+ FlirLogger.log(.streaming, "⚠️ No streams available on camera")
497
531
  }
498
532
 
533
+ // Start battery polling (like Android does)
534
+ startBatteryPolling()
535
+
499
536
  // Notify delegate on main thread
500
537
  DispatchQueue.main.async { [weak self] in
501
538
  guard let self = self else { return }
@@ -510,7 +547,7 @@ import ThermalSDK
510
547
  }
511
548
 
512
549
  } catch {
513
- NSLog("[FlirManager] Connection failed: \(error.localizedDescription)")
550
+ FlirLogger.logError(.connection, "Connection failed", error: error)
514
551
  _isConnected = false
515
552
  camera = nil
516
553
  DispatchQueue.main.async { [weak self] in
@@ -547,7 +584,7 @@ import ThermalSDK
547
584
  @objc public func startStream() {
548
585
  #if FLIR_ENABLED
549
586
  guard let streams = camera?.getStreams(), !streams.isEmpty else {
550
- NSLog("[FlirManager] No streams available")
587
+ FlirLogger.log(.streaming, "⚠️ No streams available")
551
588
  return
552
589
  }
553
590
  startStreamInternal(streams[0])
@@ -555,7 +592,7 @@ import ThermalSDK
555
592
  }
556
593
 
557
594
  @objc public func stopStream() {
558
- NSLog("[FlirManager] Stopping stream...")
595
+ FlirLogger.log(.streaming, "Stopping stream...")
559
596
 
560
597
  #if FLIR_ENABLED
561
598
  stream?.stop()
@@ -563,18 +600,20 @@ import ThermalSDK
563
600
  streamer = nil
564
601
  _isStreaming = false
565
602
  emitStateChange("connected")
603
+ FlirLogger.log(.streaming, "Stream stopped")
566
604
  #endif
567
605
  }
568
606
 
569
607
  #if FLIR_ENABLED
570
608
  private func startStreamInternal(_ newStream: FLIRStream) {
571
- NSLog("[FlirManager] Starting stream...")
609
+ FlirLogger.log(.streaming, "Starting stream (thermal=\(newStream.isThermal))...")
572
610
 
573
611
  stream?.stop()
574
612
  stream = newStream
575
613
 
576
614
  if newStream.isThermal {
577
615
  streamer = FLIRThermalStreamer(stream: newStream)
616
+ FlirLogger.log(.streaming, "Created FLIRThermalStreamer for thermal stream")
578
617
  }
579
618
 
580
619
  newStream.delegate = self
@@ -583,9 +622,9 @@ import ThermalSDK
583
622
  try newStream.start()
584
623
  _isStreaming = true
585
624
  emitStateChange("streaming")
586
- NSLog("[FlirManager] Stream started (thermal: \(newStream.isThermal))")
625
+ FlirLogger.log(.streaming, " Stream started successfully (thermal=\(newStream.isThermal))")
587
626
  } catch {
588
- NSLog("[FlirManager] Stream start failed: \(error)")
627
+ FlirLogger.logError(.streaming, "Stream start failed", error: error)
589
628
  stream = nil
590
629
  streamer = nil
591
630
  delegate?.onError("Stream start failed: \(error.localizedDescription)")
@@ -596,9 +635,12 @@ import ThermalSDK
596
635
  // MARK: - Disconnect
597
636
 
598
637
  @objc public func disconnect() {
599
- NSLog("[FlirManager] Disconnecting...")
638
+ FlirLogger.log(.disconnect, "Disconnecting...")
600
639
 
601
640
  #if FLIR_ENABLED
641
+ // Stop battery polling
642
+ stopBatteryPolling()
643
+
602
644
  stopStream()
603
645
  camera?.disconnect()
604
646
  camera = nil
@@ -609,14 +651,19 @@ import ThermalSDK
609
651
  _isStreaming = false
610
652
  _latestImage = nil
611
653
 
654
+ // Reset frame counter for next connection
655
+ FlirLogger.resetFrameCounter()
656
+
612
657
  DispatchQueue.main.async { [weak self] in
613
658
  self?.delegate?.onDeviceDisconnected()
614
659
  self?.emitStateChange("disconnected")
615
660
  }
661
+ FlirLogger.log(.disconnect, "Disconnected successfully")
616
662
  #endif
617
663
  }
618
664
 
619
665
  @objc public func stop() {
666
+ FlirLogger.log(.disconnect, "stop() called - full shutdown")
620
667
  stopStream()
621
668
  disconnect()
622
669
  stopDiscovery()
@@ -647,10 +694,56 @@ import ThermalSDK
647
694
  return lastTemperature
648
695
  }
649
696
 
697
+ // MARK: - Battery Polling (like Android)
698
+
699
+ private func startBatteryPolling() {
700
+ FlirLogger.log(.battery, "Starting battery polling timer (5s interval)")
701
+
702
+ // Cancel any existing timer
703
+ stopBatteryPolling()
704
+
705
+ // Schedule timer on main run loop
706
+ DispatchQueue.main.async { [weak self] in
707
+ self?.batteryPollingTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
708
+ self?.pollBatteryState()
709
+ }
710
+ // Run immediately once
711
+ self?.pollBatteryState()
712
+ }
713
+ }
714
+
715
+ private func stopBatteryPolling() {
716
+ if batteryPollingTimer != nil {
717
+ FlirLogger.log(.battery, "Stopping battery polling timer")
718
+ }
719
+ batteryPollingTimer?.invalidate()
720
+ batteryPollingTimer = nil
721
+ }
722
+
723
+ private func pollBatteryState() {
724
+ let level = getBatteryLevel()
725
+ let charging = isBatteryCharging()
726
+
727
+ // Only log and emit if values changed
728
+ if level != lastPolledBatteryLevel || charging != lastPolledCharging {
729
+ lastPolledBatteryLevel = level
730
+ lastPolledCharging = charging
731
+
732
+ FlirLogger.logBattery(level: level, isCharging: charging)
733
+
734
+ // Emit to delegate/RN via notification
735
+ NotificationCenter.default.post(
736
+ name: Notification.Name("FlirBatteryUpdated"),
737
+ object: nil,
738
+ userInfo: ["level": level, "isCharging": charging]
739
+ )
740
+ }
741
+ }
742
+
650
743
  // MARK: - Emulator
651
744
 
652
745
  @objc public func startEmulator(type: String) {
653
- NSLog("[FlirManager] Starting emulator: \(type)")
746
+ FlirLogger.log(.connection, "Starting emulator with type: \(type)")
654
747
 
655
748
  #if FLIR_ENABLED
656
749
  // Create emulator identity
@@ -660,6 +753,7 @@ import ThermalSDK
660
753
  } else if type.lowercased().contains("pro") {
661
754
  cameraType = .flirOneEdgePro
662
755
  }
756
+ FlirLogger.log(.connection, "Emulator camera type: \(cameraType)")
663
757
 
664
758
  if let emulatorIdentity = FLIRIdentity(emulatorType: cameraType) {
665
759
  discoveredDevices.append(FlirDeviceInfo(
@@ -669,11 +763,15 @@ import ThermalSDK
669
763
  isEmulator: true
670
764
  ))
671
765
  identityMap[emulatorIdentity.deviceId()] = emulatorIdentity
766
+ FlirLogger.log(.connection, "Emulator identity created: \(emulatorIdentity.deviceId())")
672
767
 
673
768
  // Auto-connect to emulator
674
769
  performConnection(identity: emulatorIdentity)
770
+ } else {
771
+ FlirLogger.logError(.connection, "Failed to create emulator identity for type: \(type)")
675
772
  }
676
773
  #else
774
+ FlirLogger.logError(.connection, "FLIR SDK not available - emulator disabled")
677
775
  delegate?.onError("FLIR SDK not available - emulator disabled")
678
776
  #endif
679
777
  }
@@ -753,8 +851,12 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
753
851
  public func cameraDiscovered(_ discoveredCamera: FLIRDiscoveredCamera) {
754
852
  let identity = discoveredCamera.identity
755
853
  let deviceId = identity.deviceId()
854
+ let displayName = discoveredCamera.displayName ?? deviceId
855
+ let commType = communicationInterfaceName(identity.communicationInterface())
856
+ let isEmulator = identity.communicationInterface() == .emulator
756
857
 
757
- NSLog("[FlirManager] Camera discovered: \(deviceId)")
858
+ // Use FlirLogger for consistent logging
859
+ FlirLogger.logDeviceFound(deviceId: deviceId, name: displayName, type: commType, isEmulator: isEmulator)
758
860
 
759
861
  // Store identity for later connection
760
862
  identityMap[deviceId] = identity
@@ -762,14 +864,15 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
762
864
  // Create device info
763
865
  let deviceInfo = FlirDeviceInfo(
764
866
  deviceId: deviceId,
765
- name: discoveredCamera.displayName ?? deviceId,
766
- communicationType: communicationInterfaceName(identity.communicationInterface()),
767
- isEmulator: identity.communicationInterface() == .emulator
867
+ name: displayName,
868
+ communicationType: commType,
869
+ isEmulator: isEmulator
768
870
  )
769
871
 
770
872
  // Add to discovered list if not already present
771
873
  if !discoveredDevices.contains(where: { $0.deviceId == deviceId }) {
772
874
  discoveredDevices.append(deviceInfo)
875
+ FlirLogger.log(.discovery, "Total discovered devices: \(discoveredDevices.count)")
773
876
  }
774
877
 
775
878
  // Notify delegate
@@ -781,13 +884,14 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
781
884
 
782
885
  public func cameraLost(_ cameraIdentity: FLIRIdentity) {
783
886
  let deviceId = cameraIdentity.deviceId()
784
- NSLog("[FlirManager] Camera lost: \(deviceId)")
887
+ FlirLogger.log(.discovery, "Camera lost: \(deviceId)")
785
888
 
786
889
  identityMap.removeValue(forKey: deviceId)
787
890
  discoveredDevices.removeAll { $0.deviceId == deviceId }
788
891
 
789
892
  // If this was our connected device, handle disconnect
790
893
  if connectedDeviceId == deviceId {
894
+ FlirLogger.log(.disconnect, "Lost connected device - triggering disconnect")
791
895
  disconnect()
792
896
  }
793
897
 
@@ -798,7 +902,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
798
902
  }
799
903
 
800
904
  public func discoveryError(_ error: String, netServiceError nsnetserviceserror: Int32, on iface: FLIRCommunicationInterface) {
801
- NSLog("[FlirManager] Discovery error: \(error) (\(nsnetserviceserror)) on interface: \(iface)")
905
+ FlirLogger.logError(.discovery, "Discovery error: \(error) (code=\(nsnetserviceserror)) on interface: \(iface)")
802
906
 
803
907
  DispatchQueue.main.async { [weak self] in
804
908
  self?.delegate?.onError("Discovery error: \(error)")
@@ -806,7 +910,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
806
910
  }
807
911
 
808
912
  public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
809
- NSLog("[FlirManager] Discovery finished on interface: \(iface)")
913
+ FlirLogger.log(.discovery, "Discovery finished on interface: \(iface)")
810
914
  isScanning = false
811
915
  }
812
916
  }
@@ -815,13 +919,19 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
815
919
 
816
920
  extension FlirManager: FLIRDataReceivedDelegate {
817
921
  public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
818
- NSLog("[FlirManager] Camera disconnected: \(error?.localizedDescription ?? "no error")")
922
+ FlirLogger.logError(.disconnect, "Camera disconnected callback", error: error)
923
+
924
+ // Stop battery polling
925
+ stopBatteryPolling()
819
926
 
820
927
  _isConnected = false
821
928
  _isStreaming = false
822
929
  connectedDeviceId = nil
823
930
  connectedDeviceName = nil
824
931
 
932
+ // Reset frame counter
933
+ FlirLogger.resetFrameCounter()
934
+
825
935
  DispatchQueue.main.async { [weak self] in
826
936
  self?.delegate?.onDeviceDisconnected()
827
937
  self?.emitStateChange("disconnected")
@@ -833,7 +943,7 @@ extension FlirManager: FLIRDataReceivedDelegate {
833
943
 
834
944
  extension FlirManager: FLIRStreamDelegate {
835
945
  public func onError(_ error: Error) {
836
- NSLog("[FlirManager] Stream error: \(error)")
946
+ FlirLogger.logError(.streaming, "Stream error", error: error)
837
947
 
838
948
  DispatchQueue.main.async { [weak self] in
839
949
  self?.delegate?.onError("Stream error: \(error.localizedDescription)")
@@ -873,6 +983,9 @@ extension FlirManager: FLIRStreamDelegate {
873
983
  }
874
984
  }
875
985
 
986
+ // Rate-limited frame logging
987
+ FlirLogger.logFrame(width: Int(image.size.width), height: Int(image.size.height), temperature: lastTemperature)
988
+
876
989
  DispatchQueue.main.async { [weak self] in
877
990
  guard let self = self else { return }
878
991
  self.delegate?.onFrameReceived(
@@ -900,7 +1013,7 @@ extension FlirManager: FLIRStreamDelegate {
900
1013
  }
901
1014
  }
902
1015
  } catch {
903
- NSLog("[FlirManager] Streamer update error: \(error)")
1016
+ FlirLogger.logError(.frame, "Streamer update error", error: error)
904
1017
  }
905
1018
  }
906
1019
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.1.37",
3
+ "version": "2.1.39",
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",