ilabs-flir 2.0.6 → 2.0.7
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/FlirEventEmitter.m +1 -1
- package/ios/Flir/src/FlirModule.h +3 -0
- package/ios/Flir/src/FlirModule.m +57 -4
- package/package.json +1 -1
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
|
}
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -77,6 +77,7 @@ RCT_EXPORT_MODULE(FlirModule);
|
|
|
77
77
|
@"FlirFrameReceived",
|
|
78
78
|
@"FlirError",
|
|
79
79
|
@"FlirStateChanged"
|
|
80
|
+
, @"FlirBatteryUpdated"
|
|
80
81
|
];
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -88,6 +89,15 @@ RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
|
|
|
88
89
|
// Required for RCTEventEmitter
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// Provide a class helper so other native modules can post a battery update
|
|
93
|
+
+ (void)emitBatteryUpdateWithLevel:(NSInteger)level charging:(BOOL)charging {
|
|
94
|
+
NSDictionary *payload = @{
|
|
95
|
+
@"level": @(level),
|
|
96
|
+
@"isCharging": @(charging)
|
|
97
|
+
};
|
|
98
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirBatteryUpdated" body:payload];
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
#pragma mark - Discovery Methods
|
|
92
102
|
|
|
93
103
|
RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
@@ -334,10 +344,8 @@ RCT_EXPORT_METHOD(getTemperatureAt:(nonnull NSNumber *)x
|
|
|
334
344
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
335
345
|
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
336
346
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
temp = self.lastTemperature;
|
|
340
|
-
}
|
|
347
|
+
// Call into native FLIRManager to query temperature at point
|
|
348
|
+
double temp = [[FLIRManager shared] getTemperatureAtPoint:[x intValue] y:[y intValue]];
|
|
341
349
|
if (isnan(temp)) {
|
|
342
350
|
resolve([NSNull null]);
|
|
343
351
|
} else {
|
|
@@ -501,6 +509,51 @@ RCT_EXPORT_METHOD(getLatestFramePath:(RCTPromiseResolveBlock)resolve
|
|
|
501
509
|
});
|
|
502
510
|
}
|
|
503
511
|
|
|
512
|
+
RCT_EXPORT_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
|
|
513
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
514
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
515
|
+
#if FLIR_SDK_AVAILABLE
|
|
516
|
+
int level = [[FLIRManager shared] getBatteryLevel];
|
|
517
|
+
resolve(@(level));
|
|
518
|
+
#else
|
|
519
|
+
resolve(@(-1));
|
|
520
|
+
#endif
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
RCT_EXPORT_METHOD(isBatteryCharging:(RCTPromiseResolveBlock)resolve
|
|
525
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
526
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
527
|
+
#if FLIR_SDK_AVAILABLE
|
|
528
|
+
BOOL ch = [[FLIRManager shared] isBatteryCharging];
|
|
529
|
+
resolve(@(ch));
|
|
530
|
+
#else
|
|
531
|
+
resolve(@(NO));
|
|
532
|
+
#endif
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
RCT_EXPORT_METHOD(setPreferSdkRotation:(BOOL)prefer
|
|
537
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
538
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
539
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
540
|
+
@try {
|
|
541
|
+
[[FLIRManager shared] setPreferSdkRotation:prefer];
|
|
542
|
+
resolve(@(YES));
|
|
543
|
+
} @catch (NSException *ex) {
|
|
544
|
+
reject(@"ERR_FLIR_SET_ROTATION_PREF", ex.reason, nil);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
|
|
550
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
551
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
552
|
+
BOOL v = [[FLIRManager shared] isPreferSdkRotation];
|
|
553
|
+
resolve(@(v));
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
504
557
|
#pragma mark - Helper Methods
|
|
505
558
|
|
|
506
559
|
- (void)emitDeviceConnected {
|