ilabs-flir 2.3.8 → 2.3.10
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.
- package/android/Flir/src/main/java/flir/android/FlirCommands.java +136 -136
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +465 -509
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +391 -375
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +44 -27
- package/ios/Flir/src/FlirManager.swift +45 -55
- package/ios/Flir/src/FlirPreviewView.m +0 -3
- package/ios/Flir/src/FlirState.m +9 -3
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import android.util.Log;
|
|
|
8
8
|
import com.flir.thermalsdk.ErrorCode;
|
|
9
9
|
import com.flir.thermalsdk.androidsdk.ThermalSdkAndroid;
|
|
10
10
|
import com.flir.thermalsdk.androidsdk.image.BitmapAndroid;
|
|
11
|
+
import com.flir.thermalsdk.image.ImageBuffer;
|
|
11
12
|
import com.flir.thermalsdk.image.Palette;
|
|
12
13
|
import com.flir.thermalsdk.image.PaletteManager;
|
|
13
14
|
import com.flir.thermalsdk.image.Point;
|
|
@@ -362,32 +363,37 @@ public class FlirSdkManager {
|
|
|
362
363
|
// 1. Apply Palette
|
|
363
364
|
if (paletteToApply != null) {
|
|
364
365
|
try {
|
|
365
|
-
Palette
|
|
366
|
-
// Standard FLIR palette list from samples
|
|
367
|
-
String[] paletteNames = {"Gray", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"};
|
|
366
|
+
List<Palette> sdkPalettes = PaletteManager.getDefaultPalettes();
|
|
368
367
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
368
|
+
if (paletteToApply.equalsIgnoreCase("Gray") || paletteToApply.equalsIgnoreCase("grayscale")) {
|
|
369
|
+
// User wants Gray - map to WhiteHot which is the SDK's standard grayscale
|
|
370
|
+
for (Palette p : sdkPalettes) {
|
|
371
|
+
if (p.name.equalsIgnoreCase("WhiteHot") || p.name.equalsIgnoreCase("White hot")) {
|
|
372
|
+
thermalImage.setPalette(p);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
Palette palette = null;
|
|
378
|
+
for (Palette p : sdkPalettes) {
|
|
379
|
+
if (p.name.equalsIgnoreCase(paletteToApply)) {
|
|
380
|
+
palette = p;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (palette != null) {
|
|
386
|
+
thermalImage.setPalette(palette);
|
|
387
|
+
} else if (paletteToApply.equalsIgnoreCase("Wheel")) {
|
|
388
|
+
// Fallback for Wheel if not found - some SDKs use different names
|
|
389
|
+
for (Palette p : sdkPalettes) {
|
|
390
|
+
if (p.name.contains("Wheel") || p.name.contains("ColorWheel") || p.name.contains("Rainbow")) {
|
|
391
|
+
thermalImage.setPalette(p);
|
|
392
|
+
break;
|
|
380
393
|
}
|
|
381
|
-
} catch (Throwable t) {
|
|
382
|
-
Log.w(TAG, "Dynamic palette lookup failed, using fallback mechanism");
|
|
383
394
|
}
|
|
384
|
-
break;
|
|
385
395
|
}
|
|
386
396
|
}
|
|
387
|
-
|
|
388
|
-
if (palette != null) {
|
|
389
|
-
thermalImage.setPalette(palette);
|
|
390
|
-
}
|
|
391
397
|
} catch (Throwable t) {
|
|
392
398
|
Log.e(TAG, "Failed to apply palette: " + paletteToApply, t);
|
|
393
399
|
}
|
|
@@ -396,8 +402,9 @@ public class FlirSdkManager {
|
|
|
396
402
|
// 2. Save Radiometric Snapshot if requested
|
|
397
403
|
if (snapshotPath != null) {
|
|
398
404
|
try {
|
|
405
|
+
Log.i(TAG, "[SNAPSHOT] Attempting to save radiometric snapshot: " + snapshotPath);
|
|
399
406
|
thermalImage.saveAs(snapshotPath);
|
|
400
|
-
Log.i(TAG, "Radiometric snapshot saved
|
|
407
|
+
Log.i(TAG, "[SNAPSHOT] ✅ Success: Radiometric snapshot saved");
|
|
401
408
|
if (snapshotCallback != null) {
|
|
402
409
|
snapshotCallback.onSnapshotSaved(snapshotPath);
|
|
403
410
|
}
|
|
@@ -410,12 +417,22 @@ public class FlirSdkManager {
|
|
|
410
417
|
}
|
|
411
418
|
|
|
412
419
|
// 3. Generate Bitmap for display
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (
|
|
417
|
-
|
|
420
|
+
// We use streamer.getImage() to get the rendered image with palette applied.
|
|
421
|
+
try {
|
|
422
|
+
Bitmap newBitmap = BitmapAndroid.createBitmap(streamer.getImage()).getBitMap();
|
|
423
|
+
if (newBitmap != null) {
|
|
424
|
+
Bitmap oldBitmap = latestBitmap;
|
|
425
|
+
latestBitmap = newBitmap;
|
|
426
|
+
if (listener != null) {
|
|
427
|
+
listener.onFrame(newBitmap);
|
|
428
|
+
}
|
|
429
|
+
// Recycle old bitmap to prevent memory leak
|
|
430
|
+
if (oldBitmap != null && oldBitmap != newBitmap) {
|
|
431
|
+
oldBitmap.recycle();
|
|
432
|
+
}
|
|
418
433
|
}
|
|
434
|
+
} catch (Exception e) {
|
|
435
|
+
Log.e(TAG, "Bitmap creation failed", e);
|
|
419
436
|
}
|
|
420
437
|
});
|
|
421
438
|
}
|
|
@@ -191,7 +191,8 @@ import ThermalSDK
|
|
|
191
191
|
NSLog("[FlirManager] Network camera detected — authenticating...")
|
|
192
192
|
|
|
193
193
|
// Use UUID-based persistent certificate name (matches FLIR sample).
|
|
194
|
-
// The camera has a bug where re-auth with a different name
|
|
194
|
+
// The camera has a bug where re-auth with a different name can conflict,
|
|
195
|
+
// so we generate a UUID once and persist it in UserDefaults.
|
|
195
196
|
let certName = self.getPersistentCertificateName()
|
|
196
197
|
NSLog("[FlirManager] Using certificate name: \(certName)")
|
|
197
198
|
|
|
@@ -435,7 +436,7 @@ import ThermalSDK
|
|
|
435
436
|
// Compiler says it is NOT optional here, so direct assignment.
|
|
436
437
|
let value = spot.getValue()
|
|
437
438
|
result = value.value
|
|
438
|
-
|
|
439
|
+
|
|
439
440
|
try? measurements.remove(spot)
|
|
440
441
|
}
|
|
441
442
|
}
|
|
@@ -445,8 +446,6 @@ import ThermalSDK
|
|
|
445
446
|
#endif
|
|
446
447
|
}
|
|
447
448
|
|
|
448
|
-
|
|
449
|
-
|
|
450
449
|
@objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
|
|
451
450
|
#if FLIR_ENABLED
|
|
452
451
|
guard let streamer = streamer, _isStreaming else { return Double.nan }
|
|
@@ -458,10 +457,10 @@ import ThermalSDK
|
|
|
458
457
|
|
|
459
458
|
// Map normalized (0.0 - 1.0) to actual sensor pixels
|
|
460
459
|
let cx = max(0, min(Int(w) - 1, Int(nx * w)))
|
|
461
|
-
let
|
|
460
|
+
let cy_fixed = max(0, min(Int(h) - 1, Int(y * h)))
|
|
462
461
|
|
|
463
462
|
if let measurements = thermalImage.measurements,
|
|
464
|
-
let spot = try? measurements.addSpot(CGPoint(x: cx, y:
|
|
463
|
+
let spot = try? measurements.addSpot(CGPoint(x: cx, y: cy_fixed)) {
|
|
465
464
|
result = spot.getValue().value
|
|
466
465
|
try? measurements.remove(spot)
|
|
467
466
|
}
|
|
@@ -503,8 +502,6 @@ import ThermalSDK
|
|
|
503
502
|
|
|
504
503
|
// MARK: - Battery (stub - not needed per user)
|
|
505
504
|
|
|
506
|
-
// MARK: - Battery (stub - not needed per user)
|
|
507
|
-
|
|
508
505
|
@objc public func getBatteryLevel() -> Int { return -1 }
|
|
509
506
|
@objc public func isBatteryCharging() -> Bool { return false }
|
|
510
507
|
|
|
@@ -531,21 +528,16 @@ import ThermalSDK
|
|
|
531
528
|
}
|
|
532
529
|
|
|
533
530
|
@objc public func getAvailablePalettes() -> [String] {
|
|
534
|
-
return ["
|
|
531
|
+
return ["WhiteHot", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"]
|
|
535
532
|
}
|
|
536
533
|
|
|
537
534
|
@objc public func generatePaletteIcons() -> [[String: String]] {
|
|
538
535
|
let paletteNames = getAvailablePalettes()
|
|
539
|
-
let fileManager = FileManager.default
|
|
540
|
-
let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
541
|
-
let paletteDir = cacheDir.appendingPathComponent("flir_palettes")
|
|
542
|
-
|
|
543
536
|
var results: [[String: String]] = []
|
|
544
537
|
for name in paletteNames {
|
|
545
|
-
let iconURL = paletteDir.appendingPathComponent("\(name.lowercased()).png")
|
|
546
538
|
results.append([
|
|
547
539
|
"name": name,
|
|
548
|
-
"uri":
|
|
540
|
+
"uri": "" // No URI - rely on local assets if any
|
|
549
541
|
])
|
|
550
542
|
}
|
|
551
543
|
return results
|
|
@@ -604,20 +596,15 @@ import ThermalSDK
|
|
|
604
596
|
return newName
|
|
605
597
|
}
|
|
606
598
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if iface.contains(.lightning) { return "LIGHTNING" }
|
|
610
|
-
if iface.contains(.network) { return "NETWORK" }
|
|
611
|
-
if iface.contains(.flirOneWireless) { return "WIRELESS" }
|
|
612
|
-
if iface.contains(.emulator) { return "EMULATOR" }
|
|
599
|
+
private func interfaceName(_ iface: Int) -> String {
|
|
600
|
+
// Placeholder for interface name mapping
|
|
613
601
|
return "UNKNOWN"
|
|
614
602
|
}
|
|
615
|
-
#endif
|
|
616
603
|
}
|
|
617
604
|
|
|
605
|
+
#if FLIR_ENABLED
|
|
618
606
|
// MARK: - Discovery Delegate
|
|
619
607
|
|
|
620
|
-
#if FLIR_ENABLED
|
|
621
608
|
extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
622
609
|
public func cameraDiscovered(_ camera: FLIRDiscoveredCamera) {
|
|
623
610
|
let identity = camera.identity
|
|
@@ -632,7 +619,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
|
632
619
|
let deviceInfo = FlirDeviceInfo(
|
|
633
620
|
deviceId: deviceId,
|
|
634
621
|
name: camera.displayName ?? deviceId,
|
|
635
|
-
communicationType: interfaceName(identity.communicationInterface()),
|
|
622
|
+
communicationType: interfaceName(Int(identity.communicationInterface().rawValue)),
|
|
636
623
|
isEmulator: identity.communicationInterface() == .emulator
|
|
637
624
|
)
|
|
638
625
|
|
|
@@ -653,7 +640,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
|
653
640
|
}
|
|
654
641
|
|
|
655
642
|
public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
|
|
656
|
-
NSLog("[FlirManager] Discovery finished: \(iface)")
|
|
643
|
+
NSLog("[FlirManager] Discovery finished: \(iface.rawValue)")
|
|
657
644
|
}
|
|
658
645
|
|
|
659
646
|
public func cameraLost(_ cameraIdentity: FLIRIdentity) {
|
|
@@ -669,11 +656,9 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
|
669
656
|
}
|
|
670
657
|
}
|
|
671
658
|
}
|
|
672
|
-
#endif
|
|
673
659
|
|
|
674
660
|
// MARK: - Camera Delegate
|
|
675
661
|
|
|
676
|
-
#if FLIR_ENABLED
|
|
677
662
|
extension FlirManager: FLIRDataReceivedDelegate {
|
|
678
663
|
public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
|
|
679
664
|
NSLog("[FlirManager] Camera disconnected: \(error?.localizedDescription ?? "clean")")
|
|
@@ -690,11 +675,9 @@ extension FlirManager: FLIRDataReceivedDelegate {
|
|
|
690
675
|
}
|
|
691
676
|
}
|
|
692
677
|
}
|
|
693
|
-
#endif
|
|
694
678
|
|
|
695
679
|
// MARK: - Stream Delegate
|
|
696
680
|
|
|
697
|
-
#if FLIR_ENABLED
|
|
698
681
|
extension FlirManager: FLIRStreamDelegate {
|
|
699
682
|
public func onError(_ error: Error) {
|
|
700
683
|
NSLog("[FlirManager] Stream error: \(error)")
|
|
@@ -702,13 +685,10 @@ extension FlirManager: FLIRStreamDelegate {
|
|
|
702
685
|
}
|
|
703
686
|
|
|
704
687
|
public func onImageReceived() {
|
|
705
|
-
NSLog("[FLIR-TRACE 1️⃣] onImageReceived called on SDK thread")
|
|
706
|
-
|
|
707
688
|
// Process frame on dedicated render queue (matches sample app pattern)
|
|
708
689
|
// This prevents blocking the SDK callback thread and main thread
|
|
709
690
|
// Guard to skip frame if already processing (prevents backpressure/latency)
|
|
710
691
|
guard !_isProcessingFrame else {
|
|
711
|
-
NSLog("[FLIR-TRACE ⏩] Skipping frame (already processing)")
|
|
712
692
|
return
|
|
713
693
|
}
|
|
714
694
|
|
|
@@ -716,12 +696,9 @@ extension FlirManager: FLIRStreamDelegate {
|
|
|
716
696
|
renderQueue.async { [weak self] in
|
|
717
697
|
defer { self?._isProcessingFrame = false }
|
|
718
698
|
guard let self = self, self._isStreaming, let streamer = self.streamer else {
|
|
719
|
-
NSLog("[FLIR-TRACE ❌] No self, streamer or not streaming in renderQueue")
|
|
720
699
|
return
|
|
721
700
|
}
|
|
722
701
|
|
|
723
|
-
NSLog("[FLIR-TRACE 2️⃣] Processing on renderQueue")
|
|
724
|
-
|
|
725
702
|
objc_sync_enter(self.stateLock)
|
|
726
703
|
let currentStreamer = self.streamer
|
|
727
704
|
let streaming = self._isStreaming
|
|
@@ -740,7 +717,26 @@ extension FlirManager: FLIRStreamDelegate {
|
|
|
740
717
|
|
|
741
718
|
streamer.withThermalImage { thermalImage in
|
|
742
719
|
// 1. Apply Palette
|
|
743
|
-
|
|
720
|
+
let sdkPalettes = thermalImage.paletteManager.getDefaultPalettes()
|
|
721
|
+
var targetPalette: FLIRPalette? = nil
|
|
722
|
+
|
|
723
|
+
if paletteToApply.lowercased() == "gray" || paletteToApply.lowercased() == "grayscale" {
|
|
724
|
+
// Map Gray to WhiteHot (standard SDK name)
|
|
725
|
+
targetPalette = sdkPalettes.first(where: {
|
|
726
|
+
$0.name.lowercased() == "whitehot" || $0.name.lowercased() == "white hot"
|
|
727
|
+
})
|
|
728
|
+
} else {
|
|
729
|
+
targetPalette = sdkPalettes.first(where: { $0.name.lowercased() == paletteToApply.lowercased() })
|
|
730
|
+
|
|
731
|
+
// Fallback for Wheel
|
|
732
|
+
if targetPalette == nil && paletteToApply.lowercased() == "wheel" {
|
|
733
|
+
targetPalette = sdkPalettes.first(where: {
|
|
734
|
+
$0.name.contains("Wheel") || $0.name.contains("ColorWheel") || $0.name.contains("Rainbow")
|
|
735
|
+
})
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if let palette = targetPalette {
|
|
744
740
|
thermalImage.palette = palette
|
|
745
741
|
}
|
|
746
742
|
|
|
@@ -753,29 +749,23 @@ extension FlirManager: FLIRStreamDelegate {
|
|
|
753
749
|
NSLog("[FlirManager] Failed to save radiometric snapshot: \(error)")
|
|
754
750
|
}
|
|
755
751
|
}
|
|
752
|
+
|
|
753
|
+
// 3. Generate UIImage for display
|
|
754
|
+
// Grab the image while the thermal image is locked to ensure settings are applied
|
|
755
|
+
if let image = streamer.getImage() {
|
|
756
|
+
self._latestImage = image
|
|
757
|
+
let width = Int(image.size.width)
|
|
758
|
+
let height = Int(image.size.height)
|
|
759
|
+
|
|
760
|
+
DispatchQueue.main.async { [weak self] in
|
|
761
|
+
self?.delegate?.onFrameReceived(image, width: width, height: height)
|
|
762
|
+
}
|
|
763
|
+
}
|
|
756
764
|
}
|
|
757
|
-
|
|
758
|
-
NSLog("[FLIR-TRACE 3️⃣] Streamer updated successfully")
|
|
759
765
|
} catch {
|
|
760
|
-
NSLog("[
|
|
766
|
+
NSLog("[FlirManager] Streamer update failed: \(error)")
|
|
761
767
|
return
|
|
762
768
|
}
|
|
763
|
-
|
|
764
|
-
guard let image = streamer.getImage() else {
|
|
765
|
-
NSLog("[FLIR-TRACE ❌] streamer.getImage() returned nil")
|
|
766
|
-
return
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
NSLog("[FLIR-TRACE 4️⃣] Got image from streamer: \(image.size.width)x\(image.size.height)")
|
|
770
|
-
|
|
771
|
-
self._latestImage = image
|
|
772
|
-
let width = Int(image.size.width)
|
|
773
|
-
let height = Int(image.size.height)
|
|
774
|
-
|
|
775
|
-
DispatchQueue.main.async { [weak self] in
|
|
776
|
-
NSLog("[FLIR-TRACE 5️⃣] Dispatching to delegate.onFrameReceived on main thread")
|
|
777
|
-
self?.delegate?.onFrameReceived(image, width: width, height: height)
|
|
778
|
-
}
|
|
779
769
|
}
|
|
780
770
|
}
|
|
781
771
|
}
|
package/ios/Flir/src/FlirState.m
CHANGED
|
@@ -89,9 +89,6 @@ static FlirState *_sharedState = nil;
|
|
|
89
89
|
|
|
90
90
|
- (void)updateFrame:(UIImage *)image
|
|
91
91
|
withTemperatureData:(NSArray<NSNumber *> *)tempData {
|
|
92
|
-
if (!image)
|
|
93
|
-
return;
|
|
94
|
-
|
|
95
92
|
dispatch_async(_accessQueue, ^{
|
|
96
93
|
self.latestImage = image;
|
|
97
94
|
|
|
@@ -157,6 +154,15 @@ static FlirState *_sharedState = nil;
|
|
|
157
154
|
self->_temperatureData = nil;
|
|
158
155
|
self->_imageWidth = 0;
|
|
159
156
|
self->_imageHeight = 0;
|
|
157
|
+
|
|
158
|
+
// Notify view to clear instantly
|
|
159
|
+
if (self.onTextureUpdate) {
|
|
160
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
161
|
+
if (self.onTextureUpdate) {
|
|
162
|
+
self.onTextureUpdate(nil, 7);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
160
166
|
});
|
|
161
167
|
}
|
|
162
168
|
|