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.
- package/Flir.podspec +31 -0
- package/README.md +1271 -0
- package/android/Flir/build.gradle.kts +80 -0
- package/android/Flir/libs/flir-stubs.jar +0 -0
- package/android/Flir/src/main/AndroidManifest.xml +31 -0
- package/android/Flir/src/main/java/flir/android/CameraHandler.java +194 -0
- package/android/Flir/src/main/java/flir/android/FlirController.kt +11 -0
- package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +75 -0
- package/android/Flir/src/main/java/flir/android/FlirDownloadPackage.kt +16 -0
- package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -0
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +248 -0
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +74 -0
- package/android/Flir/src/main/java/flir/android/FlirPackage.kt +16 -0
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +191 -0
- package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -0
- package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -0
- package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -0
- package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +14 -0
- package/app.plugin.js +264 -0
- package/expo-module.config.json +6 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRBattery.h +76 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRCalibration.h +108 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRCamera.h +156 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRCameraDeviceInfo.h +53 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRCameraEvent.h +132 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRCameraImport.h +204 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRColorDistributionSettings.h +204 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRColorizer.h +82 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRDiscoveredCamera.h +44 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRDiscovery.h +132 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRDisplaySettings.h +29 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRFocus.h +70 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRFusion.h +192 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRFusionController.h +136 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRFusionTransformation.h +35 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRIdentity.h +264 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRImageBase.h +196 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRImageColorizer.h +26 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRImageStatistics.h +61 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRIsotherms.h +208 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementArea.h +38 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementCollection.h +147 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDelta.h +62 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDimensions.h +33 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementEllipse.h +49 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementLine.h +66 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementMarker.h +69 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementParameters.h +41 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementRectangle.h +36 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementReference.h +27 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementShape.h +46 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementSpot.h +33 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementsController.h +160 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRMeterLinkSensorPoll.h +247 -0
- package/ios/Flir/Framework/ThermalSDK/FLIROverlayController.h +27 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRPalette.h +60 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRPaletteController.h +36 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRPaletteManager.h +97 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRQuantification.h +55 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRRemoteControl.h +393 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRRenderer.h +35 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRRendererImpl.h +17 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRScale.h +99 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRScaleController.h +44 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRStream.h +109 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRStreamer.h +124 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRSystem.h +40 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRTemperatureRange.h +43 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalDelta.h +77 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalImage.h +331 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalImageFile.h +56 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalParameters.h +31 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalValue.h +92 -0
- package/ios/Flir/Framework/ThermalSDK/FLIRWirelessCameraDetails.h +88 -0
- package/ios/Flir/Framework/ThermalSDK/ThermalSDK.h +73 -0
- package/ios/Flir/SDKLoader/FlirSDKLoader.m +13 -0
- package/ios/Flir/SDKLoader/FlirSDKLoader.swift +175 -0
- package/ios/Flir/src/FlirEventEmitter.h +12 -0
- package/ios/Flir/src/FlirEventEmitter.m +33 -0
- package/ios/Flir/src/FlirModule.h +10 -0
- package/ios/Flir/src/FlirModule.m +381 -0
- package/ios/Flir/src/FlirPreviewView.h +13 -0
- package/ios/Flir/src/FlirPreviewView.m +24 -0
- package/ios/Flir/src/FlirState.h +20 -0
- package/ios/Flir/src/FlirState.m +79 -0
- package/ios/Flir/src/FlirViewManager.h +9 -0
- package/ios/Flir/src/FlirViewManager.m +16 -0
- package/package.json +61 -0
- package/react-native.config.js +14 -0
- package/scripts/copy_ios_libs.sh +32 -0
- package/scripts/download-sdk.js +62 -0
- package/scripts/prepare-binaries.sh +171 -0
- package/sdk-manifest.json +30 -0
- package/src/FlirDownload.ts +78 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +7 -0
- 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,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,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
|
+
}
|