ilabs-flir 2.1.32 → 2.1.33
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.
|
@@ -37,25 +37,28 @@ import java.util.concurrent.Executors;
|
|
|
37
37
|
*/
|
|
38
38
|
public class FlirSdkManager {
|
|
39
39
|
private static final String TAG = "FlirSdkManager";
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
// Singleton instance
|
|
42
42
|
private static FlirSdkManager instance;
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
// Core components
|
|
45
45
|
private final Context context;
|
|
46
|
-
// Use bounded thread pool to prevent thread explosion during rapid frame
|
|
46
|
+
// Use bounded thread pool to prevent thread explosion during rapid frame
|
|
47
|
+
// processing
|
|
47
48
|
private final Executor executor = Executors.newFixedThreadPool(2);
|
|
48
49
|
// Single-threaded executor for frame processing to ensure ordered processing
|
|
49
50
|
private final Executor frameExecutor = Executors.newSingleThreadExecutor();
|
|
50
|
-
// Battery poller scheduler - polls battery level & charging state periodically
|
|
51
|
-
|
|
51
|
+
// Battery poller scheduler - polls battery level & charging state periodically
|
|
52
|
+
// if supported
|
|
53
|
+
private final java.util.concurrent.ScheduledExecutorService batteryPoller = java.util.concurrent.Executors
|
|
54
|
+
.newSingleThreadScheduledExecutor();
|
|
52
55
|
private volatile int lastPolledBatteryLevel = -1;
|
|
53
56
|
private volatile boolean lastPolledCharging = false;
|
|
54
57
|
// Frame processing guard - skip frames if still processing previous one
|
|
55
58
|
private volatile boolean isProcessingFrame = false;
|
|
56
59
|
private long lastFrameProcessedMs = 0;
|
|
57
60
|
private static final long MIN_FRAME_INTERVAL_MS = 50; // Max ~20 FPS frame processing
|
|
58
|
-
|
|
61
|
+
|
|
59
62
|
// State
|
|
60
63
|
private boolean isInitialized = false;
|
|
61
64
|
private boolean isScanning = false;
|
|
@@ -63,30 +66,37 @@ public class FlirSdkManager {
|
|
|
63
66
|
private ThermalStreamer streamer;
|
|
64
67
|
private Stream activeStream;
|
|
65
68
|
private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
|
|
66
|
-
// When true, prefer getting SDK-provided rotated frames instead of rotating
|
|
69
|
+
// When true, prefer getting SDK-provided rotated frames instead of rotating
|
|
70
|
+
// ourselves
|
|
67
71
|
private volatile boolean preferSdkRotation = false;
|
|
68
|
-
|
|
72
|
+
|
|
69
73
|
// Listener
|
|
70
74
|
private Listener listener;
|
|
71
|
-
|
|
75
|
+
|
|
72
76
|
/**
|
|
73
77
|
* Listener interface for SDK events
|
|
74
78
|
*/
|
|
75
79
|
public interface Listener {
|
|
76
80
|
void onDeviceFound(Identity identity);
|
|
81
|
+
|
|
77
82
|
void onDeviceListUpdated(List<Identity> devices);
|
|
83
|
+
|
|
78
84
|
void onConnected(Identity identity);
|
|
85
|
+
|
|
79
86
|
void onDisconnected();
|
|
87
|
+
|
|
80
88
|
void onFrame(Bitmap bitmap);
|
|
89
|
+
|
|
81
90
|
void onError(String message);
|
|
91
|
+
|
|
82
92
|
void onBatteryUpdated(int level, boolean isCharging);
|
|
83
93
|
}
|
|
84
|
-
|
|
94
|
+
|
|
85
95
|
// Private constructor for singleton
|
|
86
96
|
private FlirSdkManager(Context context) {
|
|
87
97
|
this.context = context.getApplicationContext();
|
|
88
98
|
}
|
|
89
|
-
|
|
99
|
+
|
|
90
100
|
/**
|
|
91
101
|
* Get singleton instance
|
|
92
102
|
*/
|
|
@@ -96,7 +106,7 @@ public class FlirSdkManager {
|
|
|
96
106
|
}
|
|
97
107
|
return instance;
|
|
98
108
|
}
|
|
99
|
-
|
|
109
|
+
|
|
100
110
|
/**
|
|
101
111
|
* Set listener for SDK events
|
|
102
112
|
*/
|
|
@@ -109,16 +119,25 @@ public class FlirSdkManager {
|
|
|
109
119
|
// Try to ask SDK streamer to provide rotated images if possible
|
|
110
120
|
if (streamer != null) {
|
|
111
121
|
try {
|
|
112
|
-
// Try common method names via reflection to avoid hard dependency on exact API
|
|
122
|
+
// Try common method names via reflection to avoid hard dependency on exact API
|
|
123
|
+
// signature
|
|
113
124
|
Object obj = streamer;
|
|
114
125
|
java.lang.reflect.Method m = null;
|
|
115
|
-
try {
|
|
126
|
+
try {
|
|
127
|
+
m = obj.getClass().getMethod("setImageRotation", int.class);
|
|
128
|
+
} catch (Throwable ignored) {
|
|
129
|
+
}
|
|
116
130
|
if (m == null) {
|
|
117
|
-
try {
|
|
131
|
+
try {
|
|
132
|
+
m = obj.getClass().getMethod("setRotation", int.class);
|
|
133
|
+
} catch (Throwable ignored) {
|
|
134
|
+
}
|
|
118
135
|
}
|
|
119
136
|
if (m != null) {
|
|
120
|
-
// If caller asked SDK to rotate, choose 0 = 'auto' or prefer flag; here we
|
|
121
|
-
|
|
137
|
+
// If caller asked SDK to rotate, choose 0 = 'auto' or prefer flag; here we
|
|
138
|
+
// request SDK to respect device orientation
|
|
139
|
+
int degrees = prefer ? 0 : 0; // SDK-specific - for now, 0 requests orientation-respected frames if
|
|
140
|
+
// method interprets so
|
|
122
141
|
m.invoke(obj, degrees);
|
|
123
142
|
Log.d(TAG, "setPreferSdkRotation: requested SDK rotation via reflection");
|
|
124
143
|
} else {
|
|
@@ -130,8 +149,10 @@ public class FlirSdkManager {
|
|
|
130
149
|
}
|
|
131
150
|
}
|
|
132
151
|
|
|
133
|
-
public boolean isPreferSdkRotation() {
|
|
134
|
-
|
|
152
|
+
public boolean isPreferSdkRotation() {
|
|
153
|
+
return preferSdkRotation;
|
|
154
|
+
}
|
|
155
|
+
|
|
135
156
|
/**
|
|
136
157
|
* Initialize the FLIR Thermal SDK
|
|
137
158
|
*/
|
|
@@ -140,8 +161,18 @@ public class FlirSdkManager {
|
|
|
140
161
|
Log.d(TAG, "Already initialized");
|
|
141
162
|
return;
|
|
142
163
|
}
|
|
143
|
-
|
|
164
|
+
|
|
144
165
|
try {
|
|
166
|
+
// Explicitly load native library to ensure it's available and initialized
|
|
167
|
+
// This can help resolve issues where the automatic loading fails or happens out
|
|
168
|
+
// of order
|
|
169
|
+
try {
|
|
170
|
+
System.loadLibrary("atlas_native");
|
|
171
|
+
Log.d(TAG, "Manually loaded atlas_native library");
|
|
172
|
+
} catch (Throwable t) {
|
|
173
|
+
Log.w(TAG, "Manual load of atlas_native failed: " + t.getMessage());
|
|
174
|
+
}
|
|
175
|
+
|
|
145
176
|
ThermalSdkAndroid.init(context);
|
|
146
177
|
isInitialized = true;
|
|
147
178
|
Log.d(TAG, "SDK initialized successfully");
|
|
@@ -150,14 +181,14 @@ public class FlirSdkManager {
|
|
|
150
181
|
notifyError("SDK initialization failed: " + e.getMessage());
|
|
151
182
|
}
|
|
152
183
|
}
|
|
153
|
-
|
|
184
|
+
|
|
154
185
|
/**
|
|
155
186
|
* Check if SDK is initialized
|
|
156
187
|
*/
|
|
157
188
|
public boolean isInitialized() {
|
|
158
189
|
return isInitialized;
|
|
159
190
|
}
|
|
160
|
-
|
|
191
|
+
|
|
161
192
|
/**
|
|
162
193
|
* Start scanning for all device types (USB, Network, Emulator)
|
|
163
194
|
* Returns ALL devices - no filtering
|
|
@@ -168,31 +199,30 @@ public class FlirSdkManager {
|
|
|
168
199
|
notifyError("SDK not initialized");
|
|
169
200
|
return;
|
|
170
201
|
}
|
|
171
|
-
|
|
202
|
+
|
|
172
203
|
if (isScanning) {
|
|
173
204
|
Log.d(TAG, "Already scanning");
|
|
174
205
|
return;
|
|
175
206
|
}
|
|
176
|
-
|
|
207
|
+
|
|
177
208
|
isScanning = true;
|
|
178
209
|
discoveredDevices.clear();
|
|
179
|
-
|
|
210
|
+
|
|
180
211
|
Log.d(TAG, "Starting discovery for EMULATOR, NETWORK, USB...");
|
|
181
|
-
|
|
212
|
+
|
|
182
213
|
try {
|
|
183
214
|
DiscoveryFactory.getInstance().scan(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
);
|
|
215
|
+
discoveryListener,
|
|
216
|
+
CommunicationInterface.EMULATOR,
|
|
217
|
+
CommunicationInterface.NETWORK,
|
|
218
|
+
CommunicationInterface.USB);
|
|
189
219
|
} catch (Exception e) {
|
|
190
220
|
Log.e(TAG, "Failed to start scan", e);
|
|
191
221
|
isScanning = false;
|
|
192
222
|
notifyError("Scan failed: " + e.getMessage());
|
|
193
223
|
}
|
|
194
224
|
}
|
|
195
|
-
|
|
225
|
+
|
|
196
226
|
/**
|
|
197
227
|
* Stop scanning for devices
|
|
198
228
|
*/
|
|
@@ -200,28 +230,27 @@ public class FlirSdkManager {
|
|
|
200
230
|
if (!isScanning) {
|
|
201
231
|
return;
|
|
202
232
|
}
|
|
203
|
-
|
|
233
|
+
|
|
204
234
|
try {
|
|
205
235
|
DiscoveryFactory.getInstance().stop(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
);
|
|
236
|
+
CommunicationInterface.EMULATOR,
|
|
237
|
+
CommunicationInterface.NETWORK,
|
|
238
|
+
CommunicationInterface.USB);
|
|
210
239
|
} catch (Exception e) {
|
|
211
240
|
Log.e(TAG, "Failed to stop scan", e);
|
|
212
241
|
}
|
|
213
|
-
|
|
242
|
+
|
|
214
243
|
isScanning = false;
|
|
215
244
|
Log.d(TAG, "Discovery stopped");
|
|
216
245
|
}
|
|
217
|
-
|
|
246
|
+
|
|
218
247
|
/**
|
|
219
248
|
* Get list of discovered devices
|
|
220
249
|
*/
|
|
221
250
|
public List<Identity> getDiscoveredDevices() {
|
|
222
251
|
return new ArrayList<>(discoveredDevices);
|
|
223
252
|
}
|
|
224
|
-
|
|
253
|
+
|
|
225
254
|
/**
|
|
226
255
|
* Connect to a device
|
|
227
256
|
*/
|
|
@@ -230,22 +259,22 @@ public class FlirSdkManager {
|
|
|
230
259
|
notifyError("Invalid identity");
|
|
231
260
|
return;
|
|
232
261
|
}
|
|
233
|
-
|
|
262
|
+
|
|
234
263
|
// Disconnect if already connected
|
|
235
264
|
if (camera != null) {
|
|
236
265
|
disconnect();
|
|
237
266
|
}
|
|
238
|
-
|
|
267
|
+
|
|
239
268
|
Log.d(TAG, "Connecting to: " + identity.deviceId);
|
|
240
|
-
|
|
269
|
+
|
|
241
270
|
// Run connection on background thread since it's blocking
|
|
242
271
|
executor.execute(() -> {
|
|
243
272
|
try {
|
|
244
273
|
camera = new Camera();
|
|
245
274
|
camera.connect(identity, connectionStatusListener, new ConnectParameters());
|
|
246
|
-
|
|
275
|
+
|
|
247
276
|
Log.d(TAG, "Connected to camera");
|
|
248
|
-
|
|
277
|
+
|
|
249
278
|
if (listener != null) {
|
|
250
279
|
listener.onConnected(identity);
|
|
251
280
|
}
|
|
@@ -258,13 +287,13 @@ public class FlirSdkManager {
|
|
|
258
287
|
}
|
|
259
288
|
});
|
|
260
289
|
}
|
|
261
|
-
|
|
290
|
+
|
|
262
291
|
/**
|
|
263
292
|
* Disconnect from current device
|
|
264
293
|
*/
|
|
265
294
|
public void disconnect() {
|
|
266
295
|
stopStream();
|
|
267
|
-
|
|
296
|
+
|
|
268
297
|
if (camera != null) {
|
|
269
298
|
try {
|
|
270
299
|
camera.disconnect();
|
|
@@ -275,21 +304,21 @@ public class FlirSdkManager {
|
|
|
275
304
|
}
|
|
276
305
|
// stop battery poller
|
|
277
306
|
stopBatteryPoller();
|
|
278
|
-
|
|
307
|
+
|
|
279
308
|
if (listener != null) {
|
|
280
309
|
listener.onDisconnected();
|
|
281
310
|
}
|
|
282
|
-
|
|
311
|
+
|
|
283
312
|
Log.d(TAG, "Disconnected");
|
|
284
313
|
}
|
|
285
|
-
|
|
314
|
+
|
|
286
315
|
/**
|
|
287
316
|
* Check if connected
|
|
288
317
|
*/
|
|
289
318
|
public boolean isConnected() {
|
|
290
319
|
return camera != null;
|
|
291
320
|
}
|
|
292
|
-
|
|
321
|
+
|
|
293
322
|
/**
|
|
294
323
|
* Start streaming from connected device
|
|
295
324
|
*/
|
|
@@ -298,7 +327,7 @@ public class FlirSdkManager {
|
|
|
298
327
|
notifyError("Not connected");
|
|
299
328
|
return;
|
|
300
329
|
}
|
|
301
|
-
|
|
330
|
+
|
|
302
331
|
executor.execute(() -> {
|
|
303
332
|
try {
|
|
304
333
|
// Get available streams
|
|
@@ -307,7 +336,7 @@ public class FlirSdkManager {
|
|
|
307
336
|
notifyError("No streams available");
|
|
308
337
|
return;
|
|
309
338
|
}
|
|
310
|
-
|
|
339
|
+
|
|
311
340
|
// Find thermal stream
|
|
312
341
|
Stream thermalStream = null;
|
|
313
342
|
for (Stream stream : streams) {
|
|
@@ -316,68 +345,67 @@ public class FlirSdkManager {
|
|
|
316
345
|
break;
|
|
317
346
|
}
|
|
318
347
|
}
|
|
319
|
-
|
|
348
|
+
|
|
320
349
|
if (thermalStream == null) {
|
|
321
350
|
thermalStream = streams.get(0);
|
|
322
351
|
}
|
|
323
|
-
|
|
352
|
+
|
|
324
353
|
activeStream = thermalStream;
|
|
325
354
|
streamer = new ThermalStreamer(thermalStream);
|
|
326
|
-
|
|
355
|
+
|
|
327
356
|
// Start receiving frames using OnReceived and OnRemoteError
|
|
328
357
|
thermalStream.start(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
358
|
+
(OnReceived<Void>) v -> {
|
|
359
|
+
// FRAME DROP GUARD: Skip frame if still processing previous one
|
|
360
|
+
// This prevents thread buildup and ensures smooth frame flow
|
|
361
|
+
long now = System.currentTimeMillis();
|
|
362
|
+
if (isProcessingFrame || (now - lastFrameProcessedMs < MIN_FRAME_INTERVAL_MS)) {
|
|
363
|
+
// Drop frame - processing is behind or too soon since last frame
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Mark processing start before queuing task
|
|
368
|
+
isProcessingFrame = true;
|
|
369
|
+
|
|
370
|
+
// Use single-threaded frameExecutor to ensure ordered frame processing
|
|
371
|
+
frameExecutor.execute(() -> {
|
|
372
|
+
try {
|
|
373
|
+
if (streamer != null) {
|
|
374
|
+
streamer.update();
|
|
375
|
+
|
|
376
|
+
// Get ImageBuffer and convert to Bitmap
|
|
377
|
+
ImageBuffer imageBuffer = streamer.getImage();
|
|
378
|
+
if (imageBuffer != null && listener != null) {
|
|
379
|
+
BitmapAndroid bitmapAndroid = BitmapAndroid.createBitmap(imageBuffer);
|
|
380
|
+
Bitmap bitmap = bitmapAndroid.getBitMap();
|
|
381
|
+
if (bitmap != null) {
|
|
382
|
+
listener.onFrame(bitmap);
|
|
383
|
+
}
|
|
354
384
|
}
|
|
355
385
|
}
|
|
386
|
+
} catch (Exception e) {
|
|
387
|
+
Log.e(TAG, "Error processing frame", e);
|
|
388
|
+
} finally {
|
|
389
|
+
// Reset frame processing guard to allow next frame
|
|
390
|
+
lastFrameProcessedMs = System.currentTimeMillis();
|
|
391
|
+
isProcessingFrame = false;
|
|
356
392
|
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
isProcessingFrame = false;
|
|
363
|
-
}
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
error -> {
|
|
396
|
+
Log.e(TAG, "Stream error: " + error);
|
|
397
|
+
notifyError("Stream error: " + error);
|
|
364
398
|
});
|
|
365
|
-
|
|
366
|
-
error -> {
|
|
367
|
-
Log.e(TAG, "Stream error: " + error);
|
|
368
|
-
notifyError("Stream error: " + error);
|
|
369
|
-
}
|
|
370
|
-
);
|
|
371
|
-
|
|
399
|
+
|
|
372
400
|
Log.d(TAG, "Streaming started");
|
|
373
|
-
|
|
401
|
+
|
|
374
402
|
} catch (Exception e) {
|
|
375
403
|
Log.e(TAG, "Failed to start stream", e);
|
|
376
404
|
notifyError("Stream failed: " + e.getMessage());
|
|
377
405
|
}
|
|
378
406
|
});
|
|
379
407
|
}
|
|
380
|
-
|
|
408
|
+
|
|
381
409
|
/**
|
|
382
410
|
* Stop streaming
|
|
383
411
|
*/
|
|
@@ -390,19 +418,20 @@ public class FlirSdkManager {
|
|
|
390
418
|
}
|
|
391
419
|
activeStream = null;
|
|
392
420
|
}
|
|
393
|
-
|
|
421
|
+
|
|
394
422
|
streamer = null;
|
|
395
|
-
|
|
423
|
+
|
|
396
424
|
// Reset frame processing state
|
|
397
425
|
isProcessingFrame = false;
|
|
398
426
|
lastFrameProcessedMs = 0;
|
|
399
|
-
|
|
427
|
+
|
|
400
428
|
Log.d(TAG, "Streaming stopped");
|
|
401
429
|
}
|
|
402
|
-
|
|
430
|
+
|
|
403
431
|
/**
|
|
404
432
|
* Get temperature at a specific point in the image
|
|
405
433
|
* Queries the SDK directly - simple and no locks needed
|
|
434
|
+
*
|
|
406
435
|
* @param x X coordinate (0 to image width-1)
|
|
407
436
|
* @param y Y coordinate (0 to image height-1)
|
|
408
437
|
* @return Temperature in Celsius, or Double.NaN if not available
|
|
@@ -411,17 +440,17 @@ public class FlirSdkManager {
|
|
|
411
440
|
if (streamer == null) {
|
|
412
441
|
return Double.NaN;
|
|
413
442
|
}
|
|
414
|
-
|
|
415
|
-
final double[] result = {Double.NaN};
|
|
443
|
+
|
|
444
|
+
final double[] result = { Double.NaN };
|
|
416
445
|
try {
|
|
417
446
|
streamer.withThermalImage(thermalImage -> {
|
|
418
447
|
try {
|
|
419
448
|
int imgWidth = thermalImage.getWidth();
|
|
420
449
|
int imgHeight = thermalImage.getHeight();
|
|
421
|
-
|
|
450
|
+
|
|
422
451
|
int clampedX = Math.max(0, Math.min(imgWidth - 1, x));
|
|
423
452
|
int clampedY = Math.max(0, Math.min(imgHeight - 1, y));
|
|
424
|
-
|
|
453
|
+
|
|
425
454
|
ThermalValue value = thermalImage.getValueAt(new Point(clampedX, clampedY));
|
|
426
455
|
if (value != null) {
|
|
427
456
|
result[0] = value.asCelsius().value;
|
|
@@ -433,12 +462,13 @@ public class FlirSdkManager {
|
|
|
433
462
|
} catch (Exception e) {
|
|
434
463
|
Log.w(TAG, "Temperature query failed", e);
|
|
435
464
|
}
|
|
436
|
-
|
|
465
|
+
|
|
437
466
|
return result[0];
|
|
438
467
|
}
|
|
439
|
-
|
|
468
|
+
|
|
440
469
|
/**
|
|
441
470
|
* Get temperature at normalized coordinates (0.0 to 1.0)
|
|
471
|
+
*
|
|
442
472
|
* @param normalizedX X coordinate (0.0 to 1.0)
|
|
443
473
|
* @param normalizedY Y coordinate (0.0 to 1.0)
|
|
444
474
|
* @return Temperature in Celsius, or Double.NaN if not available
|
|
@@ -447,20 +477,20 @@ public class FlirSdkManager {
|
|
|
447
477
|
if (streamer == null) {
|
|
448
478
|
return Double.NaN;
|
|
449
479
|
}
|
|
450
|
-
|
|
451
|
-
final double[] result = {Double.NaN};
|
|
480
|
+
|
|
481
|
+
final double[] result = { Double.NaN };
|
|
452
482
|
try {
|
|
453
483
|
streamer.withThermalImage(thermalImage -> {
|
|
454
484
|
try {
|
|
455
485
|
int width = thermalImage.getWidth();
|
|
456
486
|
int height = thermalImage.getHeight();
|
|
457
|
-
|
|
487
|
+
|
|
458
488
|
int x = (int) (normalizedX * (width - 1));
|
|
459
489
|
int y = (int) (normalizedY * (height - 1));
|
|
460
|
-
|
|
490
|
+
|
|
461
491
|
x = Math.max(0, Math.min(width - 1, x));
|
|
462
492
|
y = Math.max(0, Math.min(height - 1, y));
|
|
463
|
-
|
|
493
|
+
|
|
464
494
|
ThermalValue value = thermalImage.getValueAt(new Point(x, y));
|
|
465
495
|
if (value != null) {
|
|
466
496
|
result[0] = value.asCelsius().value;
|
|
@@ -472,10 +502,10 @@ public class FlirSdkManager {
|
|
|
472
502
|
} catch (Exception e) {
|
|
473
503
|
Log.w(TAG, "Temperature query failed", e);
|
|
474
504
|
}
|
|
475
|
-
|
|
505
|
+
|
|
476
506
|
return result[0];
|
|
477
507
|
}
|
|
478
|
-
|
|
508
|
+
|
|
479
509
|
/**
|
|
480
510
|
* Set palette for thermal image rendering
|
|
481
511
|
*/
|
|
@@ -484,7 +514,7 @@ public class FlirSdkManager {
|
|
|
484
514
|
Log.w(TAG, "No active streamer");
|
|
485
515
|
return;
|
|
486
516
|
}
|
|
487
|
-
|
|
517
|
+
|
|
488
518
|
executor.execute(() -> {
|
|
489
519
|
try {
|
|
490
520
|
Palette palette = findPalette(paletteName);
|
|
@@ -499,7 +529,7 @@ public class FlirSdkManager {
|
|
|
499
529
|
}
|
|
500
530
|
});
|
|
501
531
|
}
|
|
502
|
-
|
|
532
|
+
|
|
503
533
|
/**
|
|
504
534
|
* Get list of available palettes
|
|
505
535
|
*/
|
|
@@ -517,18 +547,22 @@ public class FlirSdkManager {
|
|
|
517
547
|
}
|
|
518
548
|
|
|
519
549
|
/**
|
|
520
|
-
* Best-effort: Fetch battery level from connected camera if SDK exposes battery
|
|
550
|
+
* Best-effort: Fetch battery level from connected camera if SDK exposes battery
|
|
551
|
+
* APIs
|
|
521
552
|
* Returns -1 if unavailable
|
|
522
553
|
*/
|
|
523
554
|
public int getBatteryLevel() {
|
|
524
|
-
if (camera == null)
|
|
555
|
+
if (camera == null)
|
|
556
|
+
return -1;
|
|
525
557
|
try {
|
|
526
558
|
// Common SDK methods to try
|
|
527
559
|
try {
|
|
528
560
|
java.lang.reflect.Method m = camera.getClass().getMethod("getBatteryLevel");
|
|
529
561
|
Object r = m.invoke(camera);
|
|
530
|
-
if (r instanceof Number)
|
|
531
|
-
|
|
562
|
+
if (r instanceof Number)
|
|
563
|
+
return ((Number) r).intValue();
|
|
564
|
+
} catch (Throwable ignored) {
|
|
565
|
+
}
|
|
532
566
|
|
|
533
567
|
try {
|
|
534
568
|
java.lang.reflect.Method m = camera.getClass().getMethod("getBattery");
|
|
@@ -537,10 +571,13 @@ public class FlirSdkManager {
|
|
|
537
571
|
try {
|
|
538
572
|
java.lang.reflect.Method levelMethod = batt.getClass().getMethod("getLevel");
|
|
539
573
|
Object lv = levelMethod.invoke(batt);
|
|
540
|
-
if (lv instanceof Number)
|
|
541
|
-
|
|
574
|
+
if (lv instanceof Number)
|
|
575
|
+
return ((Number) lv).intValue();
|
|
576
|
+
} catch (Throwable ignored) {
|
|
577
|
+
}
|
|
542
578
|
}
|
|
543
|
-
} catch (Throwable ignored) {
|
|
579
|
+
} catch (Throwable ignored) {
|
|
580
|
+
}
|
|
544
581
|
} catch (Throwable t) {
|
|
545
582
|
Log.w(TAG, "Error querying battery level", t);
|
|
546
583
|
}
|
|
@@ -552,13 +589,16 @@ public class FlirSdkManager {
|
|
|
552
589
|
* Returns false if unknown
|
|
553
590
|
*/
|
|
554
591
|
public boolean isBatteryCharging() {
|
|
555
|
-
if (camera == null)
|
|
592
|
+
if (camera == null)
|
|
593
|
+
return false;
|
|
556
594
|
try {
|
|
557
595
|
try {
|
|
558
596
|
java.lang.reflect.Method m = camera.getClass().getMethod("isCharging");
|
|
559
597
|
Object r = m.invoke(camera);
|
|
560
|
-
if (r instanceof Boolean)
|
|
561
|
-
|
|
598
|
+
if (r instanceof Boolean)
|
|
599
|
+
return (Boolean) r;
|
|
600
|
+
} catch (Throwable ignored) {
|
|
601
|
+
}
|
|
562
602
|
|
|
563
603
|
try {
|
|
564
604
|
java.lang.reflect.Method m = camera.getClass().getMethod("getBattery");
|
|
@@ -567,16 +607,19 @@ public class FlirSdkManager {
|
|
|
567
607
|
try {
|
|
568
608
|
java.lang.reflect.Method isCh = batt.getClass().getMethod("isCharging");
|
|
569
609
|
Object cv = isCh.invoke(batt);
|
|
570
|
-
if (cv instanceof Boolean)
|
|
571
|
-
|
|
610
|
+
if (cv instanceof Boolean)
|
|
611
|
+
return (Boolean) cv;
|
|
612
|
+
} catch (Throwable ignored) {
|
|
613
|
+
}
|
|
572
614
|
}
|
|
573
|
-
} catch (Throwable ignored) {
|
|
615
|
+
} catch (Throwable ignored) {
|
|
616
|
+
}
|
|
574
617
|
} catch (Throwable t) {
|
|
575
618
|
Log.w(TAG, "Error querying battery charging state", t);
|
|
576
619
|
}
|
|
577
620
|
return false;
|
|
578
621
|
}
|
|
579
|
-
|
|
622
|
+
|
|
580
623
|
// Find palette by name
|
|
581
624
|
private Palette findPalette(String name) {
|
|
582
625
|
try {
|
|
@@ -595,15 +638,15 @@ public class FlirSdkManager {
|
|
|
595
638
|
}
|
|
596
639
|
return null;
|
|
597
640
|
}
|
|
598
|
-
|
|
641
|
+
|
|
599
642
|
// Discovery listener - no filtering, returns all devices
|
|
600
643
|
private final DiscoveryEventListener discoveryListener = new DiscoveryEventListener() {
|
|
601
644
|
@Override
|
|
602
645
|
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
603
646
|
Identity identity = discoveredCamera.getIdentity();
|
|
604
|
-
Log.d(TAG, "Device found: " + identity.deviceId +
|
|
605
|
-
|
|
606
|
-
|
|
647
|
+
Log.d(TAG, "Device found: " + identity.deviceId +
|
|
648
|
+
" type=" + identity.communicationInterface);
|
|
649
|
+
|
|
607
650
|
// Add to list if not already present
|
|
608
651
|
synchronized (discoveredDevices) {
|
|
609
652
|
boolean exists = false;
|
|
@@ -617,58 +660,58 @@ public class FlirSdkManager {
|
|
|
617
660
|
discoveredDevices.add(identity);
|
|
618
661
|
}
|
|
619
662
|
}
|
|
620
|
-
|
|
663
|
+
|
|
621
664
|
if (listener != null) {
|
|
622
665
|
listener.onDeviceFound(identity);
|
|
623
666
|
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
624
667
|
}
|
|
625
668
|
}
|
|
626
|
-
|
|
669
|
+
|
|
627
670
|
@Override
|
|
628
671
|
public void onCameraLost(Identity identity) {
|
|
629
672
|
Log.d(TAG, "Device lost: " + identity.deviceId);
|
|
630
|
-
|
|
673
|
+
|
|
631
674
|
synchronized (discoveredDevices) {
|
|
632
675
|
discoveredDevices.removeIf(d -> d.deviceId.equals(identity.deviceId));
|
|
633
676
|
}
|
|
634
|
-
|
|
677
|
+
|
|
635
678
|
if (listener != null) {
|
|
636
679
|
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
637
680
|
}
|
|
638
681
|
}
|
|
639
|
-
|
|
682
|
+
|
|
640
683
|
@Override
|
|
641
684
|
public void onDiscoveryError(CommunicationInterface iface, ErrorCode error) {
|
|
642
685
|
Log.e(TAG, "Discovery error: " + iface + " - " + error);
|
|
643
686
|
notifyError("Discovery error: " + error);
|
|
644
687
|
}
|
|
645
|
-
|
|
688
|
+
|
|
646
689
|
@Override
|
|
647
690
|
public void onDiscoveryFinished(CommunicationInterface iface) {
|
|
648
691
|
Log.d(TAG, "Discovery finished for: " + iface);
|
|
649
692
|
}
|
|
650
693
|
};
|
|
651
|
-
|
|
694
|
+
|
|
652
695
|
// Connection status listener
|
|
653
696
|
private final ConnectionStatusListener connectionStatusListener = new ConnectionStatusListener() {
|
|
654
697
|
@Override
|
|
655
698
|
public void onDisconnected(ErrorCode error) {
|
|
656
699
|
Log.d(TAG, "Disconnected: " + (error != null ? error : "clean"));
|
|
657
700
|
camera = null;
|
|
658
|
-
|
|
701
|
+
|
|
659
702
|
if (listener != null) {
|
|
660
703
|
listener.onDisconnected();
|
|
661
704
|
}
|
|
662
705
|
}
|
|
663
706
|
};
|
|
664
|
-
|
|
707
|
+
|
|
665
708
|
// Helper to notify errors
|
|
666
709
|
private void notifyError(String message) {
|
|
667
710
|
if (listener != null) {
|
|
668
711
|
listener.onError(message);
|
|
669
712
|
}
|
|
670
713
|
}
|
|
671
|
-
|
|
714
|
+
|
|
672
715
|
/**
|
|
673
716
|
* Cleanup resources
|
|
674
717
|
*/
|
|
@@ -682,12 +725,14 @@ public class FlirSdkManager {
|
|
|
682
725
|
}
|
|
683
726
|
|
|
684
727
|
/**
|
|
685
|
-
* Start a background poller to periodically check battery state and notify
|
|
728
|
+
* Start a background poller to periodically check battery state and notify
|
|
729
|
+
* listener
|
|
686
730
|
*/
|
|
687
731
|
private void startBatteryPoller() {
|
|
688
732
|
try {
|
|
689
733
|
batteryPoller.scheduleAtFixedRate(() -> {
|
|
690
|
-
if (camera == null)
|
|
734
|
+
if (camera == null)
|
|
735
|
+
return;
|
|
691
736
|
try {
|
|
692
737
|
int level = getBatteryLevel();
|
|
693
738
|
boolean charging = isBatteryCharging();
|
|
@@ -695,7 +740,10 @@ public class FlirSdkManager {
|
|
|
695
740
|
lastPolledBatteryLevel = level;
|
|
696
741
|
lastPolledCharging = charging;
|
|
697
742
|
if (listener != null) {
|
|
698
|
-
try {
|
|
743
|
+
try {
|
|
744
|
+
listener.onBatteryUpdated(level, charging);
|
|
745
|
+
} catch (Throwable t) {
|
|
746
|
+
}
|
|
699
747
|
}
|
|
700
748
|
}
|
|
701
749
|
} catch (Throwable t) {
|
|
@@ -713,6 +761,7 @@ public class FlirSdkManager {
|
|
|
713
761
|
private void stopBatteryPoller() {
|
|
714
762
|
try {
|
|
715
763
|
batteryPoller.shutdownNow();
|
|
716
|
-
} catch (Throwable ignored) {
|
|
764
|
+
} catch (Throwable ignored) {
|
|
765
|
+
}
|
|
717
766
|
}
|
|
718
767
|
}
|