ilabs-flir 2.4.7 → 2.4.8
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
CHANGED
|
@@ -387,6 +387,8 @@ object FlirManager {
|
|
|
387
387
|
}
|
|
388
388
|
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
389
389
|
.emit("FlirDeviceConnected", params)
|
|
390
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
391
|
+
.emit("FlirStateChanged", params)
|
|
390
392
|
} catch (e: Exception) { }
|
|
391
393
|
}
|
|
392
394
|
|
|
@@ -86,7 +86,37 @@ public class FlirSdkManager {
|
|
|
86
86
|
private volatile SnapshotCallback snapshotCallback;
|
|
87
87
|
|
|
88
88
|
private FlirSdkManager(Context context) {
|
|
89
|
-
|
|
89
|
+
// We wrap the Application context to intercept and swallow IllegalArgumentException
|
|
90
|
+
// during unregisterReceiver. The FLIR SDK has a bug in WifiScanner where it attempts
|
|
91
|
+
// to unregister a receiver that wasn't registered, which bubbles up to JNI and crashes
|
|
92
|
+
// the app via std::terminate.
|
|
93
|
+
this.context = new android.content.ContextWrapper(context.getApplicationContext()) {
|
|
94
|
+
@Override
|
|
95
|
+
public Context getApplicationContext() {
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Override
|
|
100
|
+
public android.content.Intent registerReceiver(android.content.BroadcastReceiver receiver, android.content.IntentFilter filter) {
|
|
101
|
+
try {
|
|
102
|
+
return super.registerReceiver(receiver, filter);
|
|
103
|
+
} catch (Exception e) {
|
|
104
|
+
Log.w(TAG, "Suppressed registerReceiver error: " + e.getMessage());
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Override
|
|
110
|
+
public void unregisterReceiver(android.content.BroadcastReceiver receiver) {
|
|
111
|
+
try {
|
|
112
|
+
super.unregisterReceiver(receiver);
|
|
113
|
+
} catch (IllegalArgumentException e) {
|
|
114
|
+
Log.w(TAG, "Suppressed SDK crash: Receiver not registered: " + e.getMessage());
|
|
115
|
+
} catch (Exception e) {
|
|
116
|
+
Log.w(TAG, "Suppressed unregisterReceiver error: " + e.getMessage());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
90
120
|
}
|
|
91
121
|
|
|
92
122
|
public static synchronized FlirSdkManager getInstance(Context context) {
|
|
@@ -106,8 +136,51 @@ public class FlirSdkManager {
|
|
|
106
136
|
if (isInitialized.compareAndSet(false, true)) {
|
|
107
137
|
Log.d(TAG, "Initializing FLIR SDK (async)...");
|
|
108
138
|
|
|
139
|
+
// Register a main thread looper protector to completely safeguard against WifiScanner supplicant receiver crashes
|
|
140
|
+
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
|
|
141
|
+
@Override
|
|
142
|
+
public void run() {
|
|
143
|
+
while (true) {
|
|
144
|
+
try {
|
|
145
|
+
android.os.Looper.loop();
|
|
146
|
+
} catch (Throwable t) {
|
|
147
|
+
String msg = t.getMessage();
|
|
148
|
+
boolean isSuppressed = false;
|
|
149
|
+
|
|
150
|
+
// Check if this is the notorious FLIR supplicant receiver crash
|
|
151
|
+
if (t instanceof RuntimeException && t.getCause() instanceof IllegalArgumentException) {
|
|
152
|
+
String causeMsg = t.getCause().getMessage();
|
|
153
|
+
if (causeMsg != null && (causeMsg.contains("Receiver not registered") || causeMsg.contains("WifiScanner"))) {
|
|
154
|
+
isSuppressed = true;
|
|
155
|
+
}
|
|
156
|
+
} else if (msg != null && (msg.contains("Receiver not registered") || msg.contains("WifiScanner") || msg.contains("supplicant.STATE_CHANGE"))) {
|
|
157
|
+
isSuppressed = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (isSuppressed) {
|
|
161
|
+
Log.w(TAG, "🔒 [FLIR LOOPER PROTECTOR] Intercepted and swallowed broadcast receiver crash: " + t.getMessage());
|
|
162
|
+
} else {
|
|
163
|
+
// For all other unexpected exceptions, forward to the standard uncaught exception handler
|
|
164
|
+
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
|
165
|
+
if (handler != null) {
|
|
166
|
+
handler.uncaughtException(Thread.currentThread(), t);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
109
175
|
executor.execute(() -> {
|
|
110
176
|
try {
|
|
177
|
+
try {
|
|
178
|
+
System.loadLibrary("c++_flir");
|
|
179
|
+
Log.d(TAG, "Successfully loaded legacy libc++_flir.so for FLIR SDK.");
|
|
180
|
+
} catch (UnsatisfiedLinkError e) {
|
|
181
|
+
Log.e(TAG, "Could not explicitly load libc++_flir.so. FLIR initialization might fail.", e);
|
|
182
|
+
}
|
|
183
|
+
|
|
111
184
|
ThermalSdkAndroid.init(context);
|
|
112
185
|
|
|
113
186
|
// Small delay to ensure JNI linkage is stable
|
|
@@ -203,54 +203,39 @@ import ThermalSDK
|
|
|
203
203
|
let camType = identity.cameraType()
|
|
204
204
|
NSLog("[FlirManager] Camera type: \(camType.rawValue), interface: \(iface.rawValue)")
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
// Official FLIR CameraConnector sample checks .generic camera type,
|
|
208
|
-
// but FLIR One Edge Pro over network may report a different type.
|
|
209
|
-
// Check BOTH: camera type == .generic OR interface contains .network/.flirOneWireless
|
|
210
|
-
let needsAuth = (camType == .generic) || iface.contains(.network) || iface.contains(.flirOneWireless)
|
|
206
|
+
let isNetwork = (camType == .generic) || iface.contains(.network)
|
|
211
207
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
let
|
|
219
|
-
NSLog("[FlirManager] Using certificate name: \(certName)")
|
|
208
|
+
// ── STEP 1: INITIAL TRUST HANDSHAKE (Network Cameras Only) ──
|
|
209
|
+
// DiscoverySampleSwift shows that for network cameras we first authenticate
|
|
210
|
+
// with a user-friendly device/app name to trigger the "Trust this device" prompt.
|
|
211
|
+
if isNetwork {
|
|
212
|
+
NSLog("[FlirManager] Network camera detected — initiating initial trust request...")
|
|
213
|
+
let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? "AppFactory"
|
|
214
|
+
let trustName = "\(UIDevice.current.name) \(appName)"
|
|
220
215
|
|
|
221
216
|
var status = FLIRAuthenticationStatus.pending
|
|
222
217
|
var attempts = 0
|
|
223
|
-
let maxAttempts =
|
|
218
|
+
let maxAttempts = 15 // 15 seconds window for initial prompt
|
|
224
219
|
|
|
225
220
|
while status == .pending && attempts < maxAttempts {
|
|
226
|
-
status = cam.authenticate(identity, trustedConnectionName:
|
|
227
|
-
NSLog("[FlirManager]
|
|
228
|
-
|
|
221
|
+
status = cam.authenticate(identity, trustedConnectionName: trustName)
|
|
222
|
+
NSLog("[FlirManager] Initial trust attempt \(attempts + 1)/\(maxAttempts) status: \(status.rawValue)")
|
|
229
223
|
if status == .pending {
|
|
230
|
-
// Camera waiting for user to press "Trust" on its screen
|
|
231
224
|
Thread.sleep(forTimeInterval: 1.0)
|
|
232
225
|
}
|
|
233
226
|
attempts += 1
|
|
234
227
|
}
|
|
235
|
-
|
|
236
|
-
if status != .approved {
|
|
237
|
-
NSLog("[FlirManager] Authentication failed/timed out: \(status.rawValue)")
|
|
238
|
-
self.camera = nil
|
|
239
|
-
DispatchQueue.main.async {
|
|
240
|
-
self.emitStateChange("connection_failed")
|
|
241
|
-
self.delegate?.onError("Camera authentication failed. Check the camera screen for a trust/approve prompt.")
|
|
242
|
-
}
|
|
243
|
-
return
|
|
244
|
-
}
|
|
245
|
-
NSLog("[FlirManager] Authentication approved ✅")
|
|
246
228
|
}
|
|
247
229
|
|
|
248
|
-
// ──
|
|
230
|
+
// ── STEP 2: PAIRING (Always do this before final authentication!) ──
|
|
231
|
+
// Both FLIROneCameraSwift and DiscoverySampleSwift require pairing to be established
|
|
232
|
+
// before the camera's communication channels are authenticated and connected.
|
|
249
233
|
do {
|
|
234
|
+
NSLog("[FlirManager] Pairing camera with code 0...")
|
|
250
235
|
try cam.pair(identity, code: 0)
|
|
251
|
-
NSLog("[FlirManager]
|
|
236
|
+
NSLog("[FlirManager] Pairing succeeded ✅")
|
|
252
237
|
} catch {
|
|
253
|
-
NSLog("[FlirManager]
|
|
238
|
+
NSLog("[FlirManager] Pairing failed: \(error)")
|
|
254
239
|
self._isConnected = false
|
|
255
240
|
self.camera = nil
|
|
256
241
|
DispatchQueue.main.async {
|
|
@@ -260,6 +245,37 @@ import ThermalSDK
|
|
|
260
245
|
return
|
|
261
246
|
}
|
|
262
247
|
|
|
248
|
+
// ── STEP 3: FINAL AUTHENTICATION ──
|
|
249
|
+
// Use a persistent UUID certificate for network/wireless cameras to avoid trust conflicts.
|
|
250
|
+
// For standard classic FLIR ONE/Edge devices, use "dummy" as per FLIROneCameraSwift ViewController.swift.
|
|
251
|
+
let certName = isNetwork ? self.getPersistentCertificateName() : "dummy"
|
|
252
|
+
NSLog("[FlirManager] Performing final authentication with name: \(certName)")
|
|
253
|
+
|
|
254
|
+
var status = FLIRAuthenticationStatus.pending
|
|
255
|
+
var attempts = 0
|
|
256
|
+
let maxAttempts = 30 // ~30 seconds timeout
|
|
257
|
+
|
|
258
|
+
while status == .pending && attempts < maxAttempts {
|
|
259
|
+
status = cam.authenticate(identity, trustedConnectionName: certName)
|
|
260
|
+
NSLog("[FlirManager] Final auth attempt \(attempts + 1)/\(maxAttempts) status: \(status.rawValue)")
|
|
261
|
+
|
|
262
|
+
if status == .pending {
|
|
263
|
+
Thread.sleep(forTimeInterval: 1.0)
|
|
264
|
+
}
|
|
265
|
+
attempts += 1
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if status != .approved && isNetwork {
|
|
269
|
+
NSLog("[FlirManager] Final authentication failed or timed out: \(status.rawValue)")
|
|
270
|
+
self.camera = nil
|
|
271
|
+
DispatchQueue.main.async {
|
|
272
|
+
self.emitStateChange("connection_failed")
|
|
273
|
+
self.delegate?.onError("Camera authentication failed. Check the camera screen for a trust/approve prompt.")
|
|
274
|
+
}
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
NSLog("[FlirManager] Authentication approved ✅")
|
|
278
|
+
|
|
263
279
|
// ── CONNECT ──
|
|
264
280
|
do {
|
|
265
281
|
try cam.connect()
|