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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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 =
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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, "
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|