ilabs-flir 1.0.4 → 1.0.6
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/AndroidManifest.xml +14 -0
- package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +23 -44
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +312 -313
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +183 -0
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +35 -194
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +409 -1339
- package/package.json +1 -1
- package/src/index.d.ts +60 -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
|
@@ -1,1511 +1,581 @@
|
|
|
1
1
|
package flir.android;
|
|
2
2
|
|
|
3
|
+
import android.content.Context;
|
|
3
4
|
import android.graphics.Bitmap;
|
|
4
|
-
import android.os.Handler;
|
|
5
|
-
import android.os.Looper;
|
|
6
5
|
import android.util.Log;
|
|
7
6
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
7
|
+
import com.flir.thermalsdk.androidsdk.ThermalSdkAndroid;
|
|
8
|
+
import com.flir.thermalsdk.androidsdk.image.BitmapAndroid;
|
|
9
|
+
import com.flir.thermalsdk.image.ImageBuffer;
|
|
10
|
+
import com.flir.thermalsdk.image.Palette;
|
|
11
|
+
import com.flir.thermalsdk.image.PaletteManager;
|
|
12
|
+
import com.flir.thermalsdk.image.Point;
|
|
13
|
+
import com.flir.thermalsdk.image.ThermalValue;
|
|
14
|
+
import com.flir.thermalsdk.live.Camera;
|
|
15
|
+
import com.flir.thermalsdk.live.CommunicationInterface;
|
|
16
|
+
import com.flir.thermalsdk.live.ConnectParameters;
|
|
17
|
+
import com.flir.thermalsdk.live.Identity;
|
|
18
|
+
import com.flir.thermalsdk.live.connectivity.ConnectionStatusListener;
|
|
19
|
+
import com.flir.thermalsdk.live.discovery.DiscoveredCamera;
|
|
20
|
+
import com.flir.thermalsdk.live.discovery.DiscoveryEventListener;
|
|
21
|
+
import com.flir.thermalsdk.live.discovery.DiscoveryFactory;
|
|
22
|
+
import com.flir.thermalsdk.live.remote.OnReceived;
|
|
23
|
+
import com.flir.thermalsdk.live.streaming.Stream;
|
|
24
|
+
import com.flir.thermalsdk.live.streaming.ThermalStreamer;
|
|
25
|
+
|
|
12
26
|
import java.util.ArrayList;
|
|
27
|
+
import java.util.Collections;
|
|
13
28
|
import java.util.List;
|
|
14
|
-
import java.util.concurrent.
|
|
29
|
+
import java.util.concurrent.Executor;
|
|
15
30
|
import java.util.concurrent.Executors;
|
|
16
|
-
import java.util.concurrent.ScheduledExecutorService;
|
|
17
|
-
import java.util.concurrent.ScheduledFuture;
|
|
18
|
-
import java.util.concurrent.TimeUnit;
|
|
19
|
-
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
31
|
|
|
25
32
|
/**
|
|
26
|
-
* FLIR SDK Manager -
|
|
27
|
-
*
|
|
28
|
-
* All SDK calls use reflection for loose binding - no compile-time dependency on FLIR SDK.
|
|
29
|
-
*
|
|
30
|
-
* Flow:
|
|
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)
|
|
33
|
+
* Simplified FLIR SDK Manager - handles discovery, connection, and streaming
|
|
34
|
+
* No filtering - returns all discovered devices (USB, Network, Emulator)
|
|
45
35
|
*/
|
|
46
36
|
public class FlirSdkManager {
|
|
47
37
|
private static final String TAG = "FlirSdkManager";
|
|
48
|
-
private static final String FLOW_TAG = "FLIR_FLOW"; // For step-by-step flow logging
|
|
49
38
|
|
|
50
|
-
//
|
|
51
|
-
private
|
|
52
|
-
|
|
39
|
+
// Singleton instance
|
|
40
|
+
private static FlirSdkManager instance;
|
|
41
|
+
|
|
42
|
+
// Core components
|
|
43
|
+
private final Context context;
|
|
44
|
+
// Use bounded thread pool to prevent thread explosion during rapid frame processing
|
|
45
|
+
private final Executor executor = Executors.newFixedThreadPool(2);
|
|
46
|
+
// Single-threaded executor for frame processing to ensure ordered processing
|
|
47
|
+
private final Executor frameExecutor = Executors.newSingleThreadExecutor();
|
|
48
|
+
// Frame processing guard - skip frames if still processing previous one
|
|
49
|
+
private volatile boolean isProcessingFrame = false;
|
|
50
|
+
private long lastFrameProcessedMs = 0;
|
|
51
|
+
private static final long MIN_FRAME_INTERVAL_MS = 50; // Max ~20 FPS frame processing
|
|
52
|
+
|
|
53
|
+
// State
|
|
54
|
+
private boolean isInitialized = false;
|
|
55
|
+
private boolean isScanning = false;
|
|
56
|
+
private Camera camera;
|
|
57
|
+
private ThermalStreamer streamer;
|
|
58
|
+
private Stream activeStream;
|
|
59
|
+
private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
|
|
60
|
+
|
|
61
|
+
// Listener
|
|
62
|
+
private Listener listener;
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Listener interface for SDK events
|
|
66
|
+
*/
|
|
67
|
+
public interface Listener {
|
|
68
|
+
void onDeviceFound(Identity identity);
|
|
69
|
+
void onDeviceListUpdated(List<Identity> devices);
|
|
70
|
+
void onConnected(Identity identity);
|
|
71
|
+
void onDisconnected();
|
|
72
|
+
void onFrame(Bitmap bitmap);
|
|
73
|
+
void onError(String message);
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Log.i(FLOW_TAG, "========== FLIR FLOW STARTED ==========");
|
|
76
|
+
// Private constructor for singleton
|
|
77
|
+
private FlirSdkManager(Context context) {
|
|
78
|
+
this.context = context.getApplicationContext();
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Get singleton instance
|
|
83
|
+
*/
|
|
84
|
+
public static synchronized FlirSdkManager getInstance(Context context) {
|
|
85
|
+
if (instance == null) {
|
|
86
|
+
instance = new FlirSdkManager(context);
|
|
87
|
+
}
|
|
88
|
+
return instance;
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Listener interface for callbacks
|
|
85
|
-
public interface Listener {
|
|
86
|
-
void onFrame(Bitmap bitmap);
|
|
87
|
-
void onTemperature(double temp, int x, int y);
|
|
88
|
-
void onDeviceFound(String deviceId, String deviceName, boolean isEmulator);
|
|
89
|
-
void onDeviceListUpdated(List<DeviceInfo> devices);
|
|
90
|
-
void onDeviceConnected(String deviceId, String deviceName, boolean isEmulator);
|
|
91
|
-
void onDeviceDisconnected();
|
|
92
|
-
void onDiscoveryStarted();
|
|
93
|
-
void onDiscoveryTimeout();
|
|
94
|
-
void onStreamStarted(String streamType);
|
|
95
|
-
void onError(String error);
|
|
91
|
+
/**
|
|
92
|
+
* Set listener for SDK events
|
|
93
|
+
*/
|
|
94
|
+
public void setListener(Listener listener) {
|
|
95
|
+
this.listener = listener;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Initialize the FLIR Thermal SDK
|
|
100
|
+
*/
|
|
101
|
+
public void initialize() {
|
|
102
|
+
if (isInitialized) {
|
|
103
|
+
Log.d(TAG, "Already initialized");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
try {
|
|
108
|
+
ThermalSdkAndroid.init(context);
|
|
109
|
+
isInitialized = true;
|
|
110
|
+
Log.d(TAG, "SDK initialized successfully");
|
|
111
|
+
} catch (Exception e) {
|
|
112
|
+
Log.e(TAG, "Failed to initialize SDK", e);
|
|
113
|
+
notifyError("SDK initialization failed: " + e.getMessage());
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
|
-
|
|
115
|
-
private final Listener listener;
|
|
116
|
-
private final android.content.Context appContext;
|
|
117
|
-
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
118
|
-
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
|
119
|
-
|
|
120
|
-
// SDK objects (all via reflection)
|
|
121
|
-
private ClassLoader sdkClassLoader = null;
|
|
122
|
-
private Object discoveryFactory = null;
|
|
123
|
-
private Object discoveryListener = null;
|
|
124
|
-
private Object cameraObj = null;
|
|
125
|
-
private Object streamerObj = null;
|
|
126
|
-
private Object currentStream = null;
|
|
127
|
-
private Object currentPalette = null;
|
|
128
|
-
|
|
129
|
-
// State tracking
|
|
130
|
-
private final AtomicBoolean isDiscovering = new AtomicBoolean(false);
|
|
131
|
-
private final AtomicBoolean isConnected = new AtomicBoolean(false);
|
|
132
|
-
private final AtomicBoolean isStreaming = new AtomicBoolean(false);
|
|
133
|
-
private final AtomicBoolean isEmulatorMode = new AtomicBoolean(false);
|
|
134
|
-
private final CopyOnWriteArrayList<DeviceInfo> discoveredDevices = new CopyOnWriteArrayList<>();
|
|
135
|
-
private ScheduledFuture<?> discoveryTimeoutFuture = null;
|
|
136
|
-
private DeviceInfo connectedDevice = null;
|
|
137
|
-
private EmulatorType emulatorType = EmulatorType.FLIR_ONE_EDGE;
|
|
138
|
-
|
|
139
|
-
// Frame state
|
|
140
|
-
private volatile Bitmap latestFrame = null;
|
|
141
|
-
private String sdkJarPath = null;
|
|
142
|
-
private String currentStreamKind = null;
|
|
143
|
-
|
|
144
|
-
// Frame counting for debug logging
|
|
145
|
-
private int frameCount = 0;
|
|
146
|
-
private long lastFrameLogTime = 0;
|
|
147
|
-
private int successfulBitmapCount = 0;
|
|
148
|
-
|
|
149
|
-
FlirSdkManager(Listener listener, android.content.Context context) {
|
|
150
|
-
this.listener = listener;
|
|
151
|
-
this.appContext = context != null ? context.getApplicationContext() : null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ==================== PUBLIC API ====================
|
|
155
116
|
|
|
156
117
|
/**
|
|
157
|
-
*
|
|
118
|
+
* Check if SDK is initialized
|
|
158
119
|
*/
|
|
159
|
-
public
|
|
160
|
-
|
|
161
|
-
Log.i(TAG, "[FLIR] Emulator type set to: " + type);
|
|
162
|
-
logStep("SET_EMULATOR_TYPE", "type=" + type + " (FLIR_ONE_EDGE=WiFi, FLIR_ONE=USB)");
|
|
120
|
+
public boolean isInitialized() {
|
|
121
|
+
return isInitialized;
|
|
163
122
|
}
|
|
164
123
|
|
|
165
124
|
/**
|
|
166
|
-
* Start
|
|
167
|
-
*
|
|
125
|
+
* Start scanning for all device types (USB, Network, Emulator)
|
|
126
|
+
* Returns ALL devices - no filtering
|
|
168
127
|
*/
|
|
169
|
-
public void
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
128
|
+
public void scan() {
|
|
129
|
+
if (!isInitialized) {
|
|
130
|
+
Log.e(TAG, "SDK not initialized");
|
|
131
|
+
notifyError("SDK not initialized");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
173
134
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
disconnect();
|
|
135
|
+
if (isScanning) {
|
|
136
|
+
Log.d(TAG, "Already scanning");
|
|
137
|
+
return;
|
|
178
138
|
}
|
|
179
139
|
|
|
180
|
-
|
|
140
|
+
isScanning = true;
|
|
181
141
|
discoveredDevices.clear();
|
|
182
|
-
logStep("CLEAR_DEVICES", "Cleared discovered devices list");
|
|
183
142
|
|
|
184
|
-
|
|
185
|
-
// Immediate emulator mode
|
|
186
|
-
logStep("MODE_EMULATOR", "Forcing emulator mode - skipping device discovery");
|
|
187
|
-
isEmulatorMode.set(true);
|
|
188
|
-
startEmulatorDiscovery();
|
|
189
|
-
} else {
|
|
190
|
-
// Normal discovery with timeout
|
|
191
|
-
logStep("MODE_FULL_DISCOVERY", "Starting full discovery (USB+NETWORK+EMULATOR), timeout=" + DISCOVERY_TIMEOUT_DEVICE_MS + "ms");
|
|
192
|
-
isEmulatorMode.set(false);
|
|
193
|
-
startFullDiscovery();
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Stop discovery scan
|
|
199
|
-
*/
|
|
200
|
-
public void stopDiscovery() {
|
|
201
|
-
Log.i(TAG, "[FLIR] stopDiscovery()");
|
|
202
|
-
cancelDiscoveryTimeout();
|
|
203
|
-
isDiscovering.set(false);
|
|
143
|
+
Log.d(TAG, "Starting discovery for EMULATOR, NETWORK, USB...");
|
|
204
144
|
|
|
205
145
|
try {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
146
|
+
DiscoveryFactory.getInstance().scan(
|
|
147
|
+
discoveryListener,
|
|
148
|
+
CommunicationInterface.EMULATOR,
|
|
149
|
+
CommunicationInterface.NETWORK,
|
|
150
|
+
CommunicationInterface.USB
|
|
151
|
+
);
|
|
152
|
+
} catch (Exception e) {
|
|
153
|
+
Log.e(TAG, "Failed to start scan", e);
|
|
154
|
+
isScanning = false;
|
|
155
|
+
notifyError("Scan failed: " + e.getMessage());
|
|
212
156
|
}
|
|
213
157
|
}
|
|
214
158
|
|
|
215
159
|
/**
|
|
216
|
-
*
|
|
160
|
+
* Stop scanning for devices
|
|
217
161
|
*/
|
|
218
|
-
public void
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// Find device in discovered list
|
|
222
|
-
DeviceInfo target = null;
|
|
223
|
-
for (DeviceInfo d : discoveredDevices) {
|
|
224
|
-
if (d.deviceId.equals(deviceId)) {
|
|
225
|
-
target = d;
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (target == null) {
|
|
231
|
-
notifyError("Device not found: " + deviceId);
|
|
162
|
+
public void stop() {
|
|
163
|
+
if (!isScanning) {
|
|
232
164
|
return;
|
|
233
165
|
}
|
|
234
166
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
167
|
+
try {
|
|
168
|
+
DiscoveryFactory.getInstance().stop(
|
|
169
|
+
CommunicationInterface.EMULATOR,
|
|
170
|
+
CommunicationInterface.NETWORK,
|
|
171
|
+
CommunicationInterface.USB
|
|
172
|
+
);
|
|
173
|
+
} catch (Exception e) {
|
|
174
|
+
Log.e(TAG, "Failed to stop scan", e);
|
|
238
175
|
}
|
|
239
176
|
|
|
240
|
-
|
|
241
|
-
|
|
177
|
+
isScanning = false;
|
|
178
|
+
Log.d(TAG, "Discovery stopped");
|
|
242
179
|
}
|
|
243
180
|
|
|
244
181
|
/**
|
|
245
|
-
*
|
|
182
|
+
* Get list of discovered devices
|
|
246
183
|
*/
|
|
247
|
-
public
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Stop streaming first
|
|
251
|
-
stopStreaming();
|
|
252
|
-
|
|
253
|
-
// Disconnect camera
|
|
254
|
-
if (cameraObj != null) {
|
|
255
|
-
try {
|
|
256
|
-
Method disconnectMethod = cameraObj.getClass().getMethod("disconnect");
|
|
257
|
-
disconnectMethod.invoke(cameraObj);
|
|
258
|
-
Log.i(TAG, "[FLIR] Camera disconnected");
|
|
259
|
-
} catch (Throwable t) {
|
|
260
|
-
Log.w(TAG, "[FLIR] disconnect failed: " + t.getMessage());
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
cameraObj = null;
|
|
265
|
-
streamerObj = null;
|
|
266
|
-
currentStream = null;
|
|
267
|
-
connectedDevice = null;
|
|
268
|
-
isConnected.set(false);
|
|
269
|
-
isStreaming.set(false);
|
|
270
|
-
|
|
271
|
-
if (listener != null) {
|
|
272
|
-
mainHandler.post(() -> listener.onDeviceDisconnected());
|
|
273
|
-
}
|
|
184
|
+
public List<Identity> getDiscoveredDevices() {
|
|
185
|
+
return new ArrayList<>(discoveredDevices);
|
|
274
186
|
}
|
|
275
187
|
|
|
276
188
|
/**
|
|
277
|
-
*
|
|
189
|
+
* Connect to a device
|
|
278
190
|
*/
|
|
279
|
-
public void
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (streamerObj == null) {
|
|
283
|
-
Log.w(TAG, "[FLIR] Cannot set palette - no active streamer");
|
|
191
|
+
public void connect(Identity identity) {
|
|
192
|
+
if (identity == null) {
|
|
193
|
+
notifyError("Invalid identity");
|
|
284
194
|
return;
|
|
285
195
|
}
|
|
286
196
|
|
|
287
|
-
|
|
197
|
+
// Disconnect if already connected
|
|
198
|
+
if (camera != null) {
|
|
199
|
+
disconnect();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Log.d(TAG, "Connecting to: " + identity.deviceId);
|
|
203
|
+
|
|
204
|
+
// Run connection on background thread since it's blocking
|
|
205
|
+
executor.execute(() -> {
|
|
288
206
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Method getDefaultPalettes = paletteManagerClass.getMethod("getDefaultPalettes");
|
|
292
|
-
Object palettes = getDefaultPalettes.invoke(null);
|
|
207
|
+
camera = new Camera();
|
|
208
|
+
camera.connect(identity, connectionStatusListener, new ConnectParameters());
|
|
293
209
|
|
|
294
|
-
|
|
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
|
-
}
|
|
210
|
+
Log.d(TAG, "Connected to camera");
|
|
306
211
|
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
Log.i(TAG, "[FLIR] Palette set to: " + paletteName);
|
|
310
|
-
} else {
|
|
311
|
-
Log.w(TAG, "[FLIR] Palette not found: " + paletteName);
|
|
212
|
+
if (listener != null) {
|
|
213
|
+
listener.onConnected(identity);
|
|
312
214
|
}
|
|
313
|
-
} catch (
|
|
314
|
-
Log.
|
|
215
|
+
} catch (Exception e) {
|
|
216
|
+
Log.e(TAG, "Connection failed", e);
|
|
217
|
+
camera = null;
|
|
218
|
+
notifyError("Connection failed: " + e.getMessage());
|
|
315
219
|
}
|
|
316
220
|
});
|
|
317
221
|
}
|
|
318
222
|
|
|
319
223
|
/**
|
|
320
|
-
*
|
|
321
|
-
* Called automatically when thermal streaming starts if no palette is set.
|
|
224
|
+
* Disconnect from current device
|
|
322
225
|
*/
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
}
|
|
357
|
-
}
|
|
226
|
+
public void disconnect() {
|
|
227
|
+
stopStream();
|
|
228
|
+
|
|
229
|
+
if (camera != null) {
|
|
230
|
+
try {
|
|
231
|
+
camera.disconnect();
|
|
232
|
+
} catch (Exception e) {
|
|
233
|
+
Log.e(TAG, "Error disconnecting", e);
|
|
358
234
|
}
|
|
359
|
-
|
|
360
|
-
Log.w(TAG, "[FLIR] initializeDefaultPalette failed: " + t.getMessage());
|
|
235
|
+
camera = null;
|
|
361
236
|
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Get temperature at a specific point
|
|
366
|
-
*/
|
|
367
|
-
public double getTemperatureAtPoint(int x, int y) {
|
|
368
|
-
if (streamerObj == null) return Double.NaN;
|
|
369
237
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
Method getImage = streamerObj.getClass().getMethod("getImage");
|
|
373
|
-
Object thermalImage = getImage.invoke(streamerObj);
|
|
374
|
-
|
|
375
|
-
if (thermalImage != null) {
|
|
376
|
-
// Try getValueAt(Point)
|
|
377
|
-
try {
|
|
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) {}
|
|
395
|
-
}
|
|
396
|
-
} catch (Throwable t) {
|
|
397
|
-
Log.d(TAG, "[FLIR] getTemperatureAtPoint failed: " + t.getMessage());
|
|
238
|
+
if (listener != null) {
|
|
239
|
+
listener.onDisconnected();
|
|
398
240
|
}
|
|
399
241
|
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Get latest frame bitmap
|
|
405
|
-
*/
|
|
406
|
-
public Bitmap getLatestFrame() {
|
|
407
|
-
return latestFrame;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Check if streaming is active
|
|
412
|
-
*/
|
|
413
|
-
public boolean isStreamingActive() {
|
|
414
|
-
return isStreaming.get();
|
|
242
|
+
Log.d(TAG, "Disconnected");
|
|
415
243
|
}
|
|
416
244
|
|
|
417
245
|
/**
|
|
418
246
|
* Check if connected
|
|
419
247
|
*/
|
|
420
248
|
public boolean isConnected() {
|
|
421
|
-
return
|
|
249
|
+
return camera != null;
|
|
422
250
|
}
|
|
423
251
|
|
|
424
252
|
/**
|
|
425
|
-
*
|
|
253
|
+
* Start streaming from connected device
|
|
426
254
|
*/
|
|
427
|
-
public
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Get list of discovered devices
|
|
433
|
-
*/
|
|
434
|
-
public List<DeviceInfo> getDiscoveredDevices() {
|
|
435
|
-
return new ArrayList<>(discoveredDevices);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Cleanup resources
|
|
440
|
-
*/
|
|
441
|
-
public void stop() {
|
|
442
|
-
Log.i(TAG, "[FLIR] stop()");
|
|
443
|
-
stopDiscovery();
|
|
444
|
-
disconnect();
|
|
445
|
-
scheduler.shutdownNow();
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// ==================== DISCOVERY IMPLEMENTATION ====================
|
|
449
|
-
|
|
450
|
-
private void startFullDiscovery() {
|
|
451
|
-
Log.i(TAG, "[FLIR] Starting full discovery (USB, NETWORK, EMULATOR)");
|
|
452
|
-
|
|
453
|
-
if (!initializeSdk()) {
|
|
454
|
-
Log.w(TAG, "[FLIR] SDK not available, falling back to emulator");
|
|
455
|
-
startEmulatorDiscovery();
|
|
255
|
+
public void startStream() {
|
|
256
|
+
if (camera == null) {
|
|
257
|
+
notifyError("Not connected");
|
|
456
258
|
return;
|
|
457
259
|
}
|
|
458
260
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
Class<?> discoveryFactoryClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryFactory");
|
|
467
|
-
Method getInstance = discoveryFactoryClass.getMethod("getInstance");
|
|
468
|
-
discoveryFactory = getInstance.invoke(null);
|
|
469
|
-
|
|
470
|
-
// Get CommunicationInterface enum values
|
|
471
|
-
Class<?> commIfaceClass = findSdkClass("com.flir.thermalsdk.live.CommunicationInterface");
|
|
472
|
-
Object usbInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "USB");
|
|
473
|
-
Object networkInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "NETWORK");
|
|
474
|
-
Object emulatorInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "EMULATOR");
|
|
475
|
-
|
|
476
|
-
// Create discovery listener proxy
|
|
477
|
-
Class<?> listenerClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryEventListener");
|
|
478
|
-
discoveryListener = Proxy.newProxyInstance(
|
|
479
|
-
getEffectiveClassLoader(),
|
|
480
|
-
new Class<?>[] { listenerClass },
|
|
481
|
-
(proxy, method, args) -> handleDiscoveryCallback(method.getName(), args)
|
|
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");
|
|
495
|
-
|
|
496
|
-
// Set discovery timeout
|
|
497
|
-
startDiscoveryTimeout(DISCOVERY_TIMEOUT_DEVICE_MS);
|
|
498
|
-
|
|
499
|
-
} catch (Throwable t) {
|
|
500
|
-
Log.e(TAG, "[FLIR] startFullDiscovery failed: " + t.getMessage(), t);
|
|
501
|
-
notifyError("Discovery failed: " + t.getMessage());
|
|
502
|
-
// Fallback to emulator
|
|
503
|
-
startEmulatorDiscovery();
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
private void startEmulatorDiscovery() {
|
|
508
|
-
logStep("EMULATOR_DISCOVERY_START", "type=" + emulatorType);
|
|
509
|
-
Log.i(TAG, "[FLIR] Starting emulator discovery (type=" + emulatorType + ")");
|
|
510
|
-
|
|
511
|
-
if (!initializeSdk()) {
|
|
512
|
-
logStep("SDK_INIT_FAILED", "FLIR SDK not available - check if SDK JAR is loaded");
|
|
513
|
-
notifyError("FLIR SDK not available");
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
logStep("SDK_INITIALIZED", "FLIR SDK successfully initialized");
|
|
517
|
-
|
|
518
|
-
isDiscovering.set(true);
|
|
519
|
-
isEmulatorMode.set(true);
|
|
520
|
-
|
|
521
|
-
try {
|
|
522
|
-
// Get DiscoveryFactory
|
|
523
|
-
Class<?> discoveryFactoryClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryFactory");
|
|
524
|
-
Method getInstance = discoveryFactoryClass.getMethod("getInstance");
|
|
525
|
-
discoveryFactory = getInstance.invoke(null);
|
|
526
|
-
|
|
527
|
-
// Get EMULATOR interface
|
|
528
|
-
Class<?> commIfaceClass = findSdkClass("com.flir.thermalsdk.live.CommunicationInterface");
|
|
529
|
-
Object emulatorInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "EMULATOR");
|
|
530
|
-
|
|
531
|
-
// Create discovery listener
|
|
532
|
-
Class<?> listenerClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryEventListener");
|
|
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);
|
|
546
|
-
|
|
547
|
-
logStep("EMULATOR_SCAN_STARTED", "Scanning for emulator devices...");
|
|
548
|
-
Log.i(TAG, "[FLIR] Emulator discovery started");
|
|
549
|
-
|
|
550
|
-
} catch (Throwable t) {
|
|
551
|
-
logStep("EMULATOR_DISCOVERY_ERROR", "Failed: " + t.getMessage());
|
|
552
|
-
Log.e(TAG, "[FLIR] startEmulatorDiscovery failed: " + t.getMessage(), t);
|
|
553
|
-
notifyError("Emulator discovery failed: " + t.getMessage());
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
private Object handleDiscoveryCallback(String methodName, Object[] args) {
|
|
558
|
-
switch (methodName) {
|
|
559
|
-
case "onCameraFound":
|
|
560
|
-
if (args != null && args.length > 0) {
|
|
561
|
-
handleCameraFound(args[0]);
|
|
562
|
-
}
|
|
563
|
-
break;
|
|
564
|
-
|
|
565
|
-
case "onCameraLost":
|
|
566
|
-
if (args != null && args.length > 0) {
|
|
567
|
-
handleCameraLost(args[0]);
|
|
261
|
+
executor.execute(() -> {
|
|
262
|
+
try {
|
|
263
|
+
// Get available streams
|
|
264
|
+
List<Stream> streams = camera.getStreams();
|
|
265
|
+
if (streams == null || streams.isEmpty()) {
|
|
266
|
+
notifyError("No streams available");
|
|
267
|
+
return;
|
|
568
268
|
}
|
|
569
|
-
break;
|
|
570
269
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
private void handleCameraFound(Object discoveredCamera) {
|
|
581
|
-
try {
|
|
582
|
-
// Get identity from DiscoveredCamera
|
|
583
|
-
Method getIdentity = discoveredCamera.getClass().getMethod("getIdentity");
|
|
584
|
-
Object identity = getIdentity.invoke(discoveredCamera);
|
|
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;
|
|
270
|
+
// Find thermal stream
|
|
271
|
+
Stream thermalStream = null;
|
|
272
|
+
for (Stream stream : streams) {
|
|
273
|
+
if (stream.isThermal()) {
|
|
274
|
+
thermalStream = stream;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
604
277
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
if (!exists) {
|
|
608
|
-
discoveredDevices.add(deviceInfo);
|
|
609
278
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
final List<DeviceInfo> devices = new ArrayList<>(discoveredDevices);
|
|
613
|
-
mainHandler.post(() -> {
|
|
614
|
-
listener.onDeviceFound(deviceId, deviceName, isEmulator);
|
|
615
|
-
listener.onDeviceListUpdated(devices);
|
|
616
|
-
});
|
|
279
|
+
if (thermalStream == null) {
|
|
280
|
+
thermalStream = streams.get(0);
|
|
617
281
|
}
|
|
618
282
|
|
|
619
|
-
|
|
620
|
-
|
|
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);
|
|
628
|
-
}
|
|
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);
|
|
643
|
-
|
|
644
|
-
// Remove from list
|
|
645
|
-
discoveredDevices.removeIf(d -> d.deviceId.equals(deviceId));
|
|
646
|
-
|
|
647
|
-
// If this was our connected device, disconnect
|
|
648
|
-
if (connectedDevice != null && connectedDevice.deviceId.equals(deviceId)) {
|
|
649
|
-
disconnect();
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
} catch (Throwable t) {
|
|
653
|
-
Log.w(TAG, "[FLIR] handleCameraLost failed: " + t.getMessage());
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
private void startDiscoveryTimeout(long timeoutMs) {
|
|
658
|
-
cancelDiscoveryTimeout();
|
|
659
|
-
|
|
660
|
-
if (timeoutMs <= 0) return;
|
|
661
|
-
|
|
662
|
-
discoveryTimeoutFuture = scheduler.schedule(() -> {
|
|
663
|
-
Log.i(TAG, "[FLIR] Discovery timeout");
|
|
664
|
-
isDiscovering.set(false);
|
|
665
|
-
|
|
666
|
-
if (listener != null) {
|
|
667
|
-
mainHandler.post(() -> listener.onDiscoveryTimeout());
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// If no physical devices found, try emulator
|
|
671
|
-
boolean hasPhysicalDevice = false;
|
|
672
|
-
for (DeviceInfo d : discoveredDevices) {
|
|
673
|
-
if (!d.isEmulator) {
|
|
674
|
-
hasPhysicalDevice = true;
|
|
675
|
-
break;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (!hasPhysicalDevice) {
|
|
680
|
-
Log.i(TAG, "[FLIR] No physical devices found, starting emulator");
|
|
681
|
-
startEmulatorDiscovery();
|
|
682
|
-
} else if (!discoveredDevices.isEmpty()) {
|
|
683
|
-
// Connect to first device
|
|
684
|
-
connectToIdentity(discoveredDevices.get(0));
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
}, timeoutMs, TimeUnit.MILLISECONDS);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
private void cancelDiscoveryTimeout() {
|
|
691
|
-
if (discoveryTimeoutFuture != null) {
|
|
692
|
-
discoveryTimeoutFuture.cancel(false);
|
|
693
|
-
discoveryTimeoutFuture = null;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// ==================== CONNECTION IMPLEMENTATION ====================
|
|
698
|
-
|
|
699
|
-
private void connectToIdentity(DeviceInfo deviceInfo) {
|
|
700
|
-
logStep("CONNECT_START", "Connecting to: " + deviceInfo.deviceName + " (" + deviceInfo.commInterface + ")");
|
|
701
|
-
Log.i(TAG, "[FLIR] Connecting to: " + deviceInfo.deviceName);
|
|
702
|
-
|
|
703
|
-
scheduler.submit(() -> {
|
|
704
|
-
try {
|
|
705
|
-
// Create Camera instance
|
|
706
|
-
logStep("CREATE_CAMERA", "Creating Camera instance");
|
|
707
|
-
Class<?> cameraClass = findSdkClass("com.flir.thermalsdk.live.Camera");
|
|
708
|
-
cameraObj = cameraClass.newInstance();
|
|
283
|
+
activeStream = thermalStream;
|
|
284
|
+
streamer = new ThermalStreamer(thermalStream);
|
|
709
285
|
|
|
710
|
-
//
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
|
|
286
|
+
// Start receiving frames using OnReceived and OnRemoteError
|
|
287
|
+
thermalStream.start(
|
|
288
|
+
(OnReceived<Void>) v -> {
|
|
289
|
+
// FRAME DROP GUARD: Skip frame if still processing previous one
|
|
290
|
+
// This prevents thread buildup and ensures smooth frame flow
|
|
291
|
+
long now = System.currentTimeMillis();
|
|
292
|
+
if (isProcessingFrame || (now - lastFrameProcessedMs < MIN_FRAME_INTERVAL_MS)) {
|
|
293
|
+
// Drop frame - processing is behind or too soon since last frame
|
|
294
|
+
return;
|
|
719
295
|
}
|
|
720
|
-
|
|
296
|
+
|
|
297
|
+
// Mark processing start before queuing task
|
|
298
|
+
isProcessingFrame = true;
|
|
299
|
+
|
|
300
|
+
// Use single-threaded frameExecutor to ensure ordered frame processing
|
|
301
|
+
frameExecutor.execute(() -> {
|
|
302
|
+
try {
|
|
303
|
+
if (streamer != null) {
|
|
304
|
+
streamer.update();
|
|
305
|
+
|
|
306
|
+
// Get ImageBuffer and convert to Bitmap
|
|
307
|
+
ImageBuffer imageBuffer = streamer.getImage();
|
|
308
|
+
if (imageBuffer != null && listener != null) {
|
|
309
|
+
BitmapAndroid bitmapAndroid = BitmapAndroid.createBitmap(imageBuffer);
|
|
310
|
+
Bitmap bitmap = bitmapAndroid.getBitMap();
|
|
311
|
+
if (bitmap != null) {
|
|
312
|
+
listener.onFrame(bitmap);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (Exception e) {
|
|
317
|
+
Log.e(TAG, "Error processing frame", e);
|
|
318
|
+
} finally {
|
|
319
|
+
// Reset frame processing guard to allow next frame
|
|
320
|
+
lastFrameProcessedMs = System.currentTimeMillis();
|
|
321
|
+
isProcessingFrame = false;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
error -> {
|
|
326
|
+
Log.e(TAG, "Stream error: " + error);
|
|
327
|
+
notifyError("Stream error: " + error);
|
|
721
328
|
}
|
|
722
329
|
);
|
|
723
330
|
|
|
724
|
-
|
|
725
|
-
boolean connected = false;
|
|
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) {}
|
|
737
|
-
|
|
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
|
-
isConnected.set(true);
|
|
331
|
+
Log.d(TAG, "Streaming started");
|
|
765
332
|
|
|
766
|
-
|
|
767
|
-
Log.
|
|
768
|
-
|
|
769
|
-
// Notify listener
|
|
770
|
-
if (listener != null) {
|
|
771
|
-
mainHandler.post(() -> listener.onDeviceConnected(
|
|
772
|
-
deviceInfo.deviceId, deviceInfo.deviceName, deviceInfo.isEmulator));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Start streaming after brief delay
|
|
776
|
-
logStep("SCHEDULE_STREAMING", "Scheduling stream start in 500ms");
|
|
777
|
-
scheduler.schedule(this::startStreaming, 500, TimeUnit.MILLISECONDS);
|
|
778
|
-
|
|
779
|
-
} catch (Throwable t) {
|
|
780
|
-
logStep("CONNECT_FAILED", "Connection failed: " + t.getMessage());
|
|
781
|
-
Log.e(TAG, "[FLIR] Connection failed: " + t.getMessage(), t);
|
|
782
|
-
notifyError("Connection failed: " + t.getMessage());
|
|
333
|
+
} catch (Exception e) {
|
|
334
|
+
Log.e(TAG, "Failed to start stream", e);
|
|
335
|
+
notifyError("Stream failed: " + e.getMessage());
|
|
783
336
|
}
|
|
784
337
|
});
|
|
785
338
|
}
|
|
786
339
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Stop streaming
|
|
342
|
+
*/
|
|
343
|
+
public void stopStream() {
|
|
344
|
+
if (activeStream != null) {
|
|
345
|
+
try {
|
|
346
|
+
activeStream.stop();
|
|
347
|
+
} catch (Exception e) {
|
|
348
|
+
Log.e(TAG, "Error stopping stream", e);
|
|
349
|
+
}
|
|
350
|
+
activeStream = null;
|
|
797
351
|
}
|
|
352
|
+
|
|
353
|
+
streamer = null;
|
|
354
|
+
|
|
355
|
+
// Reset frame processing state
|
|
356
|
+
isProcessingFrame = false;
|
|
357
|
+
lastFrameProcessedMs = 0;
|
|
358
|
+
|
|
359
|
+
Log.d(TAG, "Streaming stopped");
|
|
798
360
|
}
|
|
799
361
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Get temperature at a specific point in the image
|
|
364
|
+
* Queries the SDK directly - simple and no locks needed
|
|
365
|
+
* @param x X coordinate (0 to image width-1)
|
|
366
|
+
* @param y Y coordinate (0 to image height-1)
|
|
367
|
+
* @return Temperature in Celsius, or Double.NaN if not available
|
|
368
|
+
*/
|
|
369
|
+
public double getTemperatureAt(int x, int y) {
|
|
370
|
+
if (streamer == null) {
|
|
371
|
+
return Double.NaN;
|
|
807
372
|
}
|
|
808
373
|
|
|
809
|
-
|
|
810
|
-
Log.i(TAG, "[FLIR] Starting streaming...");
|
|
811
|
-
|
|
374
|
+
final double[] result = {Double.NaN};
|
|
812
375
|
try {
|
|
813
|
-
|
|
814
|
-
Method getStreams = cameraObj.getClass().getMethod("getStreams");
|
|
815
|
-
Object streams = getStreams.invoke(cameraObj);
|
|
816
|
-
|
|
817
|
-
if (streams == null || !(streams instanceof List) || ((List<?>) streams).isEmpty()) {
|
|
818
|
-
logStep("NO_STREAMS", "No streams available from camera");
|
|
819
|
-
Log.w(TAG, "[FLIR] No streams available");
|
|
820
|
-
return;
|
|
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;
|
|
833
|
-
|
|
376
|
+
streamer.withThermalImage(thermalImage -> {
|
|
834
377
|
try {
|
|
835
|
-
|
|
836
|
-
|
|
378
|
+
int imgWidth = thermalImage.getWidth();
|
|
379
|
+
int imgHeight = thermalImage.getHeight();
|
|
837
380
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
String name = getName != null ? String.valueOf(getName.invoke(s)) : "unknown";
|
|
381
|
+
int clampedX = Math.max(0, Math.min(imgWidth - 1, x));
|
|
382
|
+
int clampedY = Math.max(0, Math.min(imgHeight - 1, y));
|
|
841
383
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
chosenStream = s;
|
|
846
|
-
streamType = "thermal";
|
|
847
|
-
break;
|
|
848
|
-
} else if (chosenStream == null) {
|
|
849
|
-
chosenStream = s;
|
|
850
|
-
streamType = "visual";
|
|
851
|
-
}
|
|
852
|
-
} catch (Throwable ignored) {}
|
|
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);
|
|
872
|
-
|
|
873
|
-
// Initialize default palette if not already set
|
|
874
|
-
if (currentPalette == null) {
|
|
875
|
-
logStep("INIT_PALETTE", "Initializing default 'iron' palette");
|
|
876
|
-
initializeDefaultPalette();
|
|
877
|
-
} else {
|
|
878
|
-
logStep("PALETTE_EXISTS", "Palette already set, skipping init");
|
|
879
|
-
}
|
|
880
|
-
} else {
|
|
881
|
-
logStep("CREATE_VISUAL_STREAMER", "Creating VisualStreamer");
|
|
882
|
-
Class<?> visualStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.VisualStreamer");
|
|
883
|
-
Class<?> streamClass = findSdkClass("com.flir.thermalsdk.live.streaming.Stream");
|
|
884
|
-
streamerObj = visualStreamerClass.getConstructor(streamClass).newInstance(chosenStream);
|
|
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]);
|
|
384
|
+
ThermalValue value = thermalImage.getValueAt(new Point(clampedX, clampedY));
|
|
385
|
+
if (value != null) {
|
|
386
|
+
result[0] = value.asCelsius().value;
|
|
908
387
|
}
|
|
909
|
-
|
|
388
|
+
} catch (Exception e) {
|
|
389
|
+
Log.w(TAG, "Error querying temperature", e);
|
|
910
390
|
}
|
|
911
|
-
);
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
logStep("STREAM_STARTING", "Invoking stream.start()");
|
|
915
|
-
Method startMethod = chosenStream.getClass().getMethod("start", onReceivedClass, onErrorClass);
|
|
916
|
-
startMethod.invoke(chosenStream, onReceivedCallback, onErrorCallback);
|
|
917
|
-
|
|
918
|
-
isStreaming.set(true);
|
|
919
|
-
|
|
920
|
-
logStep("STREAM_STARTED", "Stream started successfully - type=" + streamType + ", waiting for frames...");
|
|
921
|
-
Log.i(TAG, "[FLIR] Streaming started (" + streamType + ")");
|
|
922
|
-
|
|
923
|
-
if (listener != null) {
|
|
924
|
-
final String type = streamType;
|
|
925
|
-
mainHandler.post(() -> listener.onStreamStarted(type));
|
|
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;
|
|
391
|
+
});
|
|
392
|
+
} catch (Exception e) {
|
|
393
|
+
Log.w(TAG, "Temperature query failed", e);
|
|
947
394
|
}
|
|
948
395
|
|
|
949
|
-
|
|
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());
|
|
980
|
-
}
|
|
981
|
-
if (listener != null) {
|
|
982
|
-
listener.onFrame(bitmap);
|
|
983
|
-
}
|
|
984
|
-
} else if (frameCount <= 5) {
|
|
985
|
-
logStep("BITMAP_NULL", "Visual convertToBitmap returned null at frame " + frameCount);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
} catch (Throwable t) {
|
|
990
|
-
// Log errors periodically to avoid spam
|
|
991
|
-
if (System.currentTimeMillis() % 5000 < 100) {
|
|
992
|
-
Log.d(TAG, "[FLIR] Frame processing error: " + t.getMessage());
|
|
993
|
-
}
|
|
994
|
-
}
|
|
396
|
+
return result[0];
|
|
995
397
|
}
|
|
996
398
|
|
|
997
399
|
/**
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
400
|
+
* Get temperature at normalized coordinates (0.0 to 1.0)
|
|
401
|
+
* @param normalizedX X coordinate (0.0 to 1.0)
|
|
402
|
+
* @param normalizedY Y coordinate (0.0 to 1.0)
|
|
403
|
+
* @return Temperature in Celsius, or Double.NaN if not available
|
|
1001
404
|
*/
|
|
1002
|
-
|
|
1003
|
-
if (
|
|
405
|
+
public double getTemperatureAtNormalized(double normalizedX, double normalizedY) {
|
|
406
|
+
if (streamer == null) {
|
|
407
|
+
return Double.NaN;
|
|
408
|
+
}
|
|
1004
409
|
|
|
410
|
+
final double[] result = {Double.NaN};
|
|
1005
411
|
try {
|
|
1006
|
-
|
|
1007
|
-
if (!thermalStreamerClass.isInstance(streamerObj)) {
|
|
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
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Find withThermalImage method
|
|
1018
|
-
Method withThermalImageMethod = null;
|
|
1019
|
-
for (Method m : thermalStreamerClass.getMethods()) {
|
|
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
|
|
412
|
+
streamer.withThermalImage(thermalImage -> {
|
|
1029
413
|
try {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
if (
|
|
1041
|
-
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// Create Consumer proxy that does BOTH: set palette AND create bitmap
|
|
1046
|
-
// This is the key insight from LiveStreamingKotlin!
|
|
1047
|
-
final Object imgBuffer = imageBuffer;
|
|
1048
|
-
Class<?> consumerClass = withThermalImageMethod.getParameterTypes()[0];
|
|
1049
|
-
Object consumer = Proxy.newProxyInstance(
|
|
1050
|
-
getEffectiveClassLoader(),
|
|
1051
|
-
new Class<?>[] { consumerClass },
|
|
1052
|
-
(proxy, method, args) -> {
|
|
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
|
-
}
|
|
414
|
+
int width = thermalImage.getWidth();
|
|
415
|
+
int height = thermalImage.getHeight();
|
|
416
|
+
|
|
417
|
+
int x = (int) (normalizedX * (width - 1));
|
|
418
|
+
int y = (int) (normalizedY * (height - 1));
|
|
419
|
+
|
|
420
|
+
x = Math.max(0, Math.min(width - 1, x));
|
|
421
|
+
y = Math.max(0, Math.min(height - 1, y));
|
|
422
|
+
|
|
423
|
+
ThermalValue value = thermalImage.getValueAt(new Point(x, y));
|
|
424
|
+
if (value != null) {
|
|
425
|
+
result[0] = value.asCelsius().value;
|
|
1089
426
|
}
|
|
1090
|
-
|
|
427
|
+
} catch (Exception e) {
|
|
428
|
+
Log.w(TAG, "Error querying temperature (normalized)", e);
|
|
1091
429
|
}
|
|
1092
|
-
);
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
} catch (Throwable t) {
|
|
1097
|
-
Log.d(TAG, "[FLIR] processThermalFrameWithCallback failed: " + t.getMessage());
|
|
1098
|
-
// Fallback to direct conversion
|
|
1099
|
-
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1100
|
-
if (bitmap != null) {
|
|
1101
|
-
latestFrame = bitmap;
|
|
1102
|
-
if (listener != null) listener.onFrame(bitmap);
|
|
1103
|
-
}
|
|
430
|
+
});
|
|
431
|
+
} catch (Exception e) {
|
|
432
|
+
Log.w(TAG, "Temperature query failed", e);
|
|
1104
433
|
}
|
|
434
|
+
|
|
435
|
+
return result[0];
|
|
1105
436
|
}
|
|
1106
437
|
|
|
1107
438
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
* In the SDK: thermalStreamer.withThermalImage { it.palette = selectedPalette }
|
|
1110
|
-
* NOTE: This is now mostly unused - processThermalFrameWithCallback handles both palette and bitmap
|
|
439
|
+
* Set palette for thermal image rendering
|
|
1111
440
|
*/
|
|
1112
|
-
|
|
1113
|
-
if (
|
|
441
|
+
public void setPalette(String paletteName) {
|
|
442
|
+
if (streamer == null) {
|
|
443
|
+
Log.w(TAG, "No active streamer");
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
1114
446
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
if ("withThermalImage".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1124
|
-
withThermalImageMethod = m;
|
|
1125
|
-
break;
|
|
447
|
+
executor.execute(() -> {
|
|
448
|
+
try {
|
|
449
|
+
Palette palette = findPalette(paletteName);
|
|
450
|
+
if (palette != null) {
|
|
451
|
+
streamer.withThermalImage(thermalImage -> {
|
|
452
|
+
thermalImage.setPalette(palette);
|
|
453
|
+
});
|
|
454
|
+
Log.d(TAG, "Palette set to: " + paletteName);
|
|
1126
455
|
}
|
|
456
|
+
} catch (Exception e) {
|
|
457
|
+
Log.e(TAG, "Error setting palette", e);
|
|
1127
458
|
}
|
|
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);
|
|
1136
|
-
}
|
|
1137
|
-
} catch (Throwable ignored) {}
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
|
|
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
|
-
} catch (Throwable t) {
|
|
1160
|
-
Log.d(TAG, "[FLIR] applyPaletteViaThermalImage failed: " + t.getMessage());
|
|
1161
|
-
}
|
|
459
|
+
});
|
|
1162
460
|
}
|
|
1163
461
|
|
|
1164
462
|
/**
|
|
1165
|
-
*
|
|
463
|
+
* Get list of available palettes
|
|
1166
464
|
*/
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
465
|
+
public List<String> getAvailablePalettes() {
|
|
466
|
+
List<String> names = new ArrayList<>();
|
|
1170
467
|
try {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
Method setPalette = thermalImage.getClass().getMethod("setPalette", currentPalette.getClass());
|
|
1178
|
-
setPalette.invoke(thermalImage, currentPalette);
|
|
1179
|
-
} catch (Throwable ignored) {}
|
|
468
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
469
|
+
for (Palette p : palettes) {
|
|
470
|
+
names.add(p.name);
|
|
471
|
+
}
|
|
472
|
+
} catch (Exception e) {
|
|
473
|
+
Log.e(TAG, "Error getting palettes", e);
|
|
1180
474
|
}
|
|
475
|
+
return names;
|
|
1181
476
|
}
|
|
1182
477
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
478
|
+
// Find palette by name
|
|
479
|
+
private Palette findPalette(String name) {
|
|
1186
480
|
try {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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
|
-
}
|
|
481
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
482
|
+
for (Palette p : palettes) {
|
|
483
|
+
if (p.name.equalsIgnoreCase(name)) {
|
|
484
|
+
return p;
|
|
1235
485
|
}
|
|
1236
486
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
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());
|
|
487
|
+
// Return first if not found
|
|
488
|
+
if (!palettes.isEmpty()) {
|
|
489
|
+
return palettes.get(0);
|
|
1264
490
|
}
|
|
491
|
+
} catch (Exception e) {
|
|
492
|
+
Log.e(TAG, "Error finding palette", e);
|
|
1265
493
|
}
|
|
1266
|
-
|
|
1267
|
-
streamerObj = null;
|
|
1268
|
-
currentStream = null;
|
|
1269
|
-
isStreaming.set(false);
|
|
494
|
+
return null;
|
|
1270
495
|
}
|
|
1271
496
|
|
|
1272
|
-
//
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
497
|
+
// Discovery listener - no filtering, returns all devices
|
|
498
|
+
private final DiscoveryEventListener discoveryListener = new DiscoveryEventListener() {
|
|
499
|
+
@Override
|
|
500
|
+
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
501
|
+
Identity identity = discoveredCamera.getIdentity();
|
|
502
|
+
Log.d(TAG, "Device found: " + identity.deviceId +
|
|
503
|
+
" type=" + identity.communicationInterface);
|
|
1279
504
|
|
|
1280
|
-
//
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
if (attemptLoadSdkFromAar()) {
|
|
1289
|
-
initializeThermalSdk();
|
|
1290
|
-
return true;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
return false;
|
|
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
|
-
}
|
|
1313
|
-
|
|
1314
|
-
try {
|
|
1315
|
-
// First try: Load from architecture-specific DEX (new format from FlirSDKLoader)
|
|
1316
|
-
File dexFile = FlirSDKLoader.INSTANCE.getDexPath(ctx);
|
|
1317
|
-
if (dexFile != null && dexFile.exists()) {
|
|
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;
|
|
505
|
+
// Add to list if not already present
|
|
506
|
+
synchronized (discoveredDevices) {
|
|
507
|
+
boolean exists = false;
|
|
508
|
+
for (Identity d : discoveredDevices) {
|
|
509
|
+
if (d.deviceId.equals(identity.deviceId)) {
|
|
510
|
+
exists = true;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
1341
513
|
}
|
|
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;
|
|
514
|
+
if (!exists) {
|
|
515
|
+
discoveredDevices.add(identity);
|
|
1377
516
|
}
|
|
1378
517
|
}
|
|
1379
518
|
|
|
1380
|
-
if (
|
|
1381
|
-
|
|
1382
|
-
|
|
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;
|
|
519
|
+
if (listener != null) {
|
|
520
|
+
listener.onDeviceFound(identity);
|
|
521
|
+
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
1394
522
|
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@Override
|
|
526
|
+
public void onCameraLost(Identity identity) {
|
|
527
|
+
Log.d(TAG, "Device lost: " + identity.deviceId);
|
|
1395
528
|
|
|
1396
|
-
|
|
1397
|
-
|
|
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();
|
|
529
|
+
synchronized (discoveredDevices) {
|
|
530
|
+
discoveredDevices.removeIf(d -> d.deviceId.equals(identity.deviceId));
|
|
1403
531
|
}
|
|
1404
532
|
|
|
1405
|
-
|
|
1406
|
-
|
|
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;
|
|
533
|
+
if (listener != null) {
|
|
534
|
+
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
1435
535
|
}
|
|
1436
|
-
|
|
1437
|
-
} catch (Throwable t) {
|
|
1438
|
-
Log.e(TAG, "[FLIR SDK] attemptLoadSdkFromAar failed: " + t.getMessage(), t);
|
|
1439
536
|
}
|
|
1440
537
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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;
|
|
538
|
+
@Override
|
|
539
|
+
public void onDiscoveryError(CommunicationInterface iface, ErrorCode error) {
|
|
540
|
+
Log.e(TAG, "Discovery error: " + iface + " - " + error);
|
|
541
|
+
notifyError("Discovery error: " + error);
|
|
1452
542
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
// ==================== HELPERS ====================
|
|
1460
|
-
|
|
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();
|
|
543
|
+
|
|
544
|
+
@Override
|
|
545
|
+
public void onDiscoveryFinished(CommunicationInterface iface) {
|
|
546
|
+
Log.d(TAG, "Discovery finished for: " + iface);
|
|
1468
547
|
}
|
|
1469
|
-
}
|
|
548
|
+
};
|
|
1470
549
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
if (result != null && !result.toString().isEmpty()) {
|
|
1478
|
-
return result.toString();
|
|
1479
|
-
}
|
|
1480
|
-
} catch (Throwable ignored) {}
|
|
550
|
+
// Connection status listener
|
|
551
|
+
private final ConnectionStatusListener connectionStatusListener = new ConnectionStatusListener() {
|
|
552
|
+
@Override
|
|
553
|
+
public void onDisconnected(ErrorCode error) {
|
|
554
|
+
Log.d(TAG, "Disconnected: " + (error != null ? error : "clean"));
|
|
555
|
+
camera = null;
|
|
1481
556
|
|
|
1482
|
-
|
|
1483
|
-
|
|
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;
|
|
557
|
+
if (listener != null) {
|
|
558
|
+
listener.onDisconnected();
|
|
1500
559
|
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
1504
562
|
|
|
1505
|
-
|
|
1506
|
-
|
|
563
|
+
// Helper to notify errors
|
|
564
|
+
private void notifyError(String message) {
|
|
1507
565
|
if (listener != null) {
|
|
1508
|
-
|
|
566
|
+
listener.onError(message);
|
|
1509
567
|
}
|
|
1510
568
|
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Cleanup resources
|
|
572
|
+
*/
|
|
573
|
+
public void destroy() {
|
|
574
|
+
stop();
|
|
575
|
+
disconnect();
|
|
576
|
+
discoveredDevices.clear();
|
|
577
|
+
listener = null;
|
|
578
|
+
instance = null;
|
|
579
|
+
Log.d(TAG, "Destroyed");
|
|
580
|
+
}
|
|
1511
581
|
}
|