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