ilabs-flir 2.1.36 → 2.1.38

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.
@@ -1,43 +1,67 @@
1
1
  import Foundation
2
2
  import UIKit
3
3
 
4
+ /// ObjC-compatible shim that forwards to FlirManager
5
+ /// This provides a clean API for native consumers (FilterDataProvider, etc.)
4
6
  @objc public class FLIRManager: NSObject {
5
7
  @objc public static let shared = FLIRManager()
8
+
9
+ private override init() {
10
+ super.init()
11
+ }
6
12
 
13
+ // MARK: - SDK Availability
14
+
7
15
  @objc public func isAvailable() -> Bool {
8
16
  return FlirManager.isSDKAvailable
9
17
  }
18
+
19
+ @objc public static var isSDKAvailable: Bool {
20
+ return FlirManager.isSDKAvailable
21
+ }
10
22
 
23
+ // MARK: - Temperature APIs
24
+
11
25
  @objc public func getTemperatureAtPoint(x: Int, y: Int) -> Double {
12
- // FlirManager currently doesn't expose a direct getTemperatureAtPoint API.
13
- // Return NaN for now (consumers should handle NaN) until full parity is implemented.
14
- return Double.nan
26
+ return FlirManager.shared.getTemperatureAtPoint(x, y: y)
15
27
  }
16
28
 
17
29
  @objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
18
- return Double.nan
30
+ return FlirManager.shared.getTemperatureAtNormalized(nx, y: y)
19
31
  }
20
32
 
33
+ // MARK: - Battery APIs
34
+
21
35
  @objc public func getBatteryLevel() -> Int {
22
- return -1
36
+ return FlirManager.shared.getBatteryLevel()
23
37
  }
24
38
 
25
39
  @objc public func isBatteryCharging() -> Bool {
26
- return false
40
+ return FlirManager.shared.isBatteryCharging()
27
41
  }
28
42
 
43
+ // MARK: - Rotation Preference
44
+
29
45
  @objc public func setPreferSdkRotation(_ prefer: Bool) {
30
- // FlirManager doesn't currently support rotation preference; no-op
46
+ FlirManager.shared.setPreferSdkRotation(prefer)
31
47
  }
32
48
 
33
49
  @objc public func isPreferSdkRotation() -> Bool {
34
- return false
50
+ return FlirManager.shared.isPreferSdkRotation()
35
51
  }
36
52
 
53
+ // MARK: - Frame Access
54
+
37
55
  @objc public func latestFrameImage() -> UIImage? {
38
56
  return FlirManager.shared.latestImage
39
57
  }
58
+
59
+ @objc public var latestImage: UIImage? {
60
+ return FlirManager.shared.latestImage
61
+ }
40
62
 
63
+ // MARK: - Discovery & Connection
64
+
41
65
  @objc public func startDiscovery() {
42
66
  FlirManager.shared.startDiscovery()
43
67
  }
@@ -45,4 +69,36 @@ import UIKit
45
69
  @objc public func stopDiscovery() {
46
70
  FlirManager.shared.stopDiscovery()
47
71
  }
72
+
73
+ @objc public var isConnected: Bool {
74
+ return FlirManager.shared.isConnected
75
+ }
76
+
77
+ @objc public var isStreaming: Bool {
78
+ return FlirManager.shared.isStreaming
79
+ }
80
+
81
+ @objc public var isEmulator: Bool {
82
+ return FlirManager.shared.isEmulator
83
+ }
84
+
85
+ // MARK: - Palette Control
86
+
87
+ @objc public func setPalette(_ name: String) {
88
+ FlirManager.shared.setPalette(name)
89
+ }
90
+
91
+ @objc public func setPaletteFromAcol(_ acol: Float) {
92
+ FlirManager.shared.setPaletteFromAcol(acol)
93
+ }
94
+
95
+ // MARK: - Client Lifecycle
96
+
97
+ @objc public func retainClient(_ clientId: String) {
98
+ FlirManager.shared.retainClient(clientId)
99
+ }
100
+
101
+ @objc public func releaseClient(_ clientId: String) {
102
+ FlirManager.shared.releaseClient(clientId)
103
+ }
48
104
  }
@@ -11,16 +11,40 @@
11
11
 
12
12
  // Note: React headers are provided by the app's build system
13
13
  // These imports are for documentation purposes when building within Xcode
14
- #if __has_include(<React/RCTBridgeModule.h>)
14
+ // Prefer React/ headers, but fall back to ReactCore or local headers for different RN layouts
15
15
  #if __has_include(<React/RCTBridgeModule.h>)
16
16
  #import <React/RCTBridgeModule.h>
17
+ #if __has_include(<React/RCTEventEmitter.h>)
17
18
  #import <React/RCTEventEmitter.h>
19
+ #endif
20
+ #if __has_include(<React/RCTLog.h>)
21
+ #import <React/RCTLog.h>
22
+ #endif
23
+ #if __has_include(<React/RCTViewManager.h>)
24
+ #import <React/RCTViewManager.h>
25
+ #endif
18
26
  #elif __has_include(<ReactCore/RCTBridgeModule.h>)
19
27
  #import <ReactCore/RCTBridgeModule.h>
28
+ #if __has_include(<ReactCore/RCTEventEmitter.h>)
29
+ #import <ReactCore/RCTEventEmitter.h>
30
+ #elif __has_include(<React/RCTEventEmitter.h>)
20
31
  #import <React/RCTEventEmitter.h>
32
+ #endif
33
+ #if __has_include(<ReactCore/RCTLog.h>)
34
+ #import <ReactCore/RCTLog.h>
35
+ #elif __has_include(<React/RCTLog.h>)
36
+ #import <React/RCTLog.h>
37
+ #endif
38
+ #if __has_include(<ReactCore/RCTViewManager.h>)
39
+ #import <ReactCore/RCTViewManager.h>
40
+ #elif __has_include(<React/RCTViewManager.h>)
41
+ #import <React/RCTViewManager.h>
42
+ #endif
21
43
  #elif __has_include("RCTBridgeModule.h")
22
44
  #import "RCTBridgeModule.h"
23
45
  #import "RCTEventEmitter.h"
46
+ #import "RCTLog.h"
47
+ #import "RCTViewManager.h"
24
48
  #else
25
49
  #import <Foundation/Foundation.h>
26
50
  @interface RCTEventEmitter : NSObject
@@ -28,9 +52,6 @@
28
52
  @protocol RCTBridgeModule <NSObject>
29
53
  @end
30
54
  #endif
31
- #import <React/RCTLog.h>
32
- #import <React/RCTViewManager.h>
33
- #endif
34
55
 
35
56
  // FLIR module headers (local)
36
57
  #import "FlirEventEmitter.h"
@@ -5,12 +5,19 @@
5
5
  // Event emitter for sending FLIR events to React Native
6
6
  //
7
7
 
8
+ // Prefer React/ headers, but fall back to ReactCore or local headers for different RN layouts
8
9
  #if __has_include(<React/RCTBridgeModule.h>)
9
10
  #import <React/RCTBridgeModule.h>
11
+ #if __has_include(<React/RCTEventEmitter.h>)
10
12
  #import <React/RCTEventEmitter.h>
13
+ #endif
11
14
  #elif __has_include(<ReactCore/RCTBridgeModule.h>)
12
15
  #import <ReactCore/RCTBridgeModule.h>
16
+ #if __has_include(<ReactCore/RCTEventEmitter.h>)
17
+ #import <ReactCore/RCTEventEmitter.h>
18
+ #elif __has_include(<React/RCTEventEmitter.h>)
13
19
  #import <React/RCTEventEmitter.h>
20
+ #endif
14
21
  #elif __has_include("RCTBridgeModule.h")
15
22
  #import "RCTBridgeModule.h"
16
23
  #import "RCTEventEmitter.h"
@@ -43,6 +43,7 @@ import ThermalSDK
43
43
  func onDeviceConnected(_ device: FlirDeviceInfo)
44
44
  func onDeviceDisconnected()
45
45
  func onFrameReceived(_ image: UIImage, width: Int, height: Int)
46
+ @objc optional func onFrameReceivedRaw(_ data: Data, width: Int, height: Int, bytesPerRow: Int, timestamp: Double)
46
47
  func onError(_ message: String)
47
48
  func onStateChanged(_ state: String, isConnected: Bool, isStreaming: Bool, isEmulator: Bool)
48
49
  }
@@ -96,6 +97,17 @@ import ThermalSDK
96
97
  connectedDeviceName?.lowercased().contains("emulat") == true
97
98
  }
98
99
 
100
+ // Preference: ask SDK to deliver oriented/rotated frames (if SDK supports it)
101
+ private var _preferSdkRotation: Bool = false
102
+
103
+ @objc public func setPreferSdkRotation(_ prefer: Bool) {
104
+ _preferSdkRotation = prefer
105
+ }
106
+
107
+ @objc public func isPreferSdkRotation() -> Bool {
108
+ return _preferSdkRotation
109
+ }
110
+
99
111
  @objc public func getConnectedDeviceInfo() -> String {
100
112
  return connectedDeviceName ?? "Not connected"
101
113
  }
@@ -188,6 +200,35 @@ import ThermalSDK
188
200
  return nil
189
201
  }
190
202
 
203
+ // Returns a NSDictionary with BGRA base64 data for the latest frame.
204
+ // Keys: width (Int), height (Int), bytesPerRow (Int), dataBase64 (String)
205
+ @objc public func latestFrameBitmapBase64() -> NSDictionary? {
206
+ guard let img = latestImage else { return nil }
207
+ guard let bmp = convertUIImageToBGRA(img) else { return nil }
208
+ let b64 = bmp.data.base64EncodedString()
209
+ return ["width": bmp.width, "height": bmp.height, "bytesPerRow": bmp.bytesPerRow, "dataBase64": b64]
210
+ }
211
+
212
+ // Convert a UIImage to BGRA (kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst).
213
+ private func convertUIImageToBGRA(_ image: UIImage) -> (data: Data, width: Int, height: Int, bytesPerRow: Int)? {
214
+ guard let cg = image.cgImage else { return nil }
215
+ let width = cg.width
216
+ let height = cg.height
217
+ let bytesPerRow = width * 4
218
+ let size = height * bytesPerRow
219
+ var data = Data(count: size)
220
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
221
+ let bitmapInfo = CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue
222
+ let success = data.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> Bool in
223
+ guard let base = ptr.baseAddress else { return false }
224
+ guard let ctx = CGContext(data: base, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else { return false }
225
+ let rect = CGRect(x: 0, y: 0, width: width, height: height)
226
+ ctx.draw(cg, in: rect)
227
+ return true
228
+ }
229
+ return success ? (data, width, height, bytesPerRow) : nil
230
+ }
231
+
191
232
  // Client lifecycle helpers: callers (UI/filters) can retain/release to ensure
192
233
  // discovery runs while any client is active.
193
234
  @objc public func retainClient(_ clientId: String) {
@@ -851,6 +892,23 @@ extension FlirManager: FLIRStreamDelegate {
851
892
  height: Int(image.size.height)
852
893
  )
853
894
  }
895
+
896
+ // Also provide a raw BGRA bitmap callback (optional) to delegates and a
897
+ // queryable base64 bitmap dict for RN consumers. Conversion is done
898
+ // off the main thread to avoid blocking UI.
899
+ DispatchQueue.global(qos: .utility).async { [weak self, weak image] in
900
+ guard let self = self, let image = image else { return }
901
+ if let bmp = self.convertUIImageToBGRA(image) {
902
+ let ts = Date().timeIntervalSince1970 * 1000.0
903
+ DispatchQueue.main.async {
904
+ // Notify the delegate if set (may be FlirModule or another consumer)
905
+ self.delegate?.onFrameReceivedRaw?(bmp.data, width: bmp.width, height: bmp.height, bytesPerRow: bmp.bytesPerRow, timestamp: ts)
906
+
907
+ // Post a system notification so multiple native observers can react
908
+ NotificationCenter.default.post(name: Notification.Name("FlirFrameBitmapAvailableNative"), object: nil, userInfo: ["width": bmp.width, "height": bmp.height, "bytesPerRow": bmp.bytesPerRow, "timestamp": ts])
909
+ }
910
+ }
911
+ }
854
912
  }
855
913
  } catch {
856
914
  NSLog("[FlirManager] Streamer update error: \(error)")
@@ -5,12 +5,19 @@
5
5
  // React Native bridge module for FLIR thermal camera SDK
6
6
  //
7
7
 
8
+ // Prefer React/ headers, but fall back to ReactCore or local headers for different RN layouts
8
9
  #if __has_include(<React/RCTBridgeModule.h>)
9
10
  #import <React/RCTBridgeModule.h>
11
+ #if __has_include(<React/RCTEventEmitter.h>)
10
12
  #import <React/RCTEventEmitter.h>
13
+ #endif
11
14
  #elif __has_include(<ReactCore/RCTBridgeModule.h>)
12
15
  #import <ReactCore/RCTBridgeModule.h>
16
+ #if __has_include(<ReactCore/RCTEventEmitter.h>)
17
+ #import <ReactCore/RCTEventEmitter.h>
18
+ #elif __has_include(<React/RCTEventEmitter.h>)
13
19
  #import <React/RCTEventEmitter.h>
20
+ #endif
14
21
  #elif __has_include("RCTBridgeModule.h")
15
22
  #import "RCTBridgeModule.h"
16
23
  #import "RCTEventEmitter.h"
@@ -121,7 +121,7 @@ RCT_EXPORT_MODULE(FlirModule);
121
121
  - (NSArray<NSString *> *)supportedEvents {
122
122
  return @[
123
123
  @"FlirDeviceConnected", @"FlirDeviceDisconnected", @"FlirDevicesFound",
124
- @"FlirFrameReceived", @"FlirError", @"FlirStateChanged",
124
+ @"FlirFrameReceived", @"FlirFrameBitmapAvailable", @"FlirError", @"FlirStateChanged",
125
125
  @"FlirBatteryUpdated"
126
126
  ];
127
127
  }
@@ -308,6 +308,22 @@ RCT_EXPORT_METHOD(isEmulator : (RCTPromiseResolveBlock)
308
308
  });
309
309
  }
310
310
 
311
+ RCT_EXPORT_METHOD(getLatestFrameBitmap : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
312
+ dispatch_async(dispatch_get_main_queue(), ^{
313
+ id manager = flir_manager_shared();
314
+ if (!manager || ![manager respondsToSelector:sel_registerName("latestFrameBitmapBase64")]) {
315
+ resolve([NSNull null]);
316
+ return;
317
+ }
318
+ NSDictionary *dict = ((NSDictionary * (*)(id, SEL)) objc_msgSend)(manager, sel_registerName("latestFrameBitmapBase64"));
319
+ if (!dict) {
320
+ resolve([NSNull null]);
321
+ } else {
322
+ resolve(dict);
323
+ }
324
+ });
325
+ }
326
+
311
327
  RCT_EXPORT_METHOD(isDeviceConnected : (RCTPromiseResolveBlock)
312
328
  resolve rejecter : (RCTPromiseRejectBlock)reject) {
313
329
  dispatch_async(dispatch_get_main_queue(), ^{
@@ -431,6 +447,16 @@ RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
431
447
  }];
432
448
  }
433
449
 
450
+ - (void)onFrameReceivedRaw:(NSData *)data width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow timestamp:(double)timestamp {
451
+ // Emit a lightweight event to notify JS that a raw bitmap is available; raw bytes are available via getLatestFrameBitmap()
452
+ [[FlirEventEmitter shared] sendDeviceEvent:@"FlirFrameBitmapAvailable" body:@{
453
+ @"width": @(width),
454
+ @"height": @(height),
455
+ @"bytesPerRow": @(bytesPerRow),
456
+ @"timestamp": @(timestamp)
457
+ }];
458
+ }
459
+
434
460
  - (void)onError:(NSString *)message {
435
461
  if (self.connectReject) {
436
462
  self.connectReject(@"ERR_FLIR", message, nil);
@@ -0,0 +1,50 @@
1
+ // FlirPublic.h
2
+ // Public C/ObjC API for the Flir library
3
+
4
+ #import <Foundation/Foundation.h>
5
+ #import <UIKit/UIKit.h>
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ @interface FlirDeviceInfo : NSObject
10
+ @property (nonatomic, readonly) NSString *deviceId;
11
+ @property (nonatomic, readonly) NSString *name;
12
+ @property (nonatomic, readonly) NSString *communicationType;
13
+ @property (nonatomic, readonly) BOOL isEmulator;
14
+ - (NSDictionary *)toDictionary;
15
+ @end
16
+
17
+ @protocol FlirPublicDelegate <NSObject>
18
+ - (void)onDevicesFound:(NSArray<FlirDeviceInfo *> *)devices;
19
+ - (void)onDeviceConnected:(FlirDeviceInfo *)device;
20
+ - (void)onDeviceDisconnected;
21
+ - (void)onFrameReceived:(UIImage *)image width:(NSInteger)width height:(NSInteger)height;
22
+ @optional
23
+ - (void)onFrameReceivedRaw:(NSData *)data width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow timestamp:(double)timestamp;
24
+ - (void)onError:(NSString *)message;
25
+ - (void)onStateChanged:(NSString *)state isConnected:(BOOL)isConnected isStreaming:(BOOL)isStreaming isEmulator:(BOOL)isEmulator;
26
+ @end
27
+
28
+ @interface FlirManager : NSObject
29
+ + (instancetype)shared NS_SWIFT_NAME(shared);
30
+
31
+ @property (nonatomic, weak, nullable) id<FlirPublicDelegate> delegate;
32
+
33
+ // Lifecycle
34
+ - (void)startDiscovery;
35
+ - (void)stopDiscovery;
36
+ - (void)connectToDevice:(NSString *)deviceId;
37
+ - (void)disconnect;
38
+ - (void)stop;
39
+
40
+ // Frame accessors
41
+ - (nullable NSDictionary *)latestFrameBitmapBase64; // { width, height, bytesPerRow, dataBase64 }
42
+ - (nullable NSString *)latestFrameBase64;
43
+
44
+ // Utilities
45
+ - (NSInteger)getBatteryLevel;
46
+ - (BOOL)isBatteryCharging;
47
+
48
+ @end
49
+
50
+ NS_ASSUME_NONNULL_END
@@ -5,8 +5,13 @@
5
5
  // React Native view manager for FLIR preview
6
6
  //
7
7
 
8
+ // Prefer React/ headers, but support multiple RN header layouts
8
9
  #if __has_include(<React/RCTViewManager.h>)
9
10
  #import <React/RCTViewManager.h>
11
+ #elif __has_include(<ReactCore/RCTViewManager.h>)
12
+ #import <ReactCore/RCTViewManager.h>
13
+ #elif __has_include("RCTViewManager.h")
14
+ #import "RCTViewManager.h"
10
15
  #elif __has_include(<React/RCTUIManager.h>)
11
16
  #import <React/RCTUIManager.h>
12
17
  #else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.1.36",
3
+ "version": "2.1.38",
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",