ilabs-flir 2.4.5 → 2.4.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.
@@ -86,16 +86,6 @@ object FlirManager {
86
86
 
87
87
  fun setPalette(name: String) {
88
88
  sdkManager?.setPalette(name)
89
- // Also try to update the app's global Var.cool if possible
90
- try {
91
- val palettes = getAvailablePalettes()
92
- val idx = palettes.indexOfFirst { it.equals(name, ignoreCase = true) }
93
- if (idx != -1) {
94
- updateAcol(idx.toFloat())
95
- }
96
- } catch (e: Exception) {
97
- Log.e(TAG, "setPalette: Failed to update Var.cool", e)
98
- }
99
89
  }
100
90
 
101
91
  fun getAvailablePalettes(): List<String> {
@@ -254,20 +244,19 @@ object FlirManager {
254
244
  val bitmap = latestBitmap ?: return null
255
245
 
256
246
  // Map UI normalized (0..1) to Raw sensor normalized (0..1) based on display rotation
257
- val rawCoords = when (rotation) {
258
- 0 -> Pair(nx, ny)
259
- 90, -270 -> Pair(ny, 1.0 - nx)
260
- 180, -180 -> Pair(1.0 - nx, 1.0 - ny)
261
- 270, -90 -> Pair(1.0 - ny, nx)
262
- else -> Pair(1.0 - ny, nx) // Default to -90 for backward compatibility
263
- }
247
+ // Using generic trigonometric rotation formula for total precision
248
+ val angle = -rotation.toDouble() // Inverse the display rotation
249
+ val rad = Math.toRadians(angle)
250
+ val cosA = Math.cos(rad)
251
+ val sinA = Math.sin(rad)
264
252
 
265
- val rawX = rawCoords.first
266
- val rawY = rawCoords.second
253
+ // Rotate around center (0.5, 0.5)
254
+ val dx = nx - 0.5
255
+ val dy = ny - 0.5
256
+ val rawX = dx * cosA - dy * sinA + 0.5
257
+ val rawY = dx * sinA + dy * cosA + 0.5
267
258
 
268
- val px = (rawX * bitmap.width).toInt().coerceIn(0, bitmap.width - 1)
269
- val py = (rawY * bitmap.height).toInt().coerceIn(0, bitmap.height - 1)
270
- return getTemperatureAt(px, py)
259
+ return FlirSdkManager.getInstance(reactContext).getTemperatureAtNormalized(rawX, rawY)
271
260
  }
272
261
 
273
262
  fun getTemperatureAtPoint(x: Int, y: Int): Double? = getTemperatureAt(x, y)
@@ -464,15 +453,6 @@ object FlirManager {
464
453
  }
465
454
 
466
455
  coolField?.set(null, shaderIdx)
467
-
468
- // Standard FLIR palette list
469
- val paletteNames = getAvailablePalettes()
470
- val maxEff = paletteNames.size
471
- val paletteIdx = rawIdx % maxEff
472
- val safeIdx = if (paletteIdx < 0) paletteIdx + maxEff else paletteIdx
473
-
474
- val targetPaletteName = paletteNames[safeIdx]
475
- sdkManager?.setPalette(targetPaletteName)
476
456
 
477
457
  } catch (e: Throwable) {
478
458
  Log.w(TAG, "updateAcol reflection failed: ${e.message}")
@@ -34,7 +34,7 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
34
34
  // Simple placeholder conversion: converts an ARGB color to a pseudo-temperature value.
35
35
  // Replace with SDK call when integrating thermalsdk APIs.
36
36
  @ReactMethod
37
- fun getTemperatureFromColor(color: Int, promise: com.facebook.react.bridge.Promise?) {
37
+ fun getTemperatureFromColor(color: Int, promise: Promise?) {
38
38
  try {
39
39
  val r = (color shr 16) and 0xFF
40
40
  val g = (color shr 8) and 0xFF
@@ -305,17 +305,18 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
305
305
 
306
306
  @ReactMethod
307
307
  fun stopFlir(promise: Promise?) {
308
- if (isDebounced()) {
309
- promise?.resolve(false)
310
- return
311
- }
312
- try {
313
- FlirManager.stop()
314
- promise?.resolve(true)
315
- } catch (e: Exception) {
316
- promise?.reject("ERR_FLIR_STOP", e)
317
- }
318
- }
308
+ if (isDebounced()) {
309
+ promise?.resolve(false)
310
+ return
311
+ }
312
+ try {
313
+ FlirManager.stop()
314
+ promise?.resolve(true)
315
+ } catch (e: Exception) {
316
+ promise?.reject("ERR_FLIR_STOP", e)
317
+ }
318
+ }
319
+
319
320
 
320
321
  @ReactMethod
321
322
  fun simulateFlirContextLoss(promise: Promise?) {
@@ -440,7 +441,7 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
440
441
  }
441
442
 
442
443
  @ReactMethod
443
- fun getDebugInfo(promise: com.facebook.react.bridge.Promise?) {
444
+ fun getDebugInfo(promise: Promise?) {
444
445
  try {
445
446
  val result = com.facebook.react.bridge.Arguments.createMap()
446
447
 
@@ -134,12 +134,17 @@ public class FlirSdkManager {
134
134
  notifyError("SDK not initialized");
135
135
  return;
136
136
  }
137
- if (isScanning)
138
- return;
137
+ if (isScanning) {
138
+ Log.d(TAG, "Discovery already running, ensuring clean state...");
139
+ try {
140
+ DiscoveryFactory.getInstance().stop();
141
+ } catch (Throwable ignored) {}
142
+ isScanning = false;
143
+ }
139
144
 
140
145
  isScanning = true;
141
146
  discoveredDevices.clear();
142
- Log.d(TAG, "Starting discovery...");
147
+ Log.d(TAG, "Starting discovery for all interfaces...");
143
148
 
144
149
  try {
145
150
  DiscoveryFactory.getInstance().scan(
@@ -157,19 +162,23 @@ public class FlirSdkManager {
157
162
  }
158
163
 
159
164
  public void stopScan() {
160
- if (!isScanning) return;
161
- isScanning = false;
162
- executor.execute(this::stopScanInternal);
165
+ executor.execute(() -> {
166
+ if (!isScanning) return;
167
+ isScanning = false;
168
+ stopScanInternal();
169
+ });
163
170
  }
164
171
 
165
172
  private void stopScanInternal() {
166
173
  try {
167
- Log.d(TAG, "Stopping discovery...");
174
+ Log.d(TAG, "Stopping all discovery scanners...");
168
175
  // Use zero-arg stop() as seen in official samples to stop all scanners
169
176
  DiscoveryFactory.getInstance().stop();
170
177
  Log.d(TAG, "Discovery stopped successfully");
171
- } catch (Exception e) {
172
- Log.w(TAG, "Stop scan warning (internal SDK): " + e.getMessage());
178
+ } catch (Throwable t) {
179
+ // This catches the notorious 'Receiver not registered' IllegalArgumentException
180
+ // and any other JNI-bubbled exceptions during teardown.
181
+ Log.w(TAG, "Stop scan suppressed (SDK internal race): " + t.getMessage());
173
182
  }
174
183
  }
175
184
 
@@ -297,6 +306,10 @@ public class FlirSdkManager {
297
306
 
298
307
  public void disconnect() {
299
308
  executor.execute(() -> {
309
+ if (isScanning) {
310
+ isScanning = false;
311
+ stopScanInternal();
312
+ }
300
313
  stopStreamInternal();
301
314
 
302
315
  if (camera != null) {
@@ -579,6 +592,37 @@ public class FlirSdkManager {
579
592
  return result[0];
580
593
  }
581
594
 
595
+ /**
596
+ * Samples temperature using normalized coordinates (0.0 to 1.0)
597
+ * This avoids clamping bugs when UI dimensions differ from sensor dimensions.
598
+ */
599
+ public double getTemperatureAtNormalized(double nx, double ny) {
600
+ final double[] result = { Double.NaN };
601
+ synchronized (this) {
602
+ if (streamer == null) return Double.NaN;
603
+ try {
604
+ streamer.withThermalImage(thermalImage -> {
605
+ try {
606
+ int w = thermalImage.getWidth();
607
+ int h = thermalImage.getHeight();
608
+ // Map normalized 0..1 to sensor pixels 0..w-1
609
+ int cx = (int) Math.max(0, Math.min(w - 1, nx * w));
610
+ int cy = (int) Math.max(0, Math.min(h - 1, ny * h));
611
+ ThermalValue value = thermalImage.getValueAt(new Point(cx, cy));
612
+ if (value != null) {
613
+ result[0] = value.asCelsius().value;
614
+ }
615
+ } catch (Exception e) {
616
+ Log.w(TAG, "Normalized temp query error", e);
617
+ }
618
+ });
619
+ } catch (Exception e) {
620
+ Log.w(TAG, "Normalized temp query failed", e);
621
+ }
622
+ }
623
+ return result[0];
624
+ }
625
+
582
626
  // ==================== LISTENERS ====================
583
627
 
584
628
  public void setPalette(String paletteName) {
@@ -478,22 +478,17 @@ import ThermalSDK
478
478
  let h = Double(thermalImage.getHeight())
479
479
 
480
480
  // Map UI normalized (0..1) to Raw sensor normalized (0..1) based on display rotation
481
- var rawX = nx
482
- var rawY = y
481
+ // Using generic trigonometric rotation formula for total precision
482
+ let angle = -Double(rotation) // Inverse the display rotation
483
+ let rad = angle * .pi / 180.0
484
+ let cosA = cos(rad)
485
+ let sinA = sin(rad)
483
486
 
484
- switch rotation {
485
- case 90, -270:
486
- rawX = y
487
- rawY = 1.0 - nx
488
- case 180, -180:
489
- rawX = 1.0 - nx
490
- rawY = 1.0 - y
491
- case 270, -90:
492
- rawX = 1.0 - y
493
- rawY = nx
494
- default:
495
- break // 0 deg or default
496
- }
487
+ // Rotate around center (0.5, 0.5)
488
+ let dx = nx - 0.5
489
+ let dy = y - 0.5
490
+ let rawX = dx * cosA - dy * sinA + 0.5
491
+ let rawY = dx * sinA + dy * cosA + 0.5
497
492
 
498
493
  // Map normalized (0.0 - 1.0) to actual sensor pixels
499
494
  let cx = max(0, min(Int(w) - 1, Int(rawX * w)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.4.5",
3
+ "version": "2.4.7",
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",