ilabs-flir 2.3.10 → 2.3.12

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,86 @@ 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.getCameraDetails() != null) {
206
+ String ssid = dc.getCameraDetails().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.getCameraDetails(), 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
+ }
261
+ } while (response.authenticationStatus == AuthenticationResponse.AuthenticationStatus.PENDING && attempts < MAX_AUTH_ATTEMPTS);
262
+
263
+ if (response.authenticationStatus != AuthenticationResponse.AuthenticationStatus.APPROVED) {
264
+ notifyError("Authentication failed: " + response.authenticationStatus.name());
251
265
  return;
252
266
  }
253
- Log.d(TAG, "Authentication approved");
267
+ } else {
268
+ // For all other types, stop scan if still running
269
+ if (isScanning) {
270
+ isScanning = false;
271
+ stopScanInternal();
272
+ try { Thread.sleep(300); } catch (Exception ignored) {}
273
+ }
274
+ camera = new Camera();
254
275
  }
255
276
 
277
+ Log.d(TAG, "Calling camera.connect() for " + identity.deviceId);
256
278
  camera.connect(identity, connectionStatusListener, new ConnectParameters());
257
- Log.d(TAG, "Connected to: " + identity.deviceId);
279
+ Log.d(TAG, "camera.connect() returned for " + identity.deviceId + ". isConnected=" + camera.isConnected());
258
280
 
259
281
  if (listener != null) {
260
282
  listener.onConnected(identity);
@@ -264,7 +286,7 @@ public class FlirSdkManager {
264
286
  startStreamInternal();
265
287
 
266
288
  } catch (Exception e) {
267
- Log.e(TAG, "Connection failed", e);
289
+ Log.e(TAG, "Connection failed for " + identity.deviceId, e);
268
290
  camera = null;
269
291
  notifyError("Connection failed: " + e.getMessage());
270
292
  }
@@ -295,6 +317,10 @@ public class FlirSdkManager {
295
317
  return camera != null;
296
318
  }
297
319
 
320
+ private DiscoveredCamera getDiscoveredCamera(String deviceId) {
321
+ return discoveredCameras.get(deviceId);
322
+ }
323
+
298
324
  // ==================== STREAMING ====================
299
325
 
300
326
  public void startStream() {
@@ -317,18 +343,50 @@ public class FlirSdkManager {
317
343
  }
318
344
 
319
345
  try {
346
+ // RETRY MECHANISM: For Network/Wireless cameras, the connection might take
347
+ // a few hundred milliseconds to stabilize at the native level even after
348
+ // camera.connect() returns. We retry for up to 3 seconds.
349
+ int retries = 0;
350
+ final int MAX_RETRIES = 15; // 15 * 200ms = 3 seconds
351
+ while (!camera.isConnected() && retries < MAX_RETRIES) {
352
+ try {
353
+ Thread.sleep(200);
354
+ } catch (InterruptedException ignored) {}
355
+ retries++;
356
+ if (retries % 5 == 0) {
357
+ Log.d(TAG, "Waiting for camera connection state to stabilize... (attempt " + retries + ")");
358
+ }
359
+ }
360
+
320
361
  if (!camera.isConnected()) {
321
- Log.e(TAG, "Camera not connected, cannot start stream");
362
+ Log.e(TAG, "Camera failed to report connected state after " + (retries * 200) + "ms");
322
363
  notifyError("Camera not connected");
323
364
  return;
324
365
  }
325
366
 
326
- List<Stream> streams = camera.getStreams();
367
+ Log.d(TAG, "Camera connected state confirmed after " + (retries * 200) + "ms");
368
+
369
+ // RETRY MECHANISM for getStreams: Sometimes streams are not immediately available
370
+ List<Stream> streams = null;
371
+ retries = 0;
372
+ while (retries < 10) { // 10 * 200ms = 2 seconds
373
+ streams = camera.getStreams();
374
+ if (streams != null && !streams.isEmpty()) {
375
+ break;
376
+ }
377
+ try {
378
+ Thread.sleep(200);
379
+ } catch (InterruptedException ignored) {}
380
+ retries++;
381
+ }
382
+
327
383
  if (streams == null || streams.isEmpty()) {
328
384
  notifyError("No streams available");
329
385
  return;
330
386
  }
331
387
 
388
+ Log.d(TAG, "Streams found: " + streams.size() + " (after " + (retries * 200) + "ms)");
389
+
332
390
  // Find thermal stream or use first
333
391
  Stream thermalStream = null;
334
392
  for (Stream stream : streams) {
@@ -531,6 +589,8 @@ public class FlirSdkManager {
531
589
  Identity identity = discoveredCamera.getIdentity();
532
590
  Log.d(TAG, "Device found: " + identity.deviceId);
533
591
 
592
+ discoveredCameras.put(identity.deviceId, discoveredCamera);
593
+
534
594
  synchronized (discoveredDevices) {
535
595
  boolean exists = false;
536
596
  for (Identity d : discoveredDevices) {
@@ -553,6 +613,7 @@ public class FlirSdkManager {
553
613
  @Override
554
614
  public void onCameraLost(Identity identity) {
555
615
  Log.d(TAG, "Device lost: " + identity.deviceId);
616
+ discoveredCameras.remove(identity.deviceId);
556
617
  synchronized (discoveredDevices) {
557
618
  discoveredDevices.removeIf(d -> d.deviceId.equals(identity.deviceId));
558
619
  }
@@ -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.12",
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",