ilabs-flir 1.0.0

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.
Files changed (97) hide show
  1. package/Flir.podspec +31 -0
  2. package/README.md +1271 -0
  3. package/android/Flir/build.gradle.kts +80 -0
  4. package/android/Flir/libs/flir-stubs.jar +0 -0
  5. package/android/Flir/src/main/AndroidManifest.xml +31 -0
  6. package/android/Flir/src/main/java/flir/android/CameraHandler.java +194 -0
  7. package/android/Flir/src/main/java/flir/android/FlirController.kt +11 -0
  8. package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +75 -0
  9. package/android/Flir/src/main/java/flir/android/FlirDownloadPackage.kt +16 -0
  10. package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -0
  11. package/android/Flir/src/main/java/flir/android/FlirManager.kt +248 -0
  12. package/android/Flir/src/main/java/flir/android/FlirModule.kt +74 -0
  13. package/android/Flir/src/main/java/flir/android/FlirPackage.kt +16 -0
  14. package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +191 -0
  15. package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -0
  16. package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -0
  17. package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -0
  18. package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +14 -0
  19. package/app.plugin.js +264 -0
  20. package/expo-module.config.json +6 -0
  21. package/ios/Flir/Framework/ThermalSDK/FLIRBattery.h +76 -0
  22. package/ios/Flir/Framework/ThermalSDK/FLIRCalibration.h +108 -0
  23. package/ios/Flir/Framework/ThermalSDK/FLIRCamera.h +156 -0
  24. package/ios/Flir/Framework/ThermalSDK/FLIRCameraDeviceInfo.h +53 -0
  25. package/ios/Flir/Framework/ThermalSDK/FLIRCameraEvent.h +132 -0
  26. package/ios/Flir/Framework/ThermalSDK/FLIRCameraImport.h +204 -0
  27. package/ios/Flir/Framework/ThermalSDK/FLIRColorDistributionSettings.h +204 -0
  28. package/ios/Flir/Framework/ThermalSDK/FLIRColorizer.h +82 -0
  29. package/ios/Flir/Framework/ThermalSDK/FLIRDiscoveredCamera.h +44 -0
  30. package/ios/Flir/Framework/ThermalSDK/FLIRDiscovery.h +132 -0
  31. package/ios/Flir/Framework/ThermalSDK/FLIRDisplaySettings.h +29 -0
  32. package/ios/Flir/Framework/ThermalSDK/FLIRFocus.h +70 -0
  33. package/ios/Flir/Framework/ThermalSDK/FLIRFusion.h +192 -0
  34. package/ios/Flir/Framework/ThermalSDK/FLIRFusionController.h +136 -0
  35. package/ios/Flir/Framework/ThermalSDK/FLIRFusionTransformation.h +35 -0
  36. package/ios/Flir/Framework/ThermalSDK/FLIRIdentity.h +264 -0
  37. package/ios/Flir/Framework/ThermalSDK/FLIRImageBase.h +196 -0
  38. package/ios/Flir/Framework/ThermalSDK/FLIRImageColorizer.h +26 -0
  39. package/ios/Flir/Framework/ThermalSDK/FLIRImageStatistics.h +61 -0
  40. package/ios/Flir/Framework/ThermalSDK/FLIRIsotherms.h +208 -0
  41. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementArea.h +38 -0
  42. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementCollection.h +147 -0
  43. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDelta.h +62 -0
  44. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDimensions.h +33 -0
  45. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementEllipse.h +49 -0
  46. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementLine.h +66 -0
  47. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementMarker.h +69 -0
  48. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementParameters.h +41 -0
  49. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementRectangle.h +36 -0
  50. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementReference.h +27 -0
  51. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementShape.h +46 -0
  52. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementSpot.h +33 -0
  53. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementsController.h +160 -0
  54. package/ios/Flir/Framework/ThermalSDK/FLIRMeterLinkSensorPoll.h +247 -0
  55. package/ios/Flir/Framework/ThermalSDK/FLIROverlayController.h +27 -0
  56. package/ios/Flir/Framework/ThermalSDK/FLIRPalette.h +60 -0
  57. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteController.h +36 -0
  58. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteManager.h +97 -0
  59. package/ios/Flir/Framework/ThermalSDK/FLIRQuantification.h +55 -0
  60. package/ios/Flir/Framework/ThermalSDK/FLIRRemoteControl.h +393 -0
  61. package/ios/Flir/Framework/ThermalSDK/FLIRRenderer.h +35 -0
  62. package/ios/Flir/Framework/ThermalSDK/FLIRRendererImpl.h +17 -0
  63. package/ios/Flir/Framework/ThermalSDK/FLIRScale.h +99 -0
  64. package/ios/Flir/Framework/ThermalSDK/FLIRScaleController.h +44 -0
  65. package/ios/Flir/Framework/ThermalSDK/FLIRStream.h +109 -0
  66. package/ios/Flir/Framework/ThermalSDK/FLIRStreamer.h +124 -0
  67. package/ios/Flir/Framework/ThermalSDK/FLIRSystem.h +40 -0
  68. package/ios/Flir/Framework/ThermalSDK/FLIRTemperatureRange.h +43 -0
  69. package/ios/Flir/Framework/ThermalSDK/FLIRThermalDelta.h +77 -0
  70. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImage.h +331 -0
  71. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImageFile.h +56 -0
  72. package/ios/Flir/Framework/ThermalSDK/FLIRThermalParameters.h +31 -0
  73. package/ios/Flir/Framework/ThermalSDK/FLIRThermalValue.h +92 -0
  74. package/ios/Flir/Framework/ThermalSDK/FLIRWirelessCameraDetails.h +88 -0
  75. package/ios/Flir/Framework/ThermalSDK/ThermalSDK.h +73 -0
  76. package/ios/Flir/SDKLoader/FlirSDKLoader.m +13 -0
  77. package/ios/Flir/SDKLoader/FlirSDKLoader.swift +175 -0
  78. package/ios/Flir/src/FlirEventEmitter.h +12 -0
  79. package/ios/Flir/src/FlirEventEmitter.m +33 -0
  80. package/ios/Flir/src/FlirModule.h +10 -0
  81. package/ios/Flir/src/FlirModule.m +381 -0
  82. package/ios/Flir/src/FlirPreviewView.h +13 -0
  83. package/ios/Flir/src/FlirPreviewView.m +24 -0
  84. package/ios/Flir/src/FlirState.h +20 -0
  85. package/ios/Flir/src/FlirState.m +79 -0
  86. package/ios/Flir/src/FlirViewManager.h +9 -0
  87. package/ios/Flir/src/FlirViewManager.m +16 -0
  88. package/package.json +61 -0
  89. package/react-native.config.js +14 -0
  90. package/scripts/copy_ios_libs.sh +32 -0
  91. package/scripts/download-sdk.js +62 -0
  92. package/scripts/prepare-binaries.sh +171 -0
  93. package/sdk-manifest.json +30 -0
  94. package/src/FlirDownload.ts +78 -0
  95. package/src/index.d.ts +17 -0
  96. package/src/index.js +7 -0
  97. package/src/index.ts +7 -0
@@ -0,0 +1,80 @@
1
+ plugins {
2
+ id("com.android.library")
3
+ id("org.jetbrains.kotlin.android")
4
+ id("maven-publish")
5
+ }
6
+
7
+ android {
8
+ namespace = "flir.android"
9
+ compileSdk = 34
10
+
11
+ defaultConfig {
12
+ minSdk = 24
13
+ targetSdk = 34
14
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15
+ }
16
+
17
+ compileOptions {
18
+ sourceCompatibility = JavaVersion.VERSION_17
19
+ targetCompatibility = JavaVersion.VERSION_17
20
+ }
21
+
22
+ kotlinOptions {
23
+ jvmTarget = "17"
24
+ }
25
+
26
+ publishing {
27
+ singleVariant("release") {
28
+ withJavadocJar()
29
+ withSourcesJar()
30
+ }
31
+ }
32
+ }
33
+
34
+ repositories {
35
+ google()
36
+ mavenCentral()
37
+ // Resolve local AARs which will be installed into mavenLocal by CI (JitPack)
38
+ mavenLocal()
39
+ flatDir { dirs("libs") }
40
+ }
41
+
42
+ dependencies {
43
+ // React Native
44
+ implementation("com.facebook.react:react-native:+")
45
+
46
+ // Kotlin coroutines for async downloads
47
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
48
+
49
+ // Play Feature Delivery (optional - for Play Store SDK delivery)
50
+ implementation("com.google.android.play:feature-delivery:2.1.0")
51
+ implementation("com.google.android.play:feature-delivery-ktx:2.1.0")
52
+
53
+ // FLIR SDK binary artifacts - compileOnly since they're downloaded on-demand at runtime
54
+ // These are needed for compilation but NOT bundled with the app
55
+ // Users download them at runtime via FlirDownload.download()
56
+ compileOnly(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
57
+
58
+ // Minimal compile deps to satisfy source references
59
+ implementation("androidx.annotation:annotation:1.5.0")
60
+
61
+
62
+ // Prevent duplicate SLF4J classes when a consumer also brings `org.slf4j:slf4j-api`
63
+ // The vendor AAR may embed slf4j classes; exclude the API from being pulled transitively
64
+ configurations.all {
65
+ exclude(group = "org.slf4j", module = "slf4j-api")
66
+ }
67
+ }
68
+
69
+ publishing {
70
+ publications {
71
+ create<MavenPublication>("release") {
72
+ afterEvaluate {
73
+ from(components["release"])
74
+ groupId = "com.github.PraveenOjha"
75
+ artifactId = "Flir"
76
+ version = project.version.toString().ifEmpty { "unspecified" }
77
+ }
78
+ }
79
+ }
80
+ }
Binary file
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+ <!-- ============================================ -->
5
+ <!-- FLIR Thermal SDK Required Permissions -->
6
+ <!-- These will auto-merge into your app -->
7
+ <!-- ============================================ -->
8
+
9
+ <!-- USB Host feature for FLIR ONE USB devices -->
10
+ <!-- This is the primary interface for FLIR ONE cameras -->
11
+ <uses-feature
12
+ android:name="android.hardware.usb.host"
13
+ android:required="false" />
14
+
15
+ <!-- Camera support for network-based FLIR cameras (ACE series) -->
16
+ <!-- Set to false so app can still install on devices without cameras -->
17
+ <uses-feature
18
+ android:name="android.hardware.camera"
19
+ android:required="false" />
20
+
21
+ <!-- Camera permission for ACE cameras that use device camera -->
22
+ <uses-permission android:name="android.permission.CAMERA"/>
23
+
24
+ <!-- Internet permission for ACE camera internal communication -->
25
+ <!-- Required by ACE SDK architecture for local network communication -->
26
+ <uses-permission android:name="android.permission.INTERNET" />
27
+
28
+ <!-- Note: USB permissions are handled at runtime via UsbPermissionHandler -->
29
+ <!-- No additional USB permissions needed in manifest -->
30
+
31
+ </manifest>
@@ -0,0 +1,194 @@
1
+ package flir.android;
2
+
3
+ import android.graphics.Bitmap;
4
+ import android.util.Log;
5
+
6
+ import com.flir.thermalsdk.androidsdk.image.BitmapAndroid;
7
+ import com.flir.thermalsdk.image.ThermalImage;
8
+ import com.flir.thermalsdk.live.Camera;
9
+ import com.flir.thermalsdk.live.CommunicationInterface;
10
+ import com.flir.thermalsdk.live.ConnectParameters;
11
+ import com.flir.thermalsdk.live.Identity;
12
+ import com.flir.thermalsdk.live.connectivity.ConnectionStatusListener;
13
+ import com.flir.thermalsdk.live.discovery.DiscoveryEventListener;
14
+ import com.flir.thermalsdk.live.discovery.DiscoveryFactory;
15
+ import com.flir.thermalsdk.live.streaming.Stream;
16
+ import com.flir.thermalsdk.live.streaming.ThermalStreamer;
17
+
18
+ import java.io.IOException;
19
+ import java.util.LinkedList;
20
+ import java.util.Objects;
21
+
22
+ public class CameraHandler {
23
+
24
+ private static final String TAG = "CameraHandler";
25
+
26
+ private StreamDataListener streamDataListener;
27
+
28
+ public interface StreamDataListener {
29
+ void images(FrameDataHolder dataHolder);
30
+ void images(Bitmap msxBitmap, Bitmap dcBitmap);
31
+ }
32
+
33
+ LinkedList<Identity> foundCameraIdentities = new LinkedList<>();
34
+
35
+ private Camera camera;
36
+
37
+ private Stream connectedStream;
38
+ private ThermalStreamer streamer;
39
+ // Cache the latest ThermalImage delivered by the streamer
40
+ private ThermalImage latestThermalImage;
41
+
42
+ public CameraHandler() {
43
+ Log.d(TAG, "CameraHandler constr");
44
+ }
45
+
46
+ public void startDiscovery(DiscoveryEventListener cameraDiscoveryListener, DiscoveryStatus discoveryStatus) {
47
+ DiscoveryFactory.getInstance().scan(cameraDiscoveryListener, CommunicationInterface.EMULATOR, CommunicationInterface.USB);
48
+ discoveryStatus.started();
49
+ }
50
+
51
+ public void stopDiscovery(DiscoveryStatus discoveryStatus) {
52
+ DiscoveryFactory.getInstance().stop(CommunicationInterface.EMULATOR, CommunicationInterface.USB);
53
+ discoveryStatus.stopped();
54
+ }
55
+
56
+ public synchronized void connect(Identity identity, ConnectionStatusListener connectionStatusListener) throws IOException {
57
+ Log.d(TAG, "connect identity: " + identity);
58
+ camera = new Camera();
59
+ camera.connect(identity, connectionStatusListener, new ConnectParameters());
60
+ }
61
+
62
+ public synchronized void disconnect() {
63
+ Log.d(TAG, "disconnect");
64
+ if (camera == null) {
65
+ return;
66
+ }
67
+ if (connectedStream == null) {
68
+ return;
69
+ }
70
+
71
+ if (connectedStream.isStreaming()) {
72
+ connectedStream.stop();
73
+ }
74
+ camera.disconnect();
75
+ camera = null;
76
+ }
77
+
78
+ public synchronized void startStream(StreamDataListener listener) {
79
+ this.streamDataListener = listener;
80
+ if (camera == null || !camera.isConnected()) {
81
+ Log.e(TAG, "startStream, failed, camera was null or not connected");
82
+ return;
83
+ }
84
+ connectedStream = camera.getStreams().get(0);
85
+ if (connectedStream.isThermal()) {
86
+ streamer = new ThermalStreamer(connectedStream);
87
+ } else {
88
+ Log.e(TAG, "startStream, failed, no thermal stream available for the camera");
89
+ return;
90
+ }
91
+ connectedStream.start(
92
+ unused -> {
93
+ streamer.update();
94
+ final Bitmap[] dcBitmap = new Bitmap[1];
95
+ streamer.withThermalImage(thermalImage -> {
96
+ try {
97
+ // Cache the latest ThermalImage for sampling
98
+ latestThermalImage = thermalImage;
99
+ if (thermalImage.getFusion() != null && thermalImage.getFusion().getPhoto() != null) {
100
+ dcBitmap[0] = BitmapAndroid.createBitmap(thermalImage.getFusion().getPhoto()).getBitMap();
101
+ }
102
+ // The streamer.getImage() returns the ImageBuffer expected by BitmapAndroid
103
+ final Bitmap thermalPixels = BitmapAndroid.createBitmap(streamer.getImage()).getBitMap();
104
+ if (streamDataListener != null) streamDataListener.images(thermalPixels, dcBitmap[0]);
105
+ } catch (Exception e) {
106
+ Log.e(TAG, "thermal bitmap creation error", e);
107
+ }
108
+ });
109
+ },
110
+ error -> Log.e(TAG, "Streaming error: " + error));
111
+ }
112
+
113
+ public synchronized Double getTemperatureAt(int x, int y) {
114
+ try {
115
+ if (streamer == null) return null;
116
+ ThermalImage img = latestThermalImage;
117
+ if (img == null) return null;
118
+
119
+ java.lang.reflect.Method[] methods = img.getClass().getMethods();
120
+ for (java.lang.reflect.Method m : methods) {
121
+ String name = m.getName().toLowerCase();
122
+ Class<?>[] params = m.getParameterTypes();
123
+
124
+ if ((name.contains("get") || name.contains("value") || name.contains("temperature")) && params.length == 2
125
+ && (params[0] == int.class || params[0] == Integer.class || params[0] == double.class || params[0] == float.class)
126
+ && (params[1] == int.class || params[1] == Integer.class || params[1] == double.class || params[1] == float.class)) {
127
+ try {
128
+ Object res = null;
129
+ if (params[0] == int.class && params[1] == int.class) {
130
+ res = m.invoke(img, x, y);
131
+ } else if (params[0] == double.class && params[1] == double.class) {
132
+ res = m.invoke(img, (double) x, (double) y);
133
+ } else if (params[0] == float.class && params[1] == float.class) {
134
+ res = m.invoke(img, (float) x, (float) y);
135
+ } else {
136
+ res = m.invoke(img, Integer.valueOf(x), Integer.valueOf(y));
137
+ }
138
+ if (res instanceof Number) {
139
+ return ((Number) res).doubleValue();
140
+ }
141
+ } catch (Exception e) {
142
+ }
143
+ }
144
+ }
145
+ } catch (Exception e) {
146
+ Log.e(TAG, "getTemperatureAt error", e);
147
+ }
148
+ return null;
149
+ }
150
+
151
+ public void add(Identity identity) {
152
+ foundCameraIdentities.add(identity);
153
+ }
154
+
155
+ public Identity getCppEmulator() {
156
+ for (Identity foundCameraIdentity : foundCameraIdentities) {
157
+ if (foundCameraIdentity.deviceId.contains("C++ Emulator")) {
158
+ return foundCameraIdentity;
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+
164
+ public Identity getFlirOneEmulator() {
165
+ for (Identity foundCameraIdentity : foundCameraIdentities) {
166
+ if (foundCameraIdentity.deviceId.contains("EMULATED FLIR ONE")) {
167
+ return foundCameraIdentity;
168
+ }
169
+ }
170
+ return null;
171
+ }
172
+
173
+ public Identity getFlirOne() {
174
+ for (Identity foundCameraIdentity : foundCameraIdentities) {
175
+ if (foundCameraIdentity.communicationInterface == CommunicationInterface.USB) return foundCameraIdentity;
176
+ }
177
+ return null;
178
+ }
179
+
180
+ public String getDeviceInfo() {
181
+ if (camera == null) return "N/A";
182
+ try {
183
+ if (camera.getRemoteControl() == null) return "N/A";
184
+ return camera.getRemoteControl().cameraInformation().getSync().displayName;
185
+ } catch (Exception e) {
186
+ return "N/A";
187
+ }
188
+ }
189
+
190
+ public interface DiscoveryStatus {
191
+ void started();
192
+ void stopped();
193
+ }
194
+ }
@@ -0,0 +1,11 @@
1
+ package flir.android
2
+
3
+ object FlirController {
4
+ @JvmStatic
5
+ var cameraHandler: CameraHandler? = null
6
+
7
+ @JvmStatic
8
+ fun getTemperatureAt(x: Int, y: Int): Double? {
9
+ return cameraHandler?.getTemperatureAt(x, y)
10
+ }
11
+ }
@@ -0,0 +1,75 @@
1
+ package flir.android
2
+
3
+ import com.facebook.react.bridge.*
4
+ import com.facebook.react.modules.core.DeviceEventManagerModule
5
+ import kotlinx.coroutines.*
6
+
7
+ class FlirDownloadManager(private val reactContext: ReactApplicationContext) :
8
+ ReactContextBaseJavaModule(reactContext) {
9
+
10
+ private var downloadJob: Job? = null
11
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
12
+
13
+ override fun getName() = "FlirDownloadManager"
14
+
15
+ override fun initialize() {
16
+ FlirSDKLoader.init(reactContext)
17
+ }
18
+
19
+ @ReactMethod
20
+ fun isFlirAvailable(promise: Promise) {
21
+ promise.resolve(FlirSDKLoader.isSDKAvailable(reactContext))
22
+ }
23
+
24
+ @ReactMethod
25
+ fun getDownloadSize(promise: Promise) {
26
+ promise.resolve(FlirSDKLoader.getDownloadSize(reactContext).toDouble())
27
+ }
28
+
29
+ @ReactMethod
30
+ fun downloadFlirSDK(promise: Promise) {
31
+ downloadJob = scope.launch {
32
+ val result = FlirSDKLoader.downloadDirect(reactContext) { downloaded, total ->
33
+ sendEvent("FlirDownloadProgress", Arguments.createMap().apply {
34
+ putDouble("bytesDownloaded", downloaded.toDouble())
35
+ putDouble("totalBytes", total.toDouble())
36
+ putDouble("percent", (downloaded.toDouble() / total) * 100)
37
+ })
38
+ }
39
+
40
+ result.fold(
41
+ onSuccess = {
42
+ sendEvent("FlirDownloadComplete", Arguments.createMap())
43
+ promise.resolve(true)
44
+ },
45
+ onFailure = { error ->
46
+ sendEvent("FlirDownloadError", Arguments.createMap().apply {
47
+ putString("error", error.message)
48
+ })
49
+ promise.reject("E_DOWNLOAD", error.message, error)
50
+ }
51
+ )
52
+ }
53
+ }
54
+
55
+ @ReactMethod
56
+ fun cancelDownload() {
57
+ downloadJob?.cancel()
58
+ }
59
+
60
+ @ReactMethod
61
+ fun deleteSDK(promise: Promise) {
62
+ promise.resolve(FlirSDKLoader.deleteSDK(reactContext))
63
+ }
64
+
65
+ @ReactMethod
66
+ fun addListener(eventName: String) {}
67
+
68
+ @ReactMethod
69
+ fun removeListeners(count: Int) {}
70
+
71
+ private fun sendEvent(name: String, params: WritableMap) {
72
+ reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
73
+ .emit(name, params)
74
+ }
75
+ }
@@ -0,0 +1,16 @@
1
+ package flir.android
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class FlirDownloadPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(FlirDownloadManager(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,6 @@
1
+ package flir.android
2
+
3
+ object FlirFrameCache {
4
+ @JvmStatic
5
+ var latestFramePath: String? = null
6
+ }
@@ -0,0 +1,248 @@
1
+ package flir.android
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import android.graphics.BitmapFactory
6
+ import android.graphics.Color
7
+ import android.util.Base64
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.WritableMap
10
+ import com.facebook.react.modules.core.DeviceEventManagerModule
11
+ import com.facebook.react.uimanager.ThemedReactContext
12
+ import java.io.ByteArrayOutputStream
13
+ import java.io.File
14
+ import java.io.FileOutputStream
15
+ import java.util.concurrent.atomic.AtomicLong
16
+
17
+ object FlirManager {
18
+ private val cameraHandler: CameraHandler = CameraHandler()
19
+ private val lastEmitMs = AtomicLong(0)
20
+ private val minEmitIntervalMs = 333L // ~3 fps
21
+ private var discoveryStarted = false
22
+ private var reactContext: ThemedReactContext? = null
23
+
24
+ // Emulator and device state tracking
25
+ private var isEmulatorMode = false
26
+ private var isPhysicalDeviceConnected = false
27
+ private var connectedIdentity: com.flir.thermalsdk.live.Identity? = null
28
+
29
+ // GL texture callback support for native filters
30
+ interface TextureUpdateCallback {
31
+ fun onTextureUpdate(bitmap: Bitmap, textureUnit: Int)
32
+ }
33
+
34
+ interface TemperatureCallback {
35
+ fun onTemperatureData(temperature: Double, x: Int, y: Int)
36
+ }
37
+
38
+ private var textureCallback: TextureUpdateCallback? = null
39
+ private var temperatureCallback: TemperatureCallback? = null
40
+ private var latestBitmap: Bitmap? = null
41
+
42
+ fun setTextureCallback(callback: TextureUpdateCallback?) {
43
+ textureCallback = callback
44
+ }
45
+
46
+ fun setTemperatureCallback(callback: TemperatureCallback?) {
47
+ temperatureCallback = callback
48
+ }
49
+
50
+ fun getLatestBitmap(): Bitmap? = latestBitmap
51
+
52
+ fun getTemperatureAtPoint(x: Int, y: Int): Double? {
53
+ return try {
54
+ cameraHandler.getTemperatureAt(x, y)
55
+ } catch (t: Throwable) {
56
+ null
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Check if currently running in emulator mode (no physical FLIR device)
62
+ */
63
+ fun isEmulator(): Boolean = isEmulatorMode
64
+
65
+ /**
66
+ * Check if a physical FLIR device is connected
67
+ */
68
+ fun isDeviceConnected(): Boolean = isPhysicalDeviceConnected
69
+
70
+ /**
71
+ * Get information about the connected device
72
+ */
73
+ fun getConnectedDeviceInfo(): String {
74
+ return when {
75
+ connectedIdentity == null -> "Not connected"
76
+ isEmulatorMode -> "Emulator (${connectedIdentity?.deviceId})"
77
+ else -> "Physical device (${connectedIdentity?.deviceId})"
78
+ }
79
+ }
80
+
81
+ fun init(context: Context) {
82
+ try {
83
+ val cls = Class.forName("com.flir.thermalsdk.live.ThermalSdkAndroid")
84
+ val method = cls.getMethod("init", Context::class.java)
85
+ method.invoke(null, context.applicationContext)
86
+ } catch (ignored: Throwable) {
87
+ }
88
+ }
89
+
90
+ fun startDiscoveryAndConnect(context: ThemedReactContext) {
91
+ if (discoveryStarted) return
92
+ discoveryStarted = true
93
+ reactContext = context
94
+
95
+ emitDeviceState("discovering", false)
96
+
97
+ cameraHandler.startDiscovery(object : com.flir.thermalsdk.live.discovery.DiscoveryEventListener {
98
+ override fun onCameraFound(discoveredCamera: com.flir.thermalsdk.live.discovery.DiscoveredCamera) {
99
+ cameraHandler.add(discoveredCamera.identity)
100
+
101
+ // Prioritize real device over emulator
102
+ val realDevice = cameraHandler.getFlirOne()
103
+ val emulatorDevice = cameraHandler.getFlirOneEmulator() ?: cameraHandler.getCppEmulator()
104
+ val toConnect = realDevice ?: emulatorDevice
105
+
106
+ if (toConnect != null) {
107
+ // Determine if we're connecting to emulator
108
+ isEmulatorMode = (realDevice == null)
109
+ isPhysicalDeviceConnected = (realDevice != null)
110
+ connectedIdentity = toConnect
111
+
112
+ Thread {
113
+ try {
114
+ cameraHandler.connect(toConnect, com.flir.thermalsdk.live.connectivity.ConnectionStatusListener { errorCode ->
115
+ emitDeviceState("disconnected", false)
116
+ isPhysicalDeviceConnected = false
117
+ connectedIdentity = null
118
+ })
119
+
120
+ val deviceType = if (isEmulatorMode) "emulator" else "device"
121
+ emitDeviceState("connected", true, mapOf("deviceType" to deviceType, "isEmulator" to isEmulatorMode))
122
+ FlirStatus.flirConnected = true
123
+
124
+ cameraHandler.startStream(object : CameraHandler.StreamDataListener {
125
+ override fun images(dataHolder: FrameDataHolder) {
126
+ handleIncomingFrames(dataHolder.msxBitmap, dataHolder.dcBitmap, context)
127
+ }
128
+
129
+ override fun images(msxBitmap: Bitmap?, dcBitmap: Bitmap?) {
130
+ handleIncomingFrames(msxBitmap, dcBitmap, context)
131
+ }
132
+ })
133
+ } catch (e: Exception) {
134
+ emitDeviceState("disconnected", false)
135
+ FlirStatus.flirConnected = false
136
+ isPhysicalDeviceConnected = false
137
+ connectedIdentity = null
138
+ }
139
+ }.start()
140
+ }
141
+ }
142
+
143
+ override fun onDiscoveryError(communicationInterface: com.flir.thermalsdk.live.CommunicationInterface, errorCode: com.flir.thermalsdk.ErrorCode) {
144
+ emitDeviceState("discovery_error", false)
145
+ }
146
+ }, object : CameraHandler.DiscoveryStatus {
147
+ override fun started() {}
148
+ override fun stopped() {}
149
+ })
150
+ }
151
+
152
+ fun stop() {
153
+ try {
154
+ cameraHandler.disconnect()
155
+ } catch (ignored: Throwable) {}
156
+ FlirStatus.flirConnected = false
157
+ FlirStatus.flirStreaming = false
158
+ discoveryStarted = false
159
+ reactContext = null
160
+ }
161
+
162
+ fun getLatestFramePath(): String? {
163
+ return FlirStatus.latestFramePath
164
+ }
165
+
166
+ fun getTemperatureAt(x: Int, y: Int): Double? {
167
+ return try {
168
+ cameraHandler.getTemperatureAt(x, y)
169
+ } catch (t: Throwable) {
170
+ null
171
+ }
172
+ }
173
+
174
+ private fun handleIncomingFrames(msxBitmap: Bitmap?, dcBitmap: Bitmap?, ctx: ThemedReactContext) {
175
+ val now = System.currentTimeMillis()
176
+ if (now - lastEmitMs.get() < minEmitIntervalMs) return
177
+ lastEmitMs.set(now)
178
+
179
+ val bmp = msxBitmap ?: dcBitmap ?: return
180
+ latestBitmap = bmp
181
+
182
+ // Invoke texture callback for native GL/Metal filters (texture unit 7)
183
+ textureCallback?.onTextureUpdate(bmp, 7)
184
+
185
+ // Get and emit temperature data
186
+ try {
187
+ val temp = cameraHandler.getTemperatureAt(80, 60) // center point
188
+ if (temp != null) {
189
+ temperatureCallback?.onTemperatureData(temp, 80, 60)
190
+ }
191
+ } catch (ignored: Throwable) {}
192
+
193
+ try {
194
+ val cacheDir = ctx.cacheDir
195
+ val outFile = File(cacheDir, "flir_latest_frame.png")
196
+ val fos = FileOutputStream(outFile)
197
+ bmp.compress(Bitmap.CompressFormat.PNG, 90, fos)
198
+ fos.flush()
199
+ fos.close()
200
+ FlirStatus.latestFramePath = outFile.absolutePath
201
+ FlirFrameCache.latestFramePath = outFile.absolutePath
202
+ FlirStatus.flirStreaming = true
203
+
204
+ val baos = ByteArrayOutputStream()
205
+ bmp.compress(Bitmap.CompressFormat.PNG, 70, baos)
206
+ val pngBytes = baos.toByteArray()
207
+ val base64 = Base64.encodeToString(pngBytes, Base64.NO_WRAP)
208
+
209
+ val params: WritableMap = Arguments.createMap().apply {
210
+ putString("type", "frame")
211
+ putString("path", outFile.absolutePath)
212
+ putString("base64", "data:image/png;base64," + base64)
213
+ putDouble("timestamp", (now / 1000.0))
214
+ }
215
+ try {
216
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
217
+ .emit("FlirFrame", params)
218
+ } catch (e: Exception) {}
219
+
220
+ } catch (e: Exception) {
221
+ FlirStatus.flirStreaming = false
222
+ }
223
+ }
224
+
225
+ private fun emitDeviceState(state: String, connected: Boolean, extras: Map<String, Any> = emptyMap()) {
226
+ FlirStatus.flirConnected = connected
227
+ val ctx = reactContext ?: return
228
+ val params: WritableMap = Arguments.createMap().apply {
229
+ putString("state", state)
230
+ putBoolean("connected", connected)
231
+ putString("message", if (connected) "FLIR device connected" else state)
232
+
233
+ // Add any extra parameters
234
+ extras.forEach { (key, value) ->
235
+ when (value) {
236
+ is String -> putString(key, value)
237
+ is Boolean -> putBoolean(key, value)
238
+ is Int -> putInt(key, value)
239
+ is Double -> putDouble(key, value)
240
+ }
241
+ }
242
+ }
243
+ try {
244
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
245
+ .emit("FlirDeviceConnected", params)
246
+ } catch (e: Exception) {}
247
+ }
248
+ }