ilabs-flir 2.3.10 → 2.3.11

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.
@@ -23,12 +23,15 @@ import com.flir.thermalsdk.live.connectivity.ConnectionStatusListener;
23
23
  import com.flir.thermalsdk.live.discovery.DiscoveredCamera;
24
24
  import com.flir.thermalsdk.live.discovery.DiscoveryEventListener;
25
25
  import com.flir.thermalsdk.live.discovery.DiscoveryFactory;
26
+ import com.flir.thermalsdk.androidsdk.live.connectivity.SdkWifiConnectionHelper;
26
27
  import com.flir.thermalsdk.live.streaming.Stream;
27
28
  import com.flir.thermalsdk.live.streaming.ThermalStreamer;
28
29
 
29
30
  import java.util.ArrayList;
30
31
  import java.util.Collections;
32
+ import java.util.HashMap;
31
33
  import java.util.List;
34
+ import java.util.Map;
32
35
  import java.util.concurrent.Executor;
33
36
  import java.util.concurrent.Executors;
34
37
  import java.util.concurrent.atomic.AtomicBoolean;
@@ -51,6 +54,7 @@ public class FlirSdkManager {
51
54
  private ThermalStreamer streamer;
52
55
  private Stream activeStream;
53
56
  private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
57
+ private final Map<String, DiscoveredCamera> discoveredCameras = Collections.synchronizedMap(new HashMap<>());
54
58
  private volatile Bitmap latestBitmap;
55
59
  private volatile String currentPaletteName = "WhiteHot";
56
60
  private final AtomicBoolean isProcessingFrame = new AtomicBoolean(false);
@@ -152,25 +156,19 @@ public class FlirSdkManager {
152
156
 
153
157
  public void stopScan() {
154
158
  if (!isScanning) return;
155
-
156
- // Use a temporary flag to prevent concurrent stop calls
157
159
  isScanning = false;
158
-
159
- executor.execute(() -> {
160
- try {
161
- Log.d(TAG, "Stopping discovery...");
162
- DiscoveryFactory.getInstance().stop(
163
- CommunicationInterface.EMULATOR,
164
- CommunicationInterface.USB,
165
- CommunicationInterface.NETWORK,
166
- CommunicationInterface.FLIR_ONE_WIRELESS);
167
- Log.d(TAG, "Discovery stopped successfully");
168
- } catch (Exception e) {
169
- // This is where the 'Receiver not registered' usually happens in SDK internals.
170
- // We catch it silently as it means the SDK already cleaned up or is in a weird state.
171
- Log.w(TAG, "Stop scan warning (internal SDK): " + e.getMessage());
172
- }
173
- });
160
+ executor.execute(this::stopScanInternal);
161
+ }
162
+
163
+ private void stopScanInternal() {
164
+ try {
165
+ Log.d(TAG, "Stopping discovery...");
166
+ // Use zero-arg stop() as seen in official samples to stop all scanners
167
+ DiscoveryFactory.getInstance().stop();
168
+ Log.d(TAG, "Discovery stopped successfully");
169
+ } catch (Exception e) {
170
+ Log.w(TAG, "Stop scan warning (internal SDK): " + e.getMessage());
171
+ }
174
172
  }
175
173
 
176
174
  public List<Identity> getDiscoveredDevices() {
@@ -199,62 +197,92 @@ public class FlirSdkManager {
199
197
  camera = null;
200
198
  }
201
199
 
202
- Log.d(TAG, "Connecting to: " + identity.deviceId);
203
- camera = new Camera();
204
-
205
- // ── Authenticate for NETWORK/WIRELESS cameras (required by FLIR SDK) ──
206
- // Matches the official NetworkCamera sample app pattern.
207
- // The FLIR One Edge Pro is a network/wireless camera and will reject
208
- // connections without prior authentication + trust approval.
209
- if (identity.communicationInterface == CommunicationInterface.NETWORK ||
210
- identity.communicationInterface == CommunicationInterface.FLIR_ONE_WIRELESS) {
211
- Log.d(TAG, "Network/Wireless camera detected — authenticating...");
212
-
213
- // Use a persistent application name (workaround for camera bug
214
- // where re-auth with a different name conflicts). Same pattern
215
- // as CameraAuthName in the NetworkCamera sample.
216
- SharedPreferences prefs = context.getSharedPreferences(
217
- "flir_auth", Context.MODE_PRIVATE);
218
- String authName = prefs.getString("auth_name", null);
219
- if (authName == null) {
220
- authName = context.getPackageName() + "-" +
221
- (System.currentTimeMillis() % 10000);
222
- prefs.edit().putString("auth_name", authName).apply();
200
+ // ── FLIR ONE WIRELESS (WiFi) Connection ──
201
+ // Matches the official FlirOneWireless sample. We must connect to the
202
+ // camera's WiFi Access Point before calling camera.connect().
203
+ if (identity.communicationInterface == CommunicationInterface.FLIR_ONE_WIRELESS) {
204
+ DiscoveredCamera dc = getDiscoveredCamera(identity.deviceId);
205
+ if (dc != null && dc.cameraDetails != null) {
206
+ String ssid = dc.cameraDetails.ssid;
207
+ Log.d(TAG, "Establishing WiFi connection to: " + ssid);
208
+
209
+ if (!SdkWifiConnectionHelper.isConnectedToNetwork(context, ssid)) {
210
+ // This is a blocking-style wrapper for simplicity in the executor thread
211
+ final AtomicBoolean wifiDone = new AtomicBoolean(false);
212
+ final AtomicBoolean wifiSuccess = new AtomicBoolean(false);
213
+
214
+ SdkWifiConnectionHelper.connectToWifiWithoutCode(context, dc.cameraDetails, status -> {
215
+ if (status.status == SdkWifiConnectionHelper.ConInfo.CONNECTED) {
216
+ wifiSuccess.set(true);
217
+ wifiDone.set(true);
218
+ } else if (status.status == SdkWifiConnectionHelper.ConInfo.ERROR) {
219
+ wifiSuccess.set(false);
220
+ wifiDone.set(true);
221
+ }
222
+ });
223
+
224
+ // Wait for WiFi connection (max 15 seconds)
225
+ int waitLoops = 0;
226
+ while (!wifiDone.get() && waitLoops < 30) {
227
+ try { Thread.sleep(500); } catch (Exception ignored) {}
228
+ waitLoops++;
229
+ }
230
+
231
+ if (!wifiSuccess.get()) {
232
+ notifyError("Failed to connect to camera WiFi: " + ssid);
233
+ return;
234
+ }
235
+ }
223
236
  }
237
+ }
224
238
 
239
+ // ── NETWORK Authentication (Required for A/T-series, NOT Wireless) ──
240
+ if (identity.communicationInterface == CommunicationInterface.NETWORK) {
241
+ Log.d(TAG, "Authenticating with network camera: " + identity.deviceId);
242
+
243
+ if (isScanning) {
244
+ isScanning = false;
245
+ stopScanInternal();
246
+ try { Thread.sleep(500); } catch (Exception ignored) {}
247
+ }
248
+
249
+ camera = new Camera();
250
+ String authName = "ThermalCameraFx";
225
251
  AuthenticationResponse response;
226
252
  int attempts = 0;
227
- final int MAX_AUTH_ATTEMPTS = 30; // 30 seconds max wait
253
+ final int MAX_AUTH_ATTEMPTS = 3;
254
+
228
255
  do {
229
- response = camera.authenticate(identity, authName,
230
- 41 * 1000); // 41-second timeout per attempt
231
- Log.d(TAG, "Auth attempt " + (attempts + 1) +
232
- " status: " + response.authenticationStatus);
233
-
234
- if (response.authenticationStatus ==
235
- AuthenticationResponse.AuthenticationStatus.PENDING) {
236
- // Camera is waiting for user to press "Trust" on its screen
237
- Thread.sleep(1000);
238
- }
239
256
  attempts++;
240
- } while (response.authenticationStatus ==
241
- AuthenticationResponse.AuthenticationStatus.PENDING
242
- && attempts < MAX_AUTH_ATTEMPTS);
243
-
244
- if (response.authenticationStatus !=
245
- AuthenticationResponse.AuthenticationStatus.APPROVED) {
246
- Log.e(TAG, "Authentication rejected/timed out: " +
247
- response.authenticationStatus);
248
- camera = null;
249
- notifyError("Camera authentication failed. " +
250
- "Check the camera screen for a trust prompt.");
257
+ response = camera.authenticate(identity, authName, 41 * 1000);
258
+ if (response.authenticationStatus == AuthenticationResponse.AuthenticationStatus.APPROVED) {
259
+ break;
260
+ } else if (response.authenticationStatus == AuthenticationResponse.AuthenticationStatus.REJECTED) {
261
+ notifyError("Authentication rejected by camera");
262
+ return;
263
+ }
264
+ } while (response.authenticationStatus == AuthenticationResponse.AuthenticationStatus.PENDING && attempts < MAX_AUTH_ATTEMPTS);
265
+
266
+ if (response.authenticationStatus != AuthenticationResponse.AuthenticationStatus.APPROVED) {
267
+ notifyError("Authentication failed: " + response.authenticationStatus.name());
251
268
  return;
252
269
  }
253
- Log.d(TAG, "Authentication approved");
270
+ } else {
271
+ // For all other types, stop scan if still running
272
+ if (isScanning) {
273
+ isScanning = false;
274
+ stopScanInternal();
275
+ try { Thread.sleep(300); } catch (Exception ignored) {}
276
+ }
277
+ camera = identity.camera;
278
+ if (camera == null) {
279
+ camera = new Camera();
280
+ }
254
281
  }
255
282
 
283
+ Log.d(TAG, "Calling camera.connect() for " + identity.deviceId);
256
284
  camera.connect(identity, connectionStatusListener, new ConnectParameters());
257
- Log.d(TAG, "Connected to: " + identity.deviceId);
285
+ Log.d(TAG, "camera.connect() returned for " + identity.deviceId + ". isConnected=" + camera.isConnected());
258
286
 
259
287
  if (listener != null) {
260
288
  listener.onConnected(identity);
@@ -264,7 +292,7 @@ public class FlirSdkManager {
264
292
  startStreamInternal();
265
293
 
266
294
  } catch (Exception e) {
267
- Log.e(TAG, "Connection failed", e);
295
+ Log.e(TAG, "Connection failed for " + identity.deviceId, e);
268
296
  camera = null;
269
297
  notifyError("Connection failed: " + e.getMessage());
270
298
  }
@@ -295,6 +323,10 @@ public class FlirSdkManager {
295
323
  return camera != null;
296
324
  }
297
325
 
326
+ private DiscoveredCamera getDiscoveredCamera(String deviceId) {
327
+ return discoveredCameras.get(deviceId);
328
+ }
329
+
298
330
  // ==================== STREAMING ====================
299
331
 
300
332
  public void startStream() {
@@ -317,18 +349,50 @@ public class FlirSdkManager {
317
349
  }
318
350
 
319
351
  try {
352
+ // RETRY MECHANISM: For Network/Wireless cameras, the connection might take
353
+ // a few hundred milliseconds to stabilize at the native level even after
354
+ // camera.connect() returns. We retry for up to 3 seconds.
355
+ int retries = 0;
356
+ final int MAX_RETRIES = 15; // 15 * 200ms = 3 seconds
357
+ while (!camera.isConnected() && retries < MAX_RETRIES) {
358
+ try {
359
+ Thread.sleep(200);
360
+ } catch (InterruptedException ignored) {}
361
+ retries++;
362
+ if (retries % 5 == 0) {
363
+ Log.d(TAG, "Waiting for camera connection state to stabilize... (attempt " + retries + ")");
364
+ }
365
+ }
366
+
320
367
  if (!camera.isConnected()) {
321
- Log.e(TAG, "Camera not connected, cannot start stream");
368
+ Log.e(TAG, "Camera failed to report connected state after " + (retries * 200) + "ms");
322
369
  notifyError("Camera not connected");
323
370
  return;
324
371
  }
325
372
 
326
- List<Stream> streams = camera.getStreams();
373
+ Log.d(TAG, "Camera connected state confirmed after " + (retries * 200) + "ms");
374
+
375
+ // RETRY MECHANISM for getStreams: Sometimes streams are not immediately available
376
+ List<Stream> streams = null;
377
+ retries = 0;
378
+ while (retries < 10) { // 10 * 200ms = 2 seconds
379
+ streams = camera.getStreams();
380
+ if (streams != null && !streams.isEmpty()) {
381
+ break;
382
+ }
383
+ try {
384
+ Thread.sleep(200);
385
+ } catch (InterruptedException ignored) {}
386
+ retries++;
387
+ }
388
+
327
389
  if (streams == null || streams.isEmpty()) {
328
390
  notifyError("No streams available");
329
391
  return;
330
392
  }
331
393
 
394
+ Log.d(TAG, "Streams found: " + streams.size() + " (after " + (retries * 200) + "ms)");
395
+
332
396
  // Find thermal stream or use first
333
397
  Stream thermalStream = null;
334
398
  for (Stream stream : streams) {
@@ -531,6 +595,8 @@ public class FlirSdkManager {
531
595
  Identity identity = discoveredCamera.getIdentity();
532
596
  Log.d(TAG, "Device found: " + identity.deviceId);
533
597
 
598
+ discoveredCameras.put(identity.deviceId, discoveredCamera);
599
+
534
600
  synchronized (discoveredDevices) {
535
601
  boolean exists = false;
536
602
  for (Identity d : discoveredDevices) {
@@ -553,6 +619,7 @@ public class FlirSdkManager {
553
619
  @Override
554
620
  public void onCameraLost(Identity identity) {
555
621
  Log.d(TAG, "Device lost: " + identity.deviceId);
622
+ discoveredCameras.remove(identity.deviceId);
556
623
  synchronized (discoveredDevices) {
557
624
  discoveredDevices.removeIf(d -> d.deviceId.equals(identity.deviceId));
558
625
  }
@@ -162,7 +162,13 @@ import ThermalSDK
162
162
  disconnect()
163
163
  }
164
164
 
165
- // Connect on background thread (matches sample app)
165
+ // Stop discovery before connecting to free up SDK resources
166
+ // This prevents resource contention in the native SDK layer.
167
+ if discovery != nil {
168
+ discovery?.stop()
169
+ }
170
+
171
+ // Connect on background thread (matches sample app pattern)
166
172
  DispatchQueue.global().async { [weak self] in
167
173
  guard let self = self else { return }
168
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.3.10",
3
+ "version": "2.3.11",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",