ilabs-flir 2.2.25 → 2.2.28
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/java/flir/android/FlirManager.kt +4 -2
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +10 -0
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +53 -0
- package/ios/Flir/src/FlirManager.swift +640 -576
- package/ios/Flir/src/FlirModule.m +25 -2
- package/package.json +1 -1
|
@@ -1,576 +1,640 @@
|
|
|
1
|
-
//
|
|
2
|
-
// FlirManager.swift
|
|
3
|
-
// Flir
|
|
4
|
-
//
|
|
5
|
-
// Simplified FLIR camera manager - matches sample app pattern
|
|
6
|
-
// scan → connect → stream → disconnect
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
import Foundation
|
|
10
|
-
import UIKit
|
|
11
|
-
|
|
12
|
-
#if FLIR_ENABLED
|
|
13
|
-
import ThermalSDK
|
|
14
|
-
#endif
|
|
15
|
-
|
|
16
|
-
/// Device info structure for discovered cameras
|
|
17
|
-
@objc public class FlirDeviceInfo: NSObject {
|
|
18
|
-
@objc public let deviceId: String
|
|
19
|
-
@objc public let name: String
|
|
20
|
-
@objc public let communicationType: String
|
|
21
|
-
@objc public let isEmulator: Bool
|
|
22
|
-
|
|
23
|
-
init(deviceId: String, name: String, communicationType: String, isEmulator: Bool) {
|
|
24
|
-
self.deviceId = deviceId
|
|
25
|
-
self.name = name
|
|
26
|
-
self.communicationType = communicationType
|
|
27
|
-
self.isEmulator = isEmulator
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@objc public func toDictionary() -> [String: Any] {
|
|
31
|
-
return [
|
|
32
|
-
"id": deviceId,
|
|
33
|
-
"name": name,
|
|
34
|
-
"communicationType": communicationType,
|
|
35
|
-
"isEmulator": isEmulator
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Callback protocol for FlirManager events
|
|
41
|
-
@objc public protocol FlirManagerDelegate: AnyObject {
|
|
42
|
-
func onDevicesFound(_ devices: [FlirDeviceInfo])
|
|
43
|
-
func onDeviceConnected(_ device: FlirDeviceInfo)
|
|
44
|
-
func onDeviceDisconnected()
|
|
45
|
-
func onFrameReceived(_ image: UIImage, width: Int, height: Int)
|
|
46
|
-
@objc optional func onFrameReceivedRaw(_ data: Data, width: Int, height: Int, bytesPerRow: Int, timestamp: Double)
|
|
47
|
-
func onError(_ message: String)
|
|
48
|
-
func onStateChanged(_ state: String, isConnected: Bool, isStreaming: Bool, isEmulator: Bool)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/// Main FLIR Manager - Simplified Singleton
|
|
52
|
-
@objc public class FlirManager: NSObject {
|
|
53
|
-
@objc public static let shared = FlirManager()
|
|
54
|
-
|
|
55
|
-
// MARK: - Singleton
|
|
56
|
-
|
|
57
|
-
// MARK: - Properties
|
|
58
|
-
@objc public weak var delegate: FlirManagerDelegate?
|
|
59
|
-
|
|
60
|
-
private var _isConnected = false
|
|
61
|
-
private var _isStreaming = false
|
|
62
|
-
private var _isProcessingFrame = false
|
|
63
|
-
private var connectedDeviceId: String?
|
|
64
|
-
private var connectedDeviceName: String?
|
|
65
|
-
|
|
66
|
-
// Dedicated render queue for frame processing (matches sample app pattern)
|
|
67
|
-
private let renderQueue = DispatchQueue(label: "com.flir.render")
|
|
68
|
-
|
|
69
|
-
// Latest frame
|
|
70
|
-
private var _latestImage: UIImage?
|
|
71
|
-
@objc public var latestImage: UIImage? { return _latestImage }
|
|
72
|
-
|
|
73
|
-
// Discovered devices
|
|
74
|
-
private var discoveredDevices: [FlirDeviceInfo] = []
|
|
75
|
-
|
|
76
|
-
#if FLIR_ENABLED
|
|
77
|
-
private var discovery: FLIRDiscovery?
|
|
78
|
-
private var camera: FLIRCamera?
|
|
79
|
-
private var stream: FLIRStream?
|
|
80
|
-
private var streamer: FLIRThermalStreamer?
|
|
81
|
-
private var identityMap: [String: FLIRIdentity] = [:]
|
|
82
|
-
#endif
|
|
83
|
-
|
|
84
|
-
private override init() {
|
|
85
|
-
super.init()
|
|
86
|
-
NSLog("[FlirManager] Initialized")
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// MARK: - Public State
|
|
90
|
-
|
|
91
|
-
@objc public var isConnected: Bool { return _isConnected }
|
|
92
|
-
@objc public var isStreaming: Bool { return _isStreaming }
|
|
93
|
-
@objc public var isEmulator: Bool {
|
|
94
|
-
return connectedDeviceName?.lowercased().contains("emulator") == true
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
@objc public func getDiscoveredDevices() -> [FlirDeviceInfo] {
|
|
98
|
-
return discoveredDevices
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// MARK: - Discovery
|
|
102
|
-
|
|
103
|
-
@objc public func startDiscovery() {
|
|
104
|
-
NSLog("[FlirManager] startDiscovery")
|
|
105
|
-
|
|
106
|
-
#if FLIR_ENABLED
|
|
107
|
-
discoveredDevices.removeAll()
|
|
108
|
-
identityMap.removeAll()
|
|
109
|
-
|
|
110
|
-
if discovery == nil {
|
|
111
|
-
discovery = FLIRDiscovery()
|
|
112
|
-
discovery?.delegate = self
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Match sample app: discover lightning + wireless + emulator + network
|
|
116
|
-
discovery?.start([.lightning, .flirOneWireless, .emulator, .network])
|
|
117
|
-
|
|
118
|
-
emitStateChange("discovering")
|
|
119
|
-
#else
|
|
120
|
-
delegate?.onError("FLIR SDK not available")
|
|
121
|
-
#endif
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
@objc public func stopDiscovery() {
|
|
125
|
-
NSLog("[FlirManager] stopDiscovery")
|
|
126
|
-
|
|
127
|
-
#if FLIR_ENABLED
|
|
128
|
-
discovery?.stop()
|
|
129
|
-
emitStateChange("idle")
|
|
130
|
-
#endif
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// MARK: - Connection
|
|
134
|
-
|
|
135
|
-
@objc public func connectToDevice(_ deviceId: String) {
|
|
136
|
-
NSLog("[FlirManager] connectToDevice: \(deviceId)")
|
|
137
|
-
|
|
138
|
-
#if FLIR_ENABLED
|
|
139
|
-
// Find identity
|
|
140
|
-
guard let identity = identityMap[deviceId] else {
|
|
141
|
-
NSLog("[FlirManager] Device not found: \(deviceId)")
|
|
142
|
-
delegate?.onError("Device not found: \(deviceId)")
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Disconnect if already connected
|
|
147
|
-
if _isConnected {
|
|
148
|
-
disconnect()
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Connect on background thread (matches sample app)
|
|
152
|
-
DispatchQueue.global().async { [weak self] in
|
|
153
|
-
guard let self = self else { return }
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
@objc public func
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
#
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
@objc public func
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
@objc public func
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
@objc public func
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
let
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1
|
+
//
|
|
2
|
+
// FlirManager.swift
|
|
3
|
+
// Flir
|
|
4
|
+
//
|
|
5
|
+
// Simplified FLIR camera manager - matches sample app pattern
|
|
6
|
+
// scan → connect → stream → disconnect
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
import UIKit
|
|
11
|
+
|
|
12
|
+
#if FLIR_ENABLED
|
|
13
|
+
import ThermalSDK
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
/// Device info structure for discovered cameras
|
|
17
|
+
@objc public class FlirDeviceInfo: NSObject {
|
|
18
|
+
@objc public let deviceId: String
|
|
19
|
+
@objc public let name: String
|
|
20
|
+
@objc public let communicationType: String
|
|
21
|
+
@objc public let isEmulator: Bool
|
|
22
|
+
|
|
23
|
+
init(deviceId: String, name: String, communicationType: String, isEmulator: Bool) {
|
|
24
|
+
self.deviceId = deviceId
|
|
25
|
+
self.name = name
|
|
26
|
+
self.communicationType = communicationType
|
|
27
|
+
self.isEmulator = isEmulator
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@objc public func toDictionary() -> [String: Any] {
|
|
31
|
+
return [
|
|
32
|
+
"id": deviceId,
|
|
33
|
+
"name": name,
|
|
34
|
+
"communicationType": communicationType,
|
|
35
|
+
"isEmulator": isEmulator
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Callback protocol for FlirManager events
|
|
41
|
+
@objc public protocol FlirManagerDelegate: AnyObject {
|
|
42
|
+
func onDevicesFound(_ devices: [FlirDeviceInfo])
|
|
43
|
+
func onDeviceConnected(_ device: FlirDeviceInfo)
|
|
44
|
+
func onDeviceDisconnected()
|
|
45
|
+
func onFrameReceived(_ image: UIImage, width: Int, height: Int)
|
|
46
|
+
@objc optional func onFrameReceivedRaw(_ data: Data, width: Int, height: Int, bytesPerRow: Int, timestamp: Double)
|
|
47
|
+
func onError(_ message: String)
|
|
48
|
+
func onStateChanged(_ state: String, isConnected: Bool, isStreaming: Bool, isEmulator: Bool)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Main FLIR Manager - Simplified Singleton
|
|
52
|
+
@objc public class FlirManager: NSObject {
|
|
53
|
+
@objc public static let shared = FlirManager()
|
|
54
|
+
|
|
55
|
+
// MARK: - Singleton
|
|
56
|
+
|
|
57
|
+
// MARK: - Properties
|
|
58
|
+
@objc public weak var delegate: FlirManagerDelegate?
|
|
59
|
+
|
|
60
|
+
private var _isConnected = false
|
|
61
|
+
private var _isStreaming = false
|
|
62
|
+
private var _isProcessingFrame = false
|
|
63
|
+
private var connectedDeviceId: String?
|
|
64
|
+
private var connectedDeviceName: String?
|
|
65
|
+
|
|
66
|
+
// Dedicated render queue for frame processing (matches sample app pattern)
|
|
67
|
+
private let renderQueue = DispatchQueue(label: "com.flir.render")
|
|
68
|
+
|
|
69
|
+
// Latest frame
|
|
70
|
+
private var _latestImage: UIImage?
|
|
71
|
+
@objc public var latestImage: UIImage? { return _latestImage }
|
|
72
|
+
|
|
73
|
+
// Discovered devices
|
|
74
|
+
private var discoveredDevices: [FlirDeviceInfo] = []
|
|
75
|
+
|
|
76
|
+
#if FLIR_ENABLED
|
|
77
|
+
private var discovery: FLIRDiscovery?
|
|
78
|
+
private var camera: FLIRCamera?
|
|
79
|
+
private var stream: FLIRStream?
|
|
80
|
+
private var streamer: FLIRThermalStreamer?
|
|
81
|
+
private var identityMap: [String: FLIRIdentity] = [:]
|
|
82
|
+
#endif
|
|
83
|
+
|
|
84
|
+
private override init() {
|
|
85
|
+
super.init()
|
|
86
|
+
NSLog("[FlirManager] Initialized")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MARK: - Public State
|
|
90
|
+
|
|
91
|
+
@objc public var isConnected: Bool { return _isConnected }
|
|
92
|
+
@objc public var isStreaming: Bool { return _isStreaming }
|
|
93
|
+
@objc public var isEmulator: Bool {
|
|
94
|
+
return connectedDeviceName?.lowercased().contains("emulator") == true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@objc public func getDiscoveredDevices() -> [FlirDeviceInfo] {
|
|
98
|
+
return discoveredDevices
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// MARK: - Discovery
|
|
102
|
+
|
|
103
|
+
@objc public func startDiscovery() {
|
|
104
|
+
NSLog("[FlirManager] startDiscovery")
|
|
105
|
+
|
|
106
|
+
#if FLIR_ENABLED
|
|
107
|
+
discoveredDevices.removeAll()
|
|
108
|
+
identityMap.removeAll()
|
|
109
|
+
|
|
110
|
+
if discovery == nil {
|
|
111
|
+
discovery = FLIRDiscovery()
|
|
112
|
+
discovery?.delegate = self
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Match sample app: discover lightning + wireless + emulator + network
|
|
116
|
+
discovery?.start([.lightning, .flirOneWireless, .emulator, .network])
|
|
117
|
+
|
|
118
|
+
emitStateChange("discovering")
|
|
119
|
+
#else
|
|
120
|
+
delegate?.onError("FLIR SDK not available")
|
|
121
|
+
#endif
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@objc public func stopDiscovery() {
|
|
125
|
+
NSLog("[FlirManager] stopDiscovery")
|
|
126
|
+
|
|
127
|
+
#if FLIR_ENABLED
|
|
128
|
+
discovery?.stop()
|
|
129
|
+
emitStateChange("idle")
|
|
130
|
+
#endif
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// MARK: - Connection
|
|
134
|
+
|
|
135
|
+
@objc public func connectToDevice(_ deviceId: String) {
|
|
136
|
+
NSLog("[FlirManager] connectToDevice: \(deviceId)")
|
|
137
|
+
|
|
138
|
+
#if FLIR_ENABLED
|
|
139
|
+
// Find identity
|
|
140
|
+
guard let identity = identityMap[deviceId] else {
|
|
141
|
+
NSLog("[FlirManager] Device not found: \(deviceId)")
|
|
142
|
+
delegate?.onError("Device not found: \(deviceId)")
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Disconnect if already connected
|
|
147
|
+
if _isConnected {
|
|
148
|
+
disconnect()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Connect on background thread (matches sample app)
|
|
152
|
+
DispatchQueue.global().async { [weak self] in
|
|
153
|
+
guard let self = self else { return }
|
|
154
|
+
|
|
155
|
+
// Create camera instance
|
|
156
|
+
if self.camera == nil {
|
|
157
|
+
self.camera = FLIRCamera()
|
|
158
|
+
self.camera?.delegate = self
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
guard let cam = self.camera else {
|
|
162
|
+
self.notifyError("Failed to create camera")
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let iface = identity.communicationInterface()
|
|
167
|
+
let camType = identity.cameraType()
|
|
168
|
+
NSLog("[FlirManager] Camera type: \(camType.rawValue), interface: \(iface.rawValue)")
|
|
169
|
+
|
|
170
|
+
// ── AUTHENTICATE for network cameras ──
|
|
171
|
+
// Official FLIR CameraConnector sample checks .generic camera type,
|
|
172
|
+
// but FLIR One Edge Pro over network may report a different type.
|
|
173
|
+
// Check BOTH: camera type == .generic OR interface contains .network
|
|
174
|
+
let needsAuth = (camType == .generic) || iface.contains(.network)
|
|
175
|
+
|
|
176
|
+
if needsAuth {
|
|
177
|
+
NSLog("[FlirManager] Network camera detected — authenticating...")
|
|
178
|
+
|
|
179
|
+
// Use UUID-based persistent certificate name (matches FLIR sample).
|
|
180
|
+
// The camera has a bug where re-auth with a different name conflicts.
|
|
181
|
+
let certName = self.getPersistentCertificateName()
|
|
182
|
+
NSLog("[FlirManager] Using certificate name: \(certName)")
|
|
183
|
+
|
|
184
|
+
var status = FLIRAuthenticationStatus.pending
|
|
185
|
+
var attempts = 0
|
|
186
|
+
let maxAttempts = 30 // ~30 seconds timeout
|
|
187
|
+
|
|
188
|
+
while status == .pending && attempts < maxAttempts {
|
|
189
|
+
status = cam.authenticate(identity, trustedConnectionName: certName)
|
|
190
|
+
NSLog("[FlirManager] Auth attempt \(attempts + 1)/\(maxAttempts) status: \(status.rawValue)")
|
|
191
|
+
|
|
192
|
+
if status == .pending {
|
|
193
|
+
// Camera waiting for user to press "Trust" on its screen
|
|
194
|
+
Thread.sleep(forTimeInterval: 1.0)
|
|
195
|
+
}
|
|
196
|
+
attempts += 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if status != .approved {
|
|
200
|
+
NSLog("[FlirManager] Authentication failed/timed out: \(status.rawValue)")
|
|
201
|
+
self.camera = nil
|
|
202
|
+
DispatchQueue.main.async {
|
|
203
|
+
self.emitStateChange("connection_failed")
|
|
204
|
+
self.delegate?.onError("Camera authentication failed. Check the camera screen for a trust/approve prompt.")
|
|
205
|
+
}
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
NSLog("[FlirManager] Authentication approved ✅")
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── PAIR ──
|
|
212
|
+
do {
|
|
213
|
+
try cam.pair(identity, code: 0)
|
|
214
|
+
NSLog("[FlirManager] Pair succeeded")
|
|
215
|
+
} catch {
|
|
216
|
+
NSLog("[FlirManager] Pair failed: \(error)")
|
|
217
|
+
self._isConnected = false
|
|
218
|
+
self.camera = nil
|
|
219
|
+
DispatchQueue.main.async {
|
|
220
|
+
self.emitStateChange("connection_failed")
|
|
221
|
+
self.delegate?.onError("Pairing failed: \(error.localizedDescription)")
|
|
222
|
+
}
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── CONNECT ──
|
|
227
|
+
do {
|
|
228
|
+
try cam.connect()
|
|
229
|
+
|
|
230
|
+
self._isConnected = true
|
|
231
|
+
self.connectedDeviceId = identity.deviceId()
|
|
232
|
+
self.connectedDeviceName = identity.deviceId()
|
|
233
|
+
|
|
234
|
+
NSLog("[FlirManager] Connected to: \(identity.deviceId()) ✅")
|
|
235
|
+
|
|
236
|
+
// Notify on main thread
|
|
237
|
+
let deviceInfo = FlirDeviceInfo(
|
|
238
|
+
deviceId: identity.deviceId(),
|
|
239
|
+
name: identity.deviceId(),
|
|
240
|
+
communicationType: self.interfaceName(identity.communicationInterface()),
|
|
241
|
+
isEmulator: identity.communicationInterface() == .emulator
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
DispatchQueue.main.async {
|
|
245
|
+
self.delegate?.onDeviceConnected(deviceInfo)
|
|
246
|
+
self.emitStateChange("connected")
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Auto-start streaming (matches sample app)
|
|
250
|
+
self.startStreamInternal()
|
|
251
|
+
|
|
252
|
+
} catch {
|
|
253
|
+
NSLog("[FlirManager] Connect failed: \(error)")
|
|
254
|
+
self._isConnected = false
|
|
255
|
+
self.camera = nil
|
|
256
|
+
DispatchQueue.main.async {
|
|
257
|
+
self.emitStateChange("connection_failed")
|
|
258
|
+
self.delegate?.onError("Connection failed: \(error.localizedDescription)")
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
#else
|
|
263
|
+
delegate?.onError("FLIR SDK not available")
|
|
264
|
+
#endif
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@objc public func startEmulator() {
|
|
268
|
+
NSLog("[FlirManager] startEmulator")
|
|
269
|
+
startDiscovery()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@objc public func disconnect() {
|
|
273
|
+
NSLog("[FlirManager] disconnect")
|
|
274
|
+
|
|
275
|
+
#if FLIR_ENABLED
|
|
276
|
+
stopStream()
|
|
277
|
+
camera?.disconnect()
|
|
278
|
+
camera = nil
|
|
279
|
+
_isConnected = false
|
|
280
|
+
connectedDeviceId = nil
|
|
281
|
+
connectedDeviceName = nil
|
|
282
|
+
_latestImage = nil
|
|
283
|
+
|
|
284
|
+
DispatchQueue.main.async { [weak self] in
|
|
285
|
+
self?.delegate?.onDeviceDisconnected()
|
|
286
|
+
self?.emitStateChange("disconnected")
|
|
287
|
+
}
|
|
288
|
+
#endif
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@objc public func stop() {
|
|
292
|
+
stopStream()
|
|
293
|
+
disconnect()
|
|
294
|
+
stopDiscovery()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// MARK: - Streaming
|
|
298
|
+
|
|
299
|
+
@objc public func startStream() {
|
|
300
|
+
#if FLIR_ENABLED
|
|
301
|
+
guard _isConnected else {
|
|
302
|
+
delegate?.onError("Not connected")
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
DispatchQueue.global().async { [weak self] in
|
|
307
|
+
self?.startStreamInternal()
|
|
308
|
+
}
|
|
309
|
+
#endif
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#if FLIR_ENABLED
|
|
313
|
+
private func startStreamInternal() {
|
|
314
|
+
guard let cam = camera else { return }
|
|
315
|
+
|
|
316
|
+
let streams = cam.getStreams()
|
|
317
|
+
guard !streams.isEmpty else {
|
|
318
|
+
NSLog("[FlirManager] No streams available")
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Find thermal stream or use first
|
|
323
|
+
let thermalStream = streams.first { $0.isThermal } ?? streams.first!
|
|
324
|
+
|
|
325
|
+
stream = thermalStream
|
|
326
|
+
streamer = FLIRThermalStreamer(stream: thermalStream)
|
|
327
|
+
streamer?.autoScale = true
|
|
328
|
+
streamer?.renderScale = true
|
|
329
|
+
thermalStream.delegate = self
|
|
330
|
+
|
|
331
|
+
do {
|
|
332
|
+
try thermalStream.start()
|
|
333
|
+
_isStreaming = true
|
|
334
|
+
NSLog("[FlirManager] Streaming started")
|
|
335
|
+
DispatchQueue.main.async { [weak self] in
|
|
336
|
+
self?.emitStateChange("streaming")
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
NSLog("[FlirManager] Stream start failed: \(error)")
|
|
340
|
+
stream = nil
|
|
341
|
+
streamer = nil
|
|
342
|
+
delegate?.onError("Stream failed: \(error.localizedDescription)")
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
#endif
|
|
346
|
+
|
|
347
|
+
@objc public func stopStream() {
|
|
348
|
+
NSLog("[FlirManager] stopStream")
|
|
349
|
+
|
|
350
|
+
#if FLIR_ENABLED
|
|
351
|
+
stream?.stop()
|
|
352
|
+
stream = nil
|
|
353
|
+
streamer = nil
|
|
354
|
+
_isStreaming = false
|
|
355
|
+
_latestImage = nil
|
|
356
|
+
|
|
357
|
+
if _isConnected {
|
|
358
|
+
emitStateChange("connected")
|
|
359
|
+
}
|
|
360
|
+
#endif
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// MARK: - Temperature
|
|
364
|
+
|
|
365
|
+
@objc public func getTemperatureAt(x: Int, y: Int) -> Double {
|
|
366
|
+
#if FLIR_ENABLED
|
|
367
|
+
guard let streamer = streamer else { return Double.nan }
|
|
368
|
+
|
|
369
|
+
var result = Double.nan
|
|
370
|
+
streamer.withThermalImage { thermalImage in
|
|
371
|
+
let w = thermalImage.getWidth()
|
|
372
|
+
let h = thermalImage.getHeight()
|
|
373
|
+
let cx = max(0, min(Int(w) - 1, x))
|
|
374
|
+
let cy = max(0, min(Int(h) - 1, y))
|
|
375
|
+
|
|
376
|
+
if let measurements = thermalImage.measurements,
|
|
377
|
+
let spot = try? measurements.addSpot(CGPoint(x: cx, y: cy)) {
|
|
378
|
+
|
|
379
|
+
// getValue() returns non-optional in some SDK versions, or optional in others.
|
|
380
|
+
// Compiler says it is NOT optional here, so direct assignment.
|
|
381
|
+
let value = spot.getValue()
|
|
382
|
+
result = value.value
|
|
383
|
+
|
|
384
|
+
try? measurements.remove(spot)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return result
|
|
388
|
+
#else
|
|
389
|
+
return Double.nan
|
|
390
|
+
#endif
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
|
|
396
|
+
guard let img = latestImage else { return Double.nan }
|
|
397
|
+
let px = Int(nx * Double(img.size.width))
|
|
398
|
+
let py = Int(y * Double(img.size.height))
|
|
399
|
+
return getTemperatureAt(x: px, y: py)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// MARK: - Legacy / Compatibility Methods
|
|
403
|
+
|
|
404
|
+
@objc public func setPreferSdkRotation(_ prefer: Bool) {
|
|
405
|
+
// No-op in simplified version
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@objc public func isPreferSdkRotation() -> Bool {
|
|
409
|
+
return false
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@objc public func setNetworkDiscoveryEnabled(_ enabled: Bool) {
|
|
413
|
+
// No-op - simple discovery always scans all supported types
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@objc public func startEmulator(withType type: String) {
|
|
417
|
+
NSLog("[FlirManager] startEmulator(withType: \(type))")
|
|
418
|
+
startDiscovery()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@objc public func latestFrameBitmapBase64() -> [String: Any]? {
|
|
422
|
+
// Legacy method for base64 frame data - simplified version uses onFrameReceived
|
|
423
|
+
// If absolutely needed, we could implement jpeg compression here
|
|
424
|
+
return nil
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@objc public func getConnectedDeviceInfo() -> String {
|
|
428
|
+
return connectedDeviceName ?? "Not connected"
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// MARK: - Battery (stub - not needed per user)
|
|
432
|
+
|
|
433
|
+
// MARK: - Battery (stub - not needed per user)
|
|
434
|
+
|
|
435
|
+
@objc public func getBatteryLevel() -> Int { return -1 }
|
|
436
|
+
@objc public func isBatteryCharging() -> Bool { return false }
|
|
437
|
+
|
|
438
|
+
// MARK: - Shim Compatibility
|
|
439
|
+
|
|
440
|
+
@objc public static var isSDKAvailable: Bool {
|
|
441
|
+
return true
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
@objc public func setPalette(_ name: String) {
|
|
445
|
+
// stub
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@objc public func setPaletteFromAcol(_ acol: Float) {
|
|
449
|
+
// stub
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@objc public func retainClient(_ clientId: String) {
|
|
453
|
+
// Only start discovery if not already connected
|
|
454
|
+
// Starting discovery while connected can interfere with active stream
|
|
455
|
+
if !_isConnected {
|
|
456
|
+
startDiscovery()
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
@objc public func releaseClient(_ clientId: String) {
|
|
461
|
+
// simplified manager doesn't track retain counts per client yet
|
|
462
|
+
// stopDiscovery() // Optional: could stop if count == 0
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// MARK: - Helpers
|
|
466
|
+
|
|
467
|
+
private func emitStateChange(_ state: String) {
|
|
468
|
+
DispatchQueue.main.async { [weak self] in
|
|
469
|
+
guard let self = self else { return }
|
|
470
|
+
self.delegate?.onStateChanged(state, isConnected: self._isConnected, isStreaming: self._isStreaming, isEmulator: self.isEmulator)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private func notifyError(_ message: String) {
|
|
475
|
+
DispatchQueue.main.async { [weak self] in
|
|
476
|
+
self?.delegate?.onError(message)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/// Persistent UUID-based certificate name for camera authentication.
|
|
481
|
+
/// Matches the pattern from FLIR's official CameraConnector sample.
|
|
482
|
+
/// The camera has a bug where re-auth with a different name can conflict,
|
|
483
|
+
/// so we generate a UUID once and persist it in UserDefaults.
|
|
484
|
+
private func getPersistentCertificateName() -> String {
|
|
485
|
+
guard let bundleID = Bundle.main.bundleIdentifier else { return "flir-cert-fallback" }
|
|
486
|
+
let key = "\(bundleID)-flir-cert-name"
|
|
487
|
+
let defaults = UserDefaults.standard
|
|
488
|
+
if let existing = defaults.string(forKey: key) {
|
|
489
|
+
return existing
|
|
490
|
+
}
|
|
491
|
+
let newName = UUID().uuidString
|
|
492
|
+
defaults.set(newName, forKey: key)
|
|
493
|
+
return newName
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
#if FLIR_ENABLED
|
|
497
|
+
private func interfaceName(_ iface: FLIRCommunicationInterface) -> String {
|
|
498
|
+
if iface.contains(.lightning) { return "LIGHTNING" }
|
|
499
|
+
if iface.contains(.network) { return "NETWORK" }
|
|
500
|
+
if iface.contains(.flirOneWireless) { return "WIRELESS" }
|
|
501
|
+
if iface.contains(.emulator) { return "EMULATOR" }
|
|
502
|
+
return "UNKNOWN"
|
|
503
|
+
}
|
|
504
|
+
#endif
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// MARK: - Discovery Delegate
|
|
508
|
+
|
|
509
|
+
#if FLIR_ENABLED
|
|
510
|
+
extension FlirManager: FLIRDiscoveryEventDelegate {
|
|
511
|
+
public func cameraDiscovered(_ camera: FLIRDiscoveredCamera) {
|
|
512
|
+
let identity = camera.identity
|
|
513
|
+
let deviceId = identity.deviceId()
|
|
514
|
+
|
|
515
|
+
NSLog("[FlirManager] Device found: \(deviceId)")
|
|
516
|
+
|
|
517
|
+
// Store identity
|
|
518
|
+
identityMap[deviceId] = identity
|
|
519
|
+
|
|
520
|
+
// Create device info
|
|
521
|
+
let deviceInfo = FlirDeviceInfo(
|
|
522
|
+
deviceId: deviceId,
|
|
523
|
+
name: camera.displayName ?? deviceId,
|
|
524
|
+
communicationType: interfaceName(identity.communicationInterface()),
|
|
525
|
+
isEmulator: identity.communicationInterface() == .emulator
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
// Add if not exists
|
|
529
|
+
if !discoveredDevices.contains(where: { $0.deviceId == deviceId }) {
|
|
530
|
+
discoveredDevices.append(deviceInfo)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
DispatchQueue.main.async { [weak self] in
|
|
534
|
+
guard let self = self else { return }
|
|
535
|
+
self.delegate?.onDevicesFound(self.discoveredDevices)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
public func discoveryError(_ error: String, netServiceError: Int32, on iface: FLIRCommunicationInterface) {
|
|
540
|
+
NSLog("[FlirManager] Discovery error: \(error)")
|
|
541
|
+
delegate?.onError("Discovery error: \(error)")
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
|
|
545
|
+
NSLog("[FlirManager] Discovery finished: \(iface)")
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
public func cameraLost(_ cameraIdentity: FLIRIdentity) {
|
|
549
|
+
let deviceId = cameraIdentity.deviceId()
|
|
550
|
+
NSLog("[FlirManager] Device lost: \(deviceId)")
|
|
551
|
+
|
|
552
|
+
identityMap.removeValue(forKey: deviceId)
|
|
553
|
+
discoveredDevices.removeAll { $0.deviceId == deviceId }
|
|
554
|
+
|
|
555
|
+
DispatchQueue.main.async { [weak self] in
|
|
556
|
+
guard let self = self else { return }
|
|
557
|
+
self.delegate?.onDevicesFound(self.discoveredDevices)
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
#endif
|
|
562
|
+
|
|
563
|
+
// MARK: - Camera Delegate
|
|
564
|
+
|
|
565
|
+
#if FLIR_ENABLED
|
|
566
|
+
extension FlirManager: FLIRDataReceivedDelegate {
|
|
567
|
+
public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
|
|
568
|
+
NSLog("[FlirManager] Camera disconnected: \(error?.localizedDescription ?? "clean")")
|
|
569
|
+
|
|
570
|
+
_isConnected = false
|
|
571
|
+
_isStreaming = false
|
|
572
|
+
self.camera = nil
|
|
573
|
+
stream = nil
|
|
574
|
+
streamer = nil
|
|
575
|
+
|
|
576
|
+
DispatchQueue.main.async { [weak self] in
|
|
577
|
+
self?.delegate?.onDeviceDisconnected()
|
|
578
|
+
self?.emitStateChange("disconnected")
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
#endif
|
|
583
|
+
|
|
584
|
+
// MARK: - Stream Delegate
|
|
585
|
+
|
|
586
|
+
#if FLIR_ENABLED
|
|
587
|
+
extension FlirManager: FLIRStreamDelegate {
|
|
588
|
+
public func onError(_ error: Error) {
|
|
589
|
+
NSLog("[FlirManager] Stream error: \(error)")
|
|
590
|
+
delegate?.onError("Stream error: \(error.localizedDescription)")
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
public func onImageReceived() {
|
|
594
|
+
NSLog("[FLIR-TRACE 1️⃣] onImageReceived called on SDK thread")
|
|
595
|
+
|
|
596
|
+
// Process frame on dedicated render queue (matches sample app pattern)
|
|
597
|
+
// This prevents blocking the SDK callback thread and main thread
|
|
598
|
+
// Guard to skip frame if already processing (prevents backpressure/latency)
|
|
599
|
+
guard !_isProcessingFrame else {
|
|
600
|
+
NSLog("[FLIR-TRACE ⏩] Skipping frame (already processing)")
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_isProcessingFrame = true
|
|
605
|
+
renderQueue.async { [weak self] in
|
|
606
|
+
defer { self?._isProcessingFrame = false }
|
|
607
|
+
guard let self = self, let streamer = self.streamer else {
|
|
608
|
+
NSLog("[FLIR-TRACE ❌] No self or streamer in renderQueue")
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
NSLog("[FLIR-TRACE 2️⃣] Processing on renderQueue")
|
|
613
|
+
|
|
614
|
+
do {
|
|
615
|
+
try streamer.update()
|
|
616
|
+
NSLog("[FLIR-TRACE 3️⃣] Streamer updated successfully")
|
|
617
|
+
} catch {
|
|
618
|
+
NSLog("[FLIR-TRACE ❌] Streamer update failed: \(error)")
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
guard let image = streamer.getImage() else {
|
|
623
|
+
NSLog("[FLIR-TRACE ❌] streamer.getImage() returned nil")
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
NSLog("[FLIR-TRACE 4️⃣] Got image from streamer: \(image.size.width)x\(image.size.height)")
|
|
628
|
+
|
|
629
|
+
self._latestImage = image
|
|
630
|
+
let width = Int(image.size.width)
|
|
631
|
+
let height = Int(image.size.height)
|
|
632
|
+
|
|
633
|
+
DispatchQueue.main.async { [weak self] in
|
|
634
|
+
NSLog("[FLIR-TRACE 5️⃣] Dispatching to delegate.onFrameReceived on main thread")
|
|
635
|
+
self?.delegate?.onFrameReceived(image, width: width, height: height)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
#endif
|