ilabs-flir 1.0.4 → 1.0.5
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/android/Flir/build.gradle.kts +14 -27
- package/android/Flir/libs/androidsdk-release.aar +0 -0
- package/android/Flir/libs/thermalsdk-release.aar +0 -0
- package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +23 -44
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +32 -0
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +35 -194
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +410 -1161
- package/package.json +1 -1
- package/src/index.d.ts +21 -1
- package/android/Flir/libs/flir-stubs.jar +0 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCode.java +0 -13
- package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCodeException.java +0 -14
- package/android/Flir/src/main/java/com/flir/thermalsdk/ThermalSdkAndroid.java +0 -16
- package/android/Flir/src/main/java/com/flir/thermalsdk/androidsdk/image/BitmapAndroid.java +0 -20
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/ImageBuffer.java +0 -11
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/JavaImageBuffer.java +0 -35
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/Palette.java +0 -15
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/PaletteManager.java +0 -16
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/Point.java +0 -11
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalImage.java +0 -23
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalValue.java +0 -9
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/Camera.java +0 -26
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/CameraType.java +0 -8
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/CommunicationInterface.java +0 -16
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/ConnectParameters.java +0 -16
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/Identity.java +0 -23
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/IpSettings.java +0 -9
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/RemoteControl.java +0 -16
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/connectivity/ConnectionStatusListener.java +0 -7
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/discovery/DiscoveryEventListener.java +0 -14
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/discovery/DiscoveryFactory.java +0 -33
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnReceived.java +0 -5
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnRemoteError.java +0 -7
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/Stream.java +0 -8
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/ThermalStreamer.java +0 -28
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/VisualStreamer.java +0 -18
|
@@ -5,10 +5,29 @@ import android.os.Handler;
|
|
|
5
5
|
import android.os.Looper;
|
|
6
6
|
import android.util.Log;
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
8
|
+
import com.flir.thermalsdk.ErrorCode;
|
|
9
|
+
import com.flir.thermalsdk.androidsdk.ThermalSdkAndroid;
|
|
10
|
+
import com.flir.thermalsdk.androidsdk.image.BitmapAndroid;
|
|
11
|
+
import com.flir.thermalsdk.image.ImageBuffer;
|
|
12
|
+
import com.flir.thermalsdk.image.JavaImageBuffer;
|
|
13
|
+
import com.flir.thermalsdk.image.Palette;
|
|
14
|
+
import com.flir.thermalsdk.image.PaletteManager;
|
|
15
|
+
import com.flir.thermalsdk.image.Point;
|
|
16
|
+
import com.flir.thermalsdk.image.ThermalImage;
|
|
17
|
+
import com.flir.thermalsdk.image.ThermalValue;
|
|
18
|
+
import com.flir.thermalsdk.live.Camera;
|
|
19
|
+
import com.flir.thermalsdk.live.CommunicationInterface;
|
|
20
|
+
import com.flir.thermalsdk.live.ConnectParameters;
|
|
21
|
+
import com.flir.thermalsdk.live.Identity;
|
|
22
|
+
import com.flir.thermalsdk.live.connectivity.ConnectionStatusListener;
|
|
23
|
+
import com.flir.thermalsdk.live.discovery.DiscoveredCamera;
|
|
24
|
+
import com.flir.thermalsdk.live.discovery.DiscoveryEventListener;
|
|
25
|
+
import com.flir.thermalsdk.live.discovery.DiscoveryFactory;
|
|
26
|
+
import com.flir.thermalsdk.live.remote.OnReceived;
|
|
27
|
+
import com.flir.thermalsdk.live.remote.OnRemoteError;
|
|
28
|
+
import com.flir.thermalsdk.live.streaming.Stream;
|
|
29
|
+
import com.flir.thermalsdk.live.streaming.ThermalStreamer;
|
|
30
|
+
|
|
12
31
|
import java.util.ArrayList;
|
|
13
32
|
import java.util.List;
|
|
14
33
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
@@ -17,64 +36,28 @@ import java.util.concurrent.ScheduledExecutorService;
|
|
|
17
36
|
import java.util.concurrent.ScheduledFuture;
|
|
18
37
|
import java.util.concurrent.TimeUnit;
|
|
19
38
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
20
|
-
import java.util.zip.ZipEntry;
|
|
21
|
-
import java.util.zip.ZipFile;
|
|
22
|
-
|
|
23
|
-
import dalvik.system.DexClassLoader;
|
|
24
39
|
|
|
25
40
|
/**
|
|
26
|
-
* FLIR SDK Manager - Handles device discovery, connection, and streaming
|
|
27
|
-
*
|
|
28
|
-
* All SDK calls use reflection for loose binding - no compile-time dependency on FLIR SDK.
|
|
41
|
+
* FLIR SDK Manager - Handles device discovery, connection, and streaming.
|
|
42
|
+
* Uses the official FLIR ThermalSDK directly (bundled in AAR).
|
|
29
43
|
*
|
|
30
|
-
*
|
|
31
|
-
* isFlir=true → startDiscovery()
|
|
32
|
-
* ↓ disconnect current device (if any)
|
|
33
|
-
* ↓ (timeout: 0s if isEmu=true, 5s otherwise)
|
|
34
|
-
* ├─ Devices found → emit to RN → stream from device[0]
|
|
35
|
-
* │ ↓ RN sends deviceId
|
|
36
|
-
* │ └─ disconnect → switch to deviceId
|
|
37
|
-
* ├─ No device OR isEmu=true → emulatorDiscovery()
|
|
38
|
-
* │ ↓ Check emulator type setting
|
|
39
|
-
* │ ├─ FLIR_ONE_EDGE (default)
|
|
40
|
-
* │ └─ FLIR_ONE
|
|
41
|
-
* │ └─ stream from emulator
|
|
42
|
-
* └─ Streaming: connect → start stream → upload to texture
|
|
43
|
-
* ↓ acol changes → setPalette()
|
|
44
|
-
* ↓ touch point → getTemperatureAt(x,y)
|
|
44
|
+
* Supports USB, NETWORK (FLIR ONE Edge), and EMULATOR interfaces.
|
|
45
45
|
*/
|
|
46
46
|
public class FlirSdkManager {
|
|
47
47
|
private static final String TAG = "FlirSdkManager";
|
|
48
|
-
private static final String FLOW_TAG = "FLIR_FLOW";
|
|
49
|
-
|
|
50
|
-
// Step counter for tracking flow
|
|
51
|
-
private int stepCounter = 0;
|
|
52
|
-
private long flowStartTime = 0;
|
|
53
|
-
|
|
54
|
-
private void logStep(String step, String details) {
|
|
55
|
-
stepCounter++;
|
|
56
|
-
long elapsed = flowStartTime > 0 ? System.currentTimeMillis() - flowStartTime : 0;
|
|
57
|
-
String msg = String.format("[Step %d] [+%dms] %s: %s", stepCounter, elapsed, step, details);
|
|
58
|
-
Log.i(FLOW_TAG, msg);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private void resetFlowTracking() {
|
|
62
|
-
stepCounter = 0;
|
|
63
|
-
flowStartTime = System.currentTimeMillis();
|
|
64
|
-
Log.i(FLOW_TAG, "========== FLIR FLOW STARTED ==========");
|
|
65
|
-
}
|
|
48
|
+
private static final String FLOW_TAG = "FLIR_FLOW";
|
|
66
49
|
|
|
67
50
|
// Discovery timeout in milliseconds
|
|
68
|
-
private static final long DISCOVERY_TIMEOUT_DEVICE_MS = 5000;
|
|
69
|
-
private static final long DISCOVERY_TIMEOUT_EMULATOR_MS = 0;
|
|
51
|
+
private static final long DISCOVERY_TIMEOUT_DEVICE_MS = 5000;
|
|
52
|
+
private static final long DISCOVERY_TIMEOUT_EMULATOR_MS = 0;
|
|
70
53
|
|
|
71
54
|
// Emulator types
|
|
72
55
|
public enum EmulatorType {
|
|
73
|
-
FLIR_ONE_EDGE, //
|
|
56
|
+
FLIR_ONE_EDGE, // WiFi emulator
|
|
74
57
|
FLIR_ONE // USB emulator
|
|
75
58
|
}
|
|
76
59
|
|
|
77
|
-
// Communication interfaces
|
|
60
|
+
// Communication interfaces
|
|
78
61
|
public enum CommInterface {
|
|
79
62
|
USB,
|
|
80
63
|
NETWORK,
|
|
@@ -95,15 +78,15 @@ public class FlirSdkManager {
|
|
|
95
78
|
void onError(String error);
|
|
96
79
|
}
|
|
97
80
|
|
|
98
|
-
// Device info class
|
|
81
|
+
// Device info class
|
|
99
82
|
public static class DeviceInfo {
|
|
100
83
|
public final String deviceId;
|
|
101
84
|
public final String deviceName;
|
|
102
85
|
public final boolean isEmulator;
|
|
103
86
|
public final CommInterface commInterface;
|
|
104
|
-
public final
|
|
87
|
+
public final Identity identity;
|
|
105
88
|
|
|
106
|
-
DeviceInfo(String id, String name, boolean emu, CommInterface iface,
|
|
89
|
+
DeviceInfo(String id, String name, boolean emu, CommInterface iface, Identity identity) {
|
|
107
90
|
this.deviceId = id;
|
|
108
91
|
this.deviceName = name;
|
|
109
92
|
this.isEmulator = emu;
|
|
@@ -117,14 +100,11 @@ public class FlirSdkManager {
|
|
|
117
100
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
118
101
|
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
|
119
102
|
|
|
120
|
-
// SDK objects
|
|
121
|
-
private
|
|
122
|
-
private
|
|
123
|
-
private
|
|
124
|
-
private
|
|
125
|
-
private Object streamerObj = null;
|
|
126
|
-
private Object currentStream = null;
|
|
127
|
-
private Object currentPalette = null;
|
|
103
|
+
// SDK objects
|
|
104
|
+
private Camera camera = null;
|
|
105
|
+
private Stream currentStream = null;
|
|
106
|
+
private ThermalStreamer thermalStreamer = null;
|
|
107
|
+
private Palette currentPalette = null;
|
|
128
108
|
|
|
129
109
|
// State tracking
|
|
130
110
|
private final AtomicBoolean isDiscovering = new AtomicBoolean(false);
|
|
@@ -138,87 +118,107 @@ public class FlirSdkManager {
|
|
|
138
118
|
|
|
139
119
|
// Frame state
|
|
140
120
|
private volatile Bitmap latestFrame = null;
|
|
141
|
-
private
|
|
121
|
+
private volatile ThermalImage currentThermalImage = null;
|
|
142
122
|
private String currentStreamKind = null;
|
|
143
123
|
|
|
144
|
-
//
|
|
145
|
-
private int
|
|
146
|
-
private long
|
|
147
|
-
|
|
124
|
+
// Step tracking for debugging
|
|
125
|
+
private int stepCounter = 0;
|
|
126
|
+
private long flowStartTime = 0;
|
|
127
|
+
|
|
128
|
+
// SDK initialization state
|
|
129
|
+
private static boolean sdkInitialized = false;
|
|
148
130
|
|
|
149
131
|
FlirSdkManager(Listener listener, android.content.Context context) {
|
|
150
132
|
this.listener = listener;
|
|
151
133
|
this.appContext = context != null ? context.getApplicationContext() : null;
|
|
152
134
|
}
|
|
153
135
|
|
|
136
|
+
private void logStep(String step, String details) {
|
|
137
|
+
stepCounter++;
|
|
138
|
+
long elapsed = flowStartTime > 0 ? System.currentTimeMillis() - flowStartTime : 0;
|
|
139
|
+
Log.i(FLOW_TAG, String.format("[Step %d] [+%dms] %s: %s", stepCounter, elapsed, step, details));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private void resetFlowTracking() {
|
|
143
|
+
stepCounter = 0;
|
|
144
|
+
flowStartTime = System.currentTimeMillis();
|
|
145
|
+
Log.i(FLOW_TAG, "========== FLIR FLOW STARTED ==========");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ==================== SDK INITIALIZATION ====================
|
|
149
|
+
|
|
150
|
+
private boolean initializeSdk() {
|
|
151
|
+
if (sdkInitialized) {
|
|
152
|
+
Log.d(TAG, "[FLIR SDK] Already initialized");
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (appContext == null) {
|
|
157
|
+
Log.e(TAG, "[FLIR SDK] No context available");
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
Log.i(TAG, "[FLIR SDK] Initializing ThermalSdkAndroid...");
|
|
163
|
+
ThermalSdkAndroid.init(appContext);
|
|
164
|
+
sdkInitialized = true;
|
|
165
|
+
Log.i(TAG, "[FLIR SDK] SDK Version: " + ThermalSdkAndroid.getVersion());
|
|
166
|
+
return true;
|
|
167
|
+
} catch (Throwable t) {
|
|
168
|
+
Log.e(TAG, "[FLIR SDK] Initialization failed: " + t.getMessage(), t);
|
|
169
|
+
notifyError("SDK initialization failed: " + t.getMessage());
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
154
174
|
// ==================== PUBLIC API ====================
|
|
155
175
|
|
|
156
|
-
/**
|
|
157
|
-
* Set the emulator type to use when no physical device is found
|
|
158
|
-
*/
|
|
159
176
|
public void setEmulatorType(EmulatorType type) {
|
|
160
177
|
this.emulatorType = type;
|
|
161
178
|
Log.i(TAG, "[FLIR] Emulator type set to: " + type);
|
|
162
|
-
logStep("SET_EMULATOR_TYPE", "type=" + type
|
|
179
|
+
logStep("SET_EMULATOR_TYPE", "type=" + type);
|
|
163
180
|
}
|
|
164
181
|
|
|
165
|
-
/**
|
|
166
|
-
* Start device discovery. Will disconnect current device first.
|
|
167
|
-
* @param forceEmulator If true, skip device discovery and connect to emulator immediately
|
|
168
|
-
*/
|
|
169
182
|
public void startDiscovery(boolean forceEmulator) {
|
|
170
183
|
resetFlowTracking();
|
|
171
184
|
logStep("START_DISCOVERY", "forceEmulator=" + forceEmulator + ", emulatorType=" + emulatorType);
|
|
172
185
|
Log.i(TAG, "[FLIR] startDiscovery(forceEmulator=" + forceEmulator + ")");
|
|
173
186
|
|
|
174
|
-
//
|
|
187
|
+
// Disconnect current device first
|
|
175
188
|
if (isConnected.get()) {
|
|
176
|
-
logStep("DISCONNECT_PREVIOUS", "Disconnecting current device
|
|
189
|
+
logStep("DISCONNECT_PREVIOUS", "Disconnecting current device");
|
|
177
190
|
disconnect();
|
|
178
191
|
}
|
|
179
192
|
|
|
180
|
-
// Clear discovered devices
|
|
181
193
|
discoveredDevices.clear();
|
|
182
194
|
logStep("CLEAR_DEVICES", "Cleared discovered devices list");
|
|
183
195
|
|
|
184
196
|
if (forceEmulator) {
|
|
185
|
-
|
|
186
|
-
logStep("MODE_EMULATOR", "Forcing emulator mode - skipping device discovery");
|
|
197
|
+
logStep("MODE_EMULATOR", "Forcing emulator mode");
|
|
187
198
|
isEmulatorMode.set(true);
|
|
188
199
|
startEmulatorDiscovery();
|
|
189
200
|
} else {
|
|
190
|
-
|
|
191
|
-
logStep("MODE_FULL_DISCOVERY", "Starting full discovery (USB+NETWORK+EMULATOR), timeout=" + DISCOVERY_TIMEOUT_DEVICE_MS + "ms");
|
|
201
|
+
logStep("MODE_FULL_DISCOVERY", "Starting full discovery, timeout=" + DISCOVERY_TIMEOUT_DEVICE_MS + "ms");
|
|
192
202
|
isEmulatorMode.set(false);
|
|
193
203
|
startFullDiscovery();
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
/**
|
|
198
|
-
* Stop discovery scan
|
|
199
|
-
*/
|
|
200
207
|
public void stopDiscovery() {
|
|
201
208
|
Log.i(TAG, "[FLIR] stopDiscovery()");
|
|
202
209
|
cancelDiscoveryTimeout();
|
|
203
210
|
isDiscovering.set(false);
|
|
204
211
|
|
|
205
212
|
try {
|
|
206
|
-
|
|
207
|
-
Method stopMethod = discoveryFactory.getClass().getMethod("stop");
|
|
208
|
-
stopMethod.invoke(discoveryFactory);
|
|
209
|
-
}
|
|
213
|
+
DiscoveryFactory.getInstance().stop();
|
|
210
214
|
} catch (Throwable t) {
|
|
211
215
|
Log.w(TAG, "[FLIR] stopDiscovery failed: " + t.getMessage());
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
|
|
215
|
-
/**
|
|
216
|
-
* Connect to a specific device by ID
|
|
217
|
-
*/
|
|
218
219
|
public void connectToDevice(String deviceId) {
|
|
219
220
|
Log.i(TAG, "[FLIR] connectToDevice: " + deviceId);
|
|
220
221
|
|
|
221
|
-
// Find device in discovered list
|
|
222
222
|
DeviceInfo target = null;
|
|
223
223
|
for (DeviceInfo d : discoveredDevices) {
|
|
224
224
|
if (d.deviceId.equals(deviceId)) {
|
|
@@ -232,220 +232,122 @@ public class FlirSdkManager {
|
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
// Disconnect current if needed
|
|
236
235
|
if (isConnected.get()) {
|
|
237
236
|
disconnect();
|
|
238
237
|
}
|
|
239
238
|
|
|
240
|
-
// Connect to target
|
|
241
239
|
connectToIdentity(target);
|
|
242
240
|
}
|
|
243
241
|
|
|
244
|
-
/**
|
|
245
|
-
* Disconnect current device/emulator
|
|
246
|
-
*/
|
|
247
242
|
public void disconnect() {
|
|
248
243
|
Log.i(TAG, "[FLIR] disconnect()");
|
|
249
244
|
|
|
250
|
-
// Stop streaming first
|
|
251
245
|
stopStreaming();
|
|
252
246
|
|
|
253
|
-
|
|
254
|
-
if (cameraObj != null) {
|
|
247
|
+
if (camera != null) {
|
|
255
248
|
try {
|
|
256
|
-
|
|
257
|
-
disconnectMethod.invoke(cameraObj);
|
|
258
|
-
Log.i(TAG, "[FLIR] Camera disconnected");
|
|
249
|
+
camera.disconnect();
|
|
259
250
|
} catch (Throwable t) {
|
|
260
|
-
Log.w(TAG, "[FLIR] disconnect failed: " + t.getMessage());
|
|
251
|
+
Log.w(TAG, "[FLIR] Camera disconnect failed: " + t.getMessage());
|
|
261
252
|
}
|
|
253
|
+
camera = null;
|
|
262
254
|
}
|
|
263
255
|
|
|
264
|
-
cameraObj = null;
|
|
265
|
-
streamerObj = null;
|
|
266
|
-
currentStream = null;
|
|
267
|
-
connectedDevice = null;
|
|
268
256
|
isConnected.set(false);
|
|
269
|
-
|
|
257
|
+
connectedDevice = null;
|
|
270
258
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
259
|
+
mainHandler.post(() -> {
|
|
260
|
+
if (listener != null) {
|
|
261
|
+
listener.onDeviceDisconnected();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
274
264
|
}
|
|
275
265
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
public void setPalette(String paletteName) {
|
|
280
|
-
Log.d(TAG, "[FLIR] setPalette: " + paletteName);
|
|
266
|
+
public void setStreamType(String streamType) {
|
|
267
|
+
Log.i(TAG, "[FLIR] setStreamType: " + streamType);
|
|
268
|
+
currentStreamKind = streamType;
|
|
281
269
|
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
270
|
+
if (isConnected.get() && camera != null) {
|
|
271
|
+
stopStreaming();
|
|
272
|
+
startStreaming();
|
|
285
273
|
}
|
|
286
|
-
|
|
287
|
-
scheduler.submit(() -> {
|
|
288
|
-
try {
|
|
289
|
-
// Get PaletteManager.getDefaultPalettes()
|
|
290
|
-
Class<?> paletteManagerClass = findSdkClass("com.flir.thermalsdk.image.PaletteManager");
|
|
291
|
-
Method getDefaultPalettes = paletteManagerClass.getMethod("getDefaultPalettes");
|
|
292
|
-
Object palettes = getDefaultPalettes.invoke(null);
|
|
293
|
-
|
|
294
|
-
// Find matching palette
|
|
295
|
-
Object targetPalette = null;
|
|
296
|
-
if (palettes instanceof List) {
|
|
297
|
-
for (Object p : (List<?>) palettes) {
|
|
298
|
-
Method getName = p.getClass().getMethod("getName");
|
|
299
|
-
String name = (String) getName.invoke(p);
|
|
300
|
-
if (name != null && name.equalsIgnoreCase(paletteName)) {
|
|
301
|
-
targetPalette = p;
|
|
302
|
-
break;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (targetPalette != null) {
|
|
308
|
-
currentPalette = targetPalette;
|
|
309
|
-
Log.i(TAG, "[FLIR] Palette set to: " + paletteName);
|
|
310
|
-
} else {
|
|
311
|
-
Log.w(TAG, "[FLIR] Palette not found: " + paletteName);
|
|
312
|
-
}
|
|
313
|
-
} catch (Throwable t) {
|
|
314
|
-
Log.w(TAG, "[FLIR] setPalette failed: " + t.getMessage());
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
274
|
}
|
|
318
275
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
*/
|
|
323
|
-
private void initializeDefaultPalette() {
|
|
276
|
+
public void setPalette(String paletteName) {
|
|
277
|
+
Log.i(TAG, "[FLIR] setPalette: " + paletteName);
|
|
278
|
+
|
|
324
279
|
try {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
Object ironPalette = null;
|
|
332
|
-
Object firstPalette = null;
|
|
333
|
-
|
|
334
|
-
for (Object p : (List<?>) palettes) {
|
|
335
|
-
if (firstPalette == null) firstPalette = p;
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
Method getName = p.getClass().getMethod("getName");
|
|
339
|
-
String name = (String) getName.invoke(p);
|
|
340
|
-
if (name != null && name.toLowerCase().contains("iron")) {
|
|
341
|
-
ironPalette = p;
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
} catch (Throwable ignored) {}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
currentPalette = ironPalette != null ? ironPalette : firstPalette;
|
|
348
|
-
|
|
349
|
-
if (currentPalette != null) {
|
|
350
|
-
try {
|
|
351
|
-
Method getName = currentPalette.getClass().getMethod("getName");
|
|
352
|
-
String name = (String) getName.invoke(currentPalette);
|
|
353
|
-
Log.i(TAG, "[FLIR] Default palette initialized: " + name);
|
|
354
|
-
} catch (Throwable ignored) {
|
|
355
|
-
Log.i(TAG, "[FLIR] Default palette initialized");
|
|
356
|
-
}
|
|
280
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
281
|
+
for (Palette p : palettes) {
|
|
282
|
+
if (p.name.equalsIgnoreCase(paletteName)) {
|
|
283
|
+
currentPalette = p;
|
|
284
|
+
Log.i(TAG, "[FLIR] Palette set to: " + p.name);
|
|
285
|
+
return;
|
|
357
286
|
}
|
|
358
287
|
}
|
|
288
|
+
Log.w(TAG, "[FLIR] Palette not found: " + paletteName);
|
|
359
289
|
} catch (Throwable t) {
|
|
360
|
-
Log.w(TAG, "[FLIR]
|
|
290
|
+
Log.w(TAG, "[FLIR] setPalette failed: " + t.getMessage());
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public void getTemperatureAt(int x, int y, Bitmap source) {
|
|
295
|
+
Log.d(TAG, "[FLIR] getTemperatureAt(" + x + ", " + y + ")");
|
|
296
|
+
double temp = getTemperatureAtPoint(x, y);
|
|
297
|
+
if (!Double.isNaN(temp) && listener != null) {
|
|
298
|
+
mainHandler.post(() -> listener.onTemperature(temp, x, y));
|
|
361
299
|
}
|
|
362
300
|
}
|
|
363
301
|
|
|
364
302
|
/**
|
|
365
|
-
* Get temperature at a specific point
|
|
303
|
+
* Get temperature at a specific point from the current thermal image
|
|
366
304
|
*/
|
|
367
305
|
public double getTemperatureAtPoint(int x, int y) {
|
|
368
|
-
if (
|
|
306
|
+
if (currentThermalImage == null) {
|
|
307
|
+
return Double.NaN;
|
|
308
|
+
}
|
|
369
309
|
|
|
370
310
|
try {
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
311
|
+
// Clamp coordinates to image bounds
|
|
312
|
+
int imgWidth = currentThermalImage.getWidth();
|
|
313
|
+
int imgHeight = currentThermalImage.getHeight();
|
|
314
|
+
int clampedX = Math.max(0, Math.min(x, imgWidth - 1));
|
|
315
|
+
int clampedY = Math.max(0, Math.min(y, imgHeight - 1));
|
|
374
316
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
Method getValueAt = thermalImage.getClass().getMethod("getValueAt", android.graphics.Point.class);
|
|
379
|
-
android.graphics.Point p = new android.graphics.Point(x, y);
|
|
380
|
-
Object temp = getValueAt.invoke(thermalImage, p);
|
|
381
|
-
if (temp instanceof Double) return (Double) temp;
|
|
382
|
-
if (temp instanceof Float) return ((Float) temp).doubleValue();
|
|
383
|
-
} catch (NoSuchMethodException ignored) {}
|
|
384
|
-
|
|
385
|
-
// Try getValues().getValueAt(x, y)
|
|
386
|
-
try {
|
|
387
|
-
Method getValues = thermalImage.getClass().getMethod("getValues");
|
|
388
|
-
Object values = getValues.invoke(thermalImage);
|
|
389
|
-
if (values != null) {
|
|
390
|
-
Method valGetAt = values.getClass().getMethod("getValueAt", int.class, int.class);
|
|
391
|
-
Object temp = valGetAt.invoke(values, x, y);
|
|
392
|
-
if (temp instanceof Double) return (Double) temp;
|
|
393
|
-
}
|
|
394
|
-
} catch (Throwable ignored) {}
|
|
317
|
+
ThermalValue value = currentThermalImage.getValueAt(new Point(clampedX, clampedY));
|
|
318
|
+
if (value != null) {
|
|
319
|
+
return value.asCelsius().value;
|
|
395
320
|
}
|
|
396
321
|
} catch (Throwable t) {
|
|
397
|
-
Log.
|
|
322
|
+
Log.w(TAG, "[FLIR] getTemperatureAtPoint failed: " + t.getMessage());
|
|
398
323
|
}
|
|
399
|
-
|
|
400
324
|
return Double.NaN;
|
|
401
325
|
}
|
|
402
326
|
|
|
403
|
-
/**
|
|
404
|
-
* Get latest frame bitmap
|
|
405
|
-
*/
|
|
406
327
|
public Bitmap getLatestFrame() {
|
|
407
328
|
return latestFrame;
|
|
408
329
|
}
|
|
409
330
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
*/
|
|
413
|
-
public boolean isStreamingActive() {
|
|
414
|
-
return isStreaming.get();
|
|
331
|
+
public List<DeviceInfo> getDiscoveredDevices() {
|
|
332
|
+
return new ArrayList<>(discoveredDevices);
|
|
415
333
|
}
|
|
416
334
|
|
|
417
|
-
/**
|
|
418
|
-
* Check if connected
|
|
419
|
-
*/
|
|
420
335
|
public boolean isConnected() {
|
|
421
336
|
return isConnected.get();
|
|
422
337
|
}
|
|
423
338
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
*/
|
|
427
|
-
public String getCurrentStreamKind() {
|
|
428
|
-
return currentStreamKind;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Get list of discovered devices
|
|
433
|
-
*/
|
|
434
|
-
public List<DeviceInfo> getDiscoveredDevices() {
|
|
435
|
-
return new ArrayList<>(discoveredDevices);
|
|
339
|
+
public boolean isStreaming() {
|
|
340
|
+
return isStreaming.get();
|
|
436
341
|
}
|
|
437
342
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
*/
|
|
441
|
-
public void stop() {
|
|
442
|
-
Log.i(TAG, "[FLIR] stop()");
|
|
343
|
+
public void destroy() {
|
|
344
|
+
Log.i(TAG, "[FLIR] destroy()");
|
|
443
345
|
stopDiscovery();
|
|
444
346
|
disconnect();
|
|
445
|
-
scheduler.
|
|
347
|
+
scheduler.shutdown();
|
|
446
348
|
}
|
|
447
349
|
|
|
448
|
-
// ==================== DISCOVERY
|
|
350
|
+
// ==================== DISCOVERY ====================
|
|
449
351
|
|
|
450
352
|
private void startFullDiscovery() {
|
|
451
353
|
Log.i(TAG, "[FLIR] Starting full discovery (USB, NETWORK, EMULATOR)");
|
|
@@ -457,49 +359,37 @@ public class FlirSdkManager {
|
|
|
457
359
|
}
|
|
458
360
|
|
|
459
361
|
isDiscovering.set(true);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
362
|
+
mainHandler.post(() -> {
|
|
363
|
+
if (listener != null) {
|
|
364
|
+
listener.onDiscoveryStarted();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
463
367
|
|
|
464
368
|
try {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
// Create interface array [USB, NETWORK, EMULATOR]
|
|
485
|
-
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 3);
|
|
486
|
-
java.lang.reflect.Array.set(ifaceArray, 0, usbInterface);
|
|
487
|
-
java.lang.reflect.Array.set(ifaceArray, 1, networkInterface);
|
|
488
|
-
java.lang.reflect.Array.set(ifaceArray, 2, emulatorInterface);
|
|
489
|
-
|
|
490
|
-
// Start discovery scan
|
|
491
|
-
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass, ifaceArray.getClass());
|
|
492
|
-
scanMethod.invoke(discoveryFactory, discoveryListener, ifaceArray);
|
|
493
|
-
|
|
494
|
-
Log.i(TAG, "[FLIR] Discovery scan started for USB, NETWORK, EMULATOR");
|
|
369
|
+
CommunicationInterface[] interfaces = {
|
|
370
|
+
CommunicationInterface.USB,
|
|
371
|
+
CommunicationInterface.NETWORK,
|
|
372
|
+
CommunicationInterface.EMULATOR
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
DiscoveryFactory.getInstance().scan(new DiscoveryEventListener() {
|
|
376
|
+
@Override
|
|
377
|
+
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
378
|
+
handleCameraFound(discoveredCamera);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
@Override
|
|
382
|
+
public void onDiscoveryError(CommunicationInterface iface, ErrorCode errorCode) {
|
|
383
|
+
Log.w(TAG, "[FLIR] Discovery error on " + iface + ": " + errorCode);
|
|
384
|
+
}
|
|
385
|
+
}, interfaces);
|
|
495
386
|
|
|
496
|
-
// Set discovery
|
|
497
|
-
|
|
387
|
+
// Set timeout for device discovery
|
|
388
|
+
scheduleDiscoveryTimeout(DISCOVERY_TIMEOUT_DEVICE_MS);
|
|
498
389
|
|
|
499
390
|
} catch (Throwable t) {
|
|
500
391
|
Log.e(TAG, "[FLIR] startFullDiscovery failed: " + t.getMessage(), t);
|
|
501
392
|
notifyError("Discovery failed: " + t.getMessage());
|
|
502
|
-
// Fallback to emulator
|
|
503
393
|
startEmulatorDiscovery();
|
|
504
394
|
}
|
|
505
395
|
}
|
|
@@ -509,1003 +399,362 @@ public class FlirSdkManager {
|
|
|
509
399
|
Log.i(TAG, "[FLIR] Starting emulator discovery (type=" + emulatorType + ")");
|
|
510
400
|
|
|
511
401
|
if (!initializeSdk()) {
|
|
512
|
-
|
|
513
|
-
notifyError("FLIR SDK not available");
|
|
402
|
+
notifyError("SDK initialization failed");
|
|
514
403
|
return;
|
|
515
404
|
}
|
|
516
|
-
logStep("SDK_INITIALIZED", "FLIR SDK successfully initialized");
|
|
517
405
|
|
|
518
406
|
isDiscovering.set(true);
|
|
519
407
|
isEmulatorMode.set(true);
|
|
520
408
|
|
|
409
|
+
mainHandler.post(() -> {
|
|
410
|
+
if (listener != null) {
|
|
411
|
+
listener.onDiscoveryStarted();
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
521
415
|
try {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
discoveryListener = Proxy.newProxyInstance(
|
|
534
|
-
getEffectiveClassLoader(),
|
|
535
|
-
new Class<?>[] { listenerClass },
|
|
536
|
-
(proxy, method, args) -> handleDiscoveryCallback(method.getName(), args)
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
// Create interface array [EMULATOR]
|
|
540
|
-
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 1);
|
|
541
|
-
java.lang.reflect.Array.set(ifaceArray, 0, emulatorInterface);
|
|
542
|
-
|
|
543
|
-
// Start discovery
|
|
544
|
-
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass, ifaceArray.getClass());
|
|
545
|
-
scanMethod.invoke(discoveryFactory, discoveryListener, ifaceArray);
|
|
416
|
+
DiscoveryFactory.getInstance().scan(new DiscoveryEventListener() {
|
|
417
|
+
@Override
|
|
418
|
+
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
419
|
+
handleCameraFound(discoveredCamera);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
@Override
|
|
423
|
+
public void onDiscoveryError(CommunicationInterface iface, ErrorCode errorCode) {
|
|
424
|
+
Log.w(TAG, "[FLIR] Emulator discovery error: " + errorCode);
|
|
425
|
+
}
|
|
426
|
+
}, CommunicationInterface.EMULATOR);
|
|
546
427
|
|
|
547
|
-
|
|
548
|
-
|
|
428
|
+
// Short timeout for emulator
|
|
429
|
+
scheduleDiscoveryTimeout(2000);
|
|
549
430
|
|
|
550
431
|
} catch (Throwable t) {
|
|
551
|
-
logStep("EMULATOR_DISCOVERY_ERROR", "Failed: " + t.getMessage());
|
|
552
432
|
Log.e(TAG, "[FLIR] startEmulatorDiscovery failed: " + t.getMessage(), t);
|
|
553
433
|
notifyError("Emulator discovery failed: " + t.getMessage());
|
|
554
434
|
}
|
|
555
435
|
}
|
|
556
436
|
|
|
557
|
-
private
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
437
|
+
private void handleCameraFound(DiscoveredCamera discoveredCamera) {
|
|
438
|
+
Identity identity = discoveredCamera.getIdentity();
|
|
439
|
+
String deviceId = identity.deviceId;
|
|
440
|
+
String deviceName = identity.toString();
|
|
441
|
+
CommunicationInterface iface = identity.communicationInterface;
|
|
442
|
+
boolean isEmulator = (iface == CommunicationInterface.EMULATOR);
|
|
443
|
+
|
|
444
|
+
Log.i(TAG, "[FLIR] Camera found: " + deviceName + " (" + iface + ")");
|
|
445
|
+
logStep("DEVICE_FOUND", "id=" + deviceId + ", name=" + deviceName + ", interface=" + iface);
|
|
446
|
+
|
|
447
|
+
CommInterface commIface;
|
|
448
|
+
switch (iface) {
|
|
449
|
+
case USB:
|
|
450
|
+
commIface = CommInterface.USB;
|
|
569
451
|
break;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (args != null && args.length > 1) {
|
|
573
|
-
Log.w(TAG, "[FLIR] Discovery error: " + args[1]);
|
|
574
|
-
}
|
|
452
|
+
case NETWORK:
|
|
453
|
+
commIface = CommInterface.NETWORK;
|
|
575
454
|
break;
|
|
455
|
+
default:
|
|
456
|
+
commIface = CommInterface.EMULATOR;
|
|
576
457
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// Extract device info from identity
|
|
587
|
-
String deviceId = extractDeviceId(identity);
|
|
588
|
-
String deviceName = extractDeviceName(identity);
|
|
589
|
-
CommInterface commInterface = extractCommInterface(identity);
|
|
590
|
-
boolean isEmulator = commInterface == CommInterface.EMULATOR;
|
|
591
|
-
|
|
592
|
-
logStep("DEVICE_FOUND", "name=" + deviceName + ", id=" + deviceId + ", interface=" + commInterface + ", isEmulator=" + isEmulator);
|
|
593
|
-
Log.i(TAG, "[FLIR] Camera found: " + deviceName + " (" + commInterface + ")");
|
|
594
|
-
|
|
595
|
-
// Create device info
|
|
596
|
-
DeviceInfo deviceInfo = new DeviceInfo(deviceId, deviceName, isEmulator, commInterface, identity);
|
|
597
|
-
|
|
598
|
-
// Add to list if not already present
|
|
599
|
-
boolean exists = false;
|
|
600
|
-
for (DeviceInfo d : discoveredDevices) {
|
|
601
|
-
if (d.deviceId.equals(deviceId)) {
|
|
602
|
-
exists = true;
|
|
603
|
-
break;
|
|
604
|
-
}
|
|
458
|
+
|
|
459
|
+
DeviceInfo deviceInfo = new DeviceInfo(deviceId, deviceName, isEmulator, commIface, identity);
|
|
460
|
+
|
|
461
|
+
// Avoid duplicates
|
|
462
|
+
boolean exists = false;
|
|
463
|
+
for (DeviceInfo d : discoveredDevices) {
|
|
464
|
+
if (d.deviceId.equals(deviceId)) {
|
|
465
|
+
exists = true;
|
|
466
|
+
break;
|
|
605
467
|
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!exists) {
|
|
471
|
+
discoveredDevices.add(deviceInfo);
|
|
606
472
|
|
|
607
|
-
|
|
608
|
-
discoveredDevices.add(deviceInfo);
|
|
609
|
-
|
|
610
|
-
// Notify listener
|
|
473
|
+
mainHandler.post(() -> {
|
|
611
474
|
if (listener != null) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
listener.onDeviceFound(deviceId, deviceName, isEmulator);
|
|
615
|
-
listener.onDeviceListUpdated(devices);
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// If this is first device found (not emulator in normal mode), connect to it
|
|
620
|
-
if (!isEmulator && discoveredDevices.size() == 1 && !isEmulatorMode.get()) {
|
|
621
|
-
cancelDiscoveryTimeout();
|
|
622
|
-
stopDiscovery();
|
|
623
|
-
connectToIdentity(deviceInfo);
|
|
624
|
-
} else if (isEmulator && isEmulatorMode.get()) {
|
|
625
|
-
// In emulator mode, connect to first emulator found
|
|
626
|
-
stopDiscovery();
|
|
627
|
-
connectToIdentity(deviceInfo);
|
|
475
|
+
listener.onDeviceFound(deviceId, deviceName, isEmulator);
|
|
476
|
+
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
628
477
|
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
} catch (Throwable t) {
|
|
632
|
-
Log.e(TAG, "[FLIR] handleCameraFound failed: " + t.getMessage(), t);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
private void handleCameraLost(Object discoveredCamera) {
|
|
637
|
-
try {
|
|
638
|
-
Method getIdentity = discoveredCamera.getClass().getMethod("getIdentity");
|
|
639
|
-
Object identity = getIdentity.invoke(discoveredCamera);
|
|
640
|
-
String deviceId = extractDeviceId(identity);
|
|
641
|
-
|
|
642
|
-
Log.i(TAG, "[FLIR] Camera lost: " + deviceId);
|
|
478
|
+
});
|
|
643
479
|
|
|
644
|
-
//
|
|
645
|
-
discoveredDevices.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
disconnect();
|
|
480
|
+
// Auto-connect to first device found
|
|
481
|
+
if (discoveredDevices.size() == 1 && !isConnected.get()) {
|
|
482
|
+
cancelDiscoveryTimeout();
|
|
483
|
+
logStep("AUTO_CONNECT", "Connecting to first found device");
|
|
484
|
+
connectToIdentity(deviceInfo);
|
|
650
485
|
}
|
|
651
|
-
|
|
652
|
-
} catch (Throwable t) {
|
|
653
|
-
Log.w(TAG, "[FLIR] handleCameraLost failed: " + t.getMessage());
|
|
654
486
|
}
|
|
655
487
|
}
|
|
656
488
|
|
|
657
|
-
private void
|
|
489
|
+
private void scheduleDiscoveryTimeout(long timeoutMs) {
|
|
658
490
|
cancelDiscoveryTimeout();
|
|
659
491
|
|
|
660
|
-
if (timeoutMs <= 0) return;
|
|
661
|
-
|
|
662
492
|
discoveryTimeoutFuture = scheduler.schedule(() -> {
|
|
663
|
-
Log.i(TAG, "[FLIR] Discovery timeout");
|
|
664
|
-
|
|
493
|
+
Log.i(TAG, "[FLIR] Discovery timeout after " + timeoutMs + "ms");
|
|
494
|
+
logStep("DISCOVERY_TIMEOUT", "timeout=" + timeoutMs + "ms, devicesFound=" + discoveredDevices.size());
|
|
665
495
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
496
|
+
isDiscovering.set(false);
|
|
497
|
+
stopDiscovery();
|
|
669
498
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (!d.isEmulator) {
|
|
674
|
-
hasPhysicalDevice = true;
|
|
675
|
-
break;
|
|
499
|
+
mainHandler.post(() -> {
|
|
500
|
+
if (listener != null) {
|
|
501
|
+
listener.onDiscoveryTimeout();
|
|
676
502
|
}
|
|
677
|
-
}
|
|
503
|
+
});
|
|
678
504
|
|
|
679
|
-
|
|
680
|
-
|
|
505
|
+
// If no devices found, try emulator
|
|
506
|
+
if (discoveredDevices.isEmpty() && !isEmulatorMode.get()) {
|
|
507
|
+
logStep("FALLBACK_EMULATOR", "No devices found, trying emulator");
|
|
681
508
|
startEmulatorDiscovery();
|
|
682
|
-
} else if (!discoveredDevices.isEmpty()) {
|
|
683
|
-
// Connect to first device
|
|
684
|
-
connectToIdentity(discoveredDevices.get(0));
|
|
685
509
|
}
|
|
686
|
-
|
|
687
510
|
}, timeoutMs, TimeUnit.MILLISECONDS);
|
|
688
511
|
}
|
|
689
512
|
|
|
690
513
|
private void cancelDiscoveryTimeout() {
|
|
691
|
-
if (discoveryTimeoutFuture != null) {
|
|
514
|
+
if (discoveryTimeoutFuture != null && !discoveryTimeoutFuture.isDone()) {
|
|
692
515
|
discoveryTimeoutFuture.cancel(false);
|
|
693
516
|
discoveryTimeoutFuture = null;
|
|
694
517
|
}
|
|
695
518
|
}
|
|
696
519
|
|
|
697
|
-
// ==================== CONNECTION
|
|
520
|
+
// ==================== CONNECTION ====================
|
|
698
521
|
|
|
699
|
-
private void connectToIdentity(DeviceInfo
|
|
700
|
-
logStep("CONNECT_START", "
|
|
701
|
-
Log.i(TAG, "[FLIR] Connecting to: " +
|
|
522
|
+
private void connectToIdentity(DeviceInfo device) {
|
|
523
|
+
logStep("CONNECT_START", "device=" + device.deviceName);
|
|
524
|
+
Log.i(TAG, "[FLIR] Connecting to: " + device.deviceName);
|
|
702
525
|
|
|
703
|
-
scheduler.
|
|
526
|
+
scheduler.execute(() -> {
|
|
704
527
|
try {
|
|
705
|
-
|
|
706
|
-
logStep("CREATE_CAMERA", "Creating Camera instance");
|
|
707
|
-
Class<?> cameraClass = findSdkClass("com.flir.thermalsdk.live.Camera");
|
|
708
|
-
cameraObj = cameraClass.newInstance();
|
|
528
|
+
camera = new Camera();
|
|
709
529
|
|
|
710
|
-
//
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
new
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
Log.
|
|
718
|
-
|
|
530
|
+
// Connect using Identity, error callback, and ConnectParameters
|
|
531
|
+
// Note: connect is blocking, so we're on a background thread
|
|
532
|
+
camera.connect(
|
|
533
|
+
device.identity,
|
|
534
|
+
new ConnectionStatusListener() {
|
|
535
|
+
@Override
|
|
536
|
+
public void onDisconnected(ErrorCode errorCode) {
|
|
537
|
+
Log.i(TAG, "[FLIR] Disconnected: " + errorCode);
|
|
538
|
+
logStep("DISCONNECTED", "reason=" + errorCode);
|
|
539
|
+
|
|
540
|
+
isConnected.set(false);
|
|
541
|
+
connectedDevice = null;
|
|
542
|
+
|
|
543
|
+
mainHandler.post(() -> {
|
|
544
|
+
if (listener != null) {
|
|
545
|
+
listener.onDeviceDisconnected();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
719
548
|
}
|
|
720
|
-
|
|
721
|
-
|
|
549
|
+
},
|
|
550
|
+
new ConnectParameters()
|
|
722
551
|
);
|
|
723
552
|
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// Try connect(Identity, ConnectionStatusListener, ConnectParameters)
|
|
728
|
-
try {
|
|
729
|
-
Class<?> connectParamsClass = findSdkClass("com.flir.thermalsdk.live.ConnectParameters");
|
|
730
|
-
Object connectParams = connectParamsClass.newInstance();
|
|
731
|
-
Class<?> identityClass = findSdkClass("com.flir.thermalsdk.live.Identity");
|
|
732
|
-
Method connectMethod = cameraClass.getMethod("connect",
|
|
733
|
-
identityClass, connStatusClass, connectParamsClass);
|
|
734
|
-
connectMethod.invoke(cameraObj, deviceInfo.identity, connListener, connectParams);
|
|
735
|
-
connected = true;
|
|
736
|
-
} catch (NoSuchMethodException ignored) {}
|
|
553
|
+
// If we get here, connection succeeded
|
|
554
|
+
Log.i(TAG, "[FLIR] Connected to: " + device.deviceName);
|
|
555
|
+
logStep("CONNECTED", "device=" + device.deviceName);
|
|
737
556
|
|
|
738
|
-
// Try connect(Identity, ConnectionStatusListener)
|
|
739
|
-
if (!connected) {
|
|
740
|
-
try {
|
|
741
|
-
Class<?> identityClass = findSdkClass("com.flir.thermalsdk.live.Identity");
|
|
742
|
-
Method connectMethod = cameraClass.getMethod("connect",
|
|
743
|
-
identityClass, connStatusClass);
|
|
744
|
-
connectMethod.invoke(cameraObj, deviceInfo.identity, connListener);
|
|
745
|
-
connected = true;
|
|
746
|
-
} catch (NoSuchMethodException ignored) {}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// Try connect(Identity)
|
|
750
|
-
if (!connected) {
|
|
751
|
-
try {
|
|
752
|
-
Class<?> identityClass = findSdkClass("com.flir.thermalsdk.live.Identity");
|
|
753
|
-
Method connectMethod = cameraClass.getMethod("connect", identityClass);
|
|
754
|
-
connectMethod.invoke(cameraObj, deviceInfo.identity);
|
|
755
|
-
connected = true;
|
|
756
|
-
} catch (NoSuchMethodException ignored) {}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
if (!connected) {
|
|
760
|
-
throw new Exception("No suitable connect method found");
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
connectedDevice = deviceInfo;
|
|
764
557
|
isConnected.set(true);
|
|
558
|
+
connectedDevice = device;
|
|
765
559
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
mainHandler.post(() -> listener.onDeviceConnected(
|
|
772
|
-
deviceInfo.deviceId, deviceInfo.deviceName, deviceInfo.isEmulator));
|
|
773
|
-
}
|
|
560
|
+
mainHandler.post(() -> {
|
|
561
|
+
if (listener != null) {
|
|
562
|
+
listener.onDeviceConnected(device.deviceId, device.deviceName, device.isEmulator);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
774
565
|
|
|
775
|
-
// Start streaming
|
|
776
|
-
|
|
777
|
-
scheduler.schedule(this::startStreaming, 500, TimeUnit.MILLISECONDS);
|
|
566
|
+
// Start streaming automatically
|
|
567
|
+
startStreaming();
|
|
778
568
|
|
|
779
569
|
} catch (Throwable t) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
570
|
+
Log.e(TAG, "[FLIR] Connect error: " + t.getMessage(), t);
|
|
571
|
+
isConnected.set(false);
|
|
572
|
+
camera = null;
|
|
573
|
+
notifyError("Connect error: " + t.getMessage());
|
|
783
574
|
}
|
|
784
575
|
});
|
|
785
576
|
}
|
|
786
577
|
|
|
787
|
-
|
|
788
|
-
cameraObj = null;
|
|
789
|
-
streamerObj = null;
|
|
790
|
-
currentStream = null;
|
|
791
|
-
connectedDevice = null;
|
|
792
|
-
isConnected.set(false);
|
|
793
|
-
isStreaming.set(false);
|
|
794
|
-
|
|
795
|
-
if (listener != null) {
|
|
796
|
-
mainHandler.post(() -> listener.onDeviceDisconnected());
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
// ==================== STREAMING IMPLEMENTATION ====================
|
|
578
|
+
// ==================== STREAMING ====================
|
|
801
579
|
|
|
802
580
|
private void startStreaming() {
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
Log.w(TAG, "[FLIR] Cannot start streaming - no camera");
|
|
581
|
+
if (camera == null || !isConnected.get()) {
|
|
582
|
+
Log.w(TAG, "[FLIR] Cannot start streaming - not connected");
|
|
806
583
|
return;
|
|
807
584
|
}
|
|
808
585
|
|
|
809
|
-
logStep("STREAM_START", "
|
|
586
|
+
logStep("STREAM_START", "streamType=" + currentStreamKind);
|
|
810
587
|
Log.i(TAG, "[FLIR] Starting streaming...");
|
|
811
588
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
List<?> streamList = (List<?>) streams;
|
|
824
|
-
logStep("STREAMS_FOUND", "Found " + streamList.size() + " stream(s)");
|
|
825
|
-
Log.i(TAG, "[FLIR] Found " + streamList.size() + " stream(s)");
|
|
826
|
-
|
|
827
|
-
// Prefer thermal stream, fallback to first available
|
|
828
|
-
Object chosenStream = null;
|
|
829
|
-
String streamType = "unknown";
|
|
830
|
-
|
|
831
|
-
for (Object s : streamList) {
|
|
832
|
-
if (s == null) continue;
|
|
589
|
+
scheduler.execute(() -> {
|
|
590
|
+
try {
|
|
591
|
+
// Get available streams
|
|
592
|
+
List<Stream> streams = camera.getStreams();
|
|
593
|
+
if (streams == null || streams.isEmpty()) {
|
|
594
|
+
Log.e(TAG, "[FLIR] No streams available");
|
|
595
|
+
notifyError("No streams available");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
Log.i(TAG, "[FLIR] Available streams: " + streams.size());
|
|
833
600
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
try { getName = s.getClass().getMethod("getName"); } catch (Throwable ignored) {}
|
|
840
|
-
String name = getName != null ? String.valueOf(getName.invoke(s)) : "unknown";
|
|
841
|
-
|
|
842
|
-
Log.d(TAG, "[FLIR] Stream: " + name + ", thermal=" + thermal);
|
|
843
|
-
|
|
844
|
-
if (thermal != null && thermal) {
|
|
845
|
-
chosenStream = s;
|
|
846
|
-
streamType = "thermal";
|
|
601
|
+
// Select thermal stream (prefer thermal, fallback to first)
|
|
602
|
+
Stream thermalStream = null;
|
|
603
|
+
for (Stream s : streams) {
|
|
604
|
+
if (s.isThermal()) {
|
|
605
|
+
thermalStream = s;
|
|
847
606
|
break;
|
|
848
|
-
} else if (chosenStream == null) {
|
|
849
|
-
chosenStream = s;
|
|
850
|
-
streamType = "visual";
|
|
851
607
|
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
if (chosenStream == null) {
|
|
856
|
-
chosenStream = streamList.get(0);
|
|
857
|
-
streamType = "default";
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
currentStream = chosenStream;
|
|
861
|
-
currentStreamKind = streamType;
|
|
862
|
-
|
|
863
|
-
logStep("STREAM_SELECTED", "Using " + streamType + " stream");
|
|
864
|
-
Log.i(TAG, "[FLIR] Using stream type: " + streamType);
|
|
865
|
-
|
|
866
|
-
// Create appropriate streamer
|
|
867
|
-
if ("thermal".equals(streamType)) {
|
|
868
|
-
logStep("CREATE_THERMAL_STREAMER", "Creating ThermalStreamer");
|
|
869
|
-
Class<?> thermalStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.ThermalStreamer");
|
|
870
|
-
Class<?> streamClass = findSdkClass("com.flir.thermalsdk.live.streaming.Stream");
|
|
871
|
-
streamerObj = thermalStreamerClass.getConstructor(streamClass).newInstance(chosenStream);
|
|
608
|
+
}
|
|
609
|
+
currentStream = thermalStream != null ? thermalStream : streams.get(0);
|
|
872
610
|
|
|
873
|
-
//
|
|
611
|
+
// Create ThermalStreamer for rendering
|
|
612
|
+
thermalStreamer = new ThermalStreamer(currentStream);
|
|
613
|
+
|
|
614
|
+
// Set default palette if available
|
|
874
615
|
if (currentPalette == null) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// Create OnReceived callback
|
|
888
|
-
Class<?> onReceivedClass = findSdkClass("com.flir.thermalsdk.live.remote.OnReceived");
|
|
889
|
-
Object onReceivedCallback = Proxy.newProxyInstance(
|
|
890
|
-
getEffectiveClassLoader(),
|
|
891
|
-
new Class<?>[] { onReceivedClass },
|
|
892
|
-
(proxy, method, args) -> {
|
|
893
|
-
if ("run".equals(method.getName())) {
|
|
894
|
-
scheduler.submit(this::processFrame);
|
|
895
|
-
}
|
|
896
|
-
return null;
|
|
897
|
-
}
|
|
898
|
-
);
|
|
899
|
-
|
|
900
|
-
// Create OnRemoteError callback
|
|
901
|
-
Class<?> onErrorClass = findSdkClass("com.flir.thermalsdk.live.remote.OnRemoteError");
|
|
902
|
-
Object onErrorCallback = Proxy.newProxyInstance(
|
|
903
|
-
getEffectiveClassLoader(),
|
|
904
|
-
new Class<?>[] { onErrorClass },
|
|
905
|
-
(proxy, method, args) -> {
|
|
906
|
-
if ("run".equals(method.getName()) && args != null && args.length > 0) {
|
|
907
|
-
Log.e(TAG, "[FLIR] Stream error: " + args[0]);
|
|
616
|
+
try {
|
|
617
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
618
|
+
for (Palette p : palettes) {
|
|
619
|
+
if (p.name.toLowerCase().contains("iron")) {
|
|
620
|
+
currentPalette = p;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
} catch (Throwable t) {
|
|
625
|
+
Log.w(TAG, "[FLIR] Failed to get default palette: " + t.getMessage());
|
|
908
626
|
}
|
|
909
|
-
return null;
|
|
910
627
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
} catch (Throwable t) {
|
|
929
|
-
logStep("STREAM_ERROR", "Streaming failed: " + t.getMessage());
|
|
930
|
-
Log.e(TAG, "[FLIR] startStreaming failed: " + t.getMessage(), t);
|
|
931
|
-
notifyError("Streaming failed: " + t.getMessage());
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
private void processFrame() {
|
|
936
|
-
if (streamerObj == null) return;
|
|
937
|
-
|
|
938
|
-
frameCount++;
|
|
939
|
-
long now = System.currentTimeMillis();
|
|
940
|
-
|
|
941
|
-
// Log first frame and then every 30 frames or every 5 seconds
|
|
942
|
-
boolean shouldLog = frameCount == 1 || frameCount % 30 == 0 || (now - lastFrameLogTime > 5000);
|
|
943
|
-
|
|
944
|
-
if (shouldLog) {
|
|
945
|
-
logStep("PROCESS_FRAME", "frame=" + frameCount + ", streamType=" + currentStreamKind + ", bitmapsSuccess=" + successfulBitmapCount);
|
|
946
|
-
lastFrameLogTime = now;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
try {
|
|
950
|
-
// Call streamer.update() first - this refreshes the streamer content
|
|
951
|
-
Method updateMethod = streamerObj.getClass().getMethod("update");
|
|
952
|
-
updateMethod.invoke(streamerObj);
|
|
953
|
-
|
|
954
|
-
// Get image buffer from streamer.getImage()
|
|
955
|
-
Method getImage = streamerObj.getClass().getMethod("getImage");
|
|
956
|
-
Object imageBuffer = getImage.invoke(streamerObj);
|
|
957
|
-
|
|
958
|
-
if (imageBuffer == null) {
|
|
959
|
-
if (shouldLog) logStep("FRAME_NULL_BUFFER", "imageBuffer is null at frame " + frameCount);
|
|
960
|
-
Log.d(TAG, "[FLIR] imageBuffer is null");
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
if (frameCount == 1) {
|
|
965
|
-
logStep("FIRST_BUFFER", "Got first imageBuffer, type=" + imageBuffer.getClass().getSimpleName());
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// For thermal streamer, we MUST create the bitmap INSIDE withThermalImage callback
|
|
969
|
-
// This is critical - the palette affects the imageBuffer rendering only while inside the callback
|
|
970
|
-
if ("thermal".equals(currentStreamKind)) {
|
|
971
|
-
processThermalFrameWithCallback(imageBuffer);
|
|
972
|
-
} else {
|
|
973
|
-
// For visual stream, just convert to bitmap directly
|
|
974
|
-
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
975
|
-
if (bitmap != null) {
|
|
976
|
-
successfulBitmapCount++;
|
|
977
|
-
latestFrame = bitmap;
|
|
978
|
-
if (frameCount == 1) {
|
|
979
|
-
logStep("FIRST_BITMAP", "Visual bitmap created: " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
628
|
+
|
|
629
|
+
// Start the stream with OnReceived and OnRemoteError callbacks
|
|
630
|
+
currentStream.start(
|
|
631
|
+
new OnReceived<Void>() {
|
|
632
|
+
@Override
|
|
633
|
+
public void onReceived(Void result) {
|
|
634
|
+
// Process received frame on background thread
|
|
635
|
+
scheduler.execute(() -> refreshThermalFrame());
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
new OnRemoteError() {
|
|
639
|
+
@Override
|
|
640
|
+
public void onRemoteError(ErrorCode errorCode) {
|
|
641
|
+
Log.e(TAG, "[FLIR] Stream error: " + errorCode);
|
|
642
|
+
notifyError("Stream error: " + errorCode);
|
|
643
|
+
}
|
|
980
644
|
}
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
isStreaming.set(true);
|
|
648
|
+
Log.i(TAG, "[FLIR] Stream started");
|
|
649
|
+
logStep("STREAM_STARTED", "stream=" + currentStream);
|
|
650
|
+
|
|
651
|
+
mainHandler.post(() -> {
|
|
981
652
|
if (listener != null) {
|
|
982
|
-
listener.
|
|
653
|
+
listener.onStreamStarted("thermal");
|
|
983
654
|
}
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
} catch (Throwable t) {
|
|
658
|
+
Log.e(TAG, "[FLIR] Start stream error: " + t.getMessage(), t);
|
|
659
|
+
notifyError("Stream error: " + t.getMessage());
|
|
987
660
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private void stopStreaming() {
|
|
665
|
+
if (currentStream != null) {
|
|
666
|
+
try {
|
|
667
|
+
currentStream.stop();
|
|
668
|
+
} catch (Throwable t) {
|
|
669
|
+
Log.w(TAG, "[FLIR] Stop stream error: " + t.getMessage());
|
|
993
670
|
}
|
|
671
|
+
currentStream = null;
|
|
994
672
|
}
|
|
673
|
+
thermalStreamer = null;
|
|
674
|
+
isStreaming.set(false);
|
|
995
675
|
}
|
|
996
676
|
|
|
997
677
|
/**
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1000
|
-
* This matches the LiveStreamingKotlin example exactly.
|
|
678
|
+
* Refresh thermal frame using ThermalStreamer pattern.
|
|
679
|
+
* Called when a new frame is received.
|
|
1001
680
|
*/
|
|
1002
|
-
private void
|
|
1003
|
-
if (
|
|
681
|
+
private synchronized void refreshThermalFrame() {
|
|
682
|
+
if (thermalStreamer == null) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
1004
685
|
|
|
1005
686
|
try {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
// Not a ThermalStreamer, use direct conversion
|
|
1009
|
-
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1010
|
-
if (bitmap != null) {
|
|
1011
|
-
latestFrame = bitmap;
|
|
1012
|
-
if (listener != null) listener.onFrame(bitmap);
|
|
1013
|
-
}
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
687
|
+
// Update streamer to get latest frame
|
|
688
|
+
thermalStreamer.update();
|
|
1016
689
|
|
|
1017
|
-
//
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
if ("withThermalImage".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1021
|
-
withThermalImageMethod = m;
|
|
1022
|
-
break;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
if (withThermalImageMethod == null) {
|
|
1027
|
-
Log.w(TAG, "[FLIR] withThermalImage method not found, using fallback");
|
|
1028
|
-
// Fallback: try to set palette via getThermalImage, then convert
|
|
1029
|
-
try {
|
|
1030
|
-
Method getThermalImage = streamerObj.getClass().getMethod("getThermalImage");
|
|
1031
|
-
Object thermalImage = getThermalImage.invoke(streamerObj);
|
|
1032
|
-
if (thermalImage != null && currentPalette != null) {
|
|
1033
|
-
setPaletteOnThermalImage(thermalImage);
|
|
1034
|
-
}
|
|
1035
|
-
} catch (Throwable ignored) {}
|
|
1036
|
-
|
|
1037
|
-
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1038
|
-
if (bitmap != null) {
|
|
1039
|
-
latestFrame = bitmap;
|
|
1040
|
-
if (listener != null) listener.onFrame(bitmap);
|
|
1041
|
-
}
|
|
690
|
+
// Get the image buffer from streamer
|
|
691
|
+
ImageBuffer imageBuffer = thermalStreamer.getImage();
|
|
692
|
+
if (imageBuffer == null) {
|
|
1042
693
|
return;
|
|
1043
694
|
}
|
|
1044
695
|
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
if ("accept".equals(method.getName()) && args != null && args.length > 0) {
|
|
1054
|
-
Object thermalImage = args[0];
|
|
1055
|
-
if (thermalImage != null) {
|
|
1056
|
-
// Step 1: Set palette on thermal image
|
|
1057
|
-
if (currentPalette != null) {
|
|
1058
|
-
setPaletteOnThermalImage(thermalImage);
|
|
1059
|
-
if (frameCount == 1) {
|
|
1060
|
-
logStep("PALETTE_APPLIED", "Palette applied to ThermalImage inside callback");
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// Step 2: Convert to bitmap INSIDE the callback
|
|
1065
|
-
// This is critical - must happen while we have access to thermal image context
|
|
1066
|
-
try {
|
|
1067
|
-
Bitmap bitmap = convertToBitmap(imgBuffer);
|
|
1068
|
-
if (bitmap != null) {
|
|
1069
|
-
successfulBitmapCount++;
|
|
1070
|
-
latestFrame = bitmap;
|
|
1071
|
-
if (frameCount == 1) {
|
|
1072
|
-
logStep("FIRST_THERMAL_BITMAP", "Thermal bitmap created INSIDE callback: " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
1073
|
-
}
|
|
1074
|
-
if (listener != null) {
|
|
1075
|
-
listener.onFrame(bitmap);
|
|
1076
|
-
}
|
|
1077
|
-
} else if (frameCount <= 5) {
|
|
1078
|
-
logStep("THERMAL_BITMAP_NULL", "Thermal convertToBitmap returned null at frame " + frameCount);
|
|
1079
|
-
}
|
|
1080
|
-
} catch (Throwable t) {
|
|
1081
|
-
if (frameCount <= 5) {
|
|
1082
|
-
logStep("THERMAL_BITMAP_ERROR", "Bitmap conversion failed: " + t.getMessage());
|
|
1083
|
-
}
|
|
1084
|
-
Log.d(TAG, "[FLIR] Bitmap conversion in callback failed: " + t.getMessage());
|
|
1085
|
-
}
|
|
1086
|
-
} else if (frameCount <= 5) {
|
|
1087
|
-
logStep("THERMAL_IMAGE_NULL", "ThermalImage is null in callback");
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
return null;
|
|
696
|
+
// Access thermal image safely for temperature queries and palette
|
|
697
|
+
thermalStreamer.withThermalImage(thermalImage -> {
|
|
698
|
+
// Store for temperature queries
|
|
699
|
+
currentThermalImage = thermalImage;
|
|
700
|
+
|
|
701
|
+
// Apply palette if set
|
|
702
|
+
if (currentPalette != null) {
|
|
703
|
+
thermalImage.setPalette(currentPalette);
|
|
1091
704
|
}
|
|
1092
|
-
);
|
|
705
|
+
});
|
|
1093
706
|
|
|
1094
|
-
|
|
707
|
+
// Convert to Android Bitmap
|
|
708
|
+
Bitmap bitmap = BitmapAndroid.createBitmap(imageBuffer).getBitMap();
|
|
1095
709
|
|
|
1096
|
-
} catch (Throwable t) {
|
|
1097
|
-
Log.d(TAG, "[FLIR] processThermalFrameWithCallback failed: " + t.getMessage());
|
|
1098
|
-
// Fallback to direct conversion
|
|
1099
|
-
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1100
710
|
if (bitmap != null) {
|
|
1101
711
|
latestFrame = bitmap;
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
/**
|
|
1108
|
-
* Apply palette via ThermalStreamer.withThermalImage() callback pattern.
|
|
1109
|
-
* In the SDK: thermalStreamer.withThermalImage { it.palette = selectedPalette }
|
|
1110
|
-
* NOTE: This is now mostly unused - processThermalFrameWithCallback handles both palette and bitmap
|
|
1111
|
-
*/
|
|
1112
|
-
private void applyPaletteViaThermalImage() {
|
|
1113
|
-
if (streamerObj == null || currentPalette == null) return;
|
|
1114
|
-
|
|
1115
|
-
try {
|
|
1116
|
-
Class<?> thermalStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.ThermalStreamer");
|
|
1117
|
-
if (!thermalStreamerClass.isInstance(streamerObj)) return;
|
|
1118
|
-
|
|
1119
|
-
// Find withThermalImage method - it takes a Consumer<ThermalImage> callback
|
|
1120
|
-
// We'll use reflection to create a proxy for the Consumer interface
|
|
1121
|
-
Method withThermalImageMethod = null;
|
|
1122
|
-
for (Method m : thermalStreamerClass.getMethods()) {
|
|
1123
|
-
if ("withThermalImage".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1124
|
-
withThermalImageMethod = m;
|
|
1125
|
-
break;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
if (withThermalImageMethod == null) {
|
|
1130
|
-
// Fallback: Try to get ThermalImage directly
|
|
1131
|
-
try {
|
|
1132
|
-
Method getThermalImage = streamerObj.getClass().getMethod("getThermalImage");
|
|
1133
|
-
Object thermalImage = getThermalImage.invoke(streamerObj);
|
|
1134
|
-
if (thermalImage != null) {
|
|
1135
|
-
setPaletteOnThermalImage(thermalImage);
|
|
712
|
+
|
|
713
|
+
mainHandler.post(() -> {
|
|
714
|
+
if (listener != null) {
|
|
715
|
+
listener.onFrame(bitmap);
|
|
1136
716
|
}
|
|
1137
|
-
}
|
|
1138
|
-
return;
|
|
717
|
+
});
|
|
1139
718
|
}
|
|
1140
719
|
|
|
1141
|
-
// Create Consumer proxy to call setPalette on ThermalImage
|
|
1142
|
-
Class<?> consumerClass = withThermalImageMethod.getParameterTypes()[0];
|
|
1143
|
-
Object consumer = Proxy.newProxyInstance(
|
|
1144
|
-
getEffectiveClassLoader(),
|
|
1145
|
-
new Class<?>[] { consumerClass },
|
|
1146
|
-
(proxy, method, args) -> {
|
|
1147
|
-
if ("accept".equals(method.getName()) && args != null && args.length > 0) {
|
|
1148
|
-
Object thermalImage = args[0];
|
|
1149
|
-
if (thermalImage != null) {
|
|
1150
|
-
setPaletteOnThermalImage(thermalImage);
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
return null;
|
|
1154
|
-
}
|
|
1155
|
-
);
|
|
1156
|
-
|
|
1157
|
-
withThermalImageMethod.invoke(streamerObj, consumer);
|
|
1158
|
-
|
|
1159
720
|
} catch (Throwable t) {
|
|
1160
|
-
Log.
|
|
721
|
+
Log.w(TAG, "[FLIR] refreshThermalFrame error: " + t.getMessage());
|
|
1161
722
|
}
|
|
1162
723
|
}
|
|
1163
724
|
|
|
725
|
+
// ==================== PUBLIC STOP ====================
|
|
726
|
+
|
|
1164
727
|
/**
|
|
1165
|
-
*
|
|
728
|
+
* Stop the manager - disconnect and cleanup all resources.
|
|
1166
729
|
*/
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
try {
|
|
1171
|
-
Class<?> paletteClass = findSdkClass("com.flir.thermalsdk.image.Palette");
|
|
1172
|
-
Method setPalette = thermalImage.getClass().getMethod("setPalette", paletteClass);
|
|
1173
|
-
setPalette.invoke(thermalImage, currentPalette);
|
|
1174
|
-
} catch (Throwable t) {
|
|
1175
|
-
// Try with direct class
|
|
1176
|
-
try {
|
|
1177
|
-
Method setPalette = thermalImage.getClass().getMethod("setPalette", currentPalette.getClass());
|
|
1178
|
-
setPalette.invoke(thermalImage, currentPalette);
|
|
1179
|
-
} catch (Throwable ignored) {}
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
private Bitmap convertToBitmap(Object imageBuffer) {
|
|
1184
|
-
boolean isFirstConvert = frameCount == 1;
|
|
1185
|
-
|
|
1186
|
-
try {
|
|
1187
|
-
// Try BitmapAndroid.createBitmap(imageBuffer).bitMap (or getBitMap)
|
|
1188
|
-
Class<?> bitmapAndroidClass = findSdkClass("com.flir.thermalsdk.androidsdk.image.BitmapAndroid");
|
|
1189
|
-
|
|
1190
|
-
if (isFirstConvert) {
|
|
1191
|
-
logStep("BITMAP_CONVERT_START", "imageBuffer type=" + imageBuffer.getClass().getName());
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// Find a createBitmap method that works with our imageBuffer
|
|
1195
|
-
for (Method m : bitmapAndroidClass.getMethods()) {
|
|
1196
|
-
if ("createBitmap".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1197
|
-
Class<?> paramType = m.getParameterTypes()[0];
|
|
1198
|
-
if (paramType.isInstance(imageBuffer)) {
|
|
1199
|
-
Object wrapper = m.invoke(null, imageBuffer);
|
|
1200
|
-
if (wrapper != null) {
|
|
1201
|
-
if (isFirstConvert) {
|
|
1202
|
-
logStep("BITMAP_WRAPPER", "BitmapAndroid wrapper created: " + wrapper.getClass().getSimpleName());
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
// Try different method names: bitMap, getBitMap, getBitmap
|
|
1206
|
-
String[] methodNames = {"getBitMap", "bitMap", "getBitmap"};
|
|
1207
|
-
for (String methodName : methodNames) {
|
|
1208
|
-
try {
|
|
1209
|
-
Method getBitMap = wrapper.getClass().getMethod(methodName);
|
|
1210
|
-
Object bmp = getBitMap.invoke(wrapper);
|
|
1211
|
-
if (bmp instanceof Bitmap) {
|
|
1212
|
-
Bitmap bitmap = (Bitmap) bmp;
|
|
1213
|
-
if (bitmap.getWidth() > 0 && bitmap.getHeight() > 0) {
|
|
1214
|
-
if (isFirstConvert) {
|
|
1215
|
-
logStep("BITMAP_SUCCESS", "Got bitmap via " + methodName + "(): " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
1216
|
-
}
|
|
1217
|
-
return bitmap;
|
|
1218
|
-
} else if (isFirstConvert) {
|
|
1219
|
-
logStep("BITMAP_EMPTY", "Bitmap has zero dimensions via " + methodName);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
} catch (NoSuchMethodException ignored) {}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Also try as a field access (Kotlin property)
|
|
1226
|
-
try {
|
|
1227
|
-
java.lang.reflect.Field field = wrapper.getClass().getField("bitMap");
|
|
1228
|
-
Object bmp = field.get(wrapper);
|
|
1229
|
-
if (bmp instanceof Bitmap) {
|
|
1230
|
-
return (Bitmap) bmp;
|
|
1231
|
-
}
|
|
1232
|
-
} catch (Throwable ignored) {}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
Log.d(TAG, "[FLIR] BitmapAndroid.createBitmap method not found for imageBuffer type: " + imageBuffer.getClass().getName());
|
|
1239
|
-
|
|
1240
|
-
} catch (Throwable t) {
|
|
1241
|
-
Log.d(TAG, "[FLIR] convertToBitmap primary method failed: " + t.getMessage());
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
// Try alternative: direct getBitmap() on imageBuffer
|
|
1245
|
-
try {
|
|
1246
|
-
Method getBitmap = imageBuffer.getClass().getMethod("getBitmap");
|
|
1247
|
-
Object bmp = getBitmap.invoke(imageBuffer);
|
|
1248
|
-
if (bmp instanceof Bitmap) {
|
|
1249
|
-
return (Bitmap) bmp;
|
|
1250
|
-
}
|
|
1251
|
-
} catch (Throwable ignored) {}
|
|
1252
|
-
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
private void stopStreaming() {
|
|
1257
|
-
if (currentStream != null) {
|
|
1258
|
-
try {
|
|
1259
|
-
Method stopMethod = currentStream.getClass().getMethod("stop");
|
|
1260
|
-
stopMethod.invoke(currentStream);
|
|
1261
|
-
Log.i(TAG, "[FLIR] Stream stopped");
|
|
1262
|
-
} catch (Throwable t) {
|
|
1263
|
-
Log.w(TAG, "[FLIR] stopStreaming failed: " + t.getMessage());
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
730
|
+
public void stop() {
|
|
731
|
+
Log.i(TAG, "[FLIR] Stopping FlirSdkManager");
|
|
1266
732
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
isStreaming.set(false);
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
// ==================== SDK LOADING ====================
|
|
1273
|
-
|
|
1274
|
-
private boolean initializeSdk() {
|
|
1275
|
-
// Try direct class loading first
|
|
1276
|
-
try {
|
|
1277
|
-
Class.forName("com.flir.thermalsdk.live.CommunicationInterface");
|
|
1278
|
-
Log.i(TAG, "[FLIR SDK] Classes available on classpath");
|
|
1279
|
-
|
|
1280
|
-
// Initialize SDK
|
|
1281
|
-
initializeThermalSdk();
|
|
1282
|
-
return true;
|
|
1283
|
-
} catch (ClassNotFoundException e) {
|
|
1284
|
-
Log.w(TAG, "[FLIR SDK] Classes not on classpath, trying AAR load");
|
|
1285
|
-
}
|
|
733
|
+
// Stop streaming
|
|
734
|
+
stopStreaming();
|
|
1286
735
|
|
|
1287
|
-
//
|
|
1288
|
-
|
|
1289
|
-
initializeThermalSdk();
|
|
1290
|
-
return true;
|
|
1291
|
-
}
|
|
736
|
+
// Disconnect camera
|
|
737
|
+
disconnect();
|
|
1292
738
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
private void initializeThermalSdk() {
|
|
1297
|
-
try {
|
|
1298
|
-
Class<?> sdkClass = findSdkClass("com.flir.thermalsdk.live.ThermalSdkAndroid");
|
|
1299
|
-
Method initMethod = sdkClass.getMethod("init", android.content.Context.class);
|
|
1300
|
-
initMethod.invoke(null, appContext);
|
|
1301
|
-
Log.i(TAG, "[FLIR SDK] ThermalSdkAndroid.init() completed");
|
|
1302
|
-
} catch (Throwable t) {
|
|
1303
|
-
Log.w(TAG, "[FLIR SDK] ThermalSdkAndroid.init() failed: " + t.getMessage());
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
private boolean attemptLoadSdkFromAar() {
|
|
1308
|
-
android.content.Context ctx = appContext;
|
|
1309
|
-
if (ctx == null) {
|
|
1310
|
-
Log.w(TAG, "[FLIR SDK] No application context available for SDK load");
|
|
1311
|
-
return false;
|
|
1312
|
-
}
|
|
739
|
+
// Stop discovery
|
|
740
|
+
stopDiscovery();
|
|
1313
741
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
Log.i(TAG, "[FLIR SDK] Found DEX file: " + dexFile.getAbsolutePath() + " (size=" + dexFile.length() + ")");
|
|
1319
|
-
|
|
1320
|
-
// Get native library directory path for DexClassLoader
|
|
1321
|
-
File nativeLibDir = FlirSDKLoader.INSTANCE.getNativeLibDir(ctx);
|
|
1322
|
-
String nativeLibPath = nativeLibDir != null ? nativeLibDir.getAbsolutePath() : null;
|
|
1323
|
-
Log.i(TAG, "[FLIR SDK] Native lib path: " + nativeLibPath);
|
|
1324
|
-
|
|
1325
|
-
// Create DexClassLoader with native lib path
|
|
1326
|
-
File dexOutDir = ctx.getDir("dex", android.content.Context.MODE_PRIVATE);
|
|
1327
|
-
DexClassLoader dcl = new DexClassLoader(
|
|
1328
|
-
dexFile.getAbsolutePath(),
|
|
1329
|
-
dexOutDir.getAbsolutePath(),
|
|
1330
|
-
nativeLibPath, // This allows SDK to find .so files
|
|
1331
|
-
ctx.getClassLoader()
|
|
1332
|
-
);
|
|
1333
|
-
|
|
1334
|
-
// Verify class loading
|
|
1335
|
-
Class<?> test = Class.forName("com.flir.thermalsdk.live.CommunicationInterface", true, dcl);
|
|
1336
|
-
if (test != null) {
|
|
1337
|
-
sdkClassLoader = dcl;
|
|
1338
|
-
sdkJarPath = dexFile.getAbsolutePath();
|
|
1339
|
-
Log.i(TAG, "[FLIR SDK] DexClassLoader created from DEX: " + dexFile.getAbsolutePath());
|
|
1340
|
-
return true;
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// Fallback: Legacy AAR loading
|
|
1345
|
-
Log.i(TAG, "[FLIR SDK] No DEX found, trying legacy AAR locations...");
|
|
1346
|
-
|
|
1347
|
-
File filesDir = ctx.getFilesDir();
|
|
1348
|
-
|
|
1349
|
-
// Candidate search locations (ordered by preference)
|
|
1350
|
-
List<File> candidates = new ArrayList<>();
|
|
1351
|
-
|
|
1352
|
-
// Primary: FlirSDKLoader download directory
|
|
1353
|
-
candidates.add(new File(filesDir, "FlirSDK/thermalsdk-release.aar"));
|
|
1354
|
-
candidates.add(new File(filesDir, "FlirSDK/androidsdk-release.aar"));
|
|
1355
|
-
candidates.add(new File(filesDir, "FlirSDK/thermalsdk.aar"));
|
|
1356
|
-
|
|
1357
|
-
// Legacy locations
|
|
1358
|
-
candidates.add(new File(filesDir, "flir-sdk/thermalsdk-release.aar"));
|
|
1359
|
-
candidates.add(new File(filesDir, "thermalsdk-release.aar"));
|
|
1360
|
-
candidates.add(new File(filesDir, "thermalsdk.aar"));
|
|
1361
|
-
|
|
1362
|
-
// External storage
|
|
1363
|
-
File extDir = ctx.getExternalFilesDir(null);
|
|
1364
|
-
if (extDir != null) {
|
|
1365
|
-
candidates.add(new File(extDir, "FlirSDK/thermalsdk-release.aar"));
|
|
1366
|
-
candidates.add(new File(extDir, "thermalsdk-release.aar"));
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// Find first existing AAR
|
|
1370
|
-
File aarFile = null;
|
|
1371
|
-
StringBuilder tried = new StringBuilder();
|
|
1372
|
-
for (File f : candidates) {
|
|
1373
|
-
tried.append(f.getAbsolutePath()).append(f.exists() ? "(✓)," : "(✗),");
|
|
1374
|
-
if (f.exists()) {
|
|
1375
|
-
aarFile = f;
|
|
1376
|
-
break;
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
if (aarFile == null) {
|
|
1381
|
-
Log.w(TAG, "[FLIR SDK] No AAR found. Tried: " + tried);
|
|
1382
|
-
return false;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
Log.i(TAG, "[FLIR SDK] Found AAR: " + aarFile.getAbsolutePath());
|
|
1386
|
-
|
|
1387
|
-
// Extract classes.jar from AAR to a private directory
|
|
1388
|
-
ZipFile zf = new ZipFile(aarFile);
|
|
1389
|
-
ZipEntry classesEntry = zf.getEntry("classes.jar");
|
|
1390
|
-
if (classesEntry == null) {
|
|
1391
|
-
Log.w(TAG, "[FLIR SDK] classes.jar not found in AAR");
|
|
1392
|
-
zf.close();
|
|
1393
|
-
return false;
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// Use getDir() for a MODE_PRIVATE directory - required for DexClassLoader security
|
|
1397
|
-
File privateDir = ctx.getDir("flir_sdk", android.content.Context.MODE_PRIVATE);
|
|
1398
|
-
File outJar = new File(privateDir, "flir-classes.jar");
|
|
1399
|
-
|
|
1400
|
-
// Delete old file if exists to ensure clean extraction
|
|
1401
|
-
if (outJar.exists()) {
|
|
1402
|
-
outJar.delete();
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
FileOutputStream fos = new FileOutputStream(outJar);
|
|
1406
|
-
java.io.InputStream is = zf.getInputStream(classesEntry);
|
|
1407
|
-
byte[] buf = new byte[8192];
|
|
1408
|
-
int r;
|
|
1409
|
-
while ((r = is.read(buf)) != -1) fos.write(buf, 0, r);
|
|
1410
|
-
is.close();
|
|
1411
|
-
fos.close();
|
|
1412
|
-
zf.close();
|
|
1413
|
-
|
|
1414
|
-
// Set file to read-only (required for Android security)
|
|
1415
|
-
outJar.setReadOnly();
|
|
1416
|
-
|
|
1417
|
-
Log.i(TAG, "[FLIR SDK] Extracted classes.jar to: " + outJar.getAbsolutePath() + " (size=" + outJar.length() + ")");
|
|
1418
|
-
|
|
1419
|
-
// Create DexClassLoader with private dex output directory
|
|
1420
|
-
File dexOutDir = ctx.getDir("dex", android.content.Context.MODE_PRIVATE);
|
|
1421
|
-
DexClassLoader dcl = new DexClassLoader(
|
|
1422
|
-
outJar.getAbsolutePath(),
|
|
1423
|
-
dexOutDir.getAbsolutePath(),
|
|
1424
|
-
null,
|
|
1425
|
-
ctx.getClassLoader()
|
|
1426
|
-
);
|
|
1427
|
-
|
|
1428
|
-
// Verify class loading
|
|
1429
|
-
Class<?> test = Class.forName("com.flir.thermalsdk.live.CommunicationInterface", true, dcl);
|
|
1430
|
-
if (test != null) {
|
|
1431
|
-
sdkClassLoader = dcl;
|
|
1432
|
-
sdkJarPath = outJar.getAbsolutePath();
|
|
1433
|
-
Log.i(TAG, "[FLIR SDK] DexClassLoader created from: " + outJar.getAbsolutePath());
|
|
1434
|
-
return true;
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
} catch (Throwable t) {
|
|
1438
|
-
Log.e(TAG, "[FLIR SDK] attemptLoadSdkFromAar failed: " + t.getMessage(), t);
|
|
1439
|
-
}
|
|
742
|
+
// Clear state
|
|
743
|
+
discoveredDevices.clear();
|
|
744
|
+
currentThermalImage = null;
|
|
745
|
+
latestFrame = null;
|
|
1440
746
|
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
private Class<?> findSdkClass(String name) throws ClassNotFoundException {
|
|
1445
|
-
try {
|
|
1446
|
-
return Class.forName(name);
|
|
1447
|
-
} catch (ClassNotFoundException e) {
|
|
1448
|
-
if (sdkClassLoader != null) {
|
|
1449
|
-
return Class.forName(name, true, sdkClassLoader);
|
|
1450
|
-
}
|
|
1451
|
-
throw e;
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
private ClassLoader getEffectiveClassLoader() {
|
|
1456
|
-
return sdkClassLoader != null ? sdkClassLoader : getClass().getClassLoader();
|
|
747
|
+
Log.i(TAG, "[FLIR] FlirSdkManager stopped");
|
|
1457
748
|
}
|
|
1458
749
|
|
|
1459
750
|
// ==================== HELPERS ====================
|
|
1460
751
|
|
|
1461
|
-
private String extractDeviceId(Object identity) {
|
|
1462
|
-
try {
|
|
1463
|
-
Method getDeviceId = identity.getClass().getMethod("getDeviceId");
|
|
1464
|
-
Object result = getDeviceId.invoke(identity);
|
|
1465
|
-
return result != null ? result.toString() : "unknown";
|
|
1466
|
-
} catch (Throwable t) {
|
|
1467
|
-
return "device_" + System.currentTimeMillis();
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
private String extractDeviceName(Object identity) {
|
|
1472
|
-
try {
|
|
1473
|
-
// Try getName() first
|
|
1474
|
-
try {
|
|
1475
|
-
Method getName = identity.getClass().getMethod("getName");
|
|
1476
|
-
Object result = getName.invoke(identity);
|
|
1477
|
-
if (result != null && !result.toString().isEmpty()) {
|
|
1478
|
-
return result.toString();
|
|
1479
|
-
}
|
|
1480
|
-
} catch (Throwable ignored) {}
|
|
1481
|
-
|
|
1482
|
-
// Try getDeviceId() as fallback
|
|
1483
|
-
Method getDeviceId = identity.getClass().getMethod("getDeviceId");
|
|
1484
|
-
Object result = getDeviceId.invoke(identity);
|
|
1485
|
-
return result != null ? result.toString() : "FLIR Camera";
|
|
1486
|
-
} catch (Throwable t) {
|
|
1487
|
-
return "FLIR Camera";
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
private CommInterface extractCommInterface(Object identity) {
|
|
1492
|
-
try {
|
|
1493
|
-
Method getCommInterface = identity.getClass().getMethod("getCommunicationInterface");
|
|
1494
|
-
Object result = getCommInterface.invoke(identity);
|
|
1495
|
-
if (result != null) {
|
|
1496
|
-
String name = result.toString();
|
|
1497
|
-
if (name.contains("USB")) return CommInterface.USB;
|
|
1498
|
-
if (name.contains("NETWORK")) return CommInterface.NETWORK;
|
|
1499
|
-
if (name.contains("EMULATOR")) return CommInterface.EMULATOR;
|
|
1500
|
-
}
|
|
1501
|
-
} catch (Throwable ignored) {}
|
|
1502
|
-
return CommInterface.EMULATOR;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
752
|
private void notifyError(String error) {
|
|
1506
753
|
Log.e(TAG, "[FLIR] Error: " + error);
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
754
|
+
mainHandler.post(() -> {
|
|
755
|
+
if (listener != null) {
|
|
756
|
+
listener.onError(error);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
1510
759
|
}
|
|
1511
760
|
}
|