ilabs-flir 2.0.6 → 2.0.8
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/README.md +2 -0
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +40 -0
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +40 -0
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +135 -0
- package/ios/Flir/src/FLIRManager.h +24 -0
- package/ios/Flir/src/FLIRManager.m +153 -0
- package/ios/Flir/src/FLIRManagerShim.swift +48 -0
- package/ios/Flir/src/FlirEventEmitter.m +1 -1
- package/ios/Flir/src/FlirManager.swift +161 -0
- package/ios/Flir/src/FlirModule.h +3 -0
- package/ios/Flir/src/FlirModule.m +114 -6
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -118,6 +118,8 @@ cd ios
|
|
|
118
118
|
pod install
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
Note: If you installed `ilabs-flir` via npm, `Podfile` autolinking will declare the `Flir` pod for your app automatically. To avoid duplicates, the published npm package will not contain `Flir.podspec` or in-repo podspecs; they are excluded with `.npmignore`. See `docs/MIGRATION_TO_NPM.md` for migration details if you previously used an in-repo `Flir.podspec`.
|
|
122
|
+
|
|
121
123
|
##### Building Without FLIR SDK (No Paid License)
|
|
122
124
|
|
|
123
125
|
If you don't have a paid FLIR developer license, you can build the app without the FLIR SDK. The module will provide fallback stub implementations:
|
|
@@ -63,6 +63,23 @@ object FlirManager {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
fun getLatestBitmap(): Bitmap? = latestBitmap
|
|
66
|
+
|
|
67
|
+
// Preference: ask SDK to deliver oriented/rotated frames (if SDK supports it)
|
|
68
|
+
fun setPreferSdkRotation(prefer: Boolean) {
|
|
69
|
+
sdkManager?.setPreferSdkRotation(prefer)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fun isPreferSdkRotation(): Boolean {
|
|
73
|
+
return sdkManager?.isPreferSdkRotation() ?: false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun getBatteryLevel(): Int {
|
|
77
|
+
return sdkManager?.getBatteryLevel() ?: -1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun isBatteryCharging(): Boolean {
|
|
81
|
+
return sdkManager?.isBatteryCharging() ?: false
|
|
82
|
+
}
|
|
66
83
|
|
|
67
84
|
/**
|
|
68
85
|
* Initialize the FLIR SDK
|
|
@@ -335,6 +352,11 @@ object FlirManager {
|
|
|
335
352
|
Log.e(TAG, "Error: $message")
|
|
336
353
|
emitError(message)
|
|
337
354
|
}
|
|
355
|
+
|
|
356
|
+
override fun onBatteryUpdated(level: Int, isCharging: Boolean) {
|
|
357
|
+
Log.d(TAG, "onBatteryUpdated: level=$level charging=$isCharging")
|
|
358
|
+
emitBatteryState(level, isCharging)
|
|
359
|
+
}
|
|
338
360
|
}
|
|
339
361
|
|
|
340
362
|
// React Native event emitters
|
|
@@ -411,6 +433,24 @@ object FlirManager {
|
|
|
411
433
|
Log.e(TAG, "Failed to emit devices found", e)
|
|
412
434
|
}
|
|
413
435
|
}
|
|
436
|
+
|
|
437
|
+
private fun emitBatteryState(level: Int, isCharging: Boolean) {
|
|
438
|
+
val ctx = reactContext
|
|
439
|
+
if (ctx == null) {
|
|
440
|
+
Log.w(TAG, "Cannot emit FlirBatteryUpdated - reactContext is null!")
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
val params = Arguments.createMap().apply {
|
|
445
|
+
putInt("level", level)
|
|
446
|
+
putBoolean("isCharging", isCharging)
|
|
447
|
+
}
|
|
448
|
+
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
449
|
+
.emit("FlirBatteryUpdated", params)
|
|
450
|
+
} catch (e: Exception) {
|
|
451
|
+
Log.e(TAG, "Failed to emit battery state", e)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
414
454
|
|
|
415
455
|
private fun emitError(message: String) {
|
|
416
456
|
val ctx = reactContext ?: return
|
|
@@ -147,6 +147,46 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
|
|
|
147
147
|
promise.reject("ERR_FLIR_DEVICES", e)
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
@ReactMethod
|
|
152
|
+
fun setPreferSdkRotation(prefer: Boolean, promise: Promise) {
|
|
153
|
+
try {
|
|
154
|
+
FlirManager.setPreferSdkRotation(prefer)
|
|
155
|
+
promise.resolve(true)
|
|
156
|
+
} catch (e: Exception) {
|
|
157
|
+
promise.reject("ERR_FLIR_SET_ROTATION_PREF", e)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@ReactMethod
|
|
162
|
+
fun isPreferSdkRotation(promise: Promise) {
|
|
163
|
+
try {
|
|
164
|
+
val v = FlirManager.isPreferSdkRotation()
|
|
165
|
+
promise.resolve(v)
|
|
166
|
+
} catch (e: Exception) {
|
|
167
|
+
promise.reject("ERR_FLIR_GET_ROTATION_PREF", e)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@ReactMethod
|
|
172
|
+
fun getBatteryLevel(promise: Promise) {
|
|
173
|
+
try {
|
|
174
|
+
val level = FlirManager.getBatteryLevel()
|
|
175
|
+
promise.resolve(level)
|
|
176
|
+
} catch (e: Exception) {
|
|
177
|
+
promise.reject("ERR_FLIR_GET_BATTERY", e)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@ReactMethod
|
|
182
|
+
fun isBatteryCharging(promise: Promise) {
|
|
183
|
+
try {
|
|
184
|
+
val v = FlirManager.isBatteryCharging()
|
|
185
|
+
promise.resolve(v)
|
|
186
|
+
} catch (e: Exception) {
|
|
187
|
+
promise.reject("ERR_FLIR_CHARGING", e)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
150
190
|
|
|
151
191
|
@ReactMethod
|
|
152
192
|
fun startEmulator(emulatorType: String, promise: Promise) {
|
|
@@ -47,6 +47,10 @@ public class FlirSdkManager {
|
|
|
47
47
|
private final Executor executor = Executors.newFixedThreadPool(2);
|
|
48
48
|
// Single-threaded executor for frame processing to ensure ordered processing
|
|
49
49
|
private final Executor frameExecutor = Executors.newSingleThreadExecutor();
|
|
50
|
+
// Battery poller scheduler - polls battery level & charging state periodically if supported
|
|
51
|
+
private final java.util.concurrent.ScheduledExecutorService batteryPoller = java.util.concurrent.Executors.newSingleThreadScheduledExecutor();
|
|
52
|
+
private volatile int lastPolledBatteryLevel = -1;
|
|
53
|
+
private volatile boolean lastPolledCharging = false;
|
|
50
54
|
// Frame processing guard - skip frames if still processing previous one
|
|
51
55
|
private volatile boolean isProcessingFrame = false;
|
|
52
56
|
private long lastFrameProcessedMs = 0;
|
|
@@ -59,6 +63,8 @@ public class FlirSdkManager {
|
|
|
59
63
|
private ThermalStreamer streamer;
|
|
60
64
|
private Stream activeStream;
|
|
61
65
|
private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
|
|
66
|
+
// When true, prefer getting SDK-provided rotated frames instead of rotating ourselves
|
|
67
|
+
private volatile boolean preferSdkRotation = false;
|
|
62
68
|
|
|
63
69
|
// Listener
|
|
64
70
|
private Listener listener;
|
|
@@ -73,6 +79,7 @@ public class FlirSdkManager {
|
|
|
73
79
|
void onDisconnected();
|
|
74
80
|
void onFrame(Bitmap bitmap);
|
|
75
81
|
void onError(String message);
|
|
82
|
+
void onBatteryUpdated(int level, boolean isCharging);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
// Private constructor for singleton
|
|
@@ -96,6 +103,34 @@ public class FlirSdkManager {
|
|
|
96
103
|
public void setListener(Listener listener) {
|
|
97
104
|
this.listener = listener;
|
|
98
105
|
}
|
|
106
|
+
|
|
107
|
+
public void setPreferSdkRotation(boolean prefer) {
|
|
108
|
+
this.preferSdkRotation = prefer;
|
|
109
|
+
// Try to ask SDK streamer to provide rotated images if possible
|
|
110
|
+
if (streamer != null) {
|
|
111
|
+
try {
|
|
112
|
+
// Try common method names via reflection to avoid hard dependency on exact API signature
|
|
113
|
+
Object obj = streamer;
|
|
114
|
+
java.lang.reflect.Method m = null;
|
|
115
|
+
try { m = obj.getClass().getMethod("setImageRotation", int.class); } catch (Throwable ignored) {}
|
|
116
|
+
if (m == null) {
|
|
117
|
+
try { m = obj.getClass().getMethod("setRotation", int.class); } catch (Throwable ignored) {}
|
|
118
|
+
}
|
|
119
|
+
if (m != null) {
|
|
120
|
+
// If caller asked SDK to rotate, choose 0 = 'auto' or prefer flag; here we request SDK to respect device orientation
|
|
121
|
+
int degrees = prefer ? 0 : 0; // SDK-specific - for now, 0 requests orientation-respected frames if method interprets so
|
|
122
|
+
m.invoke(obj, degrees);
|
|
123
|
+
Log.d(TAG, "setPreferSdkRotation: requested SDK rotation via reflection");
|
|
124
|
+
} else {
|
|
125
|
+
Log.w(TAG, "setPreferSdkRotation: SDK does not expose rotation API (reflection check)");
|
|
126
|
+
}
|
|
127
|
+
} catch (Throwable t) {
|
|
128
|
+
Log.w(TAG, "setPreferSdkRotation failed (reflection)", t);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public boolean isPreferSdkRotation() { return preferSdkRotation; }
|
|
99
134
|
|
|
100
135
|
/**
|
|
101
136
|
* Initialize the FLIR Thermal SDK
|
|
@@ -214,6 +249,8 @@ public class FlirSdkManager {
|
|
|
214
249
|
if (listener != null) {
|
|
215
250
|
listener.onConnected(identity);
|
|
216
251
|
}
|
|
252
|
+
// Start battery poller for continuous updates
|
|
253
|
+
startBatteryPoller();
|
|
217
254
|
} catch (Exception e) {
|
|
218
255
|
Log.e(TAG, "Connection failed", e);
|
|
219
256
|
camera = null;
|
|
@@ -236,6 +273,8 @@ public class FlirSdkManager {
|
|
|
236
273
|
}
|
|
237
274
|
camera = null;
|
|
238
275
|
}
|
|
276
|
+
// stop battery poller
|
|
277
|
+
stopBatteryPoller();
|
|
239
278
|
|
|
240
279
|
if (listener != null) {
|
|
241
280
|
listener.onDisconnected();
|
|
@@ -476,6 +515,67 @@ public class FlirSdkManager {
|
|
|
476
515
|
}
|
|
477
516
|
return names;
|
|
478
517
|
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Best-effort: Fetch battery level from connected camera if SDK exposes battery APIs
|
|
521
|
+
* Returns -1 if unavailable
|
|
522
|
+
*/
|
|
523
|
+
public int getBatteryLevel() {
|
|
524
|
+
if (camera == null) return -1;
|
|
525
|
+
try {
|
|
526
|
+
// Common SDK methods to try
|
|
527
|
+
try {
|
|
528
|
+
java.lang.reflect.Method m = camera.getClass().getMethod("getBatteryLevel");
|
|
529
|
+
Object r = m.invoke(camera);
|
|
530
|
+
if (r instanceof Number) return ((Number) r).intValue();
|
|
531
|
+
} catch (Throwable ignored) {}
|
|
532
|
+
|
|
533
|
+
try {
|
|
534
|
+
java.lang.reflect.Method m = camera.getClass().getMethod("getBattery");
|
|
535
|
+
Object batt = m.invoke(camera);
|
|
536
|
+
if (batt != null) {
|
|
537
|
+
try {
|
|
538
|
+
java.lang.reflect.Method levelMethod = batt.getClass().getMethod("getLevel");
|
|
539
|
+
Object lv = levelMethod.invoke(batt);
|
|
540
|
+
if (lv instanceof Number) return ((Number) lv).intValue();
|
|
541
|
+
} catch (Throwable ignored) {}
|
|
542
|
+
}
|
|
543
|
+
} catch (Throwable ignored) {}
|
|
544
|
+
} catch (Throwable t) {
|
|
545
|
+
Log.w(TAG, "Error querying battery level", t);
|
|
546
|
+
}
|
|
547
|
+
return -1;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Best-effort: Check if the camera is charging
|
|
552
|
+
* Returns false if unknown
|
|
553
|
+
*/
|
|
554
|
+
public boolean isBatteryCharging() {
|
|
555
|
+
if (camera == null) return false;
|
|
556
|
+
try {
|
|
557
|
+
try {
|
|
558
|
+
java.lang.reflect.Method m = camera.getClass().getMethod("isCharging");
|
|
559
|
+
Object r = m.invoke(camera);
|
|
560
|
+
if (r instanceof Boolean) return (Boolean) r;
|
|
561
|
+
} catch (Throwable ignored) {}
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
java.lang.reflect.Method m = camera.getClass().getMethod("getBattery");
|
|
565
|
+
Object batt = m.invoke(camera);
|
|
566
|
+
if (batt != null) {
|
|
567
|
+
try {
|
|
568
|
+
java.lang.reflect.Method isCh = batt.getClass().getMethod("isCharging");
|
|
569
|
+
Object cv = isCh.invoke(batt);
|
|
570
|
+
if (cv instanceof Boolean) return (Boolean) cv;
|
|
571
|
+
} catch (Throwable ignored) {}
|
|
572
|
+
}
|
|
573
|
+
} catch (Throwable ignored) {}
|
|
574
|
+
} catch (Throwable t) {
|
|
575
|
+
Log.w(TAG, "Error querying battery charging state", t);
|
|
576
|
+
}
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
479
579
|
|
|
480
580
|
// Find palette by name
|
|
481
581
|
private Palette findPalette(String name) {
|
|
@@ -580,4 +680,39 @@ public class FlirSdkManager {
|
|
|
580
680
|
instance = null;
|
|
581
681
|
Log.d(TAG, "Destroyed");
|
|
582
682
|
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Start a background poller to periodically check battery state and notify listener
|
|
686
|
+
*/
|
|
687
|
+
private void startBatteryPoller() {
|
|
688
|
+
try {
|
|
689
|
+
batteryPoller.scheduleAtFixedRate(() -> {
|
|
690
|
+
if (camera == null) return;
|
|
691
|
+
try {
|
|
692
|
+
int level = getBatteryLevel();
|
|
693
|
+
boolean charging = isBatteryCharging();
|
|
694
|
+
if (level != lastPolledBatteryLevel || charging != lastPolledCharging) {
|
|
695
|
+
lastPolledBatteryLevel = level;
|
|
696
|
+
lastPolledCharging = charging;
|
|
697
|
+
if (listener != null) {
|
|
698
|
+
try { listener.onBatteryUpdated(level, charging);} catch (Throwable t) {}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
} catch (Throwable t) {
|
|
702
|
+
Log.w(TAG, "Battery poller error", t);
|
|
703
|
+
}
|
|
704
|
+
}, 0, 5, java.util.concurrent.TimeUnit.SECONDS);
|
|
705
|
+
} catch (Throwable t) {
|
|
706
|
+
Log.w(TAG, "Failed to start battery poller", t);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Stop the battery poller.
|
|
712
|
+
*/
|
|
713
|
+
private void stopBatteryPoller() {
|
|
714
|
+
try {
|
|
715
|
+
batteryPoller.shutdownNow();
|
|
716
|
+
} catch (Throwable ignored) {}
|
|
717
|
+
}
|
|
583
718
|
}
|
|
@@ -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
|
+
}
|
|
@@ -32,7 +32,7 @@ RCT_EXPORT_MODULE();
|
|
|
32
32
|
return @[
|
|
33
33
|
@"FlirDeviceConnected", @"FlirDeviceDisconnected", @"FlirDevicesFound",
|
|
34
34
|
@"FlirFrameReceived", @"FlirFrame", @"FlirError", @"FlirStateChanged",
|
|
35
|
-
@"FlirTemperatureUpdate"
|
|
35
|
+
@"FlirTemperatureUpdate", @"FlirBatteryUpdated"
|
|
36
36
|
];
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -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,164 @@ 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 { 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 && !lastTemperature.isNaN {
|
|
131
|
+
result = ["temperature": 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
|
+
if let paletteManager = FLIRPaletteManager.defaultPalettes() {
|
|
231
|
+
for palette in paletteManager {
|
|
232
|
+
if let p = palette as? FLIRPalette, p.name.lowercased() == paletteName.lowercased() {
|
|
233
|
+
thermalImage.palette = p
|
|
234
|
+
NSLog("[FlirManager] ✅ Palette set to: \(paletteName)")
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
NSLog("[FlirManager] Palette not found: \(paletteName)")
|
|
239
|
+
} else {
|
|
240
|
+
NSLog("[FlirManager] SDK not available - cannot set palette")
|
|
241
|
+
}
|
|
242
|
+
#else
|
|
243
|
+
NSLog("[FlirManager] SDK not available - cannot set palette")
|
|
244
|
+
#endif
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Map a normalized acol value (0..1) to a palette name.
|
|
248
|
+
@objc public static func getPaletteNameFromAcol(_ acol: Float) -> String {
|
|
249
|
+
if acol < 0.125 { return "WhiteHot" }
|
|
250
|
+
else if acol < 0.25 { return "BlackHot" }
|
|
251
|
+
else if acol < 0.375 { return "Iron" }
|
|
252
|
+
else if acol < 0.5 { return "Rainbow" }
|
|
253
|
+
else if acol < 0.625 { return "Lava" }
|
|
254
|
+
else if acol < 0.75 { return "Arctic" }
|
|
255
|
+
else if acol < 0.875 { return "Coldest" }
|
|
256
|
+
else { return "Hottest" }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@objc public func setPaletteFromAcol(_ acol: Float) {
|
|
260
|
+
let paletteName = FlirManager.getPaletteNameFromAcol(acol)
|
|
261
|
+
NSLog("[FlirManager] Setting palette from acol=\(acol) -> \(paletteName)")
|
|
262
|
+
setPalette(paletteName)
|
|
263
|
+
}
|
|
103
264
|
|
|
104
265
|
// MARK: - SDK Availability
|
|
105
266
|
|
|
@@ -12,6 +12,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
12
12
|
|
|
13
13
|
@interface FlirModule : RCTEventEmitter <RCTBridgeModule>
|
|
14
14
|
|
|
15
|
+
// Utility for other native code to emit battery updates (level 0-100 or -1 if unknown)
|
|
16
|
+
+ (void)emitBatteryUpdateWithLevel:(NSInteger)level charging:(BOOL)charging;
|
|
17
|
+
|
|
15
18
|
@end
|
|
16
19
|
|
|
17
20
|
NS_ASSUME_NONNULL_END
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#import "FlirState.h"
|
|
12
12
|
#import <React/RCTLog.h>
|
|
13
13
|
#import <React/RCTBridge.h>
|
|
14
|
+
#import <objc/message.h>
|
|
14
15
|
|
|
15
16
|
#if __has_include(<ThermalSDK/ThermalSDK.h>)
|
|
16
17
|
#define FLIR_SDK_AVAILABLE 1
|
|
@@ -19,8 +20,62 @@
|
|
|
19
20
|
#define FLIR_SDK_AVAILABLE 0
|
|
20
21
|
#endif
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// Use runtime lookup to avoid a hard link-time dependency on `FLIRManager`.
|
|
24
|
+
// This prevents duplicate-definition and missing-symbol build failures when
|
|
25
|
+
// the Swift `FLIRManager` may or may not be available at build/link time.
|
|
26
|
+
static id flir_manager_shared(void) {
|
|
27
|
+
Class cls = NSClassFromString(@"FLIRManager");
|
|
28
|
+
if (!cls) return nil;
|
|
29
|
+
SEL sel = sel_registerName("shared");
|
|
30
|
+
if (![cls respondsToSelector:sel]) return nil;
|
|
31
|
+
id (*msgSend0)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
|
|
32
|
+
return msgSend0((id)cls, sel);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static double flir_getTemperatureAtPoint(int x, int y) {
|
|
36
|
+
id inst = flir_manager_shared();
|
|
37
|
+
if (!inst) return NAN;
|
|
38
|
+
SEL sel = sel_registerName("getTemperatureAtPoint:y:");
|
|
39
|
+
if (![inst respondsToSelector:sel]) return NAN;
|
|
40
|
+
double (*msgSend2)(id, SEL, int, int) = (double (*)(id, SEL, int, int))objc_msgSend;
|
|
41
|
+
return msgSend2(inst, sel, x, y);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static int flir_getBatteryLevel(void) {
|
|
45
|
+
id inst = flir_manager_shared();
|
|
46
|
+
if (!inst) return -1;
|
|
47
|
+
SEL sel = sel_registerName("getBatteryLevel");
|
|
48
|
+
if (![inst respondsToSelector:sel]) return -1;
|
|
49
|
+
int (*msgSend0)(id, SEL) = (int (*)(id, SEL))objc_msgSend;
|
|
50
|
+
return msgSend0(inst, sel);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static BOOL flir_isBatteryCharging(void) {
|
|
54
|
+
id inst = flir_manager_shared();
|
|
55
|
+
if (!inst) return NO;
|
|
56
|
+
SEL sel = sel_registerName("isBatteryCharging");
|
|
57
|
+
if (![inst respondsToSelector:sel]) return NO;
|
|
58
|
+
BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
59
|
+
return msgSend0(inst, sel);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static void flir_setPreferSdkRotation(BOOL prefer) {
|
|
63
|
+
id inst = flir_manager_shared();
|
|
64
|
+
if (!inst) return;
|
|
65
|
+
SEL sel = sel_registerName("setPreferSdkRotation:");
|
|
66
|
+
if (![inst respondsToSelector:sel]) return;
|
|
67
|
+
void (*msgSend1)(id, SEL, BOOL) = (void (*)(id, SEL, BOOL))objc_msgSend;
|
|
68
|
+
msgSend1(inst, sel, prefer);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static BOOL flir_isPreferSdkRotation(void) {
|
|
72
|
+
id inst = flir_manager_shared();
|
|
73
|
+
if (!inst) return NO;
|
|
74
|
+
SEL sel = sel_registerName("isPreferSdkRotation");
|
|
75
|
+
if (![inst respondsToSelector:sel]) return NO;
|
|
76
|
+
BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
77
|
+
return msgSend0(inst, sel);
|
|
78
|
+
}
|
|
24
79
|
|
|
25
80
|
@interface FlirModule()
|
|
26
81
|
#if FLIR_SDK_AVAILABLE
|
|
@@ -77,6 +132,7 @@ RCT_EXPORT_MODULE(FlirModule);
|
|
|
77
132
|
@"FlirFrameReceived",
|
|
78
133
|
@"FlirError",
|
|
79
134
|
@"FlirStateChanged"
|
|
135
|
+
, @"FlirBatteryUpdated"
|
|
80
136
|
];
|
|
81
137
|
}
|
|
82
138
|
|
|
@@ -88,6 +144,15 @@ RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
|
|
|
88
144
|
// Required for RCTEventEmitter
|
|
89
145
|
}
|
|
90
146
|
|
|
147
|
+
// Provide a class helper so other native modules can post a battery update
|
|
148
|
+
+ (void)emitBatteryUpdateWithLevel:(NSInteger)level charging:(BOOL)charging {
|
|
149
|
+
NSDictionary *payload = @{
|
|
150
|
+
@"level": @(level),
|
|
151
|
+
@"isCharging": @(charging)
|
|
152
|
+
};
|
|
153
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirBatteryUpdated" body:payload];
|
|
154
|
+
}
|
|
155
|
+
|
|
91
156
|
#pragma mark - Discovery Methods
|
|
92
157
|
|
|
93
158
|
RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
@@ -334,10 +399,8 @@ RCT_EXPORT_METHOD(getTemperatureAt:(nonnull NSNumber *)x
|
|
|
334
399
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
335
400
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
336
401
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
temp = self.lastTemperature;
|
|
340
|
-
}
|
|
402
|
+
// Call into native FLIRManager to query temperature at point (runtime lookup)
|
|
403
|
+
double temp = flir_getTemperatureAtPoint([x intValue], [y intValue]);
|
|
341
404
|
if (isnan(temp)) {
|
|
342
405
|
resolve([NSNull null]);
|
|
343
406
|
} else {
|
|
@@ -501,6 +564,51 @@ RCT_EXPORT_METHOD(getLatestFramePath:(RCTPromiseResolveBlock)resolve
|
|
|
501
564
|
});
|
|
502
565
|
}
|
|
503
566
|
|
|
567
|
+
RCT_EXPORT_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
|
|
568
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
569
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
570
|
+
#if FLIR_SDK_AVAILABLE
|
|
571
|
+
int level = flir_getBatteryLevel();
|
|
572
|
+
resolve(@(level));
|
|
573
|
+
#else
|
|
574
|
+
resolve(@(-1));
|
|
575
|
+
#endif
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
RCT_EXPORT_METHOD(isBatteryCharging:(RCTPromiseResolveBlock)resolve
|
|
580
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
581
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
582
|
+
#if FLIR_SDK_AVAILABLE
|
|
583
|
+
BOOL ch = flir_isBatteryCharging();
|
|
584
|
+
resolve(@(ch));
|
|
585
|
+
#else
|
|
586
|
+
resolve(@(NO));
|
|
587
|
+
#endif
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
RCT_EXPORT_METHOD(setPreferSdkRotation:(BOOL)prefer
|
|
592
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
593
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
594
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
595
|
+
@try {
|
|
596
|
+
flir_setPreferSdkRotation(prefer);
|
|
597
|
+
resolve(@(YES));
|
|
598
|
+
} @catch (NSException *ex) {
|
|
599
|
+
reject(@"ERR_FLIR_SET_ROTATION_PREF", ex.reason, nil);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
|
|
605
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
606
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
607
|
+
BOOL v = flir_isPreferSdkRotation();
|
|
608
|
+
resolve(@(v));
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
504
612
|
#pragma mark - Helper Methods
|
|
505
613
|
|
|
506
614
|
- (void)emitDeviceConnected {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilabs-flir",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
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",
|