ilabs-flir 2.2.12 → 2.2.14

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.
@@ -4,7 +4,6 @@ import android.content.Context
4
4
  import android.graphics.Bitmap
5
5
  import android.util.Log
6
6
  import com.facebook.react.bridge.Arguments
7
- import com.facebook.react.bridge.ReactApplicationContext
8
7
  import com.facebook.react.bridge.ReactContext
9
8
  import com.facebook.react.bridge.WritableArray
10
9
  import com.facebook.react.bridge.WritableMap
@@ -17,17 +16,15 @@ import java.util.concurrent.atomic.AtomicLong
17
16
 
18
17
  /**
19
18
  * Simplified FlirManager - bridge between React Native and FlirSdkManager
20
- * No filtering - returns ALL discovered devices (USB, Network, Emulator)
21
- * Let React Native handle any filtering logic
19
+ * Matches the simplified pattern: scan -> connect -> stream -> disconnect
22
20
  */
23
21
  object FlirManager {
24
22
  private const val TAG = "FlirManager"
25
23
 
26
24
  private var sdkManager: FlirSdkManager? = null
27
25
  private var reactContext: ReactContext? = null
28
- private var appContext: Context? = null
29
26
 
30
- // Frame rate limiting
27
+ // Frame rate limiting for RN events
31
28
  private val lastEmitMs = AtomicLong(0)
32
29
  private val minEmitIntervalMs = 100L // ~10 fps max for RN events
33
30
 
@@ -38,8 +35,12 @@ object FlirManager {
38
35
  private var isStreaming = false
39
36
  private var connectedDeviceId: String? = null
40
37
  private var connectedDeviceName: String? = null
38
+
39
+ // Concurrency control
40
+ private val shouldProcessFrames = java.util.concurrent.atomic.AtomicBoolean(false)
41
+ private val isUpdatingTexture = java.util.concurrent.atomic.AtomicBoolean(false)
41
42
 
42
- // Latest bitmap for texture updates
43
+ // Latest bitmap
43
44
  private var latestBitmap: Bitmap? = null
44
45
 
45
46
  // Callbacks
@@ -47,82 +48,49 @@ object FlirManager {
47
48
  fun onTextureUpdate(bitmap: Bitmap, textureUnit: Int)
48
49
  }
49
50
 
50
- interface TemperatureCallback {
51
- fun onTemperatureData(temperature: Double, x: Int, y: Int)
52
- }
53
-
54
51
  private var textureCallback: TextureUpdateCallback? = null
55
- private var temperatureCallback: TemperatureCallback? = null
56
52
 
57
53
  fun setTextureCallback(callback: TextureUpdateCallback?) {
58
54
  textureCallback = callback
59
55
  }
60
56
 
61
- fun setTemperatureCallback(callback: TemperatureCallback?) {
62
- temperatureCallback = callback
63
- }
64
-
65
57
  fun getLatestBitmap(): Bitmap? = latestBitmap
66
58
 
67
- // Preference: ask SDK to deliver oriented/rotated frames (if SDK supports it)
68
- fun setPreferSdkRotation(prefer: Boolean) {
69
- sdkManager?.setPreferSdkRotation(prefer)
70
- }
71
-
72
- fun isPreferSdkRotation(): Boolean {
73
- return sdkManager?.isPreferSdkRotation() ?: false
74
- }
75
-
76
- fun getBatteryLevel(): Int {
77
- return sdkManager?.getBatteryLevel() ?: -1
78
- }
79
-
80
- fun isBatteryCharging(): Boolean {
81
- return sdkManager?.isBatteryCharging() ?: false
82
- }
59
+ // Stubs for removed features
60
+ fun setPreferSdkRotation(prefer: Boolean) { /* No-op */ }
61
+ fun isPreferSdkRotation(): Boolean = false
62
+ fun getBatteryLevel(): Int = -1
63
+ fun isBatteryCharging(): Boolean = false
64
+ fun setPalette(name: String) { /* No-op */ }
65
+ fun getAvailablePalettes(): List<String> = emptyList()
83
66
 
84
67
  /**
85
68
  * Initialize the FLIR SDK
86
69
  */
87
70
  fun init(context: Context) {
88
- // Store react context for event emission if it's a React context
89
- // Always update if we get a valid ReactContext (in case previous was stale)
90
71
  if (context is ReactContext) {
91
- Log.d(TAG, "[Flir-BRIDGE-LOAD] Storing ReactContext for event emission: ${context.javaClass.simpleName}")
92
72
  reactContext = context
93
- } else {
94
- Log.d(TAG, "[Flir-BRIDGE-LOAD] Context is not ReactContext: ${context.javaClass.simpleName}")
95
73
  }
96
74
 
97
- if (isInitialized) {
98
- Log.d(TAG, "[Flir-BRIDGE-LOAD] Already initialized")
99
- return
100
- }
101
-
102
- appContext = context.applicationContext
75
+ if (isInitialized) return
103
76
 
104
77
  sdkManager = FlirSdkManager.getInstance(context)
105
78
  sdkManager?.setListener(sdkListener)
106
79
  sdkManager?.initialize()
107
80
 
108
81
  isInitialized = true
109
- Log.i(TAG, "[Flir-BRIDGE-LOAD] FlirManager initialized")
82
+ Log.i(TAG, "FlirManager initialized")
110
83
  }
111
84
 
112
85
  /**
113
- * Start scanning for devices (USB, Network, Emulator - ALL types)
86
+ * Start scanning
114
87
  */
115
88
  fun startDiscovery(retry: Boolean = false) {
116
- Log.i(TAG, "[Flir-BRIDGE-DISCOVERY] startDiscovery(retry=$retry)")
117
-
118
- if (!isInitialized && appContext != null) {
119
- init(appContext!!)
89
+ if (!isInitialized && reactContext != null) {
90
+ init(reactContext!!)
120
91
  }
121
92
 
122
- if (isScanning && !retry) {
123
- Log.d(TAG, "Already scanning")
124
- return
125
- }
93
+ if (isScanning && !retry) return
126
94
 
127
95
  isScanning = true
128
96
  emitDeviceState("discovering")
@@ -141,13 +109,12 @@ object FlirManager {
141
109
  * Stop scanning
142
110
  */
143
111
  fun stopDiscovery() {
144
- Log.i(TAG, "[Flir-BRIDGE-DISCOVERY] stopDiscovery")
145
- sdkManager?.stop()
112
+ sdkManager?.stopScan()
146
113
  isScanning = false
147
114
  }
148
115
 
149
116
  /**
150
- * Connect to a device by ID
117
+ * Connect to a device
151
118
  */
152
119
  fun connectToDevice(deviceId: String) {
153
120
  Log.i(TAG, "connectToDevice: $deviceId")
@@ -156,52 +123,19 @@ object FlirManager {
156
123
  val identity = devices.find { it.deviceId == deviceId }
157
124
 
158
125
  if (identity != null) {
159
- Log.i(TAG, "[Flir-BRIDGE-CONNECTION] Connecting to found device: $deviceId")
126
+ shouldProcessFrames.set(true)
160
127
  sdkManager?.connect(identity)
161
128
  } else {
162
- Log.e(TAG, "[Flir-BRIDGE-ERROR] Device not found: $deviceId")
129
+ Log.e(TAG, "Device not found: $deviceId")
163
130
  emitError("Device not found: $deviceId")
164
131
  }
165
132
  }
166
133
 
167
134
  /**
168
- * Switch to a different device
169
- */
170
- fun switchToDevice(deviceId: String) {
171
- if (deviceId == connectedDeviceId) {
172
- Log.d(TAG, "Already connected to: $deviceId")
173
- return
174
- }
175
-
176
- // Disconnect current and connect new
177
- if (isConnected) {
178
- sdkManager?.disconnect()
179
- }
180
- connectToDevice(deviceId)
181
- }
182
-
183
- /**
184
- * Start streaming from connected device
185
- */
186
- fun startStream() {
187
- Log.i(TAG, "startStream")
188
- sdkManager?.startStream()
189
- }
190
-
191
- /**
192
- * Stop streaming
193
- */
194
- fun stopStream() {
195
- Log.i(TAG, "[Flir-BRIDGE-STREAMING] stopStream")
196
- sdkManager?.stopStream()
197
- isStreaming = false
198
- }
199
-
200
- /**
201
- * Disconnect from current device
135
+ * Disconnect
202
136
  */
203
137
  fun disconnect() {
204
- Log.i(TAG, "[Flir-BRIDGE-DISCONNECT] disconnect")
138
+ shouldProcessFrames.set(false)
205
139
  sdkManager?.disconnect()
206
140
  isConnected = false
207
141
  isStreaming = false
@@ -213,49 +147,33 @@ object FlirManager {
213
147
  * Stop everything
214
148
  */
215
149
  fun stop() {
216
- Log.i(TAG, "stop")
217
- stopStream()
150
+ shouldProcessFrames.set(false)
218
151
  disconnect()
219
152
  stopDiscovery()
220
153
  latestBitmap = null
221
154
  }
222
155
 
156
+ // Stub legacy methods
157
+ fun startStream() { /* handled automatically by connect */ }
158
+ fun stopStream() { sdkManager?.stopStream() }
159
+
223
160
  /**
224
- * Get temperature at point in image coordinates
161
+ * Get temperature
225
162
  */
226
163
  fun getTemperatureAt(x: Int, y: Int): Double? {
227
- return sdkManager?.getTemperatureAt(x, y)?.takeIf { !it.isNaN() }
164
+ val temp = sdkManager?.getTemperatureAt(x, y)
165
+ return if (temp != null && !temp.isNaN()) temp else null
228
166
  }
229
167
 
230
- /**
231
- * Get temperature at normalized coordinates (0.0 to 1.0)
232
- */
233
- fun getTemperatureAtNormalized(normalizedX: Double, normalizedY: Double): Double? {
234
- return sdkManager?.getTemperatureAtNormalized(normalizedX, normalizedY)?.takeIf { !it.isNaN() }
168
+ fun getTemperatureAtNormalized(nx: Double, ny: Double): Double? {
169
+ // Not implemented in simplified version
170
+ return null
235
171
  }
236
172
 
237
- /**
238
- * Alias for getTemperatureAt
239
- */
240
173
  fun getTemperatureAtPoint(x: Int, y: Int): Double? = getTemperatureAt(x, y)
241
174
 
242
175
  /**
243
- * Set palette
244
- */
245
- fun setPalette(name: String) {
246
- Log.d(TAG, "setPalette: $name")
247
- sdkManager?.setPalette(name)
248
- }
249
-
250
- /**
251
- * Get available palettes
252
- */
253
- fun getAvailablePalettes(): List<String> {
254
- return sdkManager?.availablePalettes ?: emptyList()
255
- }
256
-
257
- /**
258
- * Get list of discovered devices
176
+ * Get discovered devices
259
177
  */
260
178
  fun getDiscoveredDevices(): List<Identity> {
261
179
  return sdkManager?.discoveredDevices ?: emptyList()
@@ -269,14 +187,11 @@ object FlirManager {
269
187
  fun isEmulator(): Boolean = connectedDeviceName?.contains("EMULAT", ignoreCase = true) == true
270
188
  fun isDeviceConnected(): Boolean = isConnected
271
189
 
272
- /**
273
- * Get connected device info
274
- */
275
190
  fun getConnectedDeviceInfo(): String {
276
191
  return connectedDeviceName ?: "Not connected"
277
192
  }
278
193
 
279
- /**
194
+ /**
280
195
  * Get latest frame as file path (for RN)
281
196
  */
282
197
  fun getLatestFramePath(): String? {
@@ -288,7 +203,6 @@ object FlirManager {
288
203
  }
289
204
  file.absolutePath
290
205
  } catch (t: Throwable) {
291
- Log.e(TAG, "Failed to save frame", t)
292
206
  null
293
207
  }
294
208
  }
@@ -296,30 +210,24 @@ object FlirManager {
296
210
  // SDK Listener
297
211
  private val sdkListener = object : FlirSdkManager.Listener {
298
212
  override fun onDeviceFound(identity: Identity) {
299
- Log.i(TAG, "Device found: ${identity.deviceId}")
213
+ // Devices updated event handles the list, but we can log unique finds
300
214
  }
301
215
 
302
216
  override fun onDeviceListUpdated(devices: List<Identity>) {
303
- Log.i(TAG, "Devices updated: ${devices.size} found")
304
- devices.forEach {
305
- Log.d(TAG, " - ${it.deviceId} (${it.communicationInterface})")
306
- }
217
+ Log.d(TAG, "Devices found: ${devices.size}")
307
218
  emitDevicesFound(devices)
308
219
  }
309
220
 
310
- override fun onConnected(identity: Identity?) {
311
- Log.i(TAG, "Connected to: ${identity?.deviceId}")
221
+ override fun onConnected(identity: Identity) {
222
+ Log.i(TAG, "Connected to: ${identity.deviceId}")
312
223
  isConnected = true
313
- connectedDeviceId = identity?.deviceId
314
- connectedDeviceName = identity?.deviceId
224
+ connectedDeviceId = identity.deviceId
225
+ connectedDeviceName = identity.deviceId
315
226
  emitDeviceState("connected")
316
-
317
- // Auto-start streaming when connected
318
- startStream()
319
227
  }
320
228
 
321
229
  override fun onDisconnected() {
322
- Log.i(TAG, "[Flir-BRIDGE-DISCONNECT] Disconnected callback")
230
+ Log.i(TAG, "Disconnected")
323
231
  isConnected = false
324
232
  isStreaming = false
325
233
  connectedDeviceId = null
@@ -328,53 +236,46 @@ object FlirManager {
328
236
  }
329
237
 
330
238
  override fun onFrame(bitmap: Bitmap) {
331
- if (bitmap.isRecycled || bitmap.width <= 0 || bitmap.height <= 0) {
239
+ // IMMEDIATE STOP CHECK
240
+ if (!shouldProcessFrames.get()) {
332
241
  return
333
242
  }
334
-
243
+
335
244
  latestBitmap = bitmap
336
- isStreaming = true
337
245
 
338
- // Notify texture callback (for GL rendering)
246
+ // If this is the first frame, notify JS that we are now streaming
247
+ if (!isStreaming) {
248
+ isStreaming = true
249
+ emitDeviceState("streaming")
250
+ }
251
+
252
+ // NON-BLOCKING TEXTURE UPDATE (Drop frames if busy)
339
253
  if (textureCallback != null) {
340
- textureCallback?.onTextureUpdate(bitmap, 0)
341
- } else {
342
- // Log only occasionally to avoid spam
343
- if (System.currentTimeMillis() % 5000 < 100) {
344
- Log.w(TAG, "⚠️ Frame received but textureCallback is null - texture won't update!")
254
+ if (isUpdatingTexture.compareAndSet(false, true)) {
255
+ try {
256
+ textureCallback?.onTextureUpdate(bitmap, 0)
257
+ } finally {
258
+ isUpdatingTexture.set(false)
259
+ }
260
+ } else {
261
+ // Log.v(TAG, "Dropping frame - texture update busy")
345
262
  }
346
263
  }
347
264
 
348
- // Rate-limited RN event
349
- emitFrameToReactNative(bitmap)
265
+ // Notify RN - disabled
266
+ // emitFrameToReactNative(bitmap)
350
267
  }
351
268
 
352
269
  override fun onError(message: String) {
353
- Log.e(TAG, "Error: $message")
354
-
355
- // Parse error code if present (format: "CODE: message")
356
- val parts = message.split(": ", limit = 2)
357
- val errorCode = if (parts.size == 2) parts[0] else "FLIR_ERROR"
358
- val errorMessage = if (parts.size == 2) parts[1] else message
359
-
360
- // Auto-disable streaming on critical errors to allow retry
361
- if (errorCode.contains("NATIVE") || errorCode.contains("INIT")) {
362
- Log.w(TAG, "[Flir-BRIDGE-ERROR] Critical error detected, stopping stream")
363
- isStreaming = false
364
- stopStream()
365
- }
366
-
367
- emitError(errorCode, errorMessage)
368
- }
369
-
370
- override fun onBatteryUpdated(level: Int, isCharging: Boolean) {
371
- Log.d(TAG, "[Flir-BRIDGE-BATTERY] onBatteryUpdated: level=$level charging=$isCharging")
372
- emitBatteryState(level, isCharging)
270
+ emitError(message)
373
271
  }
374
272
  }
375
273
 
376
- // React Native event emitters
274
+ // React Native Emitters
275
+
377
276
  private fun emitFrameToReactNative(bitmap: Bitmap) {
277
+ // PERF: Disabled to reduce bridge traffic
278
+ /*
378
279
  val now = System.currentTimeMillis()
379
280
  if (now - lastEmitMs.get() < minEmitIntervalMs) return
380
281
  lastEmitMs.set(now)
@@ -388,18 +289,12 @@ object FlirManager {
388
289
  }
389
290
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
390
291
  .emit("FlirFrameReceived", params)
391
- } catch (e: Exception) {
392
- // Ignore
393
- }
292
+ } catch (e: Exception) { }
293
+ */
394
294
  }
395
295
 
396
296
  private fun emitDeviceState(state: String) {
397
- val ctx = reactContext
398
- if (ctx == null) {
399
- Log.e(TAG, "[Flir-BRIDGE-ERROR] Cannot emit FlirDeviceConnected($state) - reactContext is null!")
400
- return
401
- }
402
- Log.d(TAG, "[Flir-BRIDGE-CONNECTION] Emitting FlirDeviceConnected: $state")
297
+ val ctx = reactContext ?: return
403
298
  try {
404
299
  val params = Arguments.createMap().apply {
405
300
  putString("state", state)
@@ -407,28 +302,20 @@ object FlirManager {
407
302
  putBoolean("isStreaming", isStreaming)
408
303
  putBoolean("isEmulator", isEmulator())
409
304
  connectedDeviceName?.let { putString("deviceName", it) }
410
- connectedDeviceId?.let { putString("deviceId", it) }
411
305
  }
412
306
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
413
307
  .emit("FlirDeviceConnected", params)
414
- } catch (e: Exception) {
415
- Log.e(TAG, "Failed to emit device state", e)
416
- }
308
+ } catch (e: Exception) { }
417
309
  }
418
310
 
419
311
  private fun emitDevicesFound(devices: List<Identity>) {
420
- val ctx = reactContext
421
- if (ctx == null) {
422
- Log.e(TAG, "Cannot emit FlirDevicesFound - reactContext is null!")
423
- return
424
- }
425
- Log.d(TAG, "Emitting FlirDevicesFound with ${devices.size} devices")
312
+ val ctx = reactContext ?: return
426
313
  try {
427
314
  val params = Arguments.createMap()
428
315
  val devicesArray: WritableArray = Arguments.createArray()
429
316
 
430
317
  devices.forEach { identity ->
431
- val deviceMap: WritableMap = Arguments.createMap().apply {
318
+ val deviceMap = Arguments.createMap().apply {
432
319
  putString("id", identity.deviceId)
433
320
  putString("name", identity.deviceId)
434
321
  putString("communicationType", identity.communicationInterface.name)
@@ -442,97 +329,34 @@ object FlirManager {
442
329
 
443
330
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
444
331
  .emit("FlirDevicesFound", params)
445
- Log.d(TAG, "[Flir-BRIDGE-DISCOVERY] Successfully emitted FlirDevicesFound (${devices.size} devices)")
446
- } catch (e: Exception) {
447
- Log.e(TAG, "[Flir-BRIDGE-ERROR] Failed to emit devices found", e)
448
- }
449
- }
450
-
451
- private fun emitBatteryState(level: Int, isCharging: Boolean) {
452
- val ctx = reactContext
453
- if (ctx == null) {
454
- Log.w(TAG, "Cannot emit FlirBatteryUpdated - reactContext is null!")
455
- return
456
- }
457
- try {
458
- val params = Arguments.createMap().apply {
459
- putInt("level", level)
460
- putBoolean("isCharging", isCharging)
461
- }
462
- ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
463
- .emit("FlirBatteryUpdated", params)
464
- } catch (e: Exception) {
465
- Log.e(TAG, "Failed to emit battery state", e)
466
- }
332
+ } catch (e: Exception) { }
467
333
  }
468
334
 
469
335
  private fun emitError(message: String) {
470
- emitError("FLIR_ERROR", message)
471
- }
472
-
473
- private fun emitError(errorCode: String, message: String) {
474
336
  val ctx = reactContext ?: return
475
337
  try {
476
338
  val params = Arguments.createMap().apply {
477
- putString("code", errorCode)
478
339
  putString("error", message)
479
- putString("message", message) // For backward compatibility
480
- putBoolean("canRetry", errorCode.contains("NATIVE") || errorCode.contains("INIT"))
340
+ putString("message", message)
481
341
  }
482
342
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
483
343
  .emit("FlirError", params)
484
- Log.d(TAG, "[Flir-BRIDGE-ERROR] Emitted FlirError: [$errorCode] $message")
485
- } catch (e: Exception) {
486
- Log.e(TAG, "Failed to emit error", e)
487
- }
344
+ } catch (e: Exception) { }
488
345
  }
489
346
 
490
- // Legacy compatibility
491
- @JvmStatic
492
- fun getInstance(): FlirManager = this
347
+ // Legacy methods placeholders
348
+ @JvmStatic fun getInstance(): FlirManager = this
493
349
 
494
350
  interface DiscoveryCallback {
495
351
  fun onDeviceFound(deviceName: String)
496
352
  fun onDiscoveryTimeout()
497
353
  fun onEmulatorEnabled()
498
354
  }
499
-
500
- private var discoveryCallback: DiscoveryCallback? = null
501
-
502
- fun setDiscoveryCallback(callback: DiscoveryCallback?) {
503
- discoveryCallback = callback
504
- }
505
-
506
- // Legacy methods - no-ops or simple forwards
507
- fun setEmulatorMode(enabled: Boolean) {
508
- Log.d(TAG, "setEmulatorMode($enabled) - legacy, use startDiscovery() instead")
509
- if (enabled) {
510
- startDiscovery(retry = true)
511
- }
512
- }
513
-
514
- fun enableEmulatorMode() = setEmulatorMode(true)
515
-
516
- fun forceEmulatorMode(type: String = "FLIR_ONE_EDGE") {
517
- Log.d(TAG, "forceEmulatorMode($type) - legacy, use startDiscovery() instead")
518
- startDiscovery(retry = true)
519
- }
520
-
521
- fun setPreferredEmulatorType(type: String) {
522
- Log.d(TAG, "setPreferredEmulatorType($type) - legacy, no longer used")
523
- }
524
-
525
- fun updateAcol(value: Float) {
526
- // No-op - not used in simplified version
527
- }
528
-
529
- /**
530
- * Cleanup
531
- */
532
- fun destroy() {
533
- stop()
534
- sdkManager?.destroy()
535
- sdkManager = null
536
- isInitialized = false
537
- }
355
+ fun setDiscoveryCallback(callback: DiscoveryCallback?) { /* No-op */ }
356
+ fun setEmulatorMode(enabled: Boolean) { startDiscovery() }
357
+ fun enableEmulatorMode() = startDiscovery()
358
+ fun forceEmulatorMode(type: String = "FLIR_ONE_EDGE") { startDiscovery() }
359
+ fun setPreferredEmulatorType(type: String) { }
360
+ fun updateAcol(value: Float) { }
361
+ fun destroy() { stop() }
538
362
  }