ilabs-flir 2.1.1 → 2.1.3
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 +95 -51
- package/ios/Flir/src/FlirModule.m +95 -10
- 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,72 @@ 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
|
-
if let streams =
|
|
456
|
+
if let streams = cam.getStreams(), !streams.isEmpty {
|
|
419
457
|
NSLog("[FlirManager] Found \(streams.count) streams")
|
|
420
458
|
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
459
|
+
// Find and start the first thermal stream (preferred) or any stream
|
|
460
|
+
let thermalStream = streams.first { $0.isThermal } ?? streams.first
|
|
461
|
+
if let streamToStart = thermalStream {
|
|
462
|
+
startStreamInternal(streamToStart)
|
|
424
463
|
}
|
|
464
|
+
} else {
|
|
465
|
+
NSLog("[FlirManager] No streams available on camera")
|
|
425
466
|
}
|
|
426
467
|
|
|
468
|
+
// Notify delegate on main thread
|
|
427
469
|
DispatchQueue.main.async { [weak self] in
|
|
428
470
|
guard let self = self else { return }
|
|
429
471
|
let deviceInfo = FlirDeviceInfo(
|
|
@@ -437,7 +479,9 @@ import ThermalSDK
|
|
|
437
479
|
}
|
|
438
480
|
|
|
439
481
|
} catch {
|
|
440
|
-
NSLog("[FlirManager] Connection failed: \(error)")
|
|
482
|
+
NSLog("[FlirManager] Connection failed: \(error.localizedDescription)")
|
|
483
|
+
_isConnected = false
|
|
484
|
+
camera = nil
|
|
441
485
|
DispatchQueue.main.async { [weak self] in
|
|
442
486
|
self?.delegate?.onError("Connection failed: \(error.localizedDescription)")
|
|
443
487
|
}
|
|
@@ -156,6 +156,39 @@ RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
|
|
|
156
156
|
|
|
157
157
|
#pragma mark - Discovery Methods
|
|
158
158
|
|
|
159
|
+
// Network discovery on iOS 14+ requires Local Network privacy keys.
|
|
160
|
+
// In USB/Bluetooth-only builds (or when the user denied permission), attempting
|
|
161
|
+
// Bonjour discovery can fail noisily or crash depending on SDK internals.
|
|
162
|
+
// We default to enabling network discovery only when the host app declares
|
|
163
|
+
// NSLocalNetworkUsageDescription, and allow an explicit override via
|
|
164
|
+
// setNetworkDiscoveryEnabled.
|
|
165
|
+
- (BOOL)shouldEnableNetworkDiscovery {
|
|
166
|
+
// Explicit override if app sets it.
|
|
167
|
+
NSString *key = @"ilabsFlir.networkDiscoveryEnabled";
|
|
168
|
+
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
|
169
|
+
if ([defaults objectForKey:key] != nil) {
|
|
170
|
+
return [defaults boolForKey:key];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Safe default: require Local Network usage description to be present.
|
|
174
|
+
id desc = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocalNetworkUsageDescription"];
|
|
175
|
+
if ([desc isKindOfClass:[NSString class]] && ((NSString *)desc).length > 0) {
|
|
176
|
+
return YES;
|
|
177
|
+
}
|
|
178
|
+
return NO;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
RCT_EXPORT_METHOD(setNetworkDiscoveryEnabled:(BOOL)enabled
|
|
182
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
183
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
184
|
+
#if FLIR_SDK_AVAILABLE
|
|
185
|
+
[[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ilabsFlir.networkDiscoveryEnabled"];
|
|
186
|
+
resolve(@(YES));
|
|
187
|
+
#else
|
|
188
|
+
resolve(@(YES));
|
|
189
|
+
#endif
|
|
190
|
+
}
|
|
191
|
+
|
|
159
192
|
RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
160
193
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
161
194
|
#if FLIR_SDK_AVAILABLE
|
|
@@ -197,11 +230,18 @@ RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
|
197
230
|
self.discovery.delegate = self;
|
|
198
231
|
}
|
|
199
232
|
|
|
200
|
-
// Start discovery on
|
|
233
|
+
// Start discovery on allowed interfaces.
|
|
234
|
+
// Always include wired/BLE/emulator. Only include network when the app has
|
|
235
|
+
// Local Network usage description (or the app explicitly enabled it).
|
|
201
236
|
FLIRCommunicationInterface interfaces = FLIRCommunicationInterfaceLightning |
|
|
202
|
-
FLIRCommunicationInterfaceNetwork |
|
|
203
237
|
FLIRCommunicationInterfaceFlirOneWireless |
|
|
204
|
-
FLIRCommunicationInterfaceEmulator
|
|
238
|
+
FLIRCommunicationInterfaceEmulator |
|
|
239
|
+
FLIRCommunicationInterfaceUSB;
|
|
240
|
+
if ([self shouldEnableNetworkDiscovery]) {
|
|
241
|
+
interfaces |= FLIRCommunicationInterfaceNetwork;
|
|
242
|
+
} else {
|
|
243
|
+
RCTLogInfo(@"[FlirModule] Network discovery disabled (missing NSLocalNetworkUsageDescription or overridden)");
|
|
244
|
+
}
|
|
205
245
|
[self.discovery start:interfaces];
|
|
206
246
|
|
|
207
247
|
[self emitStateChange:@"discovering"];
|
|
@@ -323,7 +363,7 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
323
363
|
|
|
324
364
|
NSError *error = nil;
|
|
325
365
|
|
|
326
|
-
// Handle authentication for generic cameras
|
|
366
|
+
// Handle authentication for generic cameras (network cameras)
|
|
327
367
|
if ([identity cameraType] == FLIRCameraType_generic) {
|
|
328
368
|
NSString *certName = [self getCertificateName];
|
|
329
369
|
FLIRAuthenticationStatus status = pending;
|
|
@@ -334,9 +374,34 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
334
374
|
[NSThread sleepForTimeInterval:1.0];
|
|
335
375
|
}
|
|
336
376
|
}
|
|
377
|
+
RCTLogInfo(@"[FlirModule] Authentication status: %d", (int)status);
|
|
337
378
|
}
|
|
338
379
|
|
|
339
|
-
|
|
380
|
+
// Use the proven connection pattern from FLIR SDK samples:
|
|
381
|
+
// Step 1: Pair with identity (required for FLIR One devices)
|
|
382
|
+
@try {
|
|
383
|
+
[self.camera pair:identity code:0];
|
|
384
|
+
RCTLogInfo(@"[FlirModule] Paired with: %@", [identity deviceId]);
|
|
385
|
+
} @catch (NSException *exception) {
|
|
386
|
+
RCTLogError(@"[FlirModule] Pair failed: %@", exception.reason);
|
|
387
|
+
NSError *pairError = [NSError errorWithDomain:@"FlirModule" code:1001 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Pair failed"}];
|
|
388
|
+
if (completion) completion(NO, pairError);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Step 2: Connect (no identity parameter - uses paired identity)
|
|
393
|
+
BOOL connected = NO;
|
|
394
|
+
@try {
|
|
395
|
+
[self.camera connect:&error];
|
|
396
|
+
connected = (error == nil);
|
|
397
|
+
if (connected) {
|
|
398
|
+
RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
|
|
399
|
+
}
|
|
400
|
+
} @catch (NSException *exception) {
|
|
401
|
+
RCTLogError(@"[FlirModule] Connect exception: %@", exception.reason);
|
|
402
|
+
error = [NSError errorWithDomain:@"FlirModule" code:1002 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Connect failed"}];
|
|
403
|
+
connected = NO;
|
|
404
|
+
}
|
|
340
405
|
|
|
341
406
|
if (connected) {
|
|
342
407
|
self.connectedIdentity = identity;
|
|
@@ -352,14 +417,25 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
352
417
|
self.connectedDeviceName = displayName;
|
|
353
418
|
self.isConnected = YES;
|
|
354
419
|
|
|
355
|
-
RCTLogInfo(@"[FlirModule]
|
|
420
|
+
RCTLogInfo(@"[FlirModule] Successfully connected to: %@", displayName);
|
|
356
421
|
|
|
357
|
-
// Get available streams
|
|
422
|
+
// Get available streams and prefer thermal stream
|
|
358
423
|
NSArray<FLIRStream *> *streams = [self.camera getStreams];
|
|
359
424
|
if (streams.count > 0) {
|
|
360
425
|
RCTLogInfo(@"[FlirModule] Found %lu streams", (unsigned long)streams.count);
|
|
361
|
-
|
|
362
|
-
|
|
426
|
+
|
|
427
|
+
// Find thermal stream (preferred) or use first stream
|
|
428
|
+
FLIRStream *streamToStart = nil;
|
|
429
|
+
for (FLIRStream *stream in streams) {
|
|
430
|
+
if (stream.isThermal) {
|
|
431
|
+
streamToStart = stream;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!streamToStart) {
|
|
436
|
+
streamToStart = streams[0];
|
|
437
|
+
}
|
|
438
|
+
[self startStreamInternal:streamToStart];
|
|
363
439
|
}
|
|
364
440
|
|
|
365
441
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
@@ -369,10 +445,12 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
|
369
445
|
if (completion) completion(YES, nil);
|
|
370
446
|
} else {
|
|
371
447
|
RCTLogError(@"[FlirModule] Connection failed: %@", error.localizedDescription);
|
|
448
|
+
self.camera = nil;
|
|
372
449
|
if (completion) completion(NO, error);
|
|
373
450
|
}
|
|
374
451
|
}
|
|
375
452
|
|
|
453
|
+
|
|
376
454
|
- (NSString *)getCertificateName {
|
|
377
455
|
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"com.flir.app";
|
|
378
456
|
NSString *key = [NSString stringWithFormat:@"%@-cert-name", bundleID];
|
|
@@ -821,8 +899,15 @@ RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
|
|
|
821
899
|
}
|
|
822
900
|
|
|
823
901
|
- (void)discoveryError:(NSString *)error netServiceError:(int)nsnetserviceserror on:(FLIRCommunicationInterface)iface {
|
|
902
|
+
// Network discovery failures are expected when Local Network permission is missing/denied.
|
|
903
|
+
// Do not surface those as fatal errors; keep USB/BLE discovery running.
|
|
904
|
+
if ((iface & FLIRCommunicationInterfaceNetwork) == FLIRCommunicationInterfaceNetwork) {
|
|
905
|
+
RCTLogInfo(@"[FlirModule] Network discovery error (suppressed): %@ (%d)", error, nsnetserviceserror);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
824
909
|
RCTLogError(@"[FlirModule] Discovery error: %@ (%d)", error, nsnetserviceserror);
|
|
825
|
-
|
|
910
|
+
|
|
826
911
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
827
912
|
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
828
913
|
@"error": error ?: @"Unknown discovery error",
|