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,74 @@
|
|
|
1
|
+
package flir.android
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
|
|
8
|
+
class FlirModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
9
|
+
override fun getName(): String = "FlirModule"
|
|
10
|
+
|
|
11
|
+
// Simple placeholder conversion: converts an ARGB color to a pseudo-temperature value.
|
|
12
|
+
// Replace with SDK call when integrating thermalsdk APIs.
|
|
13
|
+
@ReactMethod
|
|
14
|
+
fun getTemperatureFromColor(color: Int, promise: Promise) {
|
|
15
|
+
try {
|
|
16
|
+
val r = (color shr 16) and 0xFF
|
|
17
|
+
val g = (color shr 8) and 0xFF
|
|
18
|
+
val b = color and 0xFF
|
|
19
|
+
// Luminance-like value scaled to a plausible temperature range (0°C - 400°C)
|
|
20
|
+
val lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
21
|
+
val temp = 0.0 + (lum / 255.0) * 400.0
|
|
22
|
+
promise.resolve(temp)
|
|
23
|
+
} catch (e: Exception) {
|
|
24
|
+
promise.reject("ERR_FLIR_CONVERT", e)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@ReactMethod
|
|
29
|
+
fun getLatestFramePath(promise: Promise) {
|
|
30
|
+
try {
|
|
31
|
+
val path = FlirFrameCache.latestFramePath
|
|
32
|
+
if (path != null) promise.resolve(path) else promise.resolve(null)
|
|
33
|
+
} catch (e: Exception) {
|
|
34
|
+
promise.reject("ERR_FLIR_PATH", e)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@ReactMethod
|
|
39
|
+
fun getTemperatureAt(x: Int, y: Int, promise: Promise) {
|
|
40
|
+
try {
|
|
41
|
+
val temp = FlirManager.getTemperatureAt(x, y)
|
|
42
|
+
if (temp != null) promise.resolve(temp) else promise.reject("ERR_NO_DATA", "No temperature data available")
|
|
43
|
+
} catch (e: Exception) {
|
|
44
|
+
promise.reject("ERR_FLIR_SAMPLE", e)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@ReactMethod
|
|
49
|
+
fun isEmulator(promise: Promise) {
|
|
50
|
+
try {
|
|
51
|
+
promise.resolve(FlirManager.isEmulator())
|
|
52
|
+
} catch (e: Exception) {
|
|
53
|
+
promise.reject("ERR_FLIR_EMULATOR_CHECK", e)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@ReactMethod
|
|
58
|
+
fun isDeviceConnected(promise: Promise) {
|
|
59
|
+
try {
|
|
60
|
+
promise.resolve(FlirManager.isDeviceConnected())
|
|
61
|
+
} catch (e: Exception) {
|
|
62
|
+
promise.reject("ERR_FLIR_DEVICE_CHECK", e)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@ReactMethod
|
|
67
|
+
fun getConnectedDeviceInfo(promise: Promise) {
|
|
68
|
+
try {
|
|
69
|
+
promise.resolve(FlirManager.getConnectedDeviceInfo())
|
|
70
|
+
} catch (e: Exception) {
|
|
71
|
+
promise.reject("ERR_FLIR_DEVICE_INFO", e)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -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 FlirPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(FlirModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return listOf(FlirViewManager())
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
package flir.android
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.google.android.play.core.splitinstall.*
|
|
5
|
+
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
|
|
6
|
+
import kotlinx.coroutines.*
|
|
7
|
+
import org.json.JSONObject
|
|
8
|
+
import java.io.File
|
|
9
|
+
import java.io.FileOutputStream
|
|
10
|
+
import java.net.URL
|
|
11
|
+
import java.security.MessageDigest
|
|
12
|
+
import java.util.zip.ZipInputStream
|
|
13
|
+
|
|
14
|
+
object FlirSDKLoader {
|
|
15
|
+
|
|
16
|
+
private const val FEATURE_MODULE = "flir_sdk"
|
|
17
|
+
private var splitInstallManager: SplitInstallManager? = null
|
|
18
|
+
|
|
19
|
+
fun init(context: Context) {
|
|
20
|
+
splitInstallManager = SplitInstallManagerFactory.create(context)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private fun getSDKDirectory(context: Context) = File(context.filesDir, "FlirSDK")
|
|
24
|
+
|
|
25
|
+
fun isSDKAvailable(context: Context): Boolean {
|
|
26
|
+
// Check Play Feature module
|
|
27
|
+
splitInstallManager?.installedModules?.let {
|
|
28
|
+
if (FEATURE_MODULE in it) return true
|
|
29
|
+
}
|
|
30
|
+
// Check direct download
|
|
31
|
+
return File(getSDKDirectory(context), "thermalsdk.aar").exists()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fun getDownloadSize(context: Context): Long {
|
|
35
|
+
return loadManifest(context)?.android?.directDownload?.sizeBytes ?: 52_428_800L
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun downloadViaPlayStore(
|
|
39
|
+
onProgress: (Float) -> Unit,
|
|
40
|
+
onComplete: () -> Unit,
|
|
41
|
+
onError: (String) -> Unit
|
|
42
|
+
) {
|
|
43
|
+
val manager = splitInstallManager ?: run {
|
|
44
|
+
onError("SplitInstallManager not initialized")
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
val request = SplitInstallRequest.newBuilder()
|
|
49
|
+
.addModule(FEATURE_MODULE)
|
|
50
|
+
.build()
|
|
51
|
+
|
|
52
|
+
manager.registerListener { state ->
|
|
53
|
+
when (state.status()) {
|
|
54
|
+
SplitInstallSessionStatus.DOWNLOADING -> {
|
|
55
|
+
val progress = state.bytesDownloaded().toFloat() / state.totalBytesToDownload()
|
|
56
|
+
onProgress(progress)
|
|
57
|
+
}
|
|
58
|
+
SplitInstallSessionStatus.INSTALLED -> onComplete()
|
|
59
|
+
SplitInstallSessionStatus.FAILED -> onError("Install failed: ${state.errorCode()}")
|
|
60
|
+
else -> {}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
manager.startInstall(request)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
suspend fun downloadDirect(
|
|
68
|
+
context: Context,
|
|
69
|
+
onProgress: (downloaded: Long, total: Long) -> Unit
|
|
70
|
+
): Result<Unit> = withContext(Dispatchers.IO) {
|
|
71
|
+
try {
|
|
72
|
+
val manifest = loadManifest(context) ?: return@withContext Result.failure(
|
|
73
|
+
Exception("Failed to load manifest"))
|
|
74
|
+
|
|
75
|
+
val downloadUrl = manifest.android.directDownload.downloadUrl
|
|
76
|
+
val expectedHash = manifest.android.directDownload.sha256
|
|
77
|
+
val totalSize = manifest.android.directDownload.sizeBytes
|
|
78
|
+
|
|
79
|
+
val sdkDir = getSDKDirectory(context).apply { mkdirs() }
|
|
80
|
+
val zipFile = File(context.cacheDir, "flir-sdk.zip")
|
|
81
|
+
|
|
82
|
+
// Download
|
|
83
|
+
URL(downloadUrl).openStream().use { input ->
|
|
84
|
+
FileOutputStream(zipFile).use { output ->
|
|
85
|
+
val buffer = ByteArray(8192)
|
|
86
|
+
var totalRead = 0L
|
|
87
|
+
var bytesRead: Int
|
|
88
|
+
|
|
89
|
+
while (input.read(buffer).also { bytesRead = it } != -1) {
|
|
90
|
+
output.write(buffer, 0, bytesRead)
|
|
91
|
+
totalRead += bytesRead
|
|
92
|
+
withContext(Dispatchers.Main) {
|
|
93
|
+
onProgress(totalRead, totalSize)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Verify checksum
|
|
100
|
+
val actualHash = sha256(zipFile)
|
|
101
|
+
if (actualHash != expectedHash) {
|
|
102
|
+
zipFile.delete()
|
|
103
|
+
return@withContext Result.failure(SecurityException("Checksum mismatch"))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Extract
|
|
107
|
+
unzip(zipFile, sdkDir)
|
|
108
|
+
zipFile.delete()
|
|
109
|
+
|
|
110
|
+
Result.success(Unit)
|
|
111
|
+
} catch (e: Exception) {
|
|
112
|
+
Result.failure(e)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fun deleteSDK(context: Context): Boolean {
|
|
117
|
+
splitInstallManager?.deferredUninstall(listOf(FEATURE_MODULE))
|
|
118
|
+
return getSDKDirectory(context).deleteRecursively()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private fun loadManifest(context: Context): SDKManifest? {
|
|
122
|
+
return try {
|
|
123
|
+
val json = context.assets.open("sdk-manifest.json").bufferedReader().readText()
|
|
124
|
+
SDKManifest.fromJson(json)
|
|
125
|
+
} catch (e: Exception) { null }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun sha256(file: File): String {
|
|
129
|
+
val digest = MessageDigest.getInstance("SHA-256")
|
|
130
|
+
file.inputStream().use { input ->
|
|
131
|
+
val buffer = ByteArray(8192)
|
|
132
|
+
var bytesRead: Int
|
|
133
|
+
while (input.read(buffer).also { bytesRead = it } != -1) {
|
|
134
|
+
digest.update(buffer, 0, bytesRead)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return digest.digest().joinToString("") { "%02x".format(it) }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private fun unzip(source: File, destination: File) {
|
|
141
|
+
ZipInputStream(source.inputStream()).use { zip ->
|
|
142
|
+
var entry = zip.nextEntry
|
|
143
|
+
while (entry != null) {
|
|
144
|
+
val file = File(destination, entry.name)
|
|
145
|
+
if (entry.isDirectory) {
|
|
146
|
+
file.mkdirs()
|
|
147
|
+
} else {
|
|
148
|
+
file.parentFile?.mkdirs()
|
|
149
|
+
file.outputStream().use { zip.copyTo(it) }
|
|
150
|
+
}
|
|
151
|
+
entry = zip.nextEntry
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
data class SDKManifest(
|
|
158
|
+
val version: String,
|
|
159
|
+
val android: AndroidManifest
|
|
160
|
+
) {
|
|
161
|
+
data class AndroidManifest(
|
|
162
|
+
val playFeatureModule: String,
|
|
163
|
+
val directDownload: DirectDownload
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
data class DirectDownload(
|
|
167
|
+
val downloadUrl: String,
|
|
168
|
+
val sha256: String,
|
|
169
|
+
val sizeBytes: Long
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
companion object {
|
|
173
|
+
fun fromJson(json: String): SDKManifest {
|
|
174
|
+
val root = JSONObject(json)
|
|
175
|
+
val android = root.getJSONObject("android")
|
|
176
|
+
val direct = android.getJSONObject("directDownload")
|
|
177
|
+
|
|
178
|
+
return SDKManifest(
|
|
179
|
+
version = root.getString("version"),
|
|
180
|
+
android = AndroidManifest(
|
|
181
|
+
playFeatureModule = android.getString("playFeatureModule"),
|
|
182
|
+
directDownload = DirectDownload(
|
|
183
|
+
downloadUrl = direct.getString("downloadUrl"),
|
|
184
|
+
sha256 = direct.getString("sha256"),
|
|
185
|
+
sizeBytes = direct.getLong("sizeBytes")
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package flir.android
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.view.TextureView
|
|
6
|
+
import android.widget.FrameLayout
|
|
7
|
+
import android.widget.TextView
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
|
+
|
|
10
|
+
class FlirView(context: ThemedReactContext) : FrameLayout(context) {
|
|
11
|
+
private val textureView: TextureView
|
|
12
|
+
|
|
13
|
+
init {
|
|
14
|
+
textureView = TextureView(context)
|
|
15
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
16
|
+
addView(textureView)
|
|
17
|
+
|
|
18
|
+
// Simple placeholder overlay to indicate native FLIR view
|
|
19
|
+
val overlay = TextView(context)
|
|
20
|
+
overlay.text = "FLIR Preview"
|
|
21
|
+
overlay.setTextColor(Color.WHITE)
|
|
22
|
+
overlay.setPadding(12, 12, 12, 12)
|
|
23
|
+
addView(overlay)
|
|
24
|
+
|
|
25
|
+
// Ensure SDK is initialized
|
|
26
|
+
FlirManager.init(context)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun onAttachedToWindow() {
|
|
30
|
+
super.onAttachedToWindow()
|
|
31
|
+
// Let the centralized manager handle discovery and streaming and emit events
|
|
32
|
+
try {
|
|
33
|
+
FlirManager.startDiscoveryAndConnect(context as ThemedReactContext)
|
|
34
|
+
} catch (ignored: Throwable) {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override fun onDetachedFromWindow() {
|
|
38
|
+
super.onDetachedFromWindow()
|
|
39
|
+
try {
|
|
40
|
+
FlirManager.stop()
|
|
41
|
+
} catch (ignored: Throwable) {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Called by JS/native module to get latest frame cache file
|
|
45
|
+
fun getLatestFramePath(): String? {
|
|
46
|
+
return FlirManager.getLatestFramePath()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package flir.android
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import android.view.View
|
|
6
|
+
|
|
7
|
+
class FlirViewManager : SimpleViewManager<View>() {
|
|
8
|
+
override fun getName(): String = "FLIRCameraView"
|
|
9
|
+
|
|
10
|
+
override fun createViewInstance(reactContext: ThemedReactContext): View {
|
|
11
|
+
return FlirView(reactContext)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package flir.android;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
|
|
5
|
+
public class FrameDataHolder {
|
|
6
|
+
|
|
7
|
+
public final Bitmap msxBitmap;
|
|
8
|
+
public final Bitmap dcBitmap;
|
|
9
|
+
|
|
10
|
+
public FrameDataHolder(Bitmap msxBitmap, Bitmap dcBitmap) {
|
|
11
|
+
this.msxBitmap = msxBitmap;
|
|
12
|
+
this.dcBitmap = dcBitmap;
|
|
13
|
+
}
|
|
14
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
const {
|
|
2
|
+
withInfoPlist,
|
|
3
|
+
withAndroidManifest,
|
|
4
|
+
withDangerousMod,
|
|
5
|
+
createRunOncePlugin,
|
|
6
|
+
} = require('@expo/config-plugins');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* FLIR Thermal SDK Config Plugin
|
|
12
|
+
*
|
|
13
|
+
* Automatically adds required iOS Info.plist and Android AndroidManifest.xml
|
|
14
|
+
* entries for FLIR device support. This eliminates the need for manual editing.
|
|
15
|
+
*
|
|
16
|
+
* Usage in app.json:
|
|
17
|
+
* {
|
|
18
|
+
* "plugins": ["ilabs-flir"]
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Or with custom descriptions:
|
|
22
|
+
* {
|
|
23
|
+
* "plugins": [
|
|
24
|
+
* ["ilabs-flir", {
|
|
25
|
+
* "bluetoothAlwaysUsageDescription": "Custom description here",
|
|
26
|
+
* "bluetoothPeripheralUsageDescription": "Custom description here"
|
|
27
|
+
* }]
|
|
28
|
+
* ]
|
|
29
|
+
* }
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const EXTERNAL_ACCESSORY_PROTOCOLS = [
|
|
33
|
+
'com.flir.rosebud.config',
|
|
34
|
+
'com.flir.rosebud.frame',
|
|
35
|
+
'com.flir.rosebud.fileio',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const DEFAULT_BLUETOOTH_ALWAYS_DESCRIPTION =
|
|
39
|
+
'This app requires Bluetooth to connect to FLIR thermal cameras via Bluetooth Low Energy';
|
|
40
|
+
|
|
41
|
+
const DEFAULT_BLUETOOTH_PERIPHERAL_DESCRIPTION =
|
|
42
|
+
'This app uses Bluetooth to communicate with FLIR thermal imaging devices';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds FLIR-specific Info.plist entries for iOS
|
|
46
|
+
*/
|
|
47
|
+
const withFlirInfoPlist = (config, props = {}) => {
|
|
48
|
+
return withInfoPlist(config, (config) => {
|
|
49
|
+
const infoPlist = config.modResults;
|
|
50
|
+
|
|
51
|
+
// Add External Accessory Protocols for FLIR ONE devices
|
|
52
|
+
// These protocols enable Lightning interface communication (FLIR ONE Classic)
|
|
53
|
+
// and prepare for Bluetooth LE devices (FLIR ONE Edge/Pro)
|
|
54
|
+
if (!infoPlist.UISupportedExternalAccessoryProtocols) {
|
|
55
|
+
infoPlist.UISupportedExternalAccessoryProtocols = [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Merge protocols without duplicates
|
|
59
|
+
const existingProtocols = infoPlist.UISupportedExternalAccessoryProtocols;
|
|
60
|
+
EXTERNAL_ACCESSORY_PROTOCOLS.forEach((protocol) => {
|
|
61
|
+
if (!existingProtocols.includes(protocol)) {
|
|
62
|
+
existingProtocols.push(protocol);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Add Bluetooth permissions for FLIR ONE Edge/Pro (BLE devices)
|
|
67
|
+
// iOS 13+ requires NSBluetoothAlwaysUsageDescription
|
|
68
|
+
if (!infoPlist.NSBluetoothAlwaysUsageDescription) {
|
|
69
|
+
infoPlist.NSBluetoothAlwaysUsageDescription =
|
|
70
|
+
props.bluetoothAlwaysUsageDescription ||
|
|
71
|
+
DEFAULT_BLUETOOTH_ALWAYS_DESCRIPTION;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Older iOS versions (pre-13) require NSBluetoothPeripheralUsageDescription
|
|
75
|
+
if (!infoPlist.NSBluetoothPeripheralUsageDescription) {
|
|
76
|
+
infoPlist.NSBluetoothPeripheralUsageDescription =
|
|
77
|
+
props.bluetoothPeripheralUsageDescription ||
|
|
78
|
+
DEFAULT_BLUETOOTH_PERIPHERAL_DESCRIPTION;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return config;
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Adds FLIR-specific AndroidManifest.xml entries for Android
|
|
87
|
+
*/
|
|
88
|
+
const withFlirAndroidManifest = (config) => {
|
|
89
|
+
return withAndroidManifest(config, (config) => {
|
|
90
|
+
const androidManifest = config.modResults;
|
|
91
|
+
const mainApplication = androidManifest.manifest;
|
|
92
|
+
|
|
93
|
+
// Ensure uses-feature array exists
|
|
94
|
+
if (!mainApplication['uses-feature']) {
|
|
95
|
+
mainApplication['uses-feature'] = [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Ensure uses-permission array exists
|
|
99
|
+
if (!mainApplication['uses-permission']) {
|
|
100
|
+
mainApplication['uses-permission'] = [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add USB host feature for FLIR ONE USB devices
|
|
104
|
+
const usbHostFeature = {
|
|
105
|
+
$: {
|
|
106
|
+
'android:name': 'android.hardware.usb.host',
|
|
107
|
+
'android:required': 'false',
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Check if USB host feature already exists
|
|
112
|
+
const hasUsbHost = mainApplication['uses-feature'].some(
|
|
113
|
+
(feature) => feature.$?.['android:name'] === 'android.hardware.usb.host'
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (!hasUsbHost) {
|
|
117
|
+
mainApplication['uses-feature'].push(usbHostFeature);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add camera permission for FLIR cameras
|
|
121
|
+
const cameraPermission = {
|
|
122
|
+
$: {
|
|
123
|
+
'android:name': 'android.permission.CAMERA',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Check if camera permission already exists
|
|
128
|
+
const hasCameraPermission = mainApplication['uses-permission'].some(
|
|
129
|
+
(permission) => permission.$?.['android:name'] === 'android.permission.CAMERA'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (!hasCameraPermission) {
|
|
133
|
+
mainApplication['uses-permission'].push(cameraPermission);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return config;
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Copies sdk-manifest.json to iOS project
|
|
142
|
+
*/
|
|
143
|
+
const withFlirManifest = (config) => {
|
|
144
|
+
return withDangerousMod(config, [
|
|
145
|
+
'ios',
|
|
146
|
+
async (config) => {
|
|
147
|
+
const src = path.join(__dirname, 'sdk-manifest.json');
|
|
148
|
+
const dst = path.join(config.modRequest.platformProjectRoot, 'sdk-manifest.json');
|
|
149
|
+
fs.copyFileSync(src, dst);
|
|
150
|
+
return config;
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Copies sdk-manifest.json to Android assets
|
|
157
|
+
*/
|
|
158
|
+
const withFlirAndroidAssets = (config) => {
|
|
159
|
+
return withDangerousMod(config, [
|
|
160
|
+
'android',
|
|
161
|
+
async (config) => {
|
|
162
|
+
const src = path.join(__dirname, 'sdk-manifest.json');
|
|
163
|
+
const dst = path.join(config.modRequest.platformProjectRoot, 'app/src/main/assets/sdk-manifest.json');
|
|
164
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
165
|
+
fs.copyFileSync(src, dst);
|
|
166
|
+
return config;
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Ensure the Flir Android Gradle module is included in generated projects.
|
|
173
|
+
*
|
|
174
|
+
* Some workflows (Expo prebuild) won't add node_modules subprojects to
|
|
175
|
+
* settings.gradle automatically unless the package exposes autolinking or
|
|
176
|
+
* the plugin performs the edit. When a consumer installs the package and
|
|
177
|
+
* runs prebuild, include the Flir module and wire an app dependency so the
|
|
178
|
+
* native bridge (FlirDownloadManager) is compiled into the app.
|
|
179
|
+
*/
|
|
180
|
+
const withFlirAndroidGradle = (config) => {
|
|
181
|
+
return withDangerousMod(config, [
|
|
182
|
+
'android',
|
|
183
|
+
async (config) => {
|
|
184
|
+
try {
|
|
185
|
+
const projectRoot = config.modRequest.platformProjectRoot;
|
|
186
|
+
const settingsGradlePath = path.join(projectRoot, 'settings.gradle');
|
|
187
|
+
const appBuildGradlePath = path.join(projectRoot, 'app', 'build.gradle');
|
|
188
|
+
|
|
189
|
+
const moduleRelPath = '../node_modules/ilabs-flir/android/Flir';
|
|
190
|
+
const includeSnippet = `\n// ilabs-flir: include Flir module\nif (new File(rootProject.projectDir, '${moduleRelPath}').exists()) {\n include ':Flir'\n project(':Flir').projectDir = new File(rootProject.projectDir, '${moduleRelPath}')\n}\n`;
|
|
191
|
+
|
|
192
|
+
if (fs.existsSync(settingsGradlePath)) {
|
|
193
|
+
let settingsTxt = fs.readFileSync(settingsGradlePath, 'utf8');
|
|
194
|
+
if (!/include\s*':Flir'/.test(settingsTxt)) {
|
|
195
|
+
fs.appendFileSync(settingsGradlePath, includeSnippet, 'utf8');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (fs.existsSync(appBuildGradlePath)) {
|
|
200
|
+
let buildTxt = fs.readFileSync(appBuildGradlePath, 'utf8');
|
|
201
|
+
// Only add implementation project(':Flir') if it's not already present
|
|
202
|
+
if (!/project\('\:Flir'\)/.test(buildTxt)) {
|
|
203
|
+
const depSnippet = `\n // ilabs-flir: include :Flir when available\n if (new File(rootDir.getParent(), '${moduleRelPath}').exists()) {\n implementation project(':Flir')\n }\n`;
|
|
204
|
+
|
|
205
|
+
// Find the first 'dependencies {' occurrence and find its matching closing brace
|
|
206
|
+
const depIndex = buildTxt.search(/\bdependencies\s*\{/);
|
|
207
|
+
if (depIndex !== -1) {
|
|
208
|
+
// Walk forward to find the matching closing brace for the dependencies block
|
|
209
|
+
let depth = 0;
|
|
210
|
+
let insertPos = -1;
|
|
211
|
+
for (let i = depIndex; i < buildTxt.length; i++) {
|
|
212
|
+
const ch = buildTxt[i];
|
|
213
|
+
if (ch === '{') depth++;
|
|
214
|
+
else if (ch === '}') {
|
|
215
|
+
depth--;
|
|
216
|
+
if (depth === 0) { insertPos = i; break; }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (insertPos !== -1) {
|
|
221
|
+
// Insert snippet just before the closing '}' of the dependencies block
|
|
222
|
+
buildTxt = buildTxt.slice(0, insertPos) + depSnippet + buildTxt.slice(insertPos);
|
|
223
|
+
fs.writeFileSync(appBuildGradlePath, buildTxt, 'utf8');
|
|
224
|
+
} else {
|
|
225
|
+
// Fallback: append to the file
|
|
226
|
+
fs.appendFileSync(appBuildGradlePath, '\n' + depSnippet, 'utf8');
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
// No dependencies block found! Append a new one with the snippet.
|
|
230
|
+
fs.appendFileSync(appBuildGradlePath, '\n' + 'dependencies {' + depSnippet + '\n}\n', 'utf8');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.warn('[flir-config-plugin] Failed to patch Android Gradle files:', err && err.message);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return config;
|
|
239
|
+
},
|
|
240
|
+
]);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Main plugin that combines iOS and Android configurations
|
|
245
|
+
*/
|
|
246
|
+
const withFlirThermalSDK = (config, props = {}) => {
|
|
247
|
+
// Apply iOS modifications
|
|
248
|
+
config = withFlirInfoPlist(config, props);
|
|
249
|
+
config = withFlirManifest(config);
|
|
250
|
+
|
|
251
|
+
// Apply Android modifications
|
|
252
|
+
config = withFlirAndroidManifest(config);
|
|
253
|
+
config = withFlirAndroidAssets(config);
|
|
254
|
+
// Ensure the Flir Gradle module is included in generated native projects
|
|
255
|
+
config = withFlirAndroidGradle(config);
|
|
256
|
+
|
|
257
|
+
return config;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
module.exports = createRunOncePlugin(
|
|
261
|
+
withFlirThermalSDK,
|
|
262
|
+
'ilabs-flir',
|
|
263
|
+
'2.0.2'
|
|
264
|
+
);
|