ilabs-flir 2.0.7 → 2.0.9

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.
@@ -0,0 +1,24 @@
1
+ // ObjC shim to expose `FLIRManager` from the npm package and forward to `FlirManager` at runtime
2
+ #import <Foundation/Foundation.h>
3
+ #import <UIKit/UIKit.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface FLIRManager : NSObject
8
+
9
+ + (instancetype)shared;
10
+
11
+ - (BOOL)isAvailable;
12
+ - (double)getTemperatureAtPoint:(int)x y:(int)y;
13
+ - (double)getTemperatureAtNormalized:(double)nx y:(double)ny;
14
+ - (int)getBatteryLevel;
15
+ - (BOOL)isBatteryCharging;
16
+ - (void)setPreferSdkRotation:(BOOL)prefer;
17
+ - (BOOL)isPreferSdkRotation;
18
+ - (nullable UIImage *)latestFrameImage;
19
+ - (void)startDiscovery;
20
+ - (void)stopDiscovery;
21
+
22
+ @end
23
+
24
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,153 @@
1
+ // ObjC shim implementation forwarding to Swift `FlirManager` via runtime selectors
2
+ #import "FLIRManager.h"
3
+ #import <objc/message.h>
4
+
5
+ @implementation FLIRManager
6
+
7
+ + (instancetype)shared {
8
+ Class cls = NSClassFromString(@"FlirManager");
9
+ if (!cls) return nil;
10
+ SEL sel = sel_registerName("shared");
11
+ if (![cls respondsToSelector:sel]) return nil;
12
+ id (*msgSend0)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
13
+ return msgSend0((id)cls, sel);
14
+ }
15
+
16
+ - (BOOL)isAvailable {
17
+ Class cls = NSClassFromString(@"FlirManager");
18
+ SEL sel = sel_registerName("isSDKAvailable");
19
+ if (!cls || ![cls respondsToSelector:sel]) return NO;
20
+ BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
21
+ return msgSend0((id)cls, sel);
22
+ }
23
+
24
+ - (double)getTemperatureAtPoint:(int)x y:(int)y {
25
+ id inst = [[self class] shared];
26
+ SEL sel = sel_registerName("getTemperatureAt: y:");
27
+ // Swift method name mangling may differ; fall back to method used by FlirModule
28
+ SEL selAlt = sel_registerName("getTemperatureAtPoint:y:");
29
+ SEL use = NULL;
30
+ if (inst && [inst respondsToSelector:sel]) use = sel;
31
+ if (inst && [inst respondsToSelector:selAlt]) use = selAlt;
32
+ if (!inst || !use) return NAN;
33
+ double (*msgSend2)(id, SEL, int, int) = (double (*)(id, SEL, int, int))objc_msgSend;
34
+ return msgSend2(inst, use, x, y);
35
+ }
36
+
37
+ - (double)getTemperatureAtNormalized:(double)nx y:(double)ny {
38
+ id inst = [[self class] shared];
39
+ SEL sel = sel_registerName("getTemperatureAtNormalized:y:");
40
+ if (!inst || ![inst respondsToSelector:sel]) return NAN;
41
+ double (*msgSend2)(id, SEL, double, double) = (double (*)(id, SEL, double, double))objc_msgSend;
42
+ return msgSend2(inst, sel, nx, ny);
43
+ }
44
+
45
+ - (int)getBatteryLevel {
46
+ id inst = [[self class] shared];
47
+ SEL sel = sel_registerName("getBatteryLevel");
48
+ if (!inst || ![inst respondsToSelector:sel]) return -1;
49
+ int (*msgSend0)(id, SEL) = (int (*)(id, SEL))objc_msgSend;
50
+ return msgSend0(inst, sel);
51
+ }
52
+
53
+ - (BOOL)isBatteryCharging {
54
+ id inst = [[self class] shared];
55
+ SEL sel = sel_registerName("isBatteryCharging");
56
+ if (!inst || ![inst respondsToSelector:sel]) return NO;
57
+ BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
58
+ return msgSend0(inst, sel);
59
+ }
60
+
61
+ - (void)setPreferSdkRotation:(BOOL)prefer {
62
+ id inst = [[self class] shared];
63
+ SEL sel = sel_registerName("setPreferSdkRotation:");
64
+ if (!inst || ![inst respondsToSelector:sel]) return;
65
+ void (*msgSend1)(id, SEL, BOOL) = (void (*)(id, SEL, BOOL))objc_msgSend;
66
+ msgSend1(inst, sel, prefer);
67
+ }
68
+
69
+ - (BOOL)isPreferSdkRotation {
70
+ id inst = [[self class] shared];
71
+ SEL sel = sel_registerName("isPreferSdkRotation");
72
+ if (!inst || ![inst respondsToSelector:sel]) return NO;
73
+ BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
74
+ return msgSend0(inst, sel);
75
+ }
76
+
77
+ - (nullable NSString *)latestFrameBase64 {
78
+ id inst = [[self class] shared];
79
+ SEL sel = sel_registerName("latestFrameBase64");
80
+ if (!inst || ![inst respondsToSelector:sel]) return nil;
81
+ id (*msgSend0)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
82
+ return (NSString *)msgSend0(inst, sel);
83
+ }
84
+
85
+ - (void)retainClient:(NSString *)clientId {
86
+ id inst = [[self class] shared];
87
+ SEL sel = sel_registerName("retainClient:");
88
+ if (!inst || ![inst respondsToSelector:sel]) return;
89
+ void (*msgSend1)(id, SEL, id) = (void (*)(id, SEL, id))objc_msgSend;
90
+ msgSend1(inst, sel, clientId);
91
+ }
92
+
93
+ - (void)releaseClient:(NSString *)clientId {
94
+ id inst = [[self class] shared];
95
+ SEL sel = sel_registerName("releaseClient:");
96
+ if (!inst || ![inst respondsToSelector:sel]) return;
97
+ void (*msgSend1)(id, SEL, id) = (void (*)(id, SEL, id))objc_msgSend;
98
+ msgSend1(inst, sel, clientId);
99
+ }
100
+
101
+ - (void)setPalette:(NSString *)paletteName {
102
+ id inst = [[self class] shared];
103
+ SEL sel = sel_registerName("setPalette:");
104
+ if (!inst || ![inst respondsToSelector:sel]) return;
105
+ void (*msgSend1)(id, SEL, id) = (void (*)(id, SEL, id))objc_msgSend;
106
+ msgSend1(inst, sel, paletteName);
107
+ }
108
+
109
+ - (void)setPaletteFromAcol:(double)acol {
110
+ id inst = [[self class] shared];
111
+ SEL sel = sel_registerName("setPaletteFromAcol:");
112
+ if (!inst || ![inst respondsToSelector:sel]) return;
113
+ void (*msgSend1)(id, SEL, double) = (void (*)(id, SEL, double))objc_msgSend;
114
+ msgSend1(inst, sel, acol);
115
+ }
116
+
117
+ - (nullable NSString *)getPaletteNameFromAcol:(double)acol {
118
+ Class cls = NSClassFromString(@"FlirManager");
119
+ SEL sel = sel_registerName("getPaletteNameFromAcol:");
120
+ if (!cls || ![cls respondsToSelector:sel]) return nil;
121
+ id (*msgSend1)(id, SEL, double) = (id (*)(id, SEL, double))objc_msgSend;
122
+ return (NSString *)msgSend1((id)cls, sel, acol);
123
+ }
124
+
125
+ - (nullable UIImage *)latestFrameImage {
126
+ id inst = [[self class] shared];
127
+ SEL sel = sel_registerName("latestImage");
128
+ SEL selAlt = sel_registerName("latestFrameImage");
129
+ SEL use = NULL;
130
+ if (inst && [inst respondsToSelector:selAlt]) use = selAlt;
131
+ else if (inst && [inst respondsToSelector:sel]) use = sel;
132
+ if (!inst || !use) return nil;
133
+ id (*msgSend0)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
134
+ return (UIImage *)msgSend0(inst, use);
135
+ }
136
+
137
+ - (void)startDiscovery {
138
+ id inst = [[self class] shared];
139
+ SEL sel = sel_registerName("startDiscovery");
140
+ if (!inst || ![inst respondsToSelector:sel]) return;
141
+ void (*msgSend0)(id, SEL) = (void (*)(id, SEL))objc_msgSend;
142
+ msgSend0(inst, sel);
143
+ }
144
+
145
+ - (void)stopDiscovery {
146
+ id inst = [[self class] shared];
147
+ SEL sel = sel_registerName("stopDiscovery");
148
+ if (!inst || ![inst respondsToSelector:sel]) return;
149
+ void (*msgSend0)(id, SEL) = (void (*)(id, SEL))objc_msgSend;
150
+ msgSend0(inst, sel);
151
+ }
152
+
153
+ @end
@@ -0,0 +1,48 @@
1
+ import Foundation
2
+ import UIKit
3
+
4
+ @objc public class FLIRManager: NSObject {
5
+ @objc public static let shared = FLIRManager()
6
+
7
+ @objc public func isAvailable() -> Bool {
8
+ return FlirManager.isSDKAvailable
9
+ }
10
+
11
+ @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
15
+ }
16
+
17
+ @objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
18
+ return Double.nan
19
+ }
20
+
21
+ @objc public func getBatteryLevel() -> Int {
22
+ return -1
23
+ }
24
+
25
+ @objc public func isBatteryCharging() -> Bool {
26
+ return false
27
+ }
28
+
29
+ @objc public func setPreferSdkRotation(_ prefer: Bool) {
30
+ // FlirManager doesn't currently support rotation preference; no-op
31
+ }
32
+
33
+ @objc public func isPreferSdkRotation() -> Bool {
34
+ return false
35
+ }
36
+
37
+ @objc public func latestFrameImage() -> UIImage? {
38
+ return FlirManager.shared.latestImage
39
+ }
40
+
41
+ @objc public func startDiscovery() {
42
+ FlirManager.shared.startDiscovery()
43
+ }
44
+
45
+ @objc public func stopDiscovery() {
46
+ FlirManager.shared.stopDiscovery()
47
+ }
48
+ }
@@ -70,6 +70,9 @@ import ThermalSDK
70
70
 
71
71
  // Discovered devices
72
72
  private var discoveredDevices: [FlirDeviceInfo] = []
73
+ // Client lifecycle for discovery/connection ownership
74
+ private var activeClients: Set<String> = []
75
+ private var shutdownWorkItem: DispatchWorkItem? = nil
73
76
 
74
77
  #if FLIR_ENABLED
75
78
  private var discovery: FLIRDiscovery?
@@ -100,6 +103,182 @@ import ThermalSDK
100
103
  @objc public func getDiscoveredDevices() -> [FlirDeviceInfo] {
101
104
  return discoveredDevices
102
105
  }
106
+
107
+ // MARK: - Temperature & Battery Access
108
+
109
+ /// Returns a temperature data dictionary for the given pixel, or nil if unavailable.
110
+ @objc public func getTemperatureData(x: Int = -1, y: Int = -1) -> [String: Any]? {
111
+ #if FLIR_ENABLED
112
+ guard let streamer = streamer else { return nil }
113
+ var result: [String: Any]? = nil
114
+ streamer.withThermalImage { [weak self] thermalImage in
115
+ // Attempt to extract per-pixel measurements if available
116
+ if let measurements = thermalImage.measurements as? [NSNumber],
117
+ measurements.count > 0,
118
+ let img = streamer.getImage() {
119
+ let width = Int(img.size.width)
120
+ let height = Int(img.size.height)
121
+ if width > 0 && height > 0 && x >= 0 && y >= 0 && x < width && y < height {
122
+ let idx = y * width + x
123
+ if idx < measurements.count {
124
+ let temp = measurements[idx].doubleValue
125
+ result = ["temperature": temp]
126
+ }
127
+ }
128
+ }
129
+ // Fallback: use lastTemperature if set
130
+ if result == nil, let s = self, !s.lastTemperature.isNaN {
131
+ result = ["temperature": s.lastTemperature]
132
+ }
133
+ }
134
+ return result
135
+ #else
136
+ return nil
137
+ #endif
138
+ }
139
+
140
+ @objc public func getTemperatureAtPoint(_ x: Int, y: Int) -> Double {
141
+ if let data = getTemperatureData(x: x, y: y), let t = data["temperature"] as? Double {
142
+ return t
143
+ }
144
+ return Double.nan
145
+ }
146
+
147
+ @objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
148
+ guard let img = latestImage else { return Double.nan }
149
+ let px = Int(nx * Double(img.size.width))
150
+ let py = Int(y * Double(img.size.height))
151
+ return getTemperatureAtPoint(px, y: py)
152
+ }
153
+
154
+ @objc public func getBatteryLevel() -> Int {
155
+ #if FLIR_ENABLED
156
+ if let cam = camera {
157
+ if let val = cam.value(forKey: "batteryLevel") as? Int { return val }
158
+ if let batt = cam.value(forKey: "battery") as? NSObject,
159
+ let lv = batt.value(forKey: "level") as? Int { return lv }
160
+ }
161
+ #endif
162
+ return -1
163
+ }
164
+
165
+ @objc public func isBatteryCharging() -> Bool {
166
+ #if FLIR_ENABLED
167
+ if let cam = camera {
168
+ if let ch = cam.value(forKey: "isCharging") as? Bool { return ch }
169
+ if let batt = cam.value(forKey: "battery") as? NSObject,
170
+ let ch = batt.value(forKey: "charging") as? Bool { return ch }
171
+ }
172
+ #endif
173
+ return false
174
+ }
175
+
176
+ @objc public func latestFrameImage() -> UIImage? {
177
+ return latestImage
178
+ }
179
+
180
+ @objc public func latestFrameBase64() -> String? {
181
+ guard let img = latestImage else { return nil }
182
+ if let data = img.jpegData(compressionQuality: 0.7) {
183
+ return data.base64EncodedString()
184
+ }
185
+ if let data = img.pngData() {
186
+ return data.base64EncodedString()
187
+ }
188
+ return nil
189
+ }
190
+
191
+ // Client lifecycle helpers: callers (UI/filters) can retain/release to ensure
192
+ // discovery runs while any client is active.
193
+ @objc public func retainClient(_ clientId: String) {
194
+ DispatchQueue.main.async {
195
+ self.activeClients.insert(clientId)
196
+ self.shutdownWorkItem?.cancel()
197
+ self.shutdownWorkItem = nil
198
+ if self.activeClients.count == 1 {
199
+ self.startDiscovery()
200
+ }
201
+ }
202
+ }
203
+
204
+ @objc public func releaseClient(_ clientId: String) {
205
+ DispatchQueue.main.async {
206
+ self.activeClients.remove(clientId)
207
+ self.shutdownWorkItem?.cancel()
208
+ let work = DispatchWorkItem { [weak self] in
209
+ guard let self = self else { return }
210
+ if self.activeClients.isEmpty {
211
+ self.stopDiscovery()
212
+ }
213
+ }
214
+ self.shutdownWorkItem = work
215
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: work)
216
+ }
217
+ }
218
+
219
+ // MARK: - Palette Control
220
+
221
+ /// Set palette by name (case-insensitive). If the SDK isn't available or the
222
+ /// palette cannot be found, this is a no-op.
223
+ @objc public func setPalette(_ paletteName: String) {
224
+ #if FLIR_ENABLED
225
+ guard let streamer = streamer, let thermalImage = streamer.getImage() else {
226
+ NSLog("[FlirManager] Cannot set palette - no active streamer")
227
+ return
228
+ }
229
+
230
+ // Use runtime-safe APIs to find and set palette to avoid compile-time
231
+ // coupling to specific SDK versions. Try FLIRPaletteManager.defaultPalettes
232
+ // via ObjC runtime, then attempt a KVC set on the returned image if possible.
233
+ if let pmClass = NSClassFromString("FLIRPaletteManager") as AnyObject?, pmClass.responds(to: Selector(("default"))) {
234
+ if let pmInstance = pmClass.perform(Selector(("default")))?.takeUnretainedValue() as? NSObject,
235
+ pmInstance.responds(to: Selector(("getDefaultPalettes"))) {
236
+ if let arr = pmInstance.perform(Selector(("getDefaultPalettes")))?.takeUnretainedValue() as? NSArray {
237
+ for palette in arr {
238
+ if let p = palette as? NSObject,
239
+ let name = p.value(forKey: "name") as? String,
240
+ name.lowercased() == paletteName.lowercased() {
241
+ if let imgObj = thermalImage as? NSObject {
242
+ // Try both 'palette' and 'Palette' keys depending on SDK
243
+ if imgObj.responds(to: Selector(("setPalette:"))) {
244
+ imgObj.perform(Selector(("setPalette:")), with: p)
245
+ } else {
246
+ imgObj.setValue(p, forKey: "Palette")
247
+ }
248
+ NSLog("[FlirManager] ✅ Palette set to: \(paletteName)")
249
+ return
250
+ }
251
+ }
252
+ }
253
+ NSLog("[FlirManager] Palette not found: \(paletteName)")
254
+ } else {
255
+ NSLog("[FlirManager] Palette manager returned unexpected type")
256
+ }
257
+ } else {
258
+ NSLog("[FlirManager] SDK palette APIs not available - cannot set palette")
259
+ }
260
+ #else
261
+ NSLog("[FlirManager] SDK not available - cannot set palette")
262
+ #endif
263
+ }
264
+
265
+ /// Map a normalized acol value (0..1) to a palette name.
266
+ @objc public static func getPaletteNameFromAcol(_ acol: Float) -> String {
267
+ if acol < 0.125 { return "WhiteHot" }
268
+ else if acol < 0.25 { return "BlackHot" }
269
+ else if acol < 0.375 { return "Iron" }
270
+ else if acol < 0.5 { return "Rainbow" }
271
+ else if acol < 0.625 { return "Lava" }
272
+ else if acol < 0.75 { return "Arctic" }
273
+ else if acol < 0.875 { return "Coldest" }
274
+ else { return "Hottest" }
275
+ }
276
+
277
+ @objc public func setPaletteFromAcol(_ acol: Float) {
278
+ let paletteName = FlirManager.getPaletteNameFromAcol(acol)
279
+ NSLog("[FlirManager] Setting palette from acol=\(acol) -> \(paletteName)")
280
+ setPalette(paletteName)
281
+ }
103
282
 
104
283
  // MARK: - SDK Availability
105
284
 
@@ -205,15 +384,34 @@ import ThermalSDK
205
384
  }
206
385
  }
207
386
 
208
- // Connect
209
- try camera?.connect(identity)
210
-
211
- connectedIdentity = identity
212
- connectedDeviceId = identity.deviceId()
213
- connectedDeviceName = identity.deviceId()
214
- _isConnected = true
215
-
216
- NSLog("[FlirManager] Connected to: \(identity.deviceId())")
387
+ // Connect (support multiple SDK signatures via ObjC runtime)
388
+ var connectedOK = false
389
+ if let cam = camera {
390
+ if cam.responds(to: Selector(("connect:error:"))) {
391
+ // Call connect:identity error:nil (ignore NSError pointer for compatibility)
392
+ _ = cam.perform(Selector(("connect:error:")), with: identity, with: nil)
393
+ connectedOK = true
394
+ } else if cam.responds(to: Selector(("connect:"))) {
395
+ _ = cam.perform(Selector(("connect:")), with: identity)
396
+ connectedOK = true
397
+ } else {
398
+ NSLog("[FlirManager] No compatible connect API on FLIRCamera")
399
+ }
400
+ }
401
+
402
+ if connectedOK {
403
+ connectedIdentity = identity
404
+ connectedDeviceId = identity.deviceId()
405
+ connectedDeviceName = identity.deviceId()
406
+ _isConnected = true
407
+ NSLog("[FlirManager] Connected to: \(identity.deviceId())")
408
+ } else {
409
+ NSLog("[FlirManager] Connection failed: no compatible connect API")
410
+ DispatchQueue.main.async { [weak self] in
411
+ self?.delegate?.onError("Connection failed: unsupported SDK API")
412
+ }
413
+ return
414
+ }
217
415
 
218
416
  // Get streams
219
417
  if let streams = camera?.getStreams(), !streams.isEmpty {
@@ -263,7 +461,7 @@ import ThermalSDK
263
461
  if iface.contains(.network) { return "NETWORK" }
264
462
  if iface.contains(.flirOneWireless) { return "WIRELESS" }
265
463
  if iface.contains(.emulator) { return "EMULATOR" }
266
- if iface.contains(.usb) { return "USB" }
464
+ if String(describing: iface).lowercased().contains("usb") { return "USB" }
267
465
  return "UNKNOWN"
268
466
  }
269
467
  #endif
@@ -575,10 +773,23 @@ extension FlirManager: FLIRStreamDelegate {
575
773
  if let image = streamer.getImage() {
576
774
  _latestImage = image
577
775
 
578
- // Get temperature from thermal image
776
+ // Get temperature from thermal image (use runtime selectors to be resilient across SDK versions)
579
777
  streamer.withThermalImage { [weak self] thermalImage in
580
- if let stats = thermalImage.getStatistics() {
581
- self?.lastTemperature = stats.getMax().value
778
+ var tempVal: Double = Double.nan
779
+ // Try getImageStatistics then fallback to getStatistics (different SDK versions)
780
+ if let statsObj = (thermalImage.perform(Selector(("getImageStatistics")))?.takeUnretainedValue() as? NSObject) ?? (thermalImage.perform(Selector(("getStatistics")))?.takeUnretainedValue() as? NSObject) {
781
+ if statsObj.responds(to: Selector(("getMax"))) {
782
+ if let maxObj = statsObj.perform(Selector(("getMax")))?.takeUnretainedValue() as? NSObject,
783
+ let val = maxObj.value(forKey: "value") as? Double {
784
+ tempVal = val
785
+ }
786
+ } else if let maxVal = statsObj.value(forKey: "max") as? NSObject,
787
+ let val = maxVal.value(forKey: "value") as? Double {
788
+ tempVal = val
789
+ }
790
+ }
791
+ if !tempVal.isNaN {
792
+ self?.lastTemperature = tempVal
582
793
  }
583
794
  }
584
795
 
@@ -11,6 +11,8 @@
11
11
  #import "FlirState.h"
12
12
  #import <React/RCTLog.h>
13
13
  #import <React/RCTBridge.h>
14
+ #import <objc/message.h>
15
+ #import <objc/runtime.h>
14
16
 
15
17
  #if __has_include(<ThermalSDK/ThermalSDK.h>)
16
18
  #define FLIR_SDK_AVAILABLE 1
@@ -19,8 +21,62 @@
19
21
  #define FLIR_SDK_AVAILABLE 0
20
22
  #endif
21
23
 
22
- // Forward declare Swift class
23
- @class FlirManager;
24
+ // Use runtime lookup to avoid a hard link-time dependency on `FLIRManager`.
25
+ // This prevents duplicate-definition and missing-symbol build failures when
26
+ // the Swift `FLIRManager` may or may not be available at build/link time.
27
+ static id flir_manager_shared(void) {
28
+ Class cls = NSClassFromString(@"FLIRManager");
29
+ if (!cls) return nil;
30
+ SEL sel = sel_registerName("shared");
31
+ if (![cls respondsToSelector:sel]) return nil;
32
+ id (*msgSend0)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
33
+ return msgSend0((id)cls, sel);
34
+ }
35
+
36
+ static double flir_getTemperatureAtPoint(int x, int y) {
37
+ id inst = flir_manager_shared();
38
+ if (!inst) return NAN;
39
+ SEL sel = sel_registerName("getTemperatureAtPoint:y:");
40
+ if (![inst respondsToSelector:sel]) return NAN;
41
+ double (*msgSend2)(id, SEL, int, int) = (double (*)(id, SEL, int, int))objc_msgSend;
42
+ return msgSend2(inst, sel, x, y);
43
+ }
44
+
45
+ static int flir_getBatteryLevel(void) {
46
+ id inst = flir_manager_shared();
47
+ if (!inst) return -1;
48
+ SEL sel = sel_registerName("getBatteryLevel");
49
+ if (![inst respondsToSelector:sel]) return -1;
50
+ int (*msgSend0)(id, SEL) = (int (*)(id, SEL))objc_msgSend;
51
+ return msgSend0(inst, sel);
52
+ }
53
+
54
+ static BOOL flir_isBatteryCharging(void) {
55
+ id inst = flir_manager_shared();
56
+ if (!inst) return NO;
57
+ SEL sel = sel_registerName("isBatteryCharging");
58
+ if (![inst respondsToSelector:sel]) return NO;
59
+ BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
60
+ return msgSend0(inst, sel);
61
+ }
62
+
63
+ static void flir_setPreferSdkRotation(BOOL prefer) {
64
+ id inst = flir_manager_shared();
65
+ if (!inst) return;
66
+ SEL sel = sel_registerName("setPreferSdkRotation:");
67
+ if (![inst respondsToSelector:sel]) return;
68
+ void (*msgSend1)(id, SEL, BOOL) = (void (*)(id, SEL, BOOL))objc_msgSend;
69
+ msgSend1(inst, sel, prefer);
70
+ }
71
+
72
+ static BOOL flir_isPreferSdkRotation(void) {
73
+ id inst = flir_manager_shared();
74
+ if (!inst) return NO;
75
+ SEL sel = sel_registerName("isPreferSdkRotation");
76
+ if (![inst respondsToSelector:sel]) return NO;
77
+ BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
78
+ return msgSend0(inst, sel);
79
+ }
24
80
 
25
81
  @interface FlirModule()
26
82
  #if FLIR_SDK_AVAILABLE
@@ -344,8 +400,8 @@ RCT_EXPORT_METHOD(getTemperatureAt:(nonnull NSNumber *)x
344
400
  resolver:(RCTPromiseResolveBlock)resolve
345
401
  rejecter:(RCTPromiseRejectBlock)reject) {
346
402
  dispatch_async(dispatch_get_main_queue(), ^{
347
- // Call into native FLIRManager to query temperature at point
348
- double temp = [[FLIRManager shared] getTemperatureAtPoint:[x intValue] y:[y intValue]];
403
+ // Call into native FLIRManager to query temperature at point (runtime lookup)
404
+ double temp = flir_getTemperatureAtPoint([x intValue], [y intValue]);
349
405
  if (isnan(temp)) {
350
406
  resolve([NSNull null]);
351
407
  } else {
@@ -513,7 +569,7 @@ RCT_EXPORT_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
513
569
  rejecter:(RCTPromiseRejectBlock)reject) {
514
570
  dispatch_async(dispatch_get_main_queue(), ^{
515
571
  #if FLIR_SDK_AVAILABLE
516
- int level = [[FLIRManager shared] getBatteryLevel];
572
+ int level = flir_getBatteryLevel();
517
573
  resolve(@(level));
518
574
  #else
519
575
  resolve(@(-1));
@@ -525,7 +581,7 @@ RCT_EXPORT_METHOD(isBatteryCharging:(RCTPromiseResolveBlock)resolve
525
581
  rejecter:(RCTPromiseRejectBlock)reject) {
526
582
  dispatch_async(dispatch_get_main_queue(), ^{
527
583
  #if FLIR_SDK_AVAILABLE
528
- BOOL ch = [[FLIRManager shared] isBatteryCharging];
584
+ BOOL ch = flir_isBatteryCharging();
529
585
  resolve(@(ch));
530
586
  #else
531
587
  resolve(@(NO));
@@ -537,8 +593,8 @@ RCT_EXPORT_METHOD(setPreferSdkRotation:(BOOL)prefer
537
593
  resolver:(RCTPromiseResolveBlock)resolve
538
594
  rejecter:(RCTPromiseRejectBlock)reject) {
539
595
  dispatch_async(dispatch_get_main_queue(), ^{
540
- @try {
541
- [[FLIRManager shared] setPreferSdkRotation:prefer];
596
+ @try {
597
+ flir_setPreferSdkRotation(prefer);
542
598
  resolve(@(YES));
543
599
  } @catch (NSException *ex) {
544
600
  reject(@"ERR_FLIR_SET_ROTATION_PREF", ex.reason, nil);
@@ -549,7 +605,7 @@ RCT_EXPORT_METHOD(setPreferSdkRotation:(BOOL)prefer
549
605
  RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
550
606
  rejecter:(RCTPromiseRejectBlock)reject) {
551
607
  dispatch_async(dispatch_get_main_queue(), ^{
552
- BOOL v = [[FLIRManager shared] isPreferSdkRotation];
608
+ BOOL v = flir_isPreferSdkRotation();
553
609
  resolve(@(v));
554
610
  });
555
611
  }
@@ -741,7 +797,13 @@ RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
741
797
 
742
798
  // Get temperature from thermal image if available
743
799
  [self.streamer withThermalImage:^(FLIRThermalImage *thermalImage) {
744
- FLIRImageStatistics *stats = [thermalImage getStatistics];
800
+ // Some SDK versions call getImageStatistics(), try both selectors
801
+ FLIRImageStatistics *stats = nil;
802
+ if ([thermalImage respondsToSelector:sel_registerName("getImageStatistics")]) {
803
+ stats = ((id (*)(id, SEL))objc_msgSend)((id)thermalImage, sel_registerName("getImageStatistics"));
804
+ } else if ([thermalImage respondsToSelector:sel_registerName("getStatistics")]) {
805
+ stats = ((id (*)(id, SEL))objc_msgSend)((id)thermalImage, sel_registerName("getStatistics"));
806
+ }
745
807
  if (stats) {
746
808
  self.lastTemperature = [[stats getMax] value];
747
809
  [FlirState shared].lastTemperature = self.lastTemperature;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
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",
@@ -15,7 +15,6 @@
15
15
  "android/Flir/build.gradle.kts",
16
16
  "ios/Flir/src/",
17
17
  "ios/Flir/SDKLoader/",
18
-
19
18
  "app.plugin.js",
20
19
  "Flir.podspec",
21
20
  "sdk-manifest.json",