ilabs-flir 2.1.2 → 2.1.31
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/ios/Flir/src/FlirManager.swift +97 -52
- package/ios/Flir/src/FlirModule.m +67 -14
- package/package.json +1 -1
|
@@ -310,23 +310,57 @@ import ThermalSDK
|
|
|
310
310
|
discovery?.delegate = self
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
//
|
|
314
|
-
|
|
313
|
+
// Build interfaces based on available permissions
|
|
314
|
+
// Always include Lightning, USB, Wireless BLE, and Emulator
|
|
315
|
+
var interfaces: FLIRCommunicationInterface = [
|
|
315
316
|
.lightning,
|
|
316
|
-
.network,
|
|
317
317
|
.flirOneWireless,
|
|
318
318
|
.emulator
|
|
319
319
|
]
|
|
320
|
+
|
|
321
|
+
// Only add network discovery if NSLocalNetworkUsageDescription is present
|
|
322
|
+
// This prevents crashes/errors when user doesn't have iOS developer registration
|
|
323
|
+
// or hasn't declared network permission
|
|
324
|
+
if shouldEnableNetworkDiscovery() {
|
|
325
|
+
interfaces.insert(.network)
|
|
326
|
+
NSLog("[FlirManager] Network discovery enabled (NSLocalNetworkUsageDescription present)")
|
|
327
|
+
} else {
|
|
328
|
+
NSLog("[FlirManager] Network discovery disabled (no NSLocalNetworkUsageDescription)")
|
|
329
|
+
}
|
|
330
|
+
|
|
320
331
|
discovery?.start(interfaces)
|
|
321
332
|
|
|
322
333
|
emitStateChange("discovering")
|
|
323
|
-
NSLog("[FlirManager] Discovery started on interfaces: Lightning, Network, FlirOneWireless, Emulator")
|
|
334
|
+
NSLog("[FlirManager] Discovery started on interfaces: Lightning, \(interfaces.contains(.network) ? "Network, " : "")FlirOneWireless, Emulator")
|
|
324
335
|
#else
|
|
325
336
|
NSLog("[FlirManager] FLIR SDK not available - discovery disabled")
|
|
326
337
|
delegate?.onError("FLIR SDK not available")
|
|
327
338
|
#endif
|
|
328
339
|
}
|
|
329
340
|
|
|
341
|
+
/// Check if network discovery should be enabled based on Info.plist permission
|
|
342
|
+
private func shouldEnableNetworkDiscovery() -> Bool {
|
|
343
|
+
// Check for explicit override first
|
|
344
|
+
let key = "ilabsFlir.networkDiscoveryEnabled"
|
|
345
|
+
if UserDefaults.standard.object(forKey: key) != nil {
|
|
346
|
+
return UserDefaults.standard.bool(forKey: key)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Safe default: require Local Network usage description to be present
|
|
350
|
+
if let desc = Bundle.main.object(forInfoDictionaryKey: "NSLocalNetworkUsageDescription") as? String,
|
|
351
|
+
!desc.isEmpty {
|
|
352
|
+
return true
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Allow explicit override of network discovery (called from React Native)
|
|
359
|
+
@objc public func setNetworkDiscoveryEnabled(_ enabled: Bool) {
|
|
360
|
+
UserDefaults.standard.set(enabled, forKey: "ilabsFlir.networkDiscoveryEnabled")
|
|
361
|
+
NSLog("[FlirManager] Network discovery override set to: \(enabled)")
|
|
362
|
+
}
|
|
363
|
+
|
|
330
364
|
@objc public func stopDiscovery() {
|
|
331
365
|
NSLog("[FlirManager] Stopping discovery...")
|
|
332
366
|
|
|
@@ -366,64 +400,73 @@ import ThermalSDK
|
|
|
366
400
|
}
|
|
367
401
|
|
|
368
402
|
private func performConnection(identity: FLIRIdentity) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
403
|
+
// Use the proven connection pattern from FLIR SDK samples:
|
|
404
|
+
// FLIROneCameraSwift uses: pair(identity, code:) then connect()
|
|
405
|
+
|
|
406
|
+
if camera == nil {
|
|
407
|
+
camera = FLIRCamera()
|
|
408
|
+
camera?.delegate = self
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
guard let cam = camera else {
|
|
412
|
+
NSLog("[FlirManager] Failed to create FLIRCamera")
|
|
413
|
+
DispatchQueue.main.async { [weak self] in
|
|
414
|
+
self?.delegate?.onError("Failed to create camera instance")
|
|
373
415
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Handle authentication for generic cameras (network cameras)
|
|
420
|
+
if identity.cameraType() == .generic {
|
|
421
|
+
let certName = getCertificateName()
|
|
422
|
+
var status = FLIRAuthenticationStatus.pending
|
|
423
|
+
while status == .pending {
|
|
424
|
+
status = cam.authenticate(identity, trustedConnectionName: certName)
|
|
425
|
+
if status == .pending {
|
|
426
|
+
NSLog("[FlirManager] Waiting for camera authentication approval...")
|
|
427
|
+
Thread.sleep(forTimeInterval: 1.0)
|
|
385
428
|
}
|
|
386
429
|
}
|
|
430
|
+
NSLog("[FlirManager] Authentication status: \(status.rawValue)")
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
do {
|
|
434
|
+
// Step 1: Pair with identity (required for FLIR One devices)
|
|
435
|
+
// The code parameter is for BLE pairing, 0 for direct connection
|
|
436
|
+
try cam.pair(identity, code: 0)
|
|
437
|
+
NSLog("[FlirManager] Paired with: \(identity.deviceId())")
|
|
387
438
|
|
|
388
|
-
// Connect (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if connectedOK {
|
|
404
|
-
connectedIdentity = identity
|
|
405
|
-
connectedDeviceId = identity.deviceId()
|
|
406
|
-
connectedDeviceName = identity.deviceId()
|
|
407
|
-
_isConnected = true
|
|
408
|
-
NSLog("[FlirManager] Connected to: \(identity.deviceId())")
|
|
409
|
-
} else {
|
|
410
|
-
NSLog("[FlirManager] Connection failed: no compatible connect API")
|
|
411
|
-
DispatchQueue.main.async { [weak self] in
|
|
412
|
-
self?.delegate?.onError("Connection failed: unsupported SDK API")
|
|
413
|
-
}
|
|
414
|
-
return
|
|
439
|
+
// Step 2: Connect (no identity parameter - uses paired identity)
|
|
440
|
+
try cam.connect()
|
|
441
|
+
NSLog("[FlirManager] Connected to: \(identity.deviceId())")
|
|
442
|
+
|
|
443
|
+
// Update state
|
|
444
|
+
connectedIdentity = identity
|
|
445
|
+
connectedDeviceId = identity.deviceId()
|
|
446
|
+
connectedDeviceName = identity.deviceId()
|
|
447
|
+
_isConnected = true
|
|
448
|
+
|
|
449
|
+
// Get camera info if available
|
|
450
|
+
if let remoteControl = cam.getRemoteControl(),
|
|
451
|
+
let cameraInfo = try? remoteControl.getCameraInformation() {
|
|
452
|
+
NSLog("[FlirManager] Camera info: \(cameraInfo)")
|
|
415
453
|
}
|
|
416
454
|
|
|
417
455
|
// Get streams
|
|
418
|
-
|
|
456
|
+
let streams = cam.getStreams()
|
|
457
|
+
if !streams.isEmpty {
|
|
419
458
|
NSLog("[FlirManager] Found \(streams.count) streams")
|
|
420
|
-
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
459
|
+
|
|
460
|
+
// Find and start the first thermal stream (preferred) or any stream
|
|
461
|
+
let thermalStream = streams.first { $0.isThermal } ?? streams.first
|
|
462
|
+
if let streamToStart = thermalStream {
|
|
463
|
+
startStreamInternal(streamToStart)
|
|
424
464
|
}
|
|
465
|
+
} else {
|
|
466
|
+
NSLog("[FlirManager] No streams available on camera")
|
|
425
467
|
}
|
|
426
468
|
|
|
469
|
+
// Notify delegate on main thread
|
|
427
470
|
DispatchQueue.main.async { [weak self] in
|
|
428
471
|
guard let self = self else { return }
|
|
429
472
|
let deviceInfo = FlirDeviceInfo(
|
|
@@ -437,7 +480,9 @@ import ThermalSDK
|
|
|
437
480
|
}
|
|
438
481
|
|
|
439
482
|
} catch {
|
|
440
|
-
NSLog("[FlirManager] Connection failed: \(error)")
|
|
483
|
+
NSLog("[FlirManager] Connection failed: \(error.localizedDescription)")
|
|
484
|
+
_isConnected = false
|
|
485
|
+
camera = nil
|
|
441
486
|
DispatchQueue.main.async { [weak self] in
|
|
442
487
|
self?.delegate?.onError("Connection failed: \(error.localizedDescription)")
|
|
443
488
|
}
|
|
@@ -363,7 +363,7 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
363
363
|
|
|
364
364
|
NSError *error = nil;
|
|
365
365
|
|
|
366
|
-
// Handle authentication for generic cameras
|
|
366
|
+
// Handle authentication for generic cameras (network cameras)
|
|
367
367
|
if ([identity cameraType] == FLIRCameraType_generic) {
|
|
368
368
|
NSString *certName = [self getCertificateName];
|
|
369
369
|
FLIRAuthenticationStatus status = pending;
|
|
@@ -374,9 +374,39 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
374
374
|
[NSThread sleepForTimeInterval:1.0];
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
|
+
RCTLogInfo(@"[FlirModule] Authentication status: %d", (int)status);
|
|
377
378
|
}
|
|
378
379
|
|
|
379
|
-
|
|
380
|
+
// Step 1: Pair with camera (required for FLIR One devices)
|
|
381
|
+
@try {
|
|
382
|
+
if (![self.camera pair:identity code:0 error:&error]) {
|
|
383
|
+
RCTLogError(@"[FlirModule] Pair failed: %@", error.localizedDescription);
|
|
384
|
+
if (completion) completion(NO, error);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
RCTLogInfo(@"[FlirModule] Paired with: %@", [identity deviceId]);
|
|
388
|
+
} @catch (NSException *exception) {
|
|
389
|
+
RCTLogError(@"[FlirModule] Pair exception: %@", exception.reason);
|
|
390
|
+
NSError *pairError = [NSError errorWithDomain:@"FlirModule" code:1001 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Pair failed"}];
|
|
391
|
+
if (completion) completion(NO, pairError);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Step 2: Connect to camera
|
|
396
|
+
BOOL connected = NO;
|
|
397
|
+
@try {
|
|
398
|
+
if (![self.camera connect:&error]) {
|
|
399
|
+
RCTLogError(@"[FlirModule] Connect failed: %@", error.localizedDescription);
|
|
400
|
+
if (completion) completion(NO, error);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
connected = YES;
|
|
404
|
+
RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
|
|
405
|
+
} @catch (NSException *exception) {
|
|
406
|
+
RCTLogError(@"[FlirModule] Connect exception: %@", exception.reason);
|
|
407
|
+
error = [NSError errorWithDomain:@"FlirModule" code:1002 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Connect failed"}];
|
|
408
|
+
connected = NO;
|
|
409
|
+
}
|
|
380
410
|
|
|
381
411
|
if (connected) {
|
|
382
412
|
self.connectedIdentity = identity;
|
|
@@ -392,14 +422,25 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
392
422
|
self.connectedDeviceName = displayName;
|
|
393
423
|
self.isConnected = YES;
|
|
394
424
|
|
|
395
|
-
RCTLogInfo(@"[FlirModule]
|
|
425
|
+
RCTLogInfo(@"[FlirModule] Successfully connected to: %@", displayName);
|
|
396
426
|
|
|
397
|
-
// Get available streams
|
|
427
|
+
// Get available streams and prefer thermal stream
|
|
398
428
|
NSArray<FLIRStream *> *streams = [self.camera getStreams];
|
|
399
429
|
if (streams.count > 0) {
|
|
400
|
-
RCTLogInfo(@"[FlirModule] Found %lu
|
|
401
|
-
|
|
402
|
-
|
|
430
|
+
RCTLogInfo(@"[FlirModule] Found %lu stream(s)", (unsigned long)streams.count);
|
|
431
|
+
|
|
432
|
+
// Find thermal stream (preferred) or use first stream
|
|
433
|
+
FLIRStream *streamToStart = nil;
|
|
434
|
+
for (FLIRStream *stream in streams) {
|
|
435
|
+
if (stream.isThermal) {
|
|
436
|
+
streamToStart = stream;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (!streamToStart) {
|
|
441
|
+
streamToStart = streams[0];
|
|
442
|
+
}
|
|
443
|
+
[self startStreamInternal:streamToStart];
|
|
403
444
|
}
|
|
404
445
|
|
|
405
446
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -409,10 +450,12 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
409
450
|
if (completion) completion(YES, nil);
|
|
410
451
|
} else {
|
|
411
452
|
RCTLogError(@"[FlirModule] Connection failed: %@", error.localizedDescription);
|
|
453
|
+
self.camera = nil;
|
|
412
454
|
if (completion) completion(NO, error);
|
|
413
455
|
}
|
|
414
456
|
}
|
|
415
457
|
|
|
458
|
+
|
|
416
459
|
- (NSString *)getCertificateName {
|
|
417
460
|
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"com.flir.app";
|
|
418
461
|
NSString *key = [NSString stringWithFormat:@"%@-cert-name", bundleID];
|
|
@@ -492,16 +535,26 @@ RCT_EXPORT_METHOD(stopFlir:(RCTPromiseResolveBlock)resolve
|
|
|
492
535
|
newStream.delegate = self;
|
|
493
536
|
|
|
494
537
|
NSError *error = nil;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
[
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
538
|
+
// Use try-catch pattern from samples
|
|
539
|
+
@try {
|
|
540
|
+
if (![newStream start:&error]) {
|
|
541
|
+
RCTLogError(@"[FlirModule] Stream start failed: %@", error.localizedDescription);
|
|
542
|
+
self.stream = nil;
|
|
543
|
+
self.streamer = nil;
|
|
544
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{@"error": error.localizedDescription ?: @"Stream start failed"}];
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
} @catch (NSException *exception) {
|
|
548
|
+
RCTLogError(@"[FlirModule] Stream start exception: %@\", exception.reason);
|
|
501
549
|
self.stream = nil;
|
|
502
550
|
self.streamer = nil;
|
|
503
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{@"error":
|
|
551
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{@"error": exception.reason ?: @"Stream start exception"}];
|
|
552
|
+
return;
|
|
504
553
|
}
|
|
554
|
+
|
|
555
|
+
self.isStreaming = YES;
|
|
556
|
+
[self emitStateChange:@"streaming"];
|
|
557
|
+
RCTLogInfo(@"[FlirModule] Stream started (thermal: %@)\", newStream.isThermal ? @\"YES\" : @\"NO\");
|
|
505
558
|
}
|
|
506
559
|
|
|
507
560
|
- (void)stopStreamInternal {
|