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.
@@ -310,23 +310,57 @@ import ThermalSDK
310
310
  discovery?.delegate = self
311
311
  }
312
312
 
313
- // Start discovery on all available interfaces
314
- let interfaces: FLIRCommunicationInterface = [
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
- do {
370
- if camera == nil {
371
- camera = FLIRCamera()
372
- camera?.delegate = self
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
- // Handle authentication for generic cameras
376
- if identity.cameraType() == .generic {
377
- let certName = getCertificateName()
378
- var status = FLIRAuthenticationStatus.pending
379
- while status == .pending {
380
- status = camera!.authenticate(identity, trustedConnectionName: certName)
381
- if status == .pending {
382
- NSLog("[FlirManager] Waiting for camera authentication approval...")
383
- Thread.sleep(forTimeInterval: 1.0)
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 (support multiple SDK signatures via ObjC runtime)
389
- var connectedOK = false
390
- if let cam = camera {
391
- if cam.responds(to: Selector(("connect:error:"))) {
392
- // Call connect:identity error:nil (ignore NSError pointer for compatibility)
393
- _ = cam.perform(Selector(("connect:error:")), with: identity, with: nil)
394
- connectedOK = true
395
- } else if cam.responds(to: Selector(("connect:"))) {
396
- _ = cam.perform(Selector(("connect:")), with: identity)
397
- connectedOK = true
398
- } else {
399
- NSLog("[FlirManager] No compatible connect API on FLIRCamera")
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
- if let streams = camera?.getStreams(), !streams.isEmpty {
456
+ let streams = cam.getStreams()
457
+ if !streams.isEmpty {
419
458
  NSLog("[FlirManager] Found \(streams.count) streams")
420
-
421
- // Auto-start first thermal stream
422
- if let firstStream = streams.first {
423
- startStreamInternal(firstStream)
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
- BOOL connected = [self.camera connect:identity error:&error];
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] Connected to: %@", [identity deviceId]);
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 streams", (unsigned long)streams.count);
401
- // Auto-start first stream
402
- [self startStreamInternal:streams[0]];
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
- if ([newStream start:&error]) {
496
- self.isStreaming = YES;
497
- [self emitStateChange:@"streaming"];
498
- RCTLogInfo(@"[FlirModule] Stream started (thermal: %@)", newStream.isThermal ? @"YES" : @"NO");
499
- } else {
500
- RCTLogError(@"[FlirModule] Stream start failed: %@", error.localizedDescription);
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": error.localizedDescription ?: @"Stream start failed"}];
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.1.2",
3
+ "version": "2.1.31",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",