ilabs-flir 2.1.31 → 2.1.33
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.
|
@@ -3,102 +3,98 @@
|
|
|
3
3
|
// Flir
|
|
4
4
|
//
|
|
5
5
|
// React Native bridge module for FLIR thermal camera SDK
|
|
6
|
-
//
|
|
6
|
+
// Delegate to FlirManager (Swift) for all functionality.
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
#import "FlirModule.h"
|
|
10
10
|
#import "FlirEventEmitter.h"
|
|
11
11
|
#import "FlirState.h"
|
|
12
|
-
#import <React/RCTLog.h>
|
|
13
12
|
#import <React/RCTBridge.h>
|
|
13
|
+
#import <React/RCTLog.h>
|
|
14
14
|
#import <objc/message.h>
|
|
15
15
|
#import <objc/runtime.h>
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
#import
|
|
20
|
-
#
|
|
21
|
-
#
|
|
17
|
+
// Import Swift-generated header for FlirManagerDelegate protocol
|
|
18
|
+
#if __has_include("Flir-Swift.h")
|
|
19
|
+
#import "Flir-Swift.h"
|
|
20
|
+
#elif __has_include(<Flir/Flir-Swift.h>)
|
|
21
|
+
#import <Flir/Flir-Swift.h>
|
|
22
22
|
#endif
|
|
23
23
|
|
|
24
|
-
//
|
|
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.
|
|
24
|
+
// Helper to access FlirManager singleton
|
|
27
25
|
static id flir_manager_shared(void) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
Class cls = NSClassFromString(@"Flir.FlirManager");
|
|
27
|
+
if (!cls) {
|
|
28
|
+
cls = NSClassFromString(@"FlirManager");
|
|
29
|
+
}
|
|
30
|
+
if (!cls)
|
|
31
|
+
return nil;
|
|
32
|
+
SEL sel = sel_registerName("shared");
|
|
33
|
+
if (![cls respondsToSelector:sel])
|
|
34
|
+
return nil;
|
|
35
|
+
id (*msgSend0)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
|
|
36
|
+
return msgSend0((id)cls, sel);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Helper for primitives
|
|
36
40
|
static double flir_getTemperatureAtPoint(int x, int y) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
41
|
+
id inst = flir_manager_shared();
|
|
42
|
+
if (!inst)
|
|
43
|
+
return NAN;
|
|
44
|
+
SEL sel = sel_registerName("getTemperatureAtPoint:y:");
|
|
45
|
+
if (![inst respondsToSelector:sel])
|
|
46
|
+
return NAN;
|
|
47
|
+
double (*msgSend2)(id, SEL, int, int) =
|
|
48
|
+
(double (*)(id, SEL, int, int))objc_msgSend;
|
|
49
|
+
return msgSend2(inst, sel, x, y);
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
static int flir_getBatteryLevel(void) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
53
|
+
id inst = flir_manager_shared();
|
|
54
|
+
if (!inst)
|
|
55
|
+
return -1;
|
|
56
|
+
SEL sel = sel_registerName("getBatteryLevel");
|
|
57
|
+
if (![inst respondsToSelector:sel])
|
|
58
|
+
return -1;
|
|
59
|
+
int (*msgSend0)(id, SEL) = (int (*)(id, SEL))objc_msgSend;
|
|
60
|
+
return msgSend0(inst, sel);
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
static BOOL flir_isBatteryCharging(void) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return
|
|
64
|
+
id inst = flir_manager_shared();
|
|
65
|
+
if (!inst)
|
|
66
|
+
return NO;
|
|
67
|
+
SEL sel = sel_registerName("isBatteryCharging");
|
|
68
|
+
if (![inst respondsToSelector:sel])
|
|
69
|
+
return NO;
|
|
70
|
+
BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
71
|
+
return msgSend0(inst, sel);
|
|
61
72
|
}
|
|
62
|
-
|
|
63
73
|
static void flir_setPreferSdkRotation(BOOL prefer) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
id inst = flir_manager_shared();
|
|
75
|
+
if (!inst)
|
|
76
|
+
return;
|
|
77
|
+
SEL sel = sel_registerName("setPreferSdkRotation:");
|
|
78
|
+
if (![inst respondsToSelector:sel])
|
|
79
|
+
return;
|
|
80
|
+
void (*msgSend1)(id, SEL, BOOL) = (void (*)(id, SEL, BOOL))objc_msgSend;
|
|
81
|
+
msgSend1(inst, sel, prefer);
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
static BOOL flir_isPreferSdkRotation(void) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return
|
|
85
|
+
id inst = flir_manager_shared();
|
|
86
|
+
if (!inst)
|
|
87
|
+
return NO;
|
|
88
|
+
SEL sel = sel_registerName("isPreferSdkRotation");
|
|
89
|
+
if (![inst respondsToSelector:sel])
|
|
90
|
+
return NO;
|
|
91
|
+
BOOL (*msgSend0)(id, SEL) = (BOOL (*)(id, SEL))objc_msgSend;
|
|
92
|
+
return msgSend0(inst, sel);
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
@interface FlirModule()
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#endif
|
|
85
|
-
|
|
86
|
-
#if FLIR_SDK_AVAILABLE
|
|
87
|
-
@property (nonatomic, strong) FLIRDiscovery *discovery;
|
|
88
|
-
@property (nonatomic, strong) FLIRCamera *camera;
|
|
89
|
-
@property (nonatomic, strong) FLIRStream *stream;
|
|
90
|
-
@property (nonatomic, strong) FLIRThermalStreamer *streamer;
|
|
91
|
-
@property (nonatomic, strong) FLIRIdentity *connectedIdentity;
|
|
92
|
-
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLIRIdentity *> *identityMap;
|
|
93
|
-
#endif
|
|
94
|
-
|
|
95
|
-
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *discoveredDevices;
|
|
96
|
-
@property (nonatomic, assign) BOOL isScanning;
|
|
97
|
-
@property (nonatomic, assign) BOOL isConnected;
|
|
98
|
-
@property (nonatomic, assign) BOOL isStreaming;
|
|
99
|
-
@property (nonatomic, copy) NSString *connectedDeviceId;
|
|
100
|
-
@property (nonatomic, copy) NSString *connectedDeviceName;
|
|
101
|
-
@property (nonatomic, assign) double lastTemperature;
|
|
95
|
+
@interface FlirModule () <FlirManagerDelegate>
|
|
96
|
+
@property(nonatomic, copy) RCTPromiseResolveBlock connectResolve;
|
|
97
|
+
@property(nonatomic, copy) RCTPromiseRejectBlock connectReject;
|
|
102
98
|
@end
|
|
103
99
|
|
|
104
100
|
@implementation FlirModule
|
|
@@ -106,907 +102,357 @@ static BOOL flir_isPreferSdkRotation(void) {
|
|
|
106
102
|
RCT_EXPORT_MODULE(FlirModule);
|
|
107
103
|
|
|
108
104
|
+ (BOOL)requiresMainQueueSetup {
|
|
109
|
-
|
|
105
|
+
return YES;
|
|
110
106
|
}
|
|
111
107
|
|
|
112
108
|
- (instancetype)init {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
_isScanning = NO;
|
|
119
|
-
_isConnected = NO;
|
|
120
|
-
_isStreaming = NO;
|
|
121
|
-
_lastTemperature = NAN;
|
|
109
|
+
if (self = [super init]) {
|
|
110
|
+
// Wire up delegate
|
|
111
|
+
id manager = flir_manager_shared();
|
|
112
|
+
if (manager) {
|
|
113
|
+
[manager setValue:self forKey:@"delegate"];
|
|
122
114
|
}
|
|
123
|
-
|
|
115
|
+
}
|
|
116
|
+
return self;
|
|
124
117
|
}
|
|
125
118
|
|
|
126
119
|
#pragma mark - Event Emitter Support
|
|
127
120
|
|
|
128
121
|
- (NSArray<NSString *> *)supportedEvents {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@"FlirError",
|
|
135
|
-
@"FlirStateChanged"
|
|
136
|
-
, @"FlirBatteryUpdated"
|
|
137
|
-
];
|
|
122
|
+
return @[
|
|
123
|
+
@"FlirDeviceConnected", @"FlirDeviceDisconnected", @"FlirDevicesFound",
|
|
124
|
+
@"FlirFrameReceived", @"FlirError", @"FlirStateChanged",
|
|
125
|
+
@"FlirBatteryUpdated"
|
|
126
|
+
];
|
|
138
127
|
}
|
|
139
128
|
|
|
140
|
-
RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {
|
|
141
|
-
|
|
129
|
+
RCT_EXPORT_METHOD(addListener : (NSString *)eventName) {
|
|
130
|
+
// Required for RCTEventEmitter
|
|
142
131
|
}
|
|
143
132
|
|
|
144
|
-
RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
|
|
145
|
-
|
|
133
|
+
RCT_EXPORT_METHOD(removeListeners : (NSInteger)count) {
|
|
134
|
+
// Required for RCTEventEmitter
|
|
146
135
|
}
|
|
147
136
|
|
|
148
|
-
// Provide a class helper so other native modules can post a battery update
|
|
149
137
|
+ (void)emitBatteryUpdateWithLevel:(NSInteger)level charging:(BOOL)charging {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
138
|
+
NSDictionary *payload = @{@"level" : @(level), @"isCharging" : @(charging)};
|
|
139
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirBatteryUpdated"
|
|
140
|
+
body:payload];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#pragma mark - Methods
|
|
144
|
+
|
|
145
|
+
RCT_EXPORT_METHOD(setNetworkDiscoveryEnabled : (BOOL)enabled resolver : (
|
|
146
|
+
RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
147
|
+
// FlirManager uses UserDefaults directly for this too
|
|
148
|
+
id manager = flir_manager_shared();
|
|
149
|
+
if (manager &&
|
|
150
|
+
[manager
|
|
151
|
+
respondsToSelector:sel_registerName("setNetworkDiscoveryEnabled:")]) {
|
|
152
|
+
((void (*)(id, SEL, BOOL))objc_msgSend)(
|
|
153
|
+
manager, sel_registerName("setNetworkDiscoveryEnabled:"), enabled);
|
|
154
|
+
} else {
|
|
155
|
+
[[NSUserDefaults standardUserDefaults]
|
|
156
|
+
setBool:enabled
|
|
157
|
+
forKey:@"ilabsFlir.networkDiscoveryEnabled"];
|
|
158
|
+
}
|
|
159
|
+
resolve(@(YES));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
RCT_EXPORT_METHOD(startDiscovery : (RCTPromiseResolveBlock)
|
|
163
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
164
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
165
|
+
id manager = flir_manager_shared();
|
|
166
|
+
if (manager &&
|
|
167
|
+
[manager respondsToSelector:sel_registerName("startDiscovery")]) {
|
|
168
|
+
((void (*)(id, SEL))objc_msgSend)(manager,
|
|
169
|
+
sel_registerName("startDiscovery"));
|
|
171
170
|
}
|
|
172
|
-
|
|
173
|
-
// Safe default: require Local Network usage description to be present.
|
|
174
|
-
id desc = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocalNetworkUsageDescription"];
|
|
175
|
-
if ([desc isKindOfClass:[NSString class]] && ((NSString *)desc).length > 0) {
|
|
176
|
-
return YES;
|
|
177
|
-
}
|
|
178
|
-
return NO;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
RCT_EXPORT_METHOD(setNetworkDiscoveryEnabled:(BOOL)enabled
|
|
182
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
183
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
184
|
-
#if FLIR_SDK_AVAILABLE
|
|
185
|
-
[[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ilabsFlir.networkDiscoveryEnabled"];
|
|
186
171
|
resolve(@(YES));
|
|
187
|
-
|
|
188
|
-
resolve(@(YES));
|
|
189
|
-
#endif
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
193
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
194
|
-
#if FLIR_SDK_AVAILABLE
|
|
195
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
196
|
-
if (self.isScanning) {
|
|
197
|
-
RCTLogInfo(@"[FlirModule] Already scanning");
|
|
198
|
-
resolve(@(YES));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
self.isScanning = YES;
|
|
203
|
-
[self.discoveredDevices removeAllObjects];
|
|
204
|
-
[self.identityMap removeAllObjects];
|
|
205
|
-
|
|
206
|
-
// Always expose emulator options to JS/UI so the user can connect even when
|
|
207
|
-
// physical devices are not present.
|
|
208
|
-
NSDictionary *emuOne = @{
|
|
209
|
-
@"id": @"emu:FLIR_ONE",
|
|
210
|
-
@"name": @"FLIR One Emulator",
|
|
211
|
-
@"communicationType": @"EMULATOR",
|
|
212
|
-
@"isEmulator": @(YES)
|
|
213
|
-
};
|
|
214
|
-
NSDictionary *emuEdge = @{
|
|
215
|
-
@"id": @"emu:FLIR_ONE_EDGE",
|
|
216
|
-
@"name": @"FLIR One Edge Emulator",
|
|
217
|
-
@"communicationType": @"EMULATOR",
|
|
218
|
-
@"isEmulator": @(YES)
|
|
219
|
-
};
|
|
220
|
-
[self.discoveredDevices addObjectsFromArray:@[ emuOne, emuEdge ]];
|
|
221
|
-
|
|
222
|
-
NSDictionary *initialDevicesBody = @{
|
|
223
|
-
@"devices": self.discoveredDevices,
|
|
224
|
-
@"count": @(self.discoveredDevices.count)
|
|
225
|
-
};
|
|
226
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:initialDevicesBody];
|
|
227
|
-
|
|
228
|
-
if (!self.discovery) {
|
|
229
|
-
self.discovery = [[FLIRDiscovery alloc] init];
|
|
230
|
-
self.discovery.delegate = self;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Start discovery on allowed interfaces.
|
|
234
|
-
// Always include wired/BLE/emulator. Only include network when the app has
|
|
235
|
-
// Local Network usage description (or the app explicitly enabled it).
|
|
236
|
-
FLIRCommunicationInterface interfaces = FLIRCommunicationInterfaceLightning |
|
|
237
|
-
FLIRCommunicationInterfaceFlirOneWireless |
|
|
238
|
-
FLIRCommunicationInterfaceEmulator |
|
|
239
|
-
FLIRCommunicationInterfaceUSB;
|
|
240
|
-
if ([self shouldEnableNetworkDiscovery]) {
|
|
241
|
-
interfaces |= FLIRCommunicationInterfaceNetwork;
|
|
242
|
-
} else {
|
|
243
|
-
RCTLogInfo(@"[FlirModule] Network discovery disabled (missing NSLocalNetworkUsageDescription or overridden)");
|
|
244
|
-
}
|
|
245
|
-
[self.discovery start:interfaces];
|
|
246
|
-
|
|
247
|
-
[self emitStateChange:@"discovering"];
|
|
248
|
-
RCTLogInfo(@"[FlirModule] Discovery started");
|
|
249
|
-
|
|
250
|
-
__weak typeof(self) weakSelf = self;
|
|
251
|
-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
252
|
-
__strong typeof(self) strongSelf = weakSelf;
|
|
253
|
-
if (!strongSelf) return;
|
|
254
|
-
if (!strongSelf.isScanning || strongSelf.isConnected) return;
|
|
255
|
-
|
|
256
|
-
BOOL hasRealDevice = NO;
|
|
257
|
-
for (NSDictionary *dev in strongSelf.discoveredDevices) {
|
|
258
|
-
NSString *did = dev[@"id"];
|
|
259
|
-
if (did.length > 0 && ![did hasPrefix:@"emu:"]) {
|
|
260
|
-
hasRealDevice = YES;
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (!hasRealDevice) {
|
|
265
|
-
[strongSelf emitStateChange:@"no_device_found"];
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
resolve(@(YES));
|
|
270
|
-
});
|
|
271
|
-
#else
|
|
272
|
-
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
273
|
-
#endif
|
|
172
|
+
});
|
|
274
173
|
}
|
|
275
174
|
|
|
276
|
-
RCT_EXPORT_METHOD(stopDiscovery:(RCTPromiseResolveBlock)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
285
|
-
#else
|
|
286
|
-
resolve(@(YES));
|
|
287
|
-
#endif
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
RCT_EXPORT_METHOD(getDiscoveredDevices:(RCTPromiseResolveBlock)resolve
|
|
291
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
292
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
293
|
-
resolve(self.discoveredDevices);
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
#pragma mark - Connection Methods
|
|
298
|
-
|
|
299
|
-
RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
300
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
301
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
302
|
-
#if FLIR_SDK_AVAILABLE
|
|
303
|
-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
304
|
-
// Synthetic emulator ids exposed during discovery.
|
|
305
|
-
if ([deviceId hasPrefix:@"emu:"]) {
|
|
306
|
-
NSString *typePart = [deviceId substringFromIndex:4];
|
|
307
|
-
FLIRCameraType cameraType = FLIRCameraType_flirOne;
|
|
308
|
-
if ([typePart.lowercaseString containsString:@"edge"]) {
|
|
309
|
-
cameraType = FLIRCameraType_flirOneEdge;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
FLIRIdentity *emulatorIdentity = [[FLIRIdentity alloc] initWithEmulatorType:cameraType];
|
|
313
|
-
if (!emulatorIdentity) {
|
|
314
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
315
|
-
reject(@"ERR_EMULATOR_INIT", @"Failed to create emulator identity", nil);
|
|
316
|
-
});
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
self.identityMap[[emulatorIdentity deviceId]] = emulatorIdentity;
|
|
321
|
-
|
|
322
|
-
[self performConnectionWithIdentity:emulatorIdentity completion:^(BOOL success, NSError *error) {
|
|
323
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
324
|
-
if (success) {
|
|
325
|
-
resolve(@(YES));
|
|
326
|
-
} else {
|
|
327
|
-
reject(@"ERR_CONNECTION_FAILED", error.localizedDescription ?: @"Connection failed", error);
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
}];
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
FLIRIdentity *identity = self.identityMap[deviceId];
|
|
335
|
-
if (!identity) {
|
|
336
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
337
|
-
reject(@"ERR_DEVICE_NOT_FOUND", [NSString stringWithFormat:@"Device not found: %@", deviceId], nil);
|
|
338
|
-
});
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
[self performConnectionWithIdentity:identity completion:^(BOOL success, NSError *error) {
|
|
343
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
344
|
-
if (success) {
|
|
345
|
-
resolve(@(YES));
|
|
346
|
-
} else {
|
|
347
|
-
reject(@"ERR_CONNECTION_FAILED", error.localizedDescription ?: @"Connection failed", error);
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}];
|
|
351
|
-
});
|
|
352
|
-
#else
|
|
353
|
-
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
354
|
-
#endif
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
#if FLIR_SDK_AVAILABLE
|
|
358
|
-
- (void)performConnectionWithIdentity:(FLIRIdentity *)identity completion:(void(^)(BOOL success, NSError *error))completion {
|
|
359
|
-
if (!self.camera) {
|
|
360
|
-
self.camera = [[FLIRCamera alloc] init];
|
|
361
|
-
self.camera.delegate = self;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
NSError *error = nil;
|
|
365
|
-
|
|
366
|
-
// Handle authentication for generic cameras (network cameras)
|
|
367
|
-
if ([identity cameraType] == FLIRCameraType_generic) {
|
|
368
|
-
NSString *certName = [self getCertificateName];
|
|
369
|
-
FLIRAuthenticationStatus status = pending;
|
|
370
|
-
while (status == pending) {
|
|
371
|
-
status = [self.camera authenticate:identity trustedConnectionName:certName];
|
|
372
|
-
if (status == pending) {
|
|
373
|
-
RCTLogInfo(@"[FlirModule] Waiting for camera authentication...");
|
|
374
|
-
[NSThread sleepForTimeInterval:1.0];
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
RCTLogInfo(@"[FlirModule] Authentication status: %d", (int)status);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Step 1: Pair with camera (required for FLIR One devices)
|
|
381
|
-
@try {
|
|
382
|
-
if (![self.camera pair:identity code:0 error:&error]) {
|
|
383
|
-
RCTLogError(@"[FlirModule] Pair failed: %@", error.localizedDescription);
|
|
384
|
-
if (completion) completion(NO, error);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
RCTLogInfo(@"[FlirModule] Paired with: %@", [identity deviceId]);
|
|
388
|
-
} @catch (NSException *exception) {
|
|
389
|
-
RCTLogError(@"[FlirModule] Pair exception: %@", exception.reason);
|
|
390
|
-
NSError *pairError = [NSError errorWithDomain:@"FlirModule" code:1001 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Pair failed"}];
|
|
391
|
-
if (completion) completion(NO, pairError);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Step 2: Connect to camera
|
|
396
|
-
BOOL connected = NO;
|
|
397
|
-
@try {
|
|
398
|
-
if (![self.camera connect:&error]) {
|
|
399
|
-
RCTLogError(@"[FlirModule] Connect failed: %@", error.localizedDescription);
|
|
400
|
-
if (completion) completion(NO, error);
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
connected = YES;
|
|
404
|
-
RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
|
|
405
|
-
} @catch (NSException *exception) {
|
|
406
|
-
RCTLogError(@"[FlirModule] Connect exception: %@", exception.reason);
|
|
407
|
-
error = [NSError errorWithDomain:@"FlirModule" code:1002 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Connect failed"}];
|
|
408
|
-
connected = NO;
|
|
175
|
+
RCT_EXPORT_METHOD(stopDiscovery : (RCTPromiseResolveBlock)
|
|
176
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
177
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
178
|
+
id manager = flir_manager_shared();
|
|
179
|
+
if (manager &&
|
|
180
|
+
[manager respondsToSelector:sel_registerName("stopDiscovery")]) {
|
|
181
|
+
((void (*)(id, SEL))objc_msgSend)(manager,
|
|
182
|
+
sel_registerName("stopDiscovery"));
|
|
409
183
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
// Get available streams and prefer thermal stream
|
|
428
|
-
NSArray<FLIRStream *> *streams = [self.camera getStreams];
|
|
429
|
-
if (streams.count > 0) {
|
|
430
|
-
RCTLogInfo(@"[FlirModule] Found %lu stream(s)", (unsigned long)streams.count);
|
|
431
|
-
|
|
432
|
-
// Find thermal stream (preferred) or use first stream
|
|
433
|
-
FLIRStream *streamToStart = nil;
|
|
434
|
-
for (FLIRStream *stream in streams) {
|
|
435
|
-
if (stream.isThermal) {
|
|
436
|
-
streamToStart = stream;
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (!streamToStart) {
|
|
441
|
-
streamToStart = streams[0];
|
|
442
|
-
}
|
|
443
|
-
[self startStreamInternal:streamToStart];
|
|
184
|
+
resolve(@(YES));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
RCT_EXPORT_METHOD(getDiscoveredDevices : (RCTPromiseResolveBlock)
|
|
189
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
190
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
191
|
+
id manager = flir_manager_shared();
|
|
192
|
+
NSMutableArray *arr = [NSMutableArray new];
|
|
193
|
+
if (manager &&
|
|
194
|
+
[manager respondsToSelector:sel_registerName("getDiscoveredDevices")]) {
|
|
195
|
+
NSArray *devs = ((NSArray * (*)(id, SEL)) objc_msgSend)(
|
|
196
|
+
manager, sel_registerName("getDiscoveredDevices"));
|
|
197
|
+
for (id d in devs) {
|
|
198
|
+
if ([d respondsToSelector:sel_registerName("toDictionary")]) {
|
|
199
|
+
[arr addObject:((NSDictionary * (*)(id, SEL)) objc_msgSend)(
|
|
200
|
+
d, sel_registerName("toDictionary"))];
|
|
444
201
|
}
|
|
445
|
-
|
|
446
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
447
|
-
[self emitDeviceConnected];
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
if (completion) completion(YES, nil);
|
|
451
|
-
} else {
|
|
452
|
-
RCTLogError(@"[FlirModule] Connection failed: %@", error.localizedDescription);
|
|
453
|
-
self.camera = nil;
|
|
454
|
-
if (completion) completion(NO, error);
|
|
202
|
+
}
|
|
455
203
|
}
|
|
204
|
+
resolve(arr);
|
|
205
|
+
});
|
|
456
206
|
}
|
|
457
207
|
|
|
208
|
+
RCT_EXPORT_METHOD(connectToDevice : (NSString *)deviceId resolver : (
|
|
209
|
+
RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
210
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
211
|
+
self.connectResolve = resolve;
|
|
212
|
+
self.connectReject = reject;
|
|
458
213
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
214
|
+
id manager = flir_manager_shared();
|
|
215
|
+
if (manager &&
|
|
216
|
+
[manager respondsToSelector:sel_registerName("connectToDevice:")]) {
|
|
217
|
+
((void (*)(id, SEL, id))objc_msgSend)(
|
|
218
|
+
manager, sel_registerName("connectToDevice:"), deviceId);
|
|
219
|
+
} else {
|
|
220
|
+
reject(@"ERR_NO_MANAGER", @"FlirManager not found", nil);
|
|
221
|
+
self.connectResolve = nil;
|
|
222
|
+
self.connectReject = nil;
|
|
466
223
|
}
|
|
467
|
-
|
|
468
|
-
NSString *newName = [[NSUUID UUID] UUIDString];
|
|
469
|
-
[[NSUserDefaults standardUserDefaults] setObject:newName forKey:key];
|
|
470
|
-
return newName;
|
|
224
|
+
});
|
|
471
225
|
}
|
|
472
|
-
#endif
|
|
473
226
|
|
|
474
|
-
RCT_EXPORT_METHOD(disconnect:(RCTPromiseResolveBlock)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
[
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
self.connectedDeviceName = nil;
|
|
484
|
-
self.isConnected = NO;
|
|
485
|
-
self.isStreaming = NO;
|
|
486
|
-
|
|
487
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
488
|
-
[self emitStateChange:@"disconnected"];
|
|
489
|
-
|
|
490
|
-
RCTLogInfo(@"[FlirModule] Disconnected");
|
|
491
|
-
resolve(@(YES));
|
|
492
|
-
});
|
|
493
|
-
#else
|
|
227
|
+
RCT_EXPORT_METHOD(disconnect : (RCTPromiseResolveBlock)
|
|
228
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
229
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
230
|
+
id manager = flir_manager_shared();
|
|
231
|
+
if (manager &&
|
|
232
|
+
[manager respondsToSelector:sel_registerName("disconnect")]) {
|
|
233
|
+
((void (*)(id, SEL))objc_msgSend)(manager,
|
|
234
|
+
sel_registerName("disconnect"));
|
|
235
|
+
}
|
|
494
236
|
resolve(@(YES));
|
|
495
|
-
|
|
237
|
+
});
|
|
496
238
|
}
|
|
497
239
|
|
|
498
|
-
RCT_EXPORT_METHOD(stopFlir:(RCTPromiseResolveBlock)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
self.camera = nil;
|
|
507
|
-
self.connectedIdentity = nil;
|
|
508
|
-
self.connectedDeviceId = nil;
|
|
509
|
-
self.connectedDeviceName = nil;
|
|
510
|
-
self.isConnected = NO;
|
|
511
|
-
self.isStreaming = NO;
|
|
512
|
-
self.isScanning = NO;
|
|
513
|
-
|
|
514
|
-
[self emitStateChange:@"stopped"];
|
|
515
|
-
RCTLogInfo(@"[FlirModule] Stopped");
|
|
516
|
-
resolve(@(YES));
|
|
517
|
-
});
|
|
518
|
-
#else
|
|
240
|
+
RCT_EXPORT_METHOD(stopFlir : (RCTPromiseResolveBlock)
|
|
241
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
242
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
243
|
+
id manager = flir_manager_shared();
|
|
244
|
+
if (manager && [manager respondsToSelector:sel_registerName("stop")]) {
|
|
245
|
+
((void (*)(id, SEL))objc_msgSend)(manager, sel_registerName("stop"));
|
|
246
|
+
}
|
|
519
247
|
resolve(@(YES));
|
|
520
|
-
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
RCT_EXPORT_METHOD(startEmulator : (NSString *)emulatorType resolver : (
|
|
252
|
+
RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
253
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
254
|
+
self.connectResolve = resolve;
|
|
255
|
+
self.connectReject = reject;
|
|
256
|
+
id manager = flir_manager_shared();
|
|
257
|
+
if (manager && [manager respondsToSelector:sel_registerName(
|
|
258
|
+
"startEmulatorWithType:")]) {
|
|
259
|
+
// Swift: startEmulator(type: String) -> exposed as startEmulatorWithType:
|
|
260
|
+
// ? Or startEmulatorWith? Swift default naming: startEmulator(type:) ->
|
|
261
|
+
// startEmulatorWithType:
|
|
262
|
+
((void (*)(id, SEL, id))objc_msgSend)(
|
|
263
|
+
manager, sel_registerName("startEmulatorWithType:"), emulatorType);
|
|
264
|
+
} else {
|
|
265
|
+
// Fallback if selector assumption wrong/mismatch
|
|
266
|
+
reject(@"ERR_NOT_IMPL",
|
|
267
|
+
@"startEmulator not implemented or signature mismatch", nil);
|
|
268
|
+
self.connectResolve = nil;
|
|
269
|
+
self.connectReject = nil;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
521
272
|
}
|
|
522
273
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
[
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
self.streamer = [[FLIRThermalStreamer alloc] initWithStream:newStream];
|
|
274
|
+
RCT_EXPORT_METHOD(getTemperatureAt : (nonnull NSNumber *)x y : (
|
|
275
|
+
nonnull NSNumber *)y resolver : (RCTPromiseResolveBlock)
|
|
276
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
277
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
278
|
+
double temp = flir_getTemperatureAtPoint([x intValue], [y intValue]);
|
|
279
|
+
if (isnan(temp)) {
|
|
280
|
+
resolve([NSNull null]);
|
|
281
|
+
} else {
|
|
282
|
+
resolve(@(temp));
|
|
533
283
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
RCT_EXPORT_METHOD(getTemperatureFromColor : (NSInteger)color resolver : (
|
|
288
|
+
RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
289
|
+
int r = (color >> 16) & 0xFF;
|
|
290
|
+
int g = (color >> 8) & 0xFF;
|
|
291
|
+
int b = color & 0xFF;
|
|
292
|
+
double lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
293
|
+
double temp = (lum / 255.0) * 400.0;
|
|
294
|
+
resolve(@(temp));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
RCT_EXPORT_METHOD(isEmulator : (RCTPromiseResolveBlock)
|
|
298
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
299
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
300
|
+
id manager = flir_manager_shared();
|
|
301
|
+
BOOL isEm = NO;
|
|
302
|
+
if (manager &&
|
|
303
|
+
[manager respondsToSelector:sel_registerName("isEmulator")]) {
|
|
304
|
+
isEm = ((BOOL (*)(id, SEL))objc_msgSend)(manager,
|
|
305
|
+
sel_registerName("isEmulator"));
|
|
553
306
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
307
|
+
resolve(@(isEm));
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
RCT_EXPORT_METHOD(isDeviceConnected : (RCTPromiseResolveBlock)
|
|
312
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
313
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
314
|
+
id manager = flir_manager_shared();
|
|
315
|
+
BOOL isC = NO;
|
|
316
|
+
if (manager &&
|
|
317
|
+
[manager respondsToSelector:sel_registerName("isConnected")]) {
|
|
318
|
+
isC = ((BOOL (*)(id, SEL))objc_msgSend)(manager,
|
|
319
|
+
sel_registerName("isConnected"));
|
|
320
|
+
}
|
|
321
|
+
resolve(@(isC));
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
RCT_EXPORT_METHOD(getConnectedDeviceInfo : (RCTPromiseResolveBlock)
|
|
326
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
327
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
328
|
+
id manager = flir_manager_shared();
|
|
329
|
+
NSString *info = @"Not connected";
|
|
330
|
+
if (manager && [manager respondsToSelector:sel_registerName(
|
|
331
|
+
"getConnectedDeviceInfo")]) {
|
|
332
|
+
info = ((NSString * (*)(id, SEL)) objc_msgSend)(
|
|
333
|
+
manager, sel_registerName("getConnectedDeviceInfo"));
|
|
334
|
+
}
|
|
335
|
+
resolve(info);
|
|
336
|
+
});
|
|
565
337
|
}
|
|
566
|
-
#endif
|
|
567
338
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
573
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
574
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
575
|
-
// Call into native FLIRManager to query temperature at point (runtime lookup)
|
|
576
|
-
double temp = flir_getTemperatureAtPoint([x intValue], [y intValue]);
|
|
577
|
-
if (isnan(temp)) {
|
|
578
|
-
resolve([NSNull null]);
|
|
579
|
-
} else {
|
|
580
|
-
resolve(@(temp));
|
|
581
|
-
}
|
|
582
|
-
});
|
|
339
|
+
RCT_EXPORT_METHOD(isSDKDownloaded : (RCTPromiseResolveBlock)
|
|
340
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
341
|
+
// Assuming integrated SDK
|
|
342
|
+
resolve(@(YES));
|
|
583
343
|
}
|
|
584
344
|
|
|
585
|
-
RCT_EXPORT_METHOD(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
// Placeholder: Convert ARGB color to pseudo-temperature
|
|
589
|
-
int r = (color >> 16) & 0xFF;
|
|
590
|
-
int g = (color >> 8) & 0xFF;
|
|
591
|
-
int b = color & 0xFF;
|
|
592
|
-
double lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
593
|
-
double temp = (lum / 255.0) * 400.0;
|
|
594
|
-
resolve(@(temp));
|
|
345
|
+
RCT_EXPORT_METHOD(getSDKStatus : (RCTPromiseResolveBlock)
|
|
346
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
347
|
+
resolve(@{@"available" : @(YES), @"arch" : @"arm64", @"platform" : @"iOS"});
|
|
595
348
|
}
|
|
596
349
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
[self.connectedDeviceName.lowercaseString containsString:@"emulat"];
|
|
604
|
-
resolve(@(isEmu));
|
|
605
|
-
});
|
|
350
|
+
RCT_EXPORT_METHOD(getBatteryLevel : (RCTPromiseResolveBlock)
|
|
351
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
352
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
353
|
+
int level = flir_getBatteryLevel();
|
|
354
|
+
resolve(@(level));
|
|
355
|
+
});
|
|
606
356
|
}
|
|
607
357
|
|
|
608
|
-
RCT_EXPORT_METHOD(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
358
|
+
RCT_EXPORT_METHOD(isBatteryCharging : (RCTPromiseResolveBlock)
|
|
359
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
360
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
361
|
+
BOOL ch = flir_isBatteryCharging();
|
|
362
|
+
resolve(@(ch));
|
|
363
|
+
});
|
|
613
364
|
}
|
|
614
365
|
|
|
615
|
-
RCT_EXPORT_METHOD(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
RCT_EXPORT_METHOD(isSDKDownloaded:(RCTPromiseResolveBlock)resolve
|
|
623
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
624
|
-
#if FLIR_SDK_AVAILABLE
|
|
366
|
+
RCT_EXPORT_METHOD(setPreferSdkRotation : (BOOL)prefer resolver : (
|
|
367
|
+
RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
368
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
369
|
+
flir_setPreferSdkRotation(prefer);
|
|
625
370
|
resolve(@(YES));
|
|
626
|
-
|
|
627
|
-
resolve(@(NO));
|
|
628
|
-
#endif
|
|
371
|
+
});
|
|
629
372
|
}
|
|
630
373
|
|
|
631
|
-
RCT_EXPORT_METHOD(
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
@"available": @(NO),
|
|
638
|
-
#endif
|
|
639
|
-
@"arch": @"arm64",
|
|
640
|
-
@"platform": @"iOS"
|
|
641
|
-
};
|
|
642
|
-
resolve(status);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
#pragma mark - Emulator
|
|
646
|
-
|
|
647
|
-
RCT_EXPORT_METHOD(startEmulator:(NSString *)emulatorType
|
|
648
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
649
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
650
|
-
#if FLIR_SDK_AVAILABLE
|
|
651
|
-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
652
|
-
FLIRCameraType cameraType = FLIRCameraType_flirOne;
|
|
653
|
-
if ([emulatorType.lowercaseString containsString:@"edge"]) {
|
|
654
|
-
cameraType = FLIRCameraType_flirOneEdge;
|
|
655
|
-
} else if ([emulatorType.lowercaseString containsString:@"pro"]) {
|
|
656
|
-
cameraType = FLIRCameraType_flirOneEdgePro;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
FLIRIdentity *emulatorIdentity = [[FLIRIdentity alloc] initWithEmulatorType:cameraType];
|
|
660
|
-
if (emulatorIdentity) {
|
|
661
|
-
self.identityMap[[emulatorIdentity deviceId]] = emulatorIdentity;
|
|
662
|
-
|
|
663
|
-
[self performConnectionWithIdentity:emulatorIdentity completion:^(BOOL success, NSError *error) {
|
|
664
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
665
|
-
if (success) {
|
|
666
|
-
resolve(@(YES));
|
|
667
|
-
} else {
|
|
668
|
-
reject(@"ERR_EMULATOR_FAILED", error.localizedDescription ?: @"Emulator start failed", error);
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
}];
|
|
672
|
-
} else {
|
|
673
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
674
|
-
reject(@"ERR_EMULATOR_INIT", @"Failed to create emulator identity", nil);
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
#else
|
|
679
|
-
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
680
|
-
#endif
|
|
374
|
+
RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
|
|
375
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
376
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
377
|
+
BOOL v = flir_isPreferSdkRotation();
|
|
378
|
+
resolve(@(v));
|
|
379
|
+
});
|
|
681
380
|
}
|
|
682
381
|
|
|
683
|
-
#pragma mark -
|
|
382
|
+
#pragma mark - FlirManagerDelegate
|
|
684
383
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
#else
|
|
692
|
-
@"initialized": @(NO),
|
|
693
|
-
@"message": @"SDK not available - built without FLIR"
|
|
694
|
-
#endif
|
|
695
|
-
};
|
|
696
|
-
resolve(result);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
RCT_EXPORT_METHOD(getDebugInfo:(RCTPromiseResolveBlock)resolve
|
|
700
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
701
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
702
|
-
NSDictionary *info = @{
|
|
703
|
-
#if FLIR_SDK_AVAILABLE
|
|
704
|
-
@"sdkAvailable": @(YES),
|
|
705
|
-
#else
|
|
706
|
-
@"sdkAvailable": @(NO),
|
|
707
|
-
#endif
|
|
708
|
-
@"arch": @"arm64",
|
|
709
|
-
@"discoveredDeviceCount": @(self.discoveredDevices.count),
|
|
710
|
-
@"isConnected": @(self.isConnected),
|
|
711
|
-
@"isStreaming": @(self.isStreaming),
|
|
712
|
-
@"connectedDevice": self.connectedDeviceName ?: @"None"
|
|
713
|
-
};
|
|
714
|
-
resolve(info);
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
RCT_EXPORT_METHOD(getLatestFramePath:(RCTPromiseResolveBlock)resolve
|
|
719
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
720
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
721
|
-
UIImage *image = [FlirState shared].latestImage;
|
|
722
|
-
if (!image) {
|
|
723
|
-
resolve([NSNull null]);
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
NSData *jpegData = UIImageJPEGRepresentation(image, 0.9);
|
|
728
|
-
if (!jpegData) {
|
|
729
|
-
resolve([NSNull null]);
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:
|
|
734
|
-
[NSString stringWithFormat:@"flir_frame_%lld.jpg", (long long)[[NSDate date] timeIntervalSince1970] * 1000]];
|
|
735
|
-
[jpegData writeToFile:tempPath atomically:YES];
|
|
736
|
-
resolve(tempPath);
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
RCT_EXPORT_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
|
|
741
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
742
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
743
|
-
#if FLIR_SDK_AVAILABLE
|
|
744
|
-
int level = flir_getBatteryLevel();
|
|
745
|
-
resolve(@(level));
|
|
746
|
-
#else
|
|
747
|
-
resolve(@(-1));
|
|
748
|
-
#endif
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
RCT_EXPORT_METHOD(isBatteryCharging:(RCTPromiseResolveBlock)resolve
|
|
753
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
754
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
755
|
-
#if FLIR_SDK_AVAILABLE
|
|
756
|
-
BOOL ch = flir_isBatteryCharging();
|
|
757
|
-
resolve(@(ch));
|
|
758
|
-
#else
|
|
759
|
-
resolve(@(NO));
|
|
760
|
-
#endif
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
RCT_EXPORT_METHOD(setPreferSdkRotation:(BOOL)prefer
|
|
765
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
766
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
767
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
768
|
-
@try {
|
|
769
|
-
flir_setPreferSdkRotation(prefer);
|
|
770
|
-
resolve(@(YES));
|
|
771
|
-
} @catch (NSException *ex) {
|
|
772
|
-
reject(@"ERR_FLIR_SET_ROTATION_PREF", ex.reason, nil);
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
|
|
778
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
779
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
780
|
-
BOOL v = flir_isPreferSdkRotation();
|
|
781
|
-
resolve(@(v));
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
#pragma mark - Helper Methods
|
|
786
|
-
|
|
787
|
-
- (void)emitDeviceConnected {
|
|
788
|
-
[self emitStateChange:@"connected"];
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
- (void)emitStateChange:(NSString *)state {
|
|
792
|
-
BOOL isEmu = NO;
|
|
793
|
-
#if FLIR_SDK_AVAILABLE
|
|
794
|
-
if (self.connectedIdentity) {
|
|
795
|
-
isEmu = ([self.connectedIdentity communicationInterface] == FLIRCommunicationInterfaceEmulator);
|
|
796
|
-
} else {
|
|
797
|
-
isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
|
|
384
|
+
- (void)onDevicesFound:(NSArray *)devices {
|
|
385
|
+
NSMutableArray *arr = [NSMutableArray new];
|
|
386
|
+
for (id d in devices) {
|
|
387
|
+
if ([d respondsToSelector:sel_registerName("toDictionary")]) {
|
|
388
|
+
[arr addObject:((NSDictionary * (*)(id, SEL))
|
|
389
|
+
objc_msgSend)(d, sel_registerName("toDictionary"))];
|
|
798
390
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
self.
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
[self.discoveredDevices addObject:deviceInfo];
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// Emit devices found event
|
|
868
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
869
|
-
NSDictionary *body = @{
|
|
870
|
-
@"devices": self.discoveredDevices,
|
|
871
|
-
@"count": @(self.discoveredDevices.count)
|
|
872
|
-
};
|
|
873
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:body];
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
- (void)cameraLost:(FLIRIdentity *)cameraIdentity {
|
|
878
|
-
NSString *deviceId = [cameraIdentity deviceId];
|
|
879
|
-
RCTLogInfo(@"[FlirModule] Camera lost: %@", deviceId);
|
|
880
|
-
|
|
881
|
-
[self.identityMap removeObjectForKey:deviceId];
|
|
882
|
-
|
|
883
|
-
NSMutableArray *toRemove = [NSMutableArray new];
|
|
884
|
-
for (NSDictionary *device in self.discoveredDevices) {
|
|
885
|
-
if ([device[@"id"] isEqualToString:deviceId]) {
|
|
886
|
-
[toRemove addObject:device];
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
[self.discoveredDevices removeObjectsInArray:toRemove];
|
|
890
|
-
|
|
891
|
-
// If this was our connected device, handle disconnect
|
|
892
|
-
if ([self.connectedDeviceId isEqualToString:deviceId]) {
|
|
893
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
894
|
-
[self stopStreamInternal];
|
|
895
|
-
self.camera = nil;
|
|
896
|
-
self.connectedIdentity = nil;
|
|
897
|
-
self.connectedDeviceId = nil;
|
|
898
|
-
self.connectedDeviceName = nil;
|
|
899
|
-
self.isConnected = NO;
|
|
900
|
-
self.isStreaming = NO;
|
|
901
|
-
|
|
902
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
903
|
-
[self emitStateChange:@"disconnected"];
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
908
|
-
NSDictionary *body = @{
|
|
909
|
-
@"devices": self.discoveredDevices,
|
|
910
|
-
@"count": @(self.discoveredDevices.count)
|
|
911
|
-
};
|
|
912
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:body];
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
- (void)discoveryError:(NSString *)error netServiceError:(int)nsnetserviceserror on:(FLIRCommunicationInterface)iface {
|
|
917
|
-
// Network discovery failures are expected when Local Network permission is missing/denied.
|
|
918
|
-
// Do not surface those as fatal errors; keep USB/BLE discovery running.
|
|
919
|
-
if ((iface & FLIRCommunicationInterfaceNetwork) == FLIRCommunicationInterfaceNetwork) {
|
|
920
|
-
RCTLogInfo(@"[FlirModule] Network discovery error (suppressed): %@ (%d)", error, nsnetserviceserror);
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
RCTLogError(@"[FlirModule] Discovery error: %@ (%d)", error, nsnetserviceserror);
|
|
925
|
-
|
|
926
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
927
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
928
|
-
@"error": error ?: @"Unknown discovery error",
|
|
929
|
-
@"type": @"discovery"
|
|
930
|
-
}];
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
- (void)discoveryFinished:(FLIRCommunicationInterface)iface {
|
|
935
|
-
RCTLogInfo(@"[FlirModule] Discovery finished");
|
|
936
|
-
self.isScanning = NO;
|
|
937
|
-
}
|
|
938
|
-
#endif
|
|
939
|
-
|
|
940
|
-
#pragma mark - FLIRDataReceivedDelegate
|
|
941
|
-
|
|
942
|
-
#if FLIR_SDK_AVAILABLE
|
|
943
|
-
- (void)onDisconnected:(FLIRCamera *)camera withError:(NSError *)error {
|
|
944
|
-
RCTLogInfo(@"[FlirModule] Camera disconnected: %@", error.localizedDescription);
|
|
945
|
-
|
|
946
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
947
|
-
self.isConnected = NO;
|
|
948
|
-
self.isStreaming = NO;
|
|
949
|
-
self.connectedDeviceId = nil;
|
|
950
|
-
self.connectedDeviceName = nil;
|
|
951
|
-
|
|
952
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
953
|
-
[self emitStateChange:@"disconnected"];
|
|
954
|
-
});
|
|
391
|
+
}
|
|
392
|
+
[[FlirEventEmitter shared]
|
|
393
|
+
sendDeviceEvent:@"FlirDevicesFound"
|
|
394
|
+
body:@{@"devices" : arr, @"count" : @(arr.count)}];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
- (void)onDeviceConnected:(id)device {
|
|
398
|
+
if (self.connectResolve) {
|
|
399
|
+
self.connectResolve(@(YES));
|
|
400
|
+
self.connectResolve = nil;
|
|
401
|
+
self.connectReject = nil;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// device is FlirDeviceInfo
|
|
405
|
+
NSMutableDictionary *body = [NSMutableDictionary new];
|
|
406
|
+
if ([device respondsToSelector:sel_registerName("toDictionary")]) {
|
|
407
|
+
[body
|
|
408
|
+
addEntriesFromDictionary:((NSDictionary * (*)(id, SEL)) objc_msgSend)(
|
|
409
|
+
device, sel_registerName("toDictionary"))];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceConnected" body:body];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
- (void)onDeviceDisconnected {
|
|
416
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected"
|
|
417
|
+
body:@{}];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
- (void)onFrameReceived:(UIImage *)image
|
|
421
|
+
width:(NSInteger)width
|
|
422
|
+
height:(NSInteger)height {
|
|
423
|
+
// Also emit event for JS consumers (though slow, some might use it)
|
|
424
|
+
[[FlirEventEmitter shared]
|
|
425
|
+
sendDeviceEvent:@"FlirFrameReceived"
|
|
426
|
+
body:@{
|
|
427
|
+
@"width" : @(width),
|
|
428
|
+
@"height" : @(height),
|
|
429
|
+
@"timestamp" :
|
|
430
|
+
@([[NSDate date] timeIntervalSince1970] * 1000)
|
|
431
|
+
}];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
- (void)onError:(NSString *)message {
|
|
435
|
+
if (self.connectReject) {
|
|
436
|
+
self.connectReject(@"ERR_FLIR", message, nil);
|
|
437
|
+
self.connectResolve = nil;
|
|
438
|
+
self.connectReject = nil;
|
|
439
|
+
}
|
|
440
|
+
[[FlirEventEmitter shared]
|
|
441
|
+
sendDeviceEvent:@"FlirError"
|
|
442
|
+
body:@{@"error" : message ?: @"Unknown error"}];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
- (void)onStateChanged:(NSString *)state
|
|
446
|
+
isConnected:(BOOL)isConnected
|
|
447
|
+
isStreaming:(BOOL)isStreaming
|
|
448
|
+
isEmulator:(BOOL)isEmulator {
|
|
449
|
+
NSDictionary *body = @{
|
|
450
|
+
@"state" : state,
|
|
451
|
+
@"isConnected" : @(isConnected),
|
|
452
|
+
@"isStreaming" : @(isStreaming),
|
|
453
|
+
@"isEmulator" : @(isEmulator)
|
|
454
|
+
};
|
|
455
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirStateChanged" body:body];
|
|
955
456
|
}
|
|
956
|
-
#endif
|
|
957
|
-
|
|
958
|
-
#pragma mark - FLIRStreamDelegate
|
|
959
|
-
|
|
960
|
-
#if FLIR_SDK_AVAILABLE
|
|
961
|
-
- (void)onError:(NSError *)error {
|
|
962
|
-
RCTLogError(@"[FlirModule] Stream error: %@", error.localizedDescription);
|
|
963
|
-
|
|
964
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
965
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
966
|
-
@"error": error.localizedDescription ?: @"Stream error",
|
|
967
|
-
@"type": @"stream"
|
|
968
|
-
}];
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
- (void)onImageReceived {
|
|
973
|
-
if (!self.streamer) return;
|
|
974
|
-
|
|
975
|
-
NSError *error = nil;
|
|
976
|
-
if ([self.streamer update:&error]) {
|
|
977
|
-
UIImage *image = [self.streamer getImage];
|
|
978
|
-
if (image) {
|
|
979
|
-
// Update shared state
|
|
980
|
-
[[FlirState shared] updateFrame:image];
|
|
981
|
-
|
|
982
|
-
// Get temperature from thermal image if available
|
|
983
|
-
[self.streamer withThermalImage:^(FLIRThermalImage *thermalImage) {
|
|
984
|
-
// Some SDK versions call getImageStatistics(), try both selectors
|
|
985
|
-
FLIRImageStatistics *stats = nil;
|
|
986
|
-
if ([thermalImage respondsToSelector:sel_registerName("getImageStatistics")]) {
|
|
987
|
-
stats = ((id (*)(id, SEL))objc_msgSend)((id)thermalImage, sel_registerName("getImageStatistics"));
|
|
988
|
-
} else if ([thermalImage respondsToSelector:sel_registerName("getStatistics")]) {
|
|
989
|
-
stats = ((id (*)(id, SEL))objc_msgSend)((id)thermalImage, sel_registerName("getStatistics"));
|
|
990
|
-
}
|
|
991
|
-
if (stats) {
|
|
992
|
-
self.lastTemperature = [[stats getMax] value];
|
|
993
|
-
[FlirState shared].lastTemperature = self.lastTemperature;
|
|
994
|
-
}
|
|
995
|
-
}];
|
|
996
|
-
|
|
997
|
-
// Emit frame received event (rate-limited by RN event queue)
|
|
998
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
999
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirFrameReceived" body:@{
|
|
1000
|
-
@"width": @(image.size.width),
|
|
1001
|
-
@"height": @(image.size.height),
|
|
1002
|
-
@"timestamp": @([[NSDate date] timeIntervalSince1970] * 1000)
|
|
1003
|
-
}];
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
} else {
|
|
1007
|
-
RCTLogError(@"[FlirModule] Streamer update error: %@", error.localizedDescription);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
#endif
|
|
1011
457
|
|
|
1012
458
|
@end
|