ilabs-flir 1.0.5 → 1.0.7
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/src/main/AndroidManifest.xml +14 -0
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +312 -313
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +151 -0
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +431 -608
- package/package.json +1 -1
- package/src/index.d.ts +39 -0
|
@@ -1,15 +1,13 @@
|
|
|
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
7
|
import com.flir.thermalsdk.ErrorCode;
|
|
9
8
|
import com.flir.thermalsdk.androidsdk.ThermalSdkAndroid;
|
|
10
9
|
import com.flir.thermalsdk.androidsdk.image.BitmapAndroid;
|
|
11
10
|
import com.flir.thermalsdk.image.ImageBuffer;
|
|
12
|
-
import com.flir.thermalsdk.image.JavaImageBuffer;
|
|
13
11
|
import com.flir.thermalsdk.image.Palette;
|
|
14
12
|
import com.flir.thermalsdk.image.PaletteManager;
|
|
15
13
|
import com.flir.thermalsdk.image.Point;
|
|
@@ -24,737 +22,562 @@ import com.flir.thermalsdk.live.discovery.DiscoveredCamera;
|
|
|
24
22
|
import com.flir.thermalsdk.live.discovery.DiscoveryEventListener;
|
|
25
23
|
import com.flir.thermalsdk.live.discovery.DiscoveryFactory;
|
|
26
24
|
import com.flir.thermalsdk.live.remote.OnReceived;
|
|
27
|
-
import com.flir.thermalsdk.live.remote.OnRemoteError;
|
|
28
25
|
import com.flir.thermalsdk.live.streaming.Stream;
|
|
29
26
|
import com.flir.thermalsdk.live.streaming.ThermalStreamer;
|
|
30
27
|
|
|
31
28
|
import java.util.ArrayList;
|
|
29
|
+
import java.util.Collections;
|
|
32
30
|
import java.util.List;
|
|
33
|
-
import java.util.concurrent.
|
|
31
|
+
import java.util.concurrent.Executor;
|
|
34
32
|
import java.util.concurrent.Executors;
|
|
35
|
-
import java.util.concurrent.ScheduledExecutorService;
|
|
36
|
-
import java.util.concurrent.ScheduledFuture;
|
|
37
|
-
import java.util.concurrent.TimeUnit;
|
|
38
|
-
import java.util.concurrent.atomic.AtomicBoolean;
|
|
39
33
|
|
|
40
34
|
/**
|
|
41
|
-
* FLIR SDK Manager -
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* Supports USB, NETWORK (FLIR ONE Edge), and EMULATOR interfaces.
|
|
35
|
+
* Simplified FLIR SDK Manager - handles discovery, connection, and streaming
|
|
36
|
+
* No filtering - returns all discovered devices (USB, Network, Emulator)
|
|
45
37
|
*/
|
|
46
38
|
public class FlirSdkManager {
|
|
47
39
|
private static final String TAG = "FlirSdkManager";
|
|
48
|
-
private static final String FLOW_TAG = "FLIR_FLOW";
|
|
49
40
|
|
|
50
|
-
//
|
|
51
|
-
private static
|
|
52
|
-
|
|
41
|
+
// Singleton instance
|
|
42
|
+
private static FlirSdkManager instance;
|
|
43
|
+
|
|
44
|
+
// Core components
|
|
45
|
+
private final Context context;
|
|
46
|
+
// Use bounded thread pool to prevent thread explosion during rapid frame processing
|
|
47
|
+
private final Executor executor = Executors.newFixedThreadPool(2);
|
|
48
|
+
// Single-threaded executor for frame processing to ensure ordered processing
|
|
49
|
+
private final Executor frameExecutor = Executors.newSingleThreadExecutor();
|
|
50
|
+
// Frame processing guard - skip frames if still processing previous one
|
|
51
|
+
private volatile boolean isProcessingFrame = false;
|
|
52
|
+
private long lastFrameProcessedMs = 0;
|
|
53
|
+
private static final long MIN_FRAME_INTERVAL_MS = 50; // Max ~20 FPS frame processing
|
|
54
|
+
|
|
55
|
+
// State
|
|
56
|
+
private boolean isInitialized = false;
|
|
57
|
+
private boolean isScanning = false;
|
|
58
|
+
private Camera camera;
|
|
59
|
+
private ThermalStreamer streamer;
|
|
60
|
+
private Stream activeStream;
|
|
61
|
+
private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
|
|
62
|
+
|
|
63
|
+
// Listener
|
|
64
|
+
private Listener listener;
|
|
53
65
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
FLIR_ONE // USB emulator
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Communication interfaces
|
|
61
|
-
public enum CommInterface {
|
|
62
|
-
USB,
|
|
63
|
-
NETWORK,
|
|
64
|
-
EMULATOR
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Listener interface for callbacks
|
|
66
|
+
/**
|
|
67
|
+
* Listener interface for SDK events
|
|
68
|
+
*/
|
|
68
69
|
public interface Listener {
|
|
70
|
+
void onDeviceFound(Identity identity);
|
|
71
|
+
void onDeviceListUpdated(List<Identity> devices);
|
|
72
|
+
void onConnected(Identity identity);
|
|
73
|
+
void onDisconnected();
|
|
69
74
|
void onFrame(Bitmap bitmap);
|
|
70
|
-
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);
|
|
75
|
+
void onError(String message);
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
}
|
|
78
|
+
// Private constructor for singleton
|
|
79
|
+
private FlirSdkManager(Context context) {
|
|
80
|
+
this.context = context.getApplicationContext();
|
|
96
81
|
}
|
|
97
|
-
|
|
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
|
-
|
|
103
|
-
// SDK objects
|
|
104
|
-
private Camera camera = null;
|
|
105
|
-
private Stream currentStream = null;
|
|
106
|
-
private ThermalStreamer thermalStreamer = null;
|
|
107
|
-
private Palette currentPalette = null;
|
|
108
82
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
private EmulatorType emulatorType = EmulatorType.FLIR_ONE_EDGE;
|
|
118
|
-
|
|
119
|
-
// Frame state
|
|
120
|
-
private volatile Bitmap latestFrame = null;
|
|
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) {
|
|
132
|
-
this.listener = listener;
|
|
133
|
-
this.appContext = context != null ? context.getApplicationContext() : null;
|
|
134
|
-
}
|
|
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));
|
|
83
|
+
/**
|
|
84
|
+
* Get singleton instance
|
|
85
|
+
*/
|
|
86
|
+
public static synchronized FlirSdkManager getInstance(Context context) {
|
|
87
|
+
if (instance == null) {
|
|
88
|
+
instance = new FlirSdkManager(context);
|
|
89
|
+
}
|
|
90
|
+
return instance;
|
|
140
91
|
}
|
|
141
92
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Set listener for SDK events
|
|
95
|
+
*/
|
|
96
|
+
public void setListener(Listener listener) {
|
|
97
|
+
this.listener = listener;
|
|
146
98
|
}
|
|
147
99
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (appContext == null) {
|
|
157
|
-
Log.e(TAG, "[FLIR SDK] No context available");
|
|
158
|
-
return false;
|
|
100
|
+
/**
|
|
101
|
+
* Initialize the FLIR Thermal SDK
|
|
102
|
+
*/
|
|
103
|
+
public void initialize() {
|
|
104
|
+
if (isInitialized) {
|
|
105
|
+
Log.d(TAG, "Already initialized");
|
|
106
|
+
return;
|
|
159
107
|
}
|
|
160
108
|
|
|
161
109
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Log.e(TAG, "[FLIR SDK] Initialization failed: " + t.getMessage(), t);
|
|
169
|
-
notifyError("SDK initialization failed: " + t.getMessage());
|
|
170
|
-
return false;
|
|
110
|
+
ThermalSdkAndroid.init(context);
|
|
111
|
+
isInitialized = true;
|
|
112
|
+
Log.d(TAG, "SDK initialized successfully");
|
|
113
|
+
} catch (Exception e) {
|
|
114
|
+
Log.e(TAG, "Failed to initialize SDK", e);
|
|
115
|
+
notifyError("SDK initialization failed: " + e.getMessage());
|
|
171
116
|
}
|
|
172
117
|
}
|
|
173
118
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
logStep("SET_EMULATOR_TYPE", "type=" + type);
|
|
119
|
+
/**
|
|
120
|
+
* Check if SDK is initialized
|
|
121
|
+
*/
|
|
122
|
+
public boolean isInitialized() {
|
|
123
|
+
return isInitialized;
|
|
180
124
|
}
|
|
181
125
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Start scanning for all device types (USB, Network, Emulator)
|
|
128
|
+
* Returns ALL devices - no filtering
|
|
129
|
+
*/
|
|
130
|
+
public void scan() {
|
|
131
|
+
if (!isInitialized) {
|
|
132
|
+
Log.e(TAG, "SDK not initialized");
|
|
133
|
+
notifyError("SDK not initialized");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
186
136
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
disconnect();
|
|
137
|
+
if (isScanning) {
|
|
138
|
+
Log.d(TAG, "Already scanning");
|
|
139
|
+
return;
|
|
191
140
|
}
|
|
192
141
|
|
|
142
|
+
isScanning = true;
|
|
193
143
|
discoveredDevices.clear();
|
|
194
|
-
logStep("CLEAR_DEVICES", "Cleared discovered devices list");
|
|
195
144
|
|
|
196
|
-
|
|
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);
|
|
145
|
+
Log.d(TAG, "Starting discovery for EMULATOR, NETWORK, USB...");
|
|
211
146
|
|
|
212
147
|
try {
|
|
213
|
-
DiscoveryFactory.getInstance().
|
|
214
|
-
|
|
215
|
-
|
|
148
|
+
DiscoveryFactory.getInstance().scan(
|
|
149
|
+
discoveryListener,
|
|
150
|
+
CommunicationInterface.EMULATOR,
|
|
151
|
+
CommunicationInterface.NETWORK,
|
|
152
|
+
CommunicationInterface.USB
|
|
153
|
+
);
|
|
154
|
+
} catch (Exception e) {
|
|
155
|
+
Log.e(TAG, "Failed to start scan", e);
|
|
156
|
+
isScanning = false;
|
|
157
|
+
notifyError("Scan failed: " + e.getMessage());
|
|
216
158
|
}
|
|
217
159
|
}
|
|
218
160
|
|
|
219
|
-
|
|
220
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Stop scanning for devices
|
|
163
|
+
*/
|
|
164
|
+
public void stop() {
|
|
165
|
+
if (!isScanning) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
221
168
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
169
|
+
try {
|
|
170
|
+
DiscoveryFactory.getInstance().stop(
|
|
171
|
+
CommunicationInterface.EMULATOR,
|
|
172
|
+
CommunicationInterface.NETWORK,
|
|
173
|
+
CommunicationInterface.USB
|
|
174
|
+
);
|
|
175
|
+
} catch (Exception e) {
|
|
176
|
+
Log.e(TAG, "Failed to stop scan", e);
|
|
228
177
|
}
|
|
229
178
|
|
|
230
|
-
|
|
231
|
-
|
|
179
|
+
isScanning = false;
|
|
180
|
+
Log.d(TAG, "Discovery stopped");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get list of discovered devices
|
|
185
|
+
*/
|
|
186
|
+
public List<Identity> getDiscoveredDevices() {
|
|
187
|
+
return new ArrayList<>(discoveredDevices);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Connect to a device
|
|
192
|
+
*/
|
|
193
|
+
public void connect(Identity identity) {
|
|
194
|
+
if (identity == null) {
|
|
195
|
+
notifyError("Invalid identity");
|
|
232
196
|
return;
|
|
233
197
|
}
|
|
234
198
|
|
|
235
|
-
if
|
|
199
|
+
// Disconnect if already connected
|
|
200
|
+
if (camera != null) {
|
|
236
201
|
disconnect();
|
|
237
202
|
}
|
|
238
203
|
|
|
239
|
-
|
|
204
|
+
Log.d(TAG, "Connecting to: " + identity.deviceId);
|
|
205
|
+
|
|
206
|
+
// Run connection on background thread since it's blocking
|
|
207
|
+
executor.execute(() -> {
|
|
208
|
+
try {
|
|
209
|
+
camera = new Camera();
|
|
210
|
+
camera.connect(identity, connectionStatusListener, new ConnectParameters());
|
|
211
|
+
|
|
212
|
+
Log.d(TAG, "Connected to camera");
|
|
213
|
+
|
|
214
|
+
if (listener != null) {
|
|
215
|
+
listener.onConnected(identity);
|
|
216
|
+
}
|
|
217
|
+
} catch (Exception e) {
|
|
218
|
+
Log.e(TAG, "Connection failed", e);
|
|
219
|
+
camera = null;
|
|
220
|
+
notifyError("Connection failed: " + e.getMessage());
|
|
221
|
+
}
|
|
222
|
+
});
|
|
240
223
|
}
|
|
241
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Disconnect from current device
|
|
227
|
+
*/
|
|
242
228
|
public void disconnect() {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
stopStreaming();
|
|
229
|
+
stopStream();
|
|
246
230
|
|
|
247
231
|
if (camera != null) {
|
|
248
232
|
try {
|
|
249
233
|
camera.disconnect();
|
|
250
|
-
} catch (
|
|
251
|
-
Log.
|
|
234
|
+
} catch (Exception e) {
|
|
235
|
+
Log.e(TAG, "Error disconnecting", e);
|
|
252
236
|
}
|
|
253
237
|
camera = null;
|
|
254
238
|
}
|
|
255
239
|
|
|
256
|
-
|
|
257
|
-
|
|
240
|
+
if (listener != null) {
|
|
241
|
+
listener.onDisconnected();
|
|
242
|
+
}
|
|
258
243
|
|
|
259
|
-
|
|
260
|
-
if (listener != null) {
|
|
261
|
-
listener.onDeviceDisconnected();
|
|
262
|
-
}
|
|
263
|
-
});
|
|
244
|
+
Log.d(TAG, "Disconnected");
|
|
264
245
|
}
|
|
265
246
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
stopStreaming();
|
|
272
|
-
startStreaming();
|
|
273
|
-
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if connected
|
|
249
|
+
*/
|
|
250
|
+
public boolean isConnected() {
|
|
251
|
+
return camera != null;
|
|
274
252
|
}
|
|
275
253
|
|
|
276
|
-
|
|
277
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Start streaming from connected device
|
|
256
|
+
*/
|
|
257
|
+
public void startStream() {
|
|
258
|
+
if (camera == null) {
|
|
259
|
+
notifyError("Not connected");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
278
262
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
263
|
+
executor.execute(() -> {
|
|
264
|
+
try {
|
|
265
|
+
// Get available streams
|
|
266
|
+
List<Stream> streams = camera.getStreams();
|
|
267
|
+
if (streams == null || streams.isEmpty()) {
|
|
268
|
+
notifyError("No streams available");
|
|
285
269
|
return;
|
|
286
270
|
}
|
|
271
|
+
|
|
272
|
+
// Find thermal stream
|
|
273
|
+
Stream thermalStream = null;
|
|
274
|
+
for (Stream stream : streams) {
|
|
275
|
+
if (stream.isThermal()) {
|
|
276
|
+
thermalStream = stream;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (thermalStream == null) {
|
|
282
|
+
thermalStream = streams.get(0);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
activeStream = thermalStream;
|
|
286
|
+
streamer = new ThermalStreamer(thermalStream);
|
|
287
|
+
|
|
288
|
+
// Start receiving frames using OnReceived and OnRemoteError
|
|
289
|
+
thermalStream.start(
|
|
290
|
+
(OnReceived<Void>) v -> {
|
|
291
|
+
// FRAME DROP GUARD: Skip frame if still processing previous one
|
|
292
|
+
// This prevents thread buildup and ensures smooth frame flow
|
|
293
|
+
long now = System.currentTimeMillis();
|
|
294
|
+
if (isProcessingFrame || (now - lastFrameProcessedMs < MIN_FRAME_INTERVAL_MS)) {
|
|
295
|
+
// Drop frame - processing is behind or too soon since last frame
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Mark processing start before queuing task
|
|
300
|
+
isProcessingFrame = true;
|
|
301
|
+
|
|
302
|
+
// Use single-threaded frameExecutor to ensure ordered frame processing
|
|
303
|
+
frameExecutor.execute(() -> {
|
|
304
|
+
try {
|
|
305
|
+
if (streamer != null) {
|
|
306
|
+
streamer.update();
|
|
307
|
+
|
|
308
|
+
// Get ImageBuffer and convert to Bitmap
|
|
309
|
+
ImageBuffer imageBuffer = streamer.getImage();
|
|
310
|
+
if (imageBuffer != null && listener != null) {
|
|
311
|
+
BitmapAndroid bitmapAndroid = BitmapAndroid.createBitmap(imageBuffer);
|
|
312
|
+
Bitmap bitmap = bitmapAndroid.getBitMap();
|
|
313
|
+
if (bitmap != null) {
|
|
314
|
+
listener.onFrame(bitmap);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} catch (Exception e) {
|
|
319
|
+
Log.e(TAG, "Error processing frame", e);
|
|
320
|
+
} finally {
|
|
321
|
+
// Reset frame processing guard to allow next frame
|
|
322
|
+
lastFrameProcessedMs = System.currentTimeMillis();
|
|
323
|
+
isProcessingFrame = false;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
error -> {
|
|
328
|
+
Log.e(TAG, "Stream error: " + error);
|
|
329
|
+
notifyError("Stream error: " + error);
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
Log.d(TAG, "Streaming started");
|
|
334
|
+
|
|
335
|
+
} catch (Exception e) {
|
|
336
|
+
Log.e(TAG, "Failed to start stream", e);
|
|
337
|
+
notifyError("Stream failed: " + e.getMessage());
|
|
287
338
|
}
|
|
288
|
-
|
|
289
|
-
} catch (Throwable t) {
|
|
290
|
-
Log.w(TAG, "[FLIR] setPalette failed: " + t.getMessage());
|
|
291
|
-
}
|
|
339
|
+
});
|
|
292
340
|
}
|
|
293
341
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Stop streaming
|
|
344
|
+
*/
|
|
345
|
+
public void stopStream() {
|
|
346
|
+
if (activeStream != null) {
|
|
347
|
+
try {
|
|
348
|
+
activeStream.stop();
|
|
349
|
+
} catch (Exception e) {
|
|
350
|
+
Log.e(TAG, "Error stopping stream", e);
|
|
351
|
+
}
|
|
352
|
+
activeStream = null;
|
|
299
353
|
}
|
|
354
|
+
|
|
355
|
+
streamer = null;
|
|
356
|
+
|
|
357
|
+
// Reset frame processing state
|
|
358
|
+
isProcessingFrame = false;
|
|
359
|
+
lastFrameProcessedMs = 0;
|
|
360
|
+
|
|
361
|
+
Log.d(TAG, "Streaming stopped");
|
|
300
362
|
}
|
|
301
363
|
|
|
302
364
|
/**
|
|
303
|
-
* Get temperature at a specific point
|
|
365
|
+
* Get temperature at a specific point in the image
|
|
366
|
+
* Queries the SDK directly - simple and no locks needed
|
|
367
|
+
* @param x X coordinate (0 to image width-1)
|
|
368
|
+
* @param y Y coordinate (0 to image height-1)
|
|
369
|
+
* @return Temperature in Celsius, or Double.NaN if not available
|
|
304
370
|
*/
|
|
305
|
-
public double
|
|
306
|
-
if (
|
|
371
|
+
public double getTemperatureAt(int x, int y) {
|
|
372
|
+
if (streamer == null) {
|
|
307
373
|
return Double.NaN;
|
|
308
374
|
}
|
|
309
375
|
|
|
376
|
+
final double[] result = {Double.NaN};
|
|
310
377
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
378
|
+
streamer.withThermalImage(thermalImage -> {
|
|
379
|
+
try {
|
|
380
|
+
int imgWidth = thermalImage.getWidth();
|
|
381
|
+
int imgHeight = thermalImage.getHeight();
|
|
382
|
+
|
|
383
|
+
int clampedX = Math.max(0, Math.min(imgWidth - 1, x));
|
|
384
|
+
int clampedY = Math.max(0, Math.min(imgHeight - 1, y));
|
|
385
|
+
|
|
386
|
+
ThermalValue value = thermalImage.getValueAt(new Point(clampedX, clampedY));
|
|
387
|
+
if (value != null) {
|
|
388
|
+
result[0] = value.asCelsius().value;
|
|
389
|
+
}
|
|
390
|
+
} catch (Exception e) {
|
|
391
|
+
Log.w(TAG, "Error querying temperature", e);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
} catch (Exception e) {
|
|
395
|
+
Log.w(TAG, "Temperature query failed", e);
|
|
323
396
|
}
|
|
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();
|
|
397
|
+
|
|
398
|
+
return result[0];
|
|
348
399
|
}
|
|
349
400
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return;
|
|
401
|
+
/**
|
|
402
|
+
* Get temperature at normalized coordinates (0.0 to 1.0)
|
|
403
|
+
* @param normalizedX X coordinate (0.0 to 1.0)
|
|
404
|
+
* @param normalizedY Y coordinate (0.0 to 1.0)
|
|
405
|
+
* @return Temperature in Celsius, or Double.NaN if not available
|
|
406
|
+
*/
|
|
407
|
+
public double getTemperatureAtNormalized(double normalizedX, double normalizedY) {
|
|
408
|
+
if (streamer == null) {
|
|
409
|
+
return Double.NaN;
|
|
359
410
|
}
|
|
360
411
|
|
|
361
|
-
|
|
362
|
-
mainHandler.post(() -> {
|
|
363
|
-
if (listener != null) {
|
|
364
|
-
listener.onDiscoveryStarted();
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
412
|
+
final double[] result = {Double.NaN};
|
|
368
413
|
try {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
414
|
+
streamer.withThermalImage(thermalImage -> {
|
|
415
|
+
try {
|
|
416
|
+
int width = thermalImage.getWidth();
|
|
417
|
+
int height = thermalImage.getHeight();
|
|
418
|
+
|
|
419
|
+
int x = (int) (normalizedX * (width - 1));
|
|
420
|
+
int y = (int) (normalizedY * (height - 1));
|
|
421
|
+
|
|
422
|
+
x = Math.max(0, Math.min(width - 1, x));
|
|
423
|
+
y = Math.max(0, Math.min(height - 1, y));
|
|
424
|
+
|
|
425
|
+
ThermalValue value = thermalImage.getValueAt(new Point(x, y));
|
|
426
|
+
if (value != null) {
|
|
427
|
+
result[0] = value.asCelsius().value;
|
|
428
|
+
}
|
|
429
|
+
} catch (Exception e) {
|
|
430
|
+
Log.w(TAG, "Error querying temperature (normalized)", e);
|
|
384
431
|
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
scheduleDiscoveryTimeout(DISCOVERY_TIMEOUT_DEVICE_MS);
|
|
389
|
-
|
|
390
|
-
} catch (Throwable t) {
|
|
391
|
-
Log.e(TAG, "[FLIR] startFullDiscovery failed: " + t.getMessage(), t);
|
|
392
|
-
notifyError("Discovery failed: " + t.getMessage());
|
|
393
|
-
startEmulatorDiscovery();
|
|
432
|
+
});
|
|
433
|
+
} catch (Exception e) {
|
|
434
|
+
Log.w(TAG, "Temperature query failed", e);
|
|
394
435
|
}
|
|
436
|
+
|
|
437
|
+
return result[0];
|
|
395
438
|
}
|
|
396
439
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
|
|
440
|
+
/**
|
|
441
|
+
* Set palette for thermal image rendering
|
|
442
|
+
*/
|
|
443
|
+
public void setPalette(String paletteName) {
|
|
444
|
+
if (streamer == null) {
|
|
445
|
+
Log.w(TAG, "No active streamer");
|
|
403
446
|
return;
|
|
404
447
|
}
|
|
405
448
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
449
|
+
executor.execute(() -> {
|
|
450
|
+
try {
|
|
451
|
+
Palette palette = findPalette(paletteName);
|
|
452
|
+
if (palette != null) {
|
|
453
|
+
streamer.withThermalImage(thermalImage -> {
|
|
454
|
+
thermalImage.setPalette(palette);
|
|
455
|
+
});
|
|
456
|
+
Log.d(TAG, "Palette set to: " + paletteName);
|
|
457
|
+
}
|
|
458
|
+
} catch (Exception e) {
|
|
459
|
+
Log.e(TAG, "Error setting palette", e);
|
|
412
460
|
}
|
|
413
461
|
});
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
DiscoveryFactory.getInstance().scan(new DiscoveryEventListener() {
|
|
417
|
-
@Override
|
|
418
|
-
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
419
|
-
handleCameraFound(discoveredCamera);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
@Override
|
|
423
|
-
public void onDiscoveryError(CommunicationInterface iface, ErrorCode errorCode) {
|
|
424
|
-
Log.w(TAG, "[FLIR] Emulator discovery error: " + errorCode);
|
|
425
|
-
}
|
|
426
|
-
}, CommunicationInterface.EMULATOR);
|
|
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
462
|
}
|
|
436
463
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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;
|
|
467
|
-
}
|
|
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
|
-
}
|
|
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);
|
|
464
|
+
/**
|
|
465
|
+
* Get list of available palettes
|
|
466
|
+
*/
|
|
467
|
+
public List<String> getAvailablePalettes() {
|
|
468
|
+
List<String> names = new ArrayList<>();
|
|
469
|
+
try {
|
|
470
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
471
|
+
for (Palette p : palettes) {
|
|
472
|
+
names.add(p.name);
|
|
485
473
|
}
|
|
474
|
+
} catch (Exception e) {
|
|
475
|
+
Log.e(TAG, "Error getting palettes", e);
|
|
486
476
|
}
|
|
477
|
+
return names;
|
|
487
478
|
}
|
|
488
479
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
isDiscovering.set(false);
|
|
497
|
-
stopDiscovery();
|
|
498
|
-
|
|
499
|
-
mainHandler.post(() -> {
|
|
500
|
-
if (listener != null) {
|
|
501
|
-
listener.onDiscoveryTimeout();
|
|
480
|
+
// Find palette by name
|
|
481
|
+
private Palette findPalette(String name) {
|
|
482
|
+
try {
|
|
483
|
+
List<Palette> palettes = PaletteManager.getDefaultPalettes();
|
|
484
|
+
for (Palette p : palettes) {
|
|
485
|
+
if (p.name.equalsIgnoreCase(name)) {
|
|
486
|
+
return p;
|
|
502
487
|
}
|
|
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();
|
|
509
488
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
private void cancelDiscoveryTimeout() {
|
|
514
|
-
if (discoveryTimeoutFuture != null && !discoveryTimeoutFuture.isDone()) {
|
|
515
|
-
discoveryTimeoutFuture.cancel(false);
|
|
516
|
-
discoveryTimeoutFuture = null;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
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(() -> {
|
|
527
|
-
try {
|
|
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(() -> {
|
|
544
|
-
if (listener != null) {
|
|
545
|
-
listener.onDeviceDisconnected();
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
}
|
|
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);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
// Start streaming automatically
|
|
567
|
-
startStreaming();
|
|
568
|
-
|
|
569
|
-
} catch (Throwable t) {
|
|
570
|
-
Log.e(TAG, "[FLIR] Connect error: " + t.getMessage(), t);
|
|
571
|
-
isConnected.set(false);
|
|
572
|
-
camera = null;
|
|
573
|
-
notifyError("Connect error: " + t.getMessage());
|
|
489
|
+
// Return first if not found
|
|
490
|
+
if (!palettes.isEmpty()) {
|
|
491
|
+
return palettes.get(0);
|
|
574
492
|
}
|
|
575
|
-
})
|
|
493
|
+
} catch (Exception e) {
|
|
494
|
+
Log.e(TAG, "Error finding palette", e);
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
576
497
|
}
|
|
577
498
|
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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;
|
|
499
|
+
// Discovery listener - no filtering, returns all devices
|
|
500
|
+
private final DiscoveryEventListener discoveryListener = new DiscoveryEventListener() {
|
|
501
|
+
@Override
|
|
502
|
+
public void onCameraFound(DiscoveredCamera discoveredCamera) {
|
|
503
|
+
Identity identity = discoveredCamera.getIdentity();
|
|
504
|
+
Log.d(TAG, "Device found: " + identity.deviceId +
|
|
505
|
+
" type=" + identity.communicationInterface);
|
|
506
|
+
|
|
507
|
+
// Add to list if not already present
|
|
508
|
+
synchronized (discoveredDevices) {
|
|
509
|
+
boolean exists = false;
|
|
510
|
+
for (Identity d : discoveredDevices) {
|
|
511
|
+
if (d.deviceId.equals(identity.deviceId)) {
|
|
512
|
+
exists = true;
|
|
606
513
|
break;
|
|
607
514
|
}
|
|
608
515
|
}
|
|
609
|
-
|
|
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;
|
|
621
|
-
break;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
} catch (Throwable t) {
|
|
625
|
-
Log.w(TAG, "[FLIR] Failed to get default palette: " + t.getMessage());
|
|
626
|
-
}
|
|
516
|
+
if (!exists) {
|
|
517
|
+
discoveredDevices.add(identity);
|
|
627
518
|
}
|
|
628
|
-
|
|
629
|
-
// Start the stream with OnReceived and OnRemoteError callbacks
|
|
630
|
-
currentStream.start(
|
|
631
|
-
new OnReceived<Void>() {
|
|
632
|
-
@Override
|
|
633
|
-
public void onReceived(Void result) {
|
|
634
|
-
// Process received frame on background thread
|
|
635
|
-
scheduler.execute(() -> refreshThermalFrame());
|
|
636
|
-
}
|
|
637
|
-
},
|
|
638
|
-
new OnRemoteError() {
|
|
639
|
-
@Override
|
|
640
|
-
public void onRemoteError(ErrorCode errorCode) {
|
|
641
|
-
Log.e(TAG, "[FLIR] Stream error: " + errorCode);
|
|
642
|
-
notifyError("Stream error: " + errorCode);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
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");
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
} catch (Throwable t) {
|
|
658
|
-
Log.e(TAG, "[FLIR] Start stream error: " + t.getMessage(), t);
|
|
659
|
-
notifyError("Stream error: " + t.getMessage());
|
|
660
519
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
if (currentStream != null) {
|
|
666
|
-
try {
|
|
667
|
-
currentStream.stop();
|
|
668
|
-
} catch (Throwable t) {
|
|
669
|
-
Log.w(TAG, "[FLIR] Stop stream error: " + t.getMessage());
|
|
520
|
+
|
|
521
|
+
if (listener != null) {
|
|
522
|
+
listener.onDeviceFound(identity);
|
|
523
|
+
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
670
524
|
}
|
|
671
|
-
currentStream = null;
|
|
672
|
-
}
|
|
673
|
-
thermalStreamer = null;
|
|
674
|
-
isStreaming.set(false);
|
|
675
|
-
}
|
|
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
525
|
}
|
|
685
526
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
527
|
+
@Override
|
|
528
|
+
public void onCameraLost(Identity identity) {
|
|
529
|
+
Log.d(TAG, "Device lost: " + identity.deviceId);
|
|
689
530
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (imageBuffer == null) {
|
|
693
|
-
return;
|
|
531
|
+
synchronized (discoveredDevices) {
|
|
532
|
+
discoveredDevices.removeIf(d -> d.deviceId.equals(identity.deviceId));
|
|
694
533
|
}
|
|
695
534
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
// Store for temperature queries
|
|
699
|
-
currentThermalImage = thermalImage;
|
|
700
|
-
|
|
701
|
-
// Apply palette if set
|
|
702
|
-
if (currentPalette != null) {
|
|
703
|
-
thermalImage.setPalette(currentPalette);
|
|
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
|
-
});
|
|
535
|
+
if (listener != null) {
|
|
536
|
+
listener.onDeviceListUpdated(new ArrayList<>(discoveredDevices));
|
|
718
537
|
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
@Override
|
|
541
|
+
public void onDiscoveryError(CommunicationInterface iface, ErrorCode error) {
|
|
542
|
+
Log.e(TAG, "Discovery error: " + iface + " - " + error);
|
|
543
|
+
notifyError("Discovery error: " + error);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@Override
|
|
547
|
+
public void onDiscoveryFinished(CommunicationInterface iface) {
|
|
548
|
+
Log.d(TAG, "Discovery finished for: " + iface);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// Connection status listener
|
|
553
|
+
private final ConnectionStatusListener connectionStatusListener = new ConnectionStatusListener() {
|
|
554
|
+
@Override
|
|
555
|
+
public void onDisconnected(ErrorCode error) {
|
|
556
|
+
Log.d(TAG, "Disconnected: " + (error != null ? error : "clean"));
|
|
557
|
+
camera = null;
|
|
719
558
|
|
|
720
|
-
|
|
721
|
-
|
|
559
|
+
if (listener != null) {
|
|
560
|
+
listener.onDisconnected();
|
|
561
|
+
}
|
|
722
562
|
}
|
|
723
|
-
}
|
|
563
|
+
};
|
|
724
564
|
|
|
725
|
-
//
|
|
565
|
+
// Helper to notify errors
|
|
566
|
+
private void notifyError(String message) {
|
|
567
|
+
if (listener != null) {
|
|
568
|
+
listener.onError(message);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
726
571
|
|
|
727
572
|
/**
|
|
728
|
-
*
|
|
573
|
+
* Cleanup resources
|
|
729
574
|
*/
|
|
730
|
-
public void
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
// Stop streaming
|
|
734
|
-
stopStreaming();
|
|
735
|
-
|
|
736
|
-
// Disconnect camera
|
|
575
|
+
public void destroy() {
|
|
576
|
+
stop();
|
|
737
577
|
disconnect();
|
|
738
|
-
|
|
739
|
-
// Stop discovery
|
|
740
|
-
stopDiscovery();
|
|
741
|
-
|
|
742
|
-
// Clear state
|
|
743
578
|
discoveredDevices.clear();
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
});
|
|
579
|
+
listener = null;
|
|
580
|
+
instance = null;
|
|
581
|
+
Log.d(TAG, "Destroyed");
|
|
759
582
|
}
|
|
760
583
|
}
|