ilabs-flir 1.0.3 → 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/FlirCommands.java +40 -15
- package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +26 -46
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +265 -42
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +32 -0
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +84 -195
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +667 -797
- package/package.json +1 -1
- package/sdk-manifest.json +14 -7
- 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/ErrorCodeException.java +0 -14
- 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/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/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/connectivity/ConnectionStatusListener.java +0 -7
- 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/flir/android/CameraHandler.java +0 -224
- package/android/Flir/src/main/java/flir/android/FlirConnectionManager.java +0 -354
- package/android/Flir/src/main/java/flir/android/FlirController.kt +0 -11
- package/android/Flir/src/main/java/flir/android/FlirDiscoveryManager.java +0 -236
- package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +0 -14
|
@@ -1,890 +1,760 @@
|
|
|
1
1
|
package flir.android;
|
|
2
2
|
|
|
3
3
|
import android.graphics.Bitmap;
|
|
4
|
+
import android.os.Handler;
|
|
5
|
+
import android.os.Looper;
|
|
4
6
|
import android.util.Log;
|
|
5
7
|
|
|
6
|
-
import
|
|
7
|
-
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
|
+
|
|
31
|
+
import java.util.ArrayList;
|
|
8
32
|
import java.util.List;
|
|
9
|
-
import java.
|
|
10
|
-
import java.io.FileOutputStream;
|
|
11
|
-
import java.util.zip.ZipEntry;
|
|
12
|
-
import java.util.zip.ZipFile;
|
|
13
|
-
import dalvik.system.DexClassLoader;
|
|
33
|
+
import java.util.concurrent.CopyOnWriteArrayList;
|
|
14
34
|
import java.util.concurrent.Executors;
|
|
15
35
|
import java.util.concurrent.ScheduledExecutorService;
|
|
36
|
+
import java.util.concurrent.ScheduledFuture;
|
|
16
37
|
import java.util.concurrent.TimeUnit;
|
|
38
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
17
39
|
|
|
18
40
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
41
|
+
* FLIR SDK Manager - Handles device discovery, connection, and streaming.
|
|
42
|
+
* Uses the official FLIR ThermalSDK directly (bundled in AAR).
|
|
43
|
+
*
|
|
44
|
+
* Supports USB, NETWORK (FLIR ONE Edge), and EMULATOR interfaces.
|
|
21
45
|
*/
|
|
22
|
-
class FlirSdkManager {
|
|
46
|
+
public class FlirSdkManager {
|
|
23
47
|
private static final String TAG = "FlirSdkManager";
|
|
48
|
+
private static final String FLOW_TAG = "FLIR_FLOW";
|
|
49
|
+
|
|
50
|
+
// Discovery timeout in milliseconds
|
|
51
|
+
private static final long DISCOVERY_TIMEOUT_DEVICE_MS = 5000;
|
|
52
|
+
private static final long DISCOVERY_TIMEOUT_EMULATOR_MS = 0;
|
|
53
|
+
|
|
54
|
+
// Emulator types
|
|
55
|
+
public enum EmulatorType {
|
|
56
|
+
FLIR_ONE_EDGE, // WiFi emulator
|
|
57
|
+
FLIR_ONE // USB emulator
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Communication interfaces
|
|
61
|
+
public enum CommInterface {
|
|
62
|
+
USB,
|
|
63
|
+
NETWORK,
|
|
64
|
+
EMULATOR
|
|
65
|
+
}
|
|
24
66
|
|
|
25
|
-
|
|
67
|
+
// Listener interface for callbacks
|
|
68
|
+
public interface Listener {
|
|
26
69
|
void onFrame(Bitmap bitmap);
|
|
27
|
-
|
|
28
70
|
void onTemperature(double temp, int x, int y);
|
|
29
|
-
|
|
30
|
-
void
|
|
31
|
-
|
|
32
|
-
void
|
|
33
|
-
|
|
34
|
-
void
|
|
71
|
+
void onDeviceFound(String deviceId, String deviceName, boolean isEmulator);
|
|
72
|
+
void onDeviceListUpdated(List<DeviceInfo> devices);
|
|
73
|
+
void onDeviceConnected(String deviceId, String deviceName, boolean isEmulator);
|
|
74
|
+
void onDeviceDisconnected();
|
|
75
|
+
void onDiscoveryStarted();
|
|
76
|
+
void onDiscoveryTimeout();
|
|
77
|
+
void onStreamStarted(String streamType);
|
|
78
|
+
void onError(String error);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Device info class
|
|
82
|
+
public static class DeviceInfo {
|
|
83
|
+
public final String deviceId;
|
|
84
|
+
public final String deviceName;
|
|
85
|
+
public final boolean isEmulator;
|
|
86
|
+
public final CommInterface commInterface;
|
|
87
|
+
public final Identity identity;
|
|
88
|
+
|
|
89
|
+
DeviceInfo(String id, String name, boolean emu, CommInterface iface, Identity identity) {
|
|
90
|
+
this.deviceId = id;
|
|
91
|
+
this.deviceName = name;
|
|
92
|
+
this.isEmulator = emu;
|
|
93
|
+
this.commInterface = iface;
|
|
94
|
+
this.identity = identity;
|
|
95
|
+
}
|
|
35
96
|
}
|
|
36
97
|
|
|
37
|
-
private Listener listener;
|
|
38
|
-
|
|
98
|
+
private final Listener listener;
|
|
99
|
+
private final android.content.Context appContext;
|
|
100
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
101
|
+
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
|
102
|
+
|
|
39
103
|
// SDK objects
|
|
40
|
-
private
|
|
41
|
-
private
|
|
42
|
-
private
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
private final
|
|
104
|
+
private Camera camera = null;
|
|
105
|
+
private Stream currentStream = null;
|
|
106
|
+
private ThermalStreamer thermalStreamer = null;
|
|
107
|
+
private Palette currentPalette = null;
|
|
108
|
+
|
|
109
|
+
// State tracking
|
|
110
|
+
private final AtomicBoolean isDiscovering = new AtomicBoolean(false);
|
|
111
|
+
private final AtomicBoolean isConnected = new AtomicBoolean(false);
|
|
112
|
+
private final AtomicBoolean isStreaming = new AtomicBoolean(false);
|
|
113
|
+
private final AtomicBoolean isEmulatorMode = new AtomicBoolean(false);
|
|
114
|
+
private final CopyOnWriteArrayList<DeviceInfo> discoveredDevices = new CopyOnWriteArrayList<>();
|
|
115
|
+
private ScheduledFuture<?> discoveryTimeoutFuture = null;
|
|
116
|
+
private DeviceInfo connectedDevice = null;
|
|
117
|
+
private EmulatorType emulatorType = EmulatorType.FLIR_ONE_EDGE;
|
|
118
|
+
|
|
119
|
+
// Frame state
|
|
47
120
|
private volatile Bitmap latestFrame = null;
|
|
48
|
-
|
|
49
|
-
|
|
121
|
+
private volatile ThermalImage currentThermalImage = null;
|
|
122
|
+
private String currentStreamKind = null;
|
|
123
|
+
|
|
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;
|
|
130
|
+
|
|
131
|
+
FlirSdkManager(Listener listener, android.content.Context context) {
|
|
50
132
|
this.listener = listener;
|
|
133
|
+
this.appContext = context != null ? context.getApplicationContext() : null;
|
|
51
134
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
case "onCameraFound":
|
|
71
|
-
Object discovered = args[0];
|
|
72
|
-
Log.i(TAG, "[FLIR SDK] Camera found: " + discovered);
|
|
73
|
-
connectToSdkCamera(discovered);
|
|
74
|
-
return null;
|
|
75
|
-
|
|
76
|
-
case "onCameraLost":
|
|
77
|
-
Log.i(TAG, "[FLIR SDK] Camera lost");
|
|
78
|
-
cameraObj = null;
|
|
79
|
-
streamerObj = null;
|
|
80
|
-
return null;
|
|
81
|
-
default:
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Get NETWORK interface for FLIR ONE Edge
|
|
87
|
-
Object networkInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "NETWORK");
|
|
88
|
-
|
|
89
|
-
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass,
|
|
90
|
-
java.lang.reflect.Array.newInstance(commIfaceClass, 0).getClass());
|
|
91
|
-
// Scan for BOTH NETWORK (FLIR ONE Edge) and EMULATOR
|
|
92
|
-
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 2);
|
|
93
|
-
java.lang.reflect.Array.set(ifaceArray, 0, networkInterface);
|
|
94
|
-
java.lang.reflect.Array.set(ifaceArray, 1, emulatorInterface);
|
|
95
|
-
scanMethod.invoke(discoveryFactory, discoveryListener, ifaceArray);
|
|
96
|
-
Log.i(TAG, "[FLIR SDK] Started discovery scan for NETWORK and EMULATOR");
|
|
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");
|
|
97
153
|
return true;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (attemptLoadSdkFromAar()) {
|
|
103
|
-
// If class is now available via sdkClassLoader retry using that loader
|
|
104
|
-
Log.i(TAG,
|
|
105
|
-
"[FLIR SDK] SDK classes found via DexClassLoader, retrying discovery using sdkClassLoader");
|
|
106
|
-
try {
|
|
107
|
-
return startSdkEmulatorWithClassLoader(sdkClassLoader);
|
|
108
|
-
} catch (Throwable t) {
|
|
109
|
-
Log.w(TAG, "[FLIR SDK] retry using sdkClassLoader failed: " + t.getMessage());
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
} catch (Throwable t) {
|
|
113
|
-
Log.w(TAG, "[FLIR SDK] DexClassLoader attempt failed: " + t.getMessage());
|
|
114
|
-
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (appContext == null) {
|
|
157
|
+
Log.e(TAG, "[FLIR SDK] No context available");
|
|
115
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;
|
|
116
167
|
} catch (Throwable t) {
|
|
117
|
-
Log.
|
|
168
|
+
Log.e(TAG, "[FLIR SDK] Initialization failed: " + t.getMessage(), t);
|
|
169
|
+
notifyError("SDK initialization failed: " + t.getMessage());
|
|
118
170
|
return false;
|
|
119
171
|
}
|
|
120
172
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
173
|
+
|
|
174
|
+
// ==================== PUBLIC API ====================
|
|
175
|
+
|
|
176
|
+
public void setEmulatorType(EmulatorType type) {
|
|
177
|
+
this.emulatorType = type;
|
|
178
|
+
Log.i(TAG, "[FLIR] Emulator type set to: " + type);
|
|
179
|
+
logStep("SET_EMULATOR_TYPE", "type=" + type);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public void startDiscovery(boolean forceEmulator) {
|
|
183
|
+
resetFlowTracking();
|
|
184
|
+
logStep("START_DISCOVERY", "forceEmulator=" + forceEmulator + ", emulatorType=" + emulatorType);
|
|
185
|
+
Log.i(TAG, "[FLIR] startDiscovery(forceEmulator=" + forceEmulator + ")");
|
|
186
|
+
|
|
187
|
+
// Disconnect current device first
|
|
188
|
+
if (isConnected.get()) {
|
|
189
|
+
logStep("DISCONNECT_PREVIOUS", "Disconnecting current device");
|
|
190
|
+
disconnect();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
discoveredDevices.clear();
|
|
194
|
+
logStep("CLEAR_DEVICES", "Cleared discovered devices list");
|
|
195
|
+
|
|
196
|
+
if (forceEmulator) {
|
|
197
|
+
logStep("MODE_EMULATOR", "Forcing emulator mode");
|
|
198
|
+
isEmulatorMode.set(true);
|
|
199
|
+
startEmulatorDiscovery();
|
|
200
|
+
} else {
|
|
201
|
+
logStep("MODE_FULL_DISCOVERY", "Starting full discovery, timeout=" + DISCOVERY_TIMEOUT_DEVICE_MS + "ms");
|
|
202
|
+
isEmulatorMode.set(false);
|
|
203
|
+
startFullDiscovery();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public void stopDiscovery() {
|
|
208
|
+
Log.i(TAG, "[FLIR] stopDiscovery()");
|
|
209
|
+
cancelDiscoveryTimeout();
|
|
210
|
+
isDiscovering.set(false);
|
|
211
|
+
|
|
127
212
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
candList.add(new File(filesDir, "thermalsdk-release.aar"));
|
|
143
|
-
} catch (Throwable ignored) {
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
candList.add(new File(filesDir, "thermalsdk.aar"));
|
|
147
|
-
} catch (Throwable ignored) {
|
|
148
|
-
}
|
|
149
|
-
// Add external files dir candidates if available
|
|
150
|
-
try {
|
|
151
|
-
if (ctx.getExternalFilesDir(null) != null) {
|
|
152
|
-
candList.add(new File(ctx.getExternalFilesDir(null), "thermalsdk-release.aar"));
|
|
153
|
-
candList.add(new File(ctx.getExternalFilesDir(null), "thermalsdk.aar"));
|
|
154
|
-
}
|
|
155
|
-
} catch (Throwable ignored) {
|
|
156
|
-
}
|
|
157
|
-
// Add common sdcard and tmp locations
|
|
158
|
-
try {
|
|
159
|
-
candList.add(new File("/sdcard/Download/thermalsdk-release.aar"));
|
|
160
|
-
} catch (Throwable ignored) {
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
candList.add(new File("/sdcard/thermalsdk-release.aar"));
|
|
164
|
-
} catch (Throwable ignored) {
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
candList.add(new File("/sdcard/thermalsdk.aar"));
|
|
168
|
-
} catch (Throwable ignored) {
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
candList.add(new File("/data/local/tmp/thermalsdk-release.aar"));
|
|
172
|
-
} catch (Throwable ignored) {
|
|
213
|
+
DiscoveryFactory.getInstance().stop();
|
|
214
|
+
} catch (Throwable t) {
|
|
215
|
+
Log.w(TAG, "[FLIR] stopDiscovery failed: " + t.getMessage());
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public void connectToDevice(String deviceId) {
|
|
220
|
+
Log.i(TAG, "[FLIR] connectToDevice: " + deviceId);
|
|
221
|
+
|
|
222
|
+
DeviceInfo target = null;
|
|
223
|
+
for (DeviceInfo d : discoveredDevices) {
|
|
224
|
+
if (d.deviceId.equals(deviceId)) {
|
|
225
|
+
target = d;
|
|
226
|
+
break;
|
|
173
227
|
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (target == null) {
|
|
231
|
+
notifyError("Device not found: " + deviceId);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (isConnected.get()) {
|
|
236
|
+
disconnect();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
connectToIdentity(target);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public void disconnect() {
|
|
243
|
+
Log.i(TAG, "[FLIR] disconnect()");
|
|
244
|
+
|
|
245
|
+
stopStreaming();
|
|
246
|
+
|
|
247
|
+
if (camera != null) {
|
|
174
248
|
try {
|
|
175
|
-
|
|
176
|
-
} catch (Throwable
|
|
177
|
-
|
|
178
|
-
File candidate = null;
|
|
179
|
-
StringBuilder tried = new StringBuilder();
|
|
180
|
-
Log.i(TAG,
|
|
181
|
-
"[FLIR SDK] FilesDir=" + filesDir.getAbsolutePath() + ", ExternalFilesDir="
|
|
182
|
-
+ (ctx.getExternalFilesDir(null) != null ? ctx.getExternalFilesDir(null).getAbsolutePath()
|
|
183
|
-
: "null"));
|
|
184
|
-
for (File cand : candList) {
|
|
185
|
-
try {
|
|
186
|
-
if (cand == null) {
|
|
187
|
-
tried.append("null,");
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
boolean exists = false;
|
|
191
|
-
try {
|
|
192
|
-
exists = cand.exists();
|
|
193
|
-
} catch (Throwable ignored) {
|
|
194
|
-
}
|
|
195
|
-
tried.append(cand.getAbsolutePath()).append(exists ? "(exists)," : "(no),");
|
|
196
|
-
if (exists) {
|
|
197
|
-
candidate = cand;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
} catch (Throwable ignored) {
|
|
201
|
-
tried.append("err,");
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (candidate == null || !candidate.exists()) {
|
|
205
|
-
Log.w(TAG, "[FLIR SDK] No SDK AAR found. Tried: " + tried.toString());
|
|
206
|
-
return false;
|
|
249
|
+
camera.disconnect();
|
|
250
|
+
} catch (Throwable t) {
|
|
251
|
+
Log.w(TAG, "[FLIR] Camera disconnect failed: " + t.getMessage());
|
|
207
252
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
253
|
+
camera = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
isConnected.set(false);
|
|
257
|
+
connectedDevice = null;
|
|
258
|
+
|
|
259
|
+
mainHandler.post(() -> {
|
|
260
|
+
if (listener != null) {
|
|
261
|
+
listener.onDeviceDisconnected();
|
|
216
262
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public void setStreamType(String streamType) {
|
|
267
|
+
Log.i(TAG, "[FLIR] setStreamType: " + streamType);
|
|
268
|
+
currentStreamKind = streamType;
|
|
269
|
+
|
|
270
|
+
if (isConnected.get() && camera != null) {
|
|
271
|
+
stopStreaming();
|
|
272
|
+
startStreaming();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public void setPalette(String paletteName) {
|
|
277
|
+
Log.i(TAG, "[FLIR] setPalette: " + paletteName);
|
|
278
|
+
|
|
279
|
+
try {
|
|
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;
|
|
238
286
|
}
|
|
239
|
-
sdkJarPath = outJar.getAbsolutePath();
|
|
240
|
-
return true;
|
|
241
287
|
}
|
|
288
|
+
Log.w(TAG, "[FLIR] Palette not found: " + paletteName);
|
|
242
289
|
} catch (Throwable t) {
|
|
243
|
-
Log.w(TAG, "[FLIR
|
|
244
|
-
try {
|
|
245
|
-
Log.i(TAG, "[FLIR SDK] Candidate list tried on failure: ");
|
|
246
|
-
} catch (Throwable ignored) {
|
|
247
|
-
}
|
|
290
|
+
Log.w(TAG, "[FLIR] setPalette failed: " + t.getMessage());
|
|
248
291
|
}
|
|
249
|
-
return false;
|
|
250
292
|
}
|
|
251
|
-
|
|
252
|
-
public
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return sdkJarPath;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private Class<?> findSdkClass(String name) throws ClassNotFoundException {
|
|
261
|
-
try {
|
|
262
|
-
return Class.forName(name);
|
|
263
|
-
} catch (ClassNotFoundException e) {
|
|
264
|
-
if (sdkClassLoader != null)
|
|
265
|
-
return Class.forName(name, true, sdkClassLoader);
|
|
266
|
-
throw e;
|
|
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));
|
|
267
299
|
}
|
|
268
300
|
}
|
|
269
|
-
|
|
270
|
-
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get temperature at a specific point from the current thermal image
|
|
304
|
+
*/
|
|
305
|
+
public double getTemperatureAtPoint(int x, int y) {
|
|
306
|
+
if (currentThermalImage == null) {
|
|
307
|
+
return Double.NaN;
|
|
308
|
+
}
|
|
309
|
+
|
|
271
310
|
try {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
Class<?> listenerClass = Class.forName("com.flir.thermalsdk.live.discovery.DiscoveryEventListener", true,
|
|
284
|
-
loader);
|
|
285
|
-
Object discoveryListener = Proxy.newProxyInstance(
|
|
286
|
-
loader,
|
|
287
|
-
new Class<?>[] { listenerClass },
|
|
288
|
-
(proxy, method, args) -> {
|
|
289
|
-
String methodName = method.getName();
|
|
290
|
-
switch (methodName) {
|
|
291
|
-
case "onCameraFound":
|
|
292
|
-
Object discovered = args[0];
|
|
293
|
-
Log.i(TAG, "[FLIR SDK] (DEX) Camera found: " + discovered);
|
|
294
|
-
connectToSdkCamera(discovered);
|
|
295
|
-
return null;
|
|
296
|
-
case "onCameraLost":
|
|
297
|
-
Log.i(TAG, "[FLIR SDK] (DEX) Camera lost");
|
|
298
|
-
cameraObj = null;
|
|
299
|
-
streamerObj = null;
|
|
300
|
-
return null;
|
|
301
|
-
default:
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// Get NETWORK interface for FLIR ONE Edge
|
|
307
|
-
Object networkInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "NETWORK");
|
|
308
|
-
|
|
309
|
-
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass,
|
|
310
|
-
java.lang.reflect.Array.newInstance(commIfaceClass, 0).getClass());
|
|
311
|
-
// Scan for BOTH NETWORK (FLIR ONE Edge) and EMULATOR
|
|
312
|
-
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 2);
|
|
313
|
-
java.lang.reflect.Array.set(ifaceArray, 0, networkInterface);
|
|
314
|
-
java.lang.reflect.Array.set(ifaceArray, 1, emulatorInterface);
|
|
315
|
-
scanMethod.invoke(discoveryFactory, discoveryListener, ifaceArray);
|
|
316
|
-
Log.i(TAG, "[FLIR SDK] (DEX) scan invoked on DiscoveryFactory");
|
|
317
|
-
Log.i(TAG, "[FLIR SDK] (DEX) Started discovery scan for NETWORK and EMULATOR");
|
|
318
|
-
return true;
|
|
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));
|
|
316
|
+
|
|
317
|
+
ThermalValue value = currentThermalImage.getValueAt(new Point(clampedX, clampedY));
|
|
318
|
+
if (value != null) {
|
|
319
|
+
return value.asCelsius().value;
|
|
320
|
+
}
|
|
319
321
|
} catch (Throwable t) {
|
|
320
|
-
Log.w(TAG, "[FLIR
|
|
321
|
-
return false;
|
|
322
|
+
Log.w(TAG, "[FLIR] getTemperatureAtPoint failed: " + t.getMessage());
|
|
322
323
|
}
|
|
324
|
+
return Double.NaN;
|
|
323
325
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
+
|
|
327
|
+
public Bitmap getLatestFrame() {
|
|
328
|
+
return latestFrame;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public List<DeviceInfo> getDiscoveredDevices() {
|
|
332
|
+
return new ArrayList<>(discoveredDevices);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public boolean isConnected() {
|
|
336
|
+
return isConnected.get();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public boolean isStreaming() {
|
|
340
|
+
return isStreaming.get();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
public void destroy() {
|
|
344
|
+
Log.i(TAG, "[FLIR] destroy()");
|
|
345
|
+
stopDiscovery();
|
|
346
|
+
disconnect();
|
|
347
|
+
scheduler.shutdown();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ==================== DISCOVERY ====================
|
|
351
|
+
|
|
352
|
+
private void startFullDiscovery() {
|
|
353
|
+
Log.i(TAG, "[FLIR] Starting full discovery (USB, NETWORK, EMULATOR)");
|
|
354
|
+
|
|
355
|
+
if (!initializeSdk()) {
|
|
356
|
+
Log.w(TAG, "[FLIR] SDK not available, falling back to emulator");
|
|
357
|
+
startEmulatorDiscovery();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
isDiscovering.set(true);
|
|
362
|
+
mainHandler.post(() -> {
|
|
363
|
+
if (listener != null) {
|
|
364
|
+
listener.onDiscoveryStarted();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
326
368
|
try {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
Log.w(TAG, "[FLIR SDK] Camera disconnected");
|
|
338
|
-
cameraObj = null;
|
|
339
|
-
streamerObj = null;
|
|
340
|
-
}
|
|
341
|
-
return null;
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Try various connect overloads to be compatible across SDK versions
|
|
345
|
-
boolean connected = false;
|
|
346
|
-
try {
|
|
347
|
-
Class<?> connectParamsClass = findSdkClass("com.flir.thermalsdk.live.ConnectParameters");
|
|
348
|
-
Object connectParams = null;
|
|
349
|
-
try {
|
|
350
|
-
connectParams = connectParamsClass.newInstance();
|
|
351
|
-
} catch (Throwable ignored) {
|
|
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);
|
|
352
379
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
} catch (Throwable ignored) {
|
|
358
|
-
try {
|
|
359
|
-
Method connectMethod2 = cameraClass.getMethod("connect", identity.getClass(), connStatusClass);
|
|
360
|
-
connectMethod2.invoke(cameraObj, identity, connListener);
|
|
361
|
-
connected = true;
|
|
362
|
-
} catch (Throwable ignored2) {
|
|
363
|
-
try {
|
|
364
|
-
Method connectSimple = cameraClass.getMethod("connect", identity.getClass());
|
|
365
|
-
connectSimple.invoke(cameraObj, identity);
|
|
366
|
-
connected = true;
|
|
367
|
-
} catch (Throwable ignored3) {
|
|
368
|
-
Log.w(TAG, "connect: All connect attempts failed: " + ignored3.getMessage());
|
|
369
|
-
}
|
|
380
|
+
|
|
381
|
+
@Override
|
|
382
|
+
public void onDiscoveryError(CommunicationInterface iface, ErrorCode errorCode) {
|
|
383
|
+
Log.w(TAG, "[FLIR] Discovery error on " + iface + ": " + errorCode);
|
|
370
384
|
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
Object nm = getName.invoke(identity);
|
|
377
|
-
if (nm != null)
|
|
378
|
-
deviceName = nm.toString();
|
|
379
|
-
} catch (Throwable ignored) {
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (listener != null) {
|
|
383
|
-
listener.onDeviceFound(deviceName);
|
|
384
|
-
listener.onEmulatorEnabled();
|
|
385
|
-
}
|
|
386
|
-
Log.d(TAG, "[FLIR SDK] Scheduling startSdkStreaming; cameraObj=" + cameraObj + ", identity=" + identity
|
|
387
|
-
+ ", connected=" + connected);
|
|
388
|
-
scheduler.schedule(this::startSdkStreaming, 500, TimeUnit.MILLISECONDS);
|
|
385
|
+
}, interfaces);
|
|
386
|
+
|
|
387
|
+
// Set timeout for device discovery
|
|
388
|
+
scheduleDiscoveryTimeout(DISCOVERY_TIMEOUT_DEVICE_MS);
|
|
389
|
+
|
|
389
390
|
} catch (Throwable t) {
|
|
390
|
-
Log.
|
|
391
|
+
Log.e(TAG, "[FLIR] startFullDiscovery failed: " + t.getMessage(), t);
|
|
392
|
+
notifyError("Discovery failed: " + t.getMessage());
|
|
393
|
+
startEmulatorDiscovery();
|
|
391
394
|
}
|
|
392
395
|
}
|
|
393
|
-
|
|
394
|
-
private void
|
|
396
|
+
|
|
397
|
+
private void startEmulatorDiscovery() {
|
|
398
|
+
logStep("EMULATOR_DISCOVERY_START", "type=" + emulatorType);
|
|
399
|
+
Log.i(TAG, "[FLIR] Starting emulator discovery (type=" + emulatorType + ")");
|
|
400
|
+
|
|
401
|
+
if (!initializeSdk()) {
|
|
402
|
+
notifyError("SDK initialization failed");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
isDiscovering.set(true);
|
|
407
|
+
isEmulatorMode.set(true);
|
|
408
|
+
|
|
409
|
+
mainHandler.post(() -> {
|
|
410
|
+
if (listener != null) {
|
|
411
|
+
listener.onDiscoveryStarted();
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
395
415
|
try {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
Object streams = getStreams.invoke(cameraObj);
|
|
401
|
-
if (streams == null)
|
|
402
|
-
Log.i(TAG, "[FLIR SDK] No streams returned from camera.getStreams()");
|
|
403
|
-
Object chosenStream = null;
|
|
404
|
-
int streamCount = 0;
|
|
405
|
-
if (streams instanceof List) {
|
|
406
|
-
List<?> streamList = (List<?>) streams;
|
|
407
|
-
streamCount = streamList.size();
|
|
408
|
-
Log.i(TAG, "[FLIR SDK] getStreams returned list with size=" + streamList.size());
|
|
409
|
-
for (Object s : streamList) {
|
|
410
|
-
if (s == null)
|
|
411
|
-
continue;
|
|
412
|
-
try {
|
|
413
|
-
Method getName = null;
|
|
414
|
-
try {
|
|
415
|
-
getName = s.getClass().getMethod("getName");
|
|
416
|
-
} catch (Throwable ignored) {
|
|
417
|
-
}
|
|
418
|
-
String name = null;
|
|
419
|
-
if (getName != null)
|
|
420
|
-
name = getName.invoke(s).toString().toLowerCase();
|
|
421
|
-
|
|
422
|
-
Method getType = null;
|
|
423
|
-
try {
|
|
424
|
-
getType = s.getClass().getMethod("getStreamType");
|
|
425
|
-
} catch (Throwable ignored) {
|
|
426
|
-
}
|
|
427
|
-
if (getType == null)
|
|
428
|
-
try {
|
|
429
|
-
getType = s.getClass().getMethod("getType");
|
|
430
|
-
} catch (Throwable ignored) {
|
|
431
|
-
}
|
|
432
|
-
String type = null;
|
|
433
|
-
if (getType != null)
|
|
434
|
-
type = getType.invoke(s).toString().toLowerCase();
|
|
435
|
-
|
|
436
|
-
String test = (name != null ? name : "") + "|" + (type != null ? type : "");
|
|
437
|
-
Log.d(TAG, "[FLIR SDK] stream candidate: name='" + name + "', type='" + type + "', test='"
|
|
438
|
-
+ test + "'");
|
|
439
|
-
if (test.contains("fusion") || test.contains("palette") || test.contains("visual")
|
|
440
|
-
|| test.contains("msx")) {
|
|
441
|
-
chosenStream = s;
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
} catch (Throwable ignored) {
|
|
445
|
-
}
|
|
416
|
+
DiscoveryFactory.getInstance().scan(new DiscoveryEventListener() {
|
|
417
|
+
@Override
|
|
418
|
+
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
419
|
+
handleCameraFound(discoveredCamera);
|
|
446
420
|
}
|
|
447
|
-
|
|
448
|
-
|
|
421
|
+
|
|
422
|
+
@Override
|
|
423
|
+
public void onDiscoveryError(CommunicationInterface iface, ErrorCode errorCode) {
|
|
424
|
+
Log.w(TAG, "[FLIR] Emulator discovery error: " + errorCode);
|
|
425
|
+
}
|
|
426
|
+
}, CommunicationInterface.EMULATOR);
|
|
427
|
+
|
|
428
|
+
// Short timeout for emulator
|
|
429
|
+
scheduleDiscoveryTimeout(2000);
|
|
430
|
+
|
|
431
|
+
} catch (Throwable t) {
|
|
432
|
+
Log.e(TAG, "[FLIR] startEmulatorDiscovery failed: " + t.getMessage(), t);
|
|
433
|
+
notifyError("Emulator discovery failed: " + t.getMessage());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
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;
|
|
451
|
+
break;
|
|
452
|
+
case NETWORK:
|
|
453
|
+
commIface = CommInterface.NETWORK;
|
|
454
|
+
break;
|
|
455
|
+
default:
|
|
456
|
+
commIface = CommInterface.EMULATOR;
|
|
457
|
+
}
|
|
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;
|
|
449
467
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.
|
|
458
|
-
|
|
459
|
-
Log.i(TAG, "[FLIR SDK] ThermalStreamer instance created: "
|
|
460
|
-
+ (streamerObj != null ? streamerObj.getClass().getName() : "null"));
|
|
461
|
-
|
|
462
|
-
boolean listenerAttached = attachStreamerListener(streamerObj);
|
|
463
|
-
if (!listenerAttached)
|
|
464
|
-
startFramePolling();
|
|
465
|
-
Log.i(TAG, "[FLIR SDK] startSdkStreaming: listenerAttached=" + listenerAttached + ", streamerObj="
|
|
466
|
-
+ (streamerObj != null));
|
|
467
|
-
|
|
468
|
-
// Notify stream kind change
|
|
469
|
-
try {
|
|
470
|
-
Method getName = chosenStream.getClass().getMethod("getName");
|
|
471
|
-
Object nm = getName.invoke(chosenStream);
|
|
472
|
-
if (nm != null && listener != null)
|
|
473
|
-
listener.onStreamKindChanged(nm.toString());
|
|
474
|
-
try {
|
|
475
|
-
sdkCurrentStreamKind = nm != null ? nm.toString() : null;
|
|
476
|
-
} catch (Throwable ignored) {
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!exists) {
|
|
471
|
+
discoveredDevices.add(deviceInfo);
|
|
472
|
+
|
|
473
|
+
mainHandler.post(() -> {
|
|
474
|
+
if (listener != null) {
|
|
475
|
+
listener.onDeviceFound(deviceId, deviceName, isEmulator);
|
|
476
|
+
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
477
477
|
}
|
|
478
|
-
}
|
|
478
|
+
});
|
|
479
|
+
|
|
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);
|
|
479
485
|
}
|
|
480
|
-
|
|
481
|
-
} catch (Throwable t) {
|
|
482
|
-
Log.w(TAG, "startSdkStreaming failed: " + t.getMessage(), t);
|
|
483
486
|
}
|
|
484
487
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
Object discoveryListener = Proxy.newProxyInstance(
|
|
500
|
-
sdkClassLoader != null ? sdkClassLoader : discoveryFactoryClass.getClassLoader(),
|
|
501
|
-
new Class<?>[] { listenerClass },
|
|
502
|
-
(proxy, method, args) -> {
|
|
503
|
-
String methodName = method.getName();
|
|
504
|
-
if ("onCameraFound".equalsIgnoreCase(methodName) && args != null && args.length > 0) {
|
|
505
|
-
try {
|
|
506
|
-
Object discovered = args[0];
|
|
507
|
-
Log.i(TAG, "[FLIR SDK] forceStartStreaming: discovered camera: " + discovered);
|
|
508
|
-
connectToSdkCamera(discovered);
|
|
509
|
-
} catch (Throwable t) {
|
|
510
|
-
Log.w(TAG, "[FLIR SDK] forceStartStreaming onCameraFound handler error: "
|
|
511
|
-
+ t.getMessage());
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return null;
|
|
515
|
-
});
|
|
516
|
-
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass,
|
|
517
|
-
java.lang.reflect.Array.newInstance(commIfaceClass, 0).getClass());
|
|
518
|
-
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 1);
|
|
519
|
-
java.lang.reflect.Array.set(ifaceArray, 0, emulatorInterface);
|
|
520
|
-
scanMethod.invoke(df, discoveryListener, ifaceArray);
|
|
521
|
-
} catch (Throwable t) {
|
|
522
|
-
Log.w(TAG, "[FLIR SDK] forceStartStreaming discovery scan failed: " + t.getMessage());
|
|
488
|
+
|
|
489
|
+
private void scheduleDiscoveryTimeout(long timeoutMs) {
|
|
490
|
+
cancelDiscoveryTimeout();
|
|
491
|
+
|
|
492
|
+
discoveryTimeoutFuture = scheduler.schedule(() -> {
|
|
493
|
+
Log.i(TAG, "[FLIR] Discovery timeout after " + timeoutMs + "ms");
|
|
494
|
+
logStep("DISCOVERY_TIMEOUT", "timeout=" + timeoutMs + "ms, devicesFound=" + discoveredDevices.size());
|
|
495
|
+
|
|
496
|
+
isDiscovering.set(false);
|
|
497
|
+
stopDiscovery();
|
|
498
|
+
|
|
499
|
+
mainHandler.post(() -> {
|
|
500
|
+
if (listener != null) {
|
|
501
|
+
listener.onDiscoveryTimeout();
|
|
523
502
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// If no devices found, try emulator
|
|
506
|
+
if (discoveredDevices.isEmpty() && !isEmulatorMode.get()) {
|
|
507
|
+
logStep("FALLBACK_EMULATOR", "No devices found, trying emulator");
|
|
508
|
+
startEmulatorDiscovery();
|
|
527
509
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
510
|
+
}, timeoutMs, TimeUnit.MILLISECONDS);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private void cancelDiscoveryTimeout() {
|
|
514
|
+
if (discoveryTimeoutFuture != null && !discoveryTimeoutFuture.isDone()) {
|
|
515
|
+
discoveryTimeoutFuture.cancel(false);
|
|
516
|
+
discoveryTimeoutFuture = null;
|
|
535
517
|
}
|
|
536
518
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
519
|
+
|
|
520
|
+
// ==================== CONNECTION ====================
|
|
521
|
+
|
|
522
|
+
private void connectToIdentity(DeviceInfo device) {
|
|
523
|
+
logStep("CONNECT_START", "device=" + device.deviceName);
|
|
524
|
+
Log.i(TAG, "[FLIR] Connecting to: " + device.deviceName);
|
|
525
|
+
|
|
526
|
+
scheduler.execute(() -> {
|
|
540
527
|
try {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
528
|
+
camera = new Camera();
|
|
529
|
+
|
|
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(() -> {
|
|
557
544
|
if (listener != null) {
|
|
558
|
-
listener.
|
|
559
|
-
Log.d(TAG, "[FLIR SDK] Frame emitted from polling: " + ((Bitmap) bmp).getWidth()
|
|
560
|
-
+ "x" + ((Bitmap) bmp).getHeight());
|
|
545
|
+
listener.onDeviceDisconnected();
|
|
561
546
|
}
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
} catch (Throwable ignored) {
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Fallback: try using
|
|
568
|
-
// BitmapAndroid.createBitmap(thermalImage.getImage()).getBitMap()
|
|
569
|
-
try {
|
|
570
|
-
Object imageBase = null;
|
|
571
|
-
try {
|
|
572
|
-
Method getImageFromThermal = thermalImage.getClass().getMethod("getImage");
|
|
573
|
-
imageBase = getImageFromThermal.invoke(thermalImage);
|
|
574
|
-
} catch (Throwable ignored) {
|
|
575
|
-
}
|
|
576
|
-
if (imageBase == null)
|
|
577
|
-
imageBase = thermalImage;
|
|
578
|
-
Class<?> bitmapAndroidClass = findSdkClass(
|
|
579
|
-
"com.flir.thermalsdk.androidsdk.image.BitmapAndroid");
|
|
580
|
-
Method createBitmap = bitmapAndroidClass.getMethod("createBitmap", imageBase.getClass());
|
|
581
|
-
Object wrapper = createBitmap.invoke(null, imageBase);
|
|
582
|
-
if (wrapper != null) {
|
|
583
|
-
Method getBitMapMethod = wrapper.getClass().getMethod("getBitMap");
|
|
584
|
-
Object bmp = getBitMapMethod.invoke(wrapper);
|
|
585
|
-
if (bmp instanceof Bitmap) {
|
|
586
|
-
latestFrame = (Bitmap) bmp;
|
|
587
|
-
if (listener != null)
|
|
588
|
-
listener.onFrame((Bitmap) bmp);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
} catch (Throwable ignored) {
|
|
547
|
+
});
|
|
592
548
|
}
|
|
593
|
-
}
|
|
549
|
+
},
|
|
550
|
+
new ConnectParameters()
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
// If we get here, connection succeeded
|
|
554
|
+
Log.i(TAG, "[FLIR] Connected to: " + device.deviceName);
|
|
555
|
+
logStep("CONNECTED", "device=" + device.deviceName);
|
|
556
|
+
|
|
557
|
+
isConnected.set(true);
|
|
558
|
+
connectedDevice = device;
|
|
559
|
+
|
|
560
|
+
mainHandler.post(() -> {
|
|
561
|
+
if (listener != null) {
|
|
562
|
+
listener.onDeviceConnected(device.deviceId, device.deviceName, device.isEmulator);
|
|
594
563
|
}
|
|
595
|
-
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Start streaming automatically
|
|
567
|
+
startStreaming();
|
|
568
|
+
|
|
596
569
|
} catch (Throwable t) {
|
|
597
|
-
|
|
598
|
-
|
|
570
|
+
Log.e(TAG, "[FLIR] Connect error: " + t.getMessage(), t);
|
|
571
|
+
isConnected.set(false);
|
|
572
|
+
camera = null;
|
|
573
|
+
notifyError("Connect error: " + t.getMessage());
|
|
599
574
|
}
|
|
600
|
-
}
|
|
575
|
+
});
|
|
601
576
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
try {
|
|
610
|
-
if (streamerObj != null) {
|
|
611
|
-
Method getImage = streamerObj.getClass().getMethod("getImage");
|
|
612
|
-
Object thermalImage = getImage.invoke(streamerObj);
|
|
613
|
-
if (thermalImage != null) {
|
|
614
|
-
try {
|
|
615
|
-
Method getValueAt = thermalImage.getClass().getMethod("getValueAt",
|
|
616
|
-
android.graphics.Point.class);
|
|
617
|
-
android.graphics.Point p = new android.graphics.Point(x, y);
|
|
618
|
-
Object temp = getValueAt.invoke(thermalImage, p);
|
|
619
|
-
if (temp instanceof Double)
|
|
620
|
-
return (Double) temp;
|
|
621
|
-
if (temp instanceof Float)
|
|
622
|
-
return ((Float) temp).doubleValue();
|
|
623
|
-
} catch (NoSuchMethodException nsme) {
|
|
624
|
-
}
|
|
625
|
-
try {
|
|
626
|
-
Method getValues = thermalImage.getClass().getMethod("getValues");
|
|
627
|
-
Object values = getValues.invoke(thermalImage);
|
|
628
|
-
if (values != null) {
|
|
629
|
-
Method valGetAt = values.getClass().getMethod("getValueAt", int.class, int.class);
|
|
630
|
-
Object temp = valGetAt.invoke(values, x, y);
|
|
631
|
-
if (temp instanceof Double)
|
|
632
|
-
return (Double) temp;
|
|
633
|
-
}
|
|
634
|
-
} catch (Throwable ignored) {
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
} catch (Throwable t) {
|
|
577
|
+
|
|
578
|
+
// ==================== STREAMING ====================
|
|
579
|
+
|
|
580
|
+
private void startStreaming() {
|
|
581
|
+
if (camera == null || !isConnected.get()) {
|
|
582
|
+
Log.w(TAG, "[FLIR] Cannot start streaming - not connected");
|
|
583
|
+
return;
|
|
639
584
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
585
|
+
|
|
586
|
+
logStep("STREAM_START", "streamType=" + currentStreamKind);
|
|
587
|
+
Log.i(TAG, "[FLIR] Starting streaming...");
|
|
588
|
+
|
|
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());
|
|
600
|
+
|
|
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;
|
|
606
|
+
break;
|
|
660
607
|
}
|
|
661
608
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
//
|
|
668
|
-
if (
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
target = p;
|
|
609
|
+
currentStream = thermalStream != null ? thermalStream : streams.get(0);
|
|
610
|
+
|
|
611
|
+
// Create ThermalStreamer for rendering
|
|
612
|
+
thermalStreamer = new ThermalStreamer(currentStream);
|
|
613
|
+
|
|
614
|
+
// Set default palette if available
|
|
615
|
+
if (currentPalette == null) {
|
|
616
|
+
try {
|
|
617
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
618
|
+
for (Palette p : palettes) {
|
|
619
|
+
if (p.name.toLowerCase().contains("iron")) {
|
|
620
|
+
currentPalette = p;
|
|
675
621
|
break;
|
|
676
622
|
}
|
|
677
|
-
} catch (Throwable ignored) {
|
|
678
623
|
}
|
|
624
|
+
} catch (Throwable t) {
|
|
625
|
+
Log.w(TAG, "[FLIR] Failed to get default palette: " + t.getMessage());
|
|
679
626
|
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
} catch (Throwable ignored) {
|
|
627
|
+
}
|
|
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());
|
|
690
636
|
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
Method getName = p.getClass().getMethod("getName");
|
|
698
|
-
String n = (String) getName.invoke(p);
|
|
699
|
-
if (n != null && n.equalsIgnoreCase(paletteName)) {
|
|
700
|
-
target = p;
|
|
701
|
-
break;
|
|
702
|
-
}
|
|
703
|
-
} catch (Throwable ignored) {
|
|
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);
|
|
704
643
|
}
|
|
705
644
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
isStreaming.set(true);
|
|
648
|
+
Log.i(TAG, "[FLIR] Stream started");
|
|
649
|
+
logStep("STREAM_STARTED", "stream=" + currentStream);
|
|
650
|
+
|
|
651
|
+
mainHandler.post(() -> {
|
|
652
|
+
if (listener != null) {
|
|
653
|
+
listener.onStreamStarted("thermal");
|
|
716
654
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
} catch (Throwable t) {
|
|
658
|
+
Log.e(TAG, "[FLIR] Start stream error: " + t.getMessage(), t);
|
|
659
|
+
notifyError("Stream error: " + t.getMessage());
|
|
723
660
|
}
|
|
724
|
-
}
|
|
725
|
-
Log.w(TAG, "setPalette failed: " + t.getMessage());
|
|
726
|
-
}
|
|
727
|
-
return false;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
public boolean isStreamingActive() {
|
|
731
|
-
return streamerObj != null;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
public String getSdkCurrentStreamKind() {
|
|
735
|
-
return sdkCurrentStreamKind;
|
|
661
|
+
});
|
|
736
662
|
}
|
|
737
|
-
|
|
738
|
-
private
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
"addStreamListener", "setOnImageAvailableListener", "addImageListener", "setDataListener",
|
|
745
|
-
"subscribe" };
|
|
746
|
-
for (String name : candidates) {
|
|
747
|
-
try {
|
|
748
|
-
for (Method m : streamerClass.getMethods()) {
|
|
749
|
-
if (!m.getName().equalsIgnoreCase(name))
|
|
750
|
-
continue;
|
|
751
|
-
Class<?>[] params = m.getParameterTypes();
|
|
752
|
-
if (params.length != 1 || !params[0].isInterface())
|
|
753
|
-
continue;
|
|
754
|
-
final Class<?> listenerInterface = params[0];
|
|
755
|
-
Object proxy = Proxy.newProxyInstance(listenerInterface.getClassLoader(),
|
|
756
|
-
new Class<?>[] { listenerInterface }, (proxyObj, method, args) -> {
|
|
757
|
-
try {
|
|
758
|
-
Log.d(TAG, "[FLIR SDK] Stream listener method invoked: " + method.getName());
|
|
759
|
-
if (args != null && args.length > 0) {
|
|
760
|
-
for (Object arg : args) {
|
|
761
|
-
if (arg == null)
|
|
762
|
-
continue;
|
|
763
|
-
try {
|
|
764
|
-
Method getFusion = arg.getClass().getMethod("getFusion");
|
|
765
|
-
if (getFusion != null) {
|
|
766
|
-
Object fusion = getFusion.invoke(arg);
|
|
767
|
-
if (fusion != null) {
|
|
768
|
-
try {
|
|
769
|
-
Method getPhoto = fusion.getClass()
|
|
770
|
-
.getMethod("getPhoto");
|
|
771
|
-
Object photo = getPhoto.invoke(fusion);
|
|
772
|
-
if (photo != null) {
|
|
773
|
-
try {
|
|
774
|
-
Method pb = photo.getClass()
|
|
775
|
-
.getMethod("getBitmap");
|
|
776
|
-
Object bmpObj = pb.invoke(photo);
|
|
777
|
-
if (bmpObj instanceof Bitmap) {
|
|
778
|
-
if (listener != null) {
|
|
779
|
-
listener.onFrame((Bitmap) bmpObj);
|
|
780
|
-
Log.d(TAG,
|
|
781
|
-
"[FLIR SDK] Streamer listener delivered bitmap via getBitmap");
|
|
782
|
-
}
|
|
783
|
-
return null;
|
|
784
|
-
}
|
|
785
|
-
} catch (Throwable ignored) {
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
} catch (Throwable ignored) {
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
} catch (Throwable ignored) {
|
|
793
|
-
}
|
|
794
|
-
try {
|
|
795
|
-
Method getBitmap = arg.getClass().getMethod("getBitmap");
|
|
796
|
-
Object bmp = getBitmap.invoke(arg);
|
|
797
|
-
if (bmp instanceof Bitmap) {
|
|
798
|
-
if (listener != null) {
|
|
799
|
-
listener.onFrame((Bitmap) bmp);
|
|
800
|
-
Log.d(TAG,
|
|
801
|
-
"[FLIR SDK] Streamer listener delivered bitmap via direct getBitmap");
|
|
802
|
-
}
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
} catch (Throwable ignored) {
|
|
806
|
-
}
|
|
807
|
-
try {
|
|
808
|
-
Method getImage = arg.getClass().getMethod("getImage");
|
|
809
|
-
Object imgObj = getImage.invoke(arg);
|
|
810
|
-
if (imgObj instanceof Bitmap) {
|
|
811
|
-
if (listener != null) {
|
|
812
|
-
listener.onFrame((Bitmap) imgObj);
|
|
813
|
-
Log.d(TAG,
|
|
814
|
-
"[FLIR SDK] Streamer listener delivered bitmap via getImage");
|
|
815
|
-
}
|
|
816
|
-
return null;
|
|
817
|
-
}
|
|
818
|
-
// fallback to BitmapAndroid.createBitmap(imgObj).getBitMap()
|
|
819
|
-
try {
|
|
820
|
-
Class<?> bitmapAndroidClass = findSdkClass(
|
|
821
|
-
"com.flir.thermalsdk.androidsdk.image.BitmapAndroid");
|
|
822
|
-
Method createBitmap = bitmapAndroidClass
|
|
823
|
-
.getMethod("createBitmap", imgObj.getClass());
|
|
824
|
-
Object wrapper = createBitmap.invoke(null, imgObj);
|
|
825
|
-
if (wrapper != null) {
|
|
826
|
-
Method getBitMap = wrapper.getClass()
|
|
827
|
-
.getMethod("getBitMap");
|
|
828
|
-
Object bmpObj = getBitMap.invoke(wrapper);
|
|
829
|
-
if (bmpObj instanceof Bitmap) {
|
|
830
|
-
if (listener != null) {
|
|
831
|
-
listener.onFrame((Bitmap) bmpObj);
|
|
832
|
-
Log.d(TAG,
|
|
833
|
-
"[FLIR SDK] Streamer listener delivered bitmap via BitmapAndroid wrapper");
|
|
834
|
-
}
|
|
835
|
-
return null;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
} catch (Throwable ignored2) {
|
|
839
|
-
}
|
|
840
|
-
} catch (Throwable ignored) {
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
} catch (Throwable t) {
|
|
845
|
-
Log.w(TAG, "Streamer listener failed: " + t.getMessage());
|
|
846
|
-
}
|
|
847
|
-
return null;
|
|
848
|
-
});
|
|
849
|
-
try {
|
|
850
|
-
m.invoke(streamer, proxy);
|
|
851
|
-
} catch (Throwable e) {
|
|
852
|
-
Log.w(TAG, "attach proxy failed: " + e.getMessage(), e);
|
|
853
|
-
continue;
|
|
854
|
-
}
|
|
855
|
-
Log.i(TAG, "Attached streamer listener using: " + m.getName());
|
|
856
|
-
return true;
|
|
857
|
-
}
|
|
858
|
-
} catch (Throwable ignored) {
|
|
859
|
-
}
|
|
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());
|
|
860
670
|
}
|
|
861
|
-
|
|
862
|
-
Log.w(TAG, "attachStreamerListener error: " + t.getMessage());
|
|
671
|
+
currentStream = null;
|
|
863
672
|
}
|
|
864
|
-
|
|
673
|
+
thermalStreamer = null;
|
|
674
|
+
isStreaming.set(false);
|
|
865
675
|
}
|
|
866
|
-
|
|
867
|
-
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Refresh thermal frame using ThermalStreamer pattern.
|
|
679
|
+
* Called when a new frame is received.
|
|
680
|
+
*/
|
|
681
|
+
private synchronized void refreshThermalFrame() {
|
|
682
|
+
if (thermalStreamer == null) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
868
686
|
try {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
687
|
+
// Update streamer to get latest frame
|
|
688
|
+
thermalStreamer.update();
|
|
689
|
+
|
|
690
|
+
// Get the image buffer from streamer
|
|
691
|
+
ImageBuffer imageBuffer = thermalStreamer.getImage();
|
|
692
|
+
if (imageBuffer == null) {
|
|
693
|
+
return;
|
|
877
694
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
695
|
+
|
|
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);
|
|
884
704
|
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Convert to Android Bitmap
|
|
708
|
+
Bitmap bitmap = BitmapAndroid.createBitmap(imageBuffer).getBitMap();
|
|
709
|
+
|
|
710
|
+
if (bitmap != null) {
|
|
711
|
+
latestFrame = bitmap;
|
|
712
|
+
|
|
713
|
+
mainHandler.post(() -> {
|
|
714
|
+
if (listener != null) {
|
|
715
|
+
listener.onFrame(bitmap);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
885
718
|
}
|
|
719
|
+
|
|
886
720
|
} catch (Throwable t) {
|
|
887
|
-
Log.w(TAG, "
|
|
721
|
+
Log.w(TAG, "[FLIR] refreshThermalFrame error: " + t.getMessage());
|
|
888
722
|
}
|
|
889
723
|
}
|
|
724
|
+
|
|
725
|
+
// ==================== PUBLIC STOP ====================
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Stop the manager - disconnect and cleanup all resources.
|
|
729
|
+
*/
|
|
730
|
+
public void stop() {
|
|
731
|
+
Log.i(TAG, "[FLIR] Stopping FlirSdkManager");
|
|
732
|
+
|
|
733
|
+
// Stop streaming
|
|
734
|
+
stopStreaming();
|
|
735
|
+
|
|
736
|
+
// Disconnect camera
|
|
737
|
+
disconnect();
|
|
738
|
+
|
|
739
|
+
// Stop discovery
|
|
740
|
+
stopDiscovery();
|
|
741
|
+
|
|
742
|
+
// Clear state
|
|
743
|
+
discoveredDevices.clear();
|
|
744
|
+
currentThermalImage = null;
|
|
745
|
+
latestFrame = null;
|
|
746
|
+
|
|
747
|
+
Log.i(TAG, "[FLIR] FlirSdkManager stopped");
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ==================== HELPERS ====================
|
|
751
|
+
|
|
752
|
+
private void notifyError(String error) {
|
|
753
|
+
Log.e(TAG, "[FLIR] Error: " + error);
|
|
754
|
+
mainHandler.post(() -> {
|
|
755
|
+
if (listener != null) {
|
|
756
|
+
listener.onError(error);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
890
760
|
}
|