ilabs-flir 2.0.4 → 2.0.5
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/Flir.podspec +139 -139
- package/README.md +1066 -1066
- package/android/Flir/build.gradle.kts +72 -72
- package/android/Flir/src/main/AndroidManifest.xml +45 -45
- package/android/Flir/src/main/java/flir/android/FlirCommands.java +136 -136
- package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +476 -476
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +257 -257
- package/android/Flir/src/main/java/flir/android/FlirPackage.kt +18 -18
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +74 -74
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +583 -583
- package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
- package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
- package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
- package/app.plugin.js +381 -381
- package/expo-module.config.json +5 -5
- package/ios/Flir/src/Flir-Bridging-Header.h +34 -34
- package/ios/Flir/src/FlirEventEmitter.h +25 -25
- package/ios/Flir/src/FlirEventEmitter.m +63 -63
- package/ios/Flir/src/FlirManager.swift +599 -599
- package/ios/Flir/src/FlirModule.h +17 -17
- package/ios/Flir/src/FlirModule.m +713 -713
- package/ios/Flir/src/FlirPreviewView.h +13 -13
- package/ios/Flir/src/FlirPreviewView.m +171 -171
- package/ios/Flir/src/FlirState.h +68 -68
- package/ios/Flir/src/FlirState.m +135 -135
- package/ios/Flir/src/FlirViewManager.h +16 -16
- package/ios/Flir/src/FlirViewManager.m +27 -27
- package/package.json +72 -71
- package/react-native.config.js +14 -14
- package/scripts/fetch-binaries.js +47 -5
- package/sdk-manifest.json +50 -50
- package/src/index.d.ts +63 -63
- package/src/index.js +7 -7
- package/src/index.ts +6 -6
|
@@ -1,713 +1,713 @@
|
|
|
1
|
-
//
|
|
2
|
-
// FlirModule.m
|
|
3
|
-
// Flir
|
|
4
|
-
//
|
|
5
|
-
// React Native bridge module for FLIR thermal camera SDK
|
|
6
|
-
// Provides discovery, connection, and streaming functionality
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
#import "FlirModule.h"
|
|
10
|
-
#import "FlirEventEmitter.h"
|
|
11
|
-
#import "FlirState.h"
|
|
12
|
-
#import <React/RCTLog.h>
|
|
13
|
-
#import <React/RCTBridge.h>
|
|
14
|
-
|
|
15
|
-
#if __has_include(<ThermalSDK/ThermalSDK.h>)
|
|
16
|
-
#define FLIR_SDK_AVAILABLE 1
|
|
17
|
-
#import <ThermalSDK/ThermalSDK.h>
|
|
18
|
-
#else
|
|
19
|
-
#define FLIR_SDK_AVAILABLE 0
|
|
20
|
-
#endif
|
|
21
|
-
|
|
22
|
-
// Forward declare Swift class
|
|
23
|
-
@class FlirManager;
|
|
24
|
-
|
|
25
|
-
@interface FlirModule()
|
|
26
|
-
#if FLIR_SDK_AVAILABLE
|
|
27
|
-
<FLIRDiscoveryEventDelegate, FLIRDataReceivedDelegate, FLIRStreamDelegate>
|
|
28
|
-
#endif
|
|
29
|
-
|
|
30
|
-
#if FLIR_SDK_AVAILABLE
|
|
31
|
-
@property (nonatomic, strong) FLIRDiscovery *discovery;
|
|
32
|
-
@property (nonatomic, strong) FLIRCamera *camera;
|
|
33
|
-
@property (nonatomic, strong) FLIRStream *stream;
|
|
34
|
-
@property (nonatomic, strong) FLIRThermalStreamer *streamer;
|
|
35
|
-
@property (nonatomic, strong) FLIRIdentity *connectedIdentity;
|
|
36
|
-
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLIRIdentity *> *identityMap;
|
|
37
|
-
#endif
|
|
38
|
-
|
|
39
|
-
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *discoveredDevices;
|
|
40
|
-
@property (nonatomic, assign) BOOL isScanning;
|
|
41
|
-
@property (nonatomic, assign) BOOL isConnected;
|
|
42
|
-
@property (nonatomic, assign) BOOL isStreaming;
|
|
43
|
-
@property (nonatomic, copy) NSString *connectedDeviceId;
|
|
44
|
-
@property (nonatomic, copy) NSString *connectedDeviceName;
|
|
45
|
-
@property (nonatomic, assign) double lastTemperature;
|
|
46
|
-
@end
|
|
47
|
-
|
|
48
|
-
@implementation FlirModule
|
|
49
|
-
|
|
50
|
-
RCT_EXPORT_MODULE(FlirModule);
|
|
51
|
-
|
|
52
|
-
+ (BOOL)requiresMainQueueSetup {
|
|
53
|
-
return YES;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
- (instancetype)init {
|
|
57
|
-
if (self = [super init]) {
|
|
58
|
-
#if FLIR_SDK_AVAILABLE
|
|
59
|
-
_identityMap = [NSMutableDictionary new];
|
|
60
|
-
#endif
|
|
61
|
-
_discoveredDevices = [NSMutableArray new];
|
|
62
|
-
_isScanning = NO;
|
|
63
|
-
_isConnected = NO;
|
|
64
|
-
_isStreaming = NO;
|
|
65
|
-
_lastTemperature = NAN;
|
|
66
|
-
}
|
|
67
|
-
return self;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
#pragma mark - Event Emitter Support
|
|
71
|
-
|
|
72
|
-
- (NSArray<NSString *> *)supportedEvents {
|
|
73
|
-
return @[
|
|
74
|
-
@"FlirDeviceConnected",
|
|
75
|
-
@"FlirDeviceDisconnected",
|
|
76
|
-
@"FlirDevicesFound",
|
|
77
|
-
@"FlirFrameReceived",
|
|
78
|
-
@"FlirError",
|
|
79
|
-
@"FlirStateChanged"
|
|
80
|
-
];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {
|
|
84
|
-
// Required for RCTEventEmitter
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
|
|
88
|
-
// Required for RCTEventEmitter
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#pragma mark - Discovery Methods
|
|
92
|
-
|
|
93
|
-
RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
94
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
95
|
-
#if FLIR_SDK_AVAILABLE
|
|
96
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
97
|
-
if (self.isScanning) {
|
|
98
|
-
RCTLogInfo(@"[FlirModule] Already scanning");
|
|
99
|
-
resolve(@(YES));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
self.isScanning = YES;
|
|
104
|
-
[self.discoveredDevices removeAllObjects];
|
|
105
|
-
[self.identityMap removeAllObjects];
|
|
106
|
-
|
|
107
|
-
if (!self.discovery) {
|
|
108
|
-
self.discovery = [[FLIRDiscovery alloc] init];
|
|
109
|
-
self.discovery.delegate = self;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Start discovery on all available interfaces
|
|
113
|
-
FLIRCommunicationInterface interfaces = FLIRCommunicationInterfaceLightning |
|
|
114
|
-
FLIRCommunicationInterfaceNetwork |
|
|
115
|
-
FLIRCommunicationInterfaceFlirOneWireless |
|
|
116
|
-
FLIRCommunicationInterfaceEmulator;
|
|
117
|
-
[self.discovery start:interfaces];
|
|
118
|
-
|
|
119
|
-
[self emitStateChange:@"discovering"];
|
|
120
|
-
RCTLogInfo(@"[FlirModule] Discovery started");
|
|
121
|
-
resolve(@(YES));
|
|
122
|
-
});
|
|
123
|
-
#else
|
|
124
|
-
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
125
|
-
#endif
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
RCT_EXPORT_METHOD(stopDiscovery:(RCTPromiseResolveBlock)resolve
|
|
129
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
130
|
-
#if FLIR_SDK_AVAILABLE
|
|
131
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
132
|
-
[self.discovery stop];
|
|
133
|
-
self.isScanning = NO;
|
|
134
|
-
RCTLogInfo(@"[FlirModule] Discovery stopped");
|
|
135
|
-
resolve(@(YES));
|
|
136
|
-
});
|
|
137
|
-
#else
|
|
138
|
-
resolve(@(YES));
|
|
139
|
-
#endif
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
RCT_EXPORT_METHOD(getDiscoveredDevices:(RCTPromiseResolveBlock)resolve
|
|
143
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
144
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
145
|
-
resolve(self.discoveredDevices);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
#pragma mark - Connection Methods
|
|
150
|
-
|
|
151
|
-
RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
152
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
153
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
154
|
-
#if FLIR_SDK_AVAILABLE
|
|
155
|
-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
156
|
-
FLIRIdentity *identity = self.identityMap[deviceId];
|
|
157
|
-
if (!identity) {
|
|
158
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
159
|
-
reject(@"ERR_DEVICE_NOT_FOUND", [NSString stringWithFormat:@"Device not found: %@", deviceId], nil);
|
|
160
|
-
});
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
[self performConnectionWithIdentity:identity completion:^(BOOL success, NSError *error) {
|
|
165
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
166
|
-
if (success) {
|
|
167
|
-
resolve(@(YES));
|
|
168
|
-
} else {
|
|
169
|
-
reject(@"ERR_CONNECTION_FAILED", error.localizedDescription ?: @"Connection failed", error);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}];
|
|
173
|
-
});
|
|
174
|
-
#else
|
|
175
|
-
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
176
|
-
#endif
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
#if FLIR_SDK_AVAILABLE
|
|
180
|
-
- (void)performConnectionWithIdentity:(FLIRIdentity *)identity completion:(void(^)(BOOL success, NSError *error))completion {
|
|
181
|
-
if (!self.camera) {
|
|
182
|
-
self.camera = [[FLIRCamera alloc] init];
|
|
183
|
-
self.camera.delegate = self;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
NSError *error = nil;
|
|
187
|
-
|
|
188
|
-
// Handle authentication for generic cameras
|
|
189
|
-
if ([identity cameraType] == FLIRCameraType_generic) {
|
|
190
|
-
NSString *certName = [self getCertificateName];
|
|
191
|
-
FLIRAuthenticationStatus status = pending;
|
|
192
|
-
while (status == pending) {
|
|
193
|
-
status = [self.camera authenticate:identity trustedConnectionName:certName];
|
|
194
|
-
if (status == pending) {
|
|
195
|
-
RCTLogInfo(@"[FlirModule] Waiting for camera authentication...");
|
|
196
|
-
[NSThread sleepForTimeInterval:1.0];
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
BOOL connected = [self.camera connect:identity error:&error];
|
|
202
|
-
|
|
203
|
-
if (connected) {
|
|
204
|
-
self.connectedIdentity = identity;
|
|
205
|
-
self.connectedDeviceId = [identity deviceId];
|
|
206
|
-
self.connectedDeviceName = [identity deviceId];
|
|
207
|
-
self.isConnected = YES;
|
|
208
|
-
|
|
209
|
-
RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
|
|
210
|
-
|
|
211
|
-
// Get available streams
|
|
212
|
-
NSArray<FLIRStream *> *streams = [self.camera getStreams];
|
|
213
|
-
if (streams.count > 0) {
|
|
214
|
-
RCTLogInfo(@"[FlirModule] Found %lu streams", (unsigned long)streams.count);
|
|
215
|
-
// Auto-start first stream
|
|
216
|
-
[self startStreamInternal:streams[0]];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
220
|
-
[self emitDeviceConnected];
|
|
221
|
-
[self emitStateChange:@"connected"];
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (completion) completion(YES, nil);
|
|
225
|
-
} else {
|
|
226
|
-
RCTLogError(@"[FlirModule] Connection failed: %@", error.localizedDescription);
|
|
227
|
-
if (completion) completion(NO, error);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
- (NSString *)getCertificateName {
|
|
232
|
-
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"com.flir.app";
|
|
233
|
-
NSString *key = [NSString stringWithFormat:@"%@-cert-name", bundleID];
|
|
234
|
-
|
|
235
|
-
NSString *existing = [[NSUserDefaults standardUserDefaults] stringForKey:key];
|
|
236
|
-
if (existing) {
|
|
237
|
-
return existing;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
NSString *newName = [[NSUUID UUID] UUIDString];
|
|
241
|
-
[[NSUserDefaults standardUserDefaults] setObject:newName forKey:key];
|
|
242
|
-
return newName;
|
|
243
|
-
}
|
|
244
|
-
#endif
|
|
245
|
-
|
|
246
|
-
RCT_EXPORT_METHOD(disconnect:(RCTPromiseResolveBlock)resolve
|
|
247
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
248
|
-
#if FLIR_SDK_AVAILABLE
|
|
249
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
250
|
-
[self stopStreamInternal];
|
|
251
|
-
[self.camera disconnect];
|
|
252
|
-
self.camera = nil;
|
|
253
|
-
self.connectedIdentity = nil;
|
|
254
|
-
self.connectedDeviceId = nil;
|
|
255
|
-
self.connectedDeviceName = nil;
|
|
256
|
-
self.isConnected = NO;
|
|
257
|
-
self.isStreaming = NO;
|
|
258
|
-
|
|
259
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
260
|
-
[self emitStateChange:@"disconnected"];
|
|
261
|
-
|
|
262
|
-
RCTLogInfo(@"[FlirModule] Disconnected");
|
|
263
|
-
resolve(@(YES));
|
|
264
|
-
});
|
|
265
|
-
#else
|
|
266
|
-
resolve(@(YES));
|
|
267
|
-
#endif
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
RCT_EXPORT_METHOD(stopFlir:(RCTPromiseResolveBlock)resolve
|
|
271
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
272
|
-
#if FLIR_SDK_AVAILABLE
|
|
273
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
274
|
-
[self stopStreamInternal];
|
|
275
|
-
[self.camera disconnect];
|
|
276
|
-
[self.discovery stop];
|
|
277
|
-
|
|
278
|
-
self.camera = nil;
|
|
279
|
-
self.connectedIdentity = nil;
|
|
280
|
-
self.connectedDeviceId = nil;
|
|
281
|
-
self.connectedDeviceName = nil;
|
|
282
|
-
self.isConnected = NO;
|
|
283
|
-
self.isStreaming = NO;
|
|
284
|
-
self.isScanning = NO;
|
|
285
|
-
|
|
286
|
-
[self emitStateChange:@"stopped"];
|
|
287
|
-
RCTLogInfo(@"[FlirModule] Stopped");
|
|
288
|
-
resolve(@(YES));
|
|
289
|
-
});
|
|
290
|
-
#else
|
|
291
|
-
resolve(@(YES));
|
|
292
|
-
#endif
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
#pragma mark - Streaming
|
|
296
|
-
|
|
297
|
-
#if FLIR_SDK_AVAILABLE
|
|
298
|
-
- (void)startStreamInternal:(FLIRStream *)newStream {
|
|
299
|
-
[self stopStreamInternal];
|
|
300
|
-
|
|
301
|
-
self.stream = newStream;
|
|
302
|
-
|
|
303
|
-
if (newStream.isThermal) {
|
|
304
|
-
self.streamer = [[FLIRThermalStreamer alloc] initWithStream:newStream];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
newStream.delegate = self;
|
|
308
|
-
|
|
309
|
-
NSError *error = nil;
|
|
310
|
-
if ([newStream start:&error]) {
|
|
311
|
-
self.isStreaming = YES;
|
|
312
|
-
[self emitStateChange:@"streaming"];
|
|
313
|
-
RCTLogInfo(@"[FlirModule] Stream started (thermal: %@)", newStream.isThermal ? @"YES" : @"NO");
|
|
314
|
-
} else {
|
|
315
|
-
RCTLogError(@"[FlirModule] Stream start failed: %@", error.localizedDescription);
|
|
316
|
-
self.stream = nil;
|
|
317
|
-
self.streamer = nil;
|
|
318
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{@"error": error.localizedDescription ?: @"Stream start failed"}];
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
- (void)stopStreamInternal {
|
|
323
|
-
[self.stream stop];
|
|
324
|
-
self.stream = nil;
|
|
325
|
-
self.streamer = nil;
|
|
326
|
-
self.isStreaming = NO;
|
|
327
|
-
}
|
|
328
|
-
#endif
|
|
329
|
-
|
|
330
|
-
#pragma mark - Temperature Methods
|
|
331
|
-
|
|
332
|
-
RCT_EXPORT_METHOD(getTemperatureAt:(nonnull NSNumber *)x
|
|
333
|
-
y:(nonnull NSNumber *)y
|
|
334
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
335
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
336
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
337
|
-
double temp = [FlirState shared].lastTemperature;
|
|
338
|
-
if (isnan(temp)) {
|
|
339
|
-
temp = self.lastTemperature;
|
|
340
|
-
}
|
|
341
|
-
if (isnan(temp)) {
|
|
342
|
-
resolve([NSNull null]);
|
|
343
|
-
} else {
|
|
344
|
-
resolve(@(temp));
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
RCT_EXPORT_METHOD(getTemperatureFromColor:(NSInteger)color
|
|
350
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
351
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
352
|
-
// Placeholder: Convert ARGB color to pseudo-temperature
|
|
353
|
-
int r = (color >> 16) & 0xFF;
|
|
354
|
-
int g = (color >> 8) & 0xFF;
|
|
355
|
-
int b = color & 0xFF;
|
|
356
|
-
double lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
357
|
-
double temp = (lum / 255.0) * 400.0;
|
|
358
|
-
resolve(@(temp));
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
#pragma mark - Status Methods
|
|
362
|
-
|
|
363
|
-
RCT_EXPORT_METHOD(isEmulator:(RCTPromiseResolveBlock)resolve
|
|
364
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
365
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
366
|
-
BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"] ||
|
|
367
|
-
[self.connectedDeviceName.lowercaseString containsString:@"emulat"];
|
|
368
|
-
resolve(@(isEmu));
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
RCT_EXPORT_METHOD(isDeviceConnected:(RCTPromiseResolveBlock)resolve
|
|
373
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
374
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
375
|
-
resolve(@(self.isConnected));
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
RCT_EXPORT_METHOD(getConnectedDeviceInfo:(RCTPromiseResolveBlock)resolve
|
|
380
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
381
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
382
|
-
resolve(self.connectedDeviceName ?: @"Not connected");
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
RCT_EXPORT_METHOD(isSDKDownloaded:(RCTPromiseResolveBlock)resolve
|
|
387
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
388
|
-
#if FLIR_SDK_AVAILABLE
|
|
389
|
-
resolve(@(YES));
|
|
390
|
-
#else
|
|
391
|
-
resolve(@(NO));
|
|
392
|
-
#endif
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
RCT_EXPORT_METHOD(getSDKStatus:(RCTPromiseResolveBlock)resolve
|
|
396
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
397
|
-
NSDictionary *status = @{
|
|
398
|
-
#if FLIR_SDK_AVAILABLE
|
|
399
|
-
@"available": @(YES),
|
|
400
|
-
#else
|
|
401
|
-
@"available": @(NO),
|
|
402
|
-
#endif
|
|
403
|
-
@"arch": @"arm64",
|
|
404
|
-
@"platform": @"iOS"
|
|
405
|
-
};
|
|
406
|
-
resolve(status);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
#pragma mark - Emulator
|
|
410
|
-
|
|
411
|
-
RCT_EXPORT_METHOD(startEmulator:(NSString *)emulatorType
|
|
412
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
413
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
414
|
-
#if FLIR_SDK_AVAILABLE
|
|
415
|
-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
416
|
-
FLIRCameraType cameraType = FLIRCameraType_flirOne;
|
|
417
|
-
if ([emulatorType.lowercaseString containsString:@"edge"]) {
|
|
418
|
-
cameraType = FLIRCameraType_flirOneEdge;
|
|
419
|
-
} else if ([emulatorType.lowercaseString containsString:@"pro"]) {
|
|
420
|
-
cameraType = FLIRCameraType_flirOneEdgePro;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
FLIRIdentity *emulatorIdentity = [[FLIRIdentity alloc] initWithEmulatorType:cameraType];
|
|
424
|
-
if (emulatorIdentity) {
|
|
425
|
-
self.identityMap[[emulatorIdentity deviceId]] = emulatorIdentity;
|
|
426
|
-
|
|
427
|
-
[self performConnectionWithIdentity:emulatorIdentity completion:^(BOOL success, NSError *error) {
|
|
428
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
429
|
-
if (success) {
|
|
430
|
-
resolve(@(YES));
|
|
431
|
-
} else {
|
|
432
|
-
reject(@"ERR_EMULATOR_FAILED", error.localizedDescription ?: @"Emulator start failed", error);
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
}];
|
|
436
|
-
} else {
|
|
437
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
438
|
-
reject(@"ERR_EMULATOR_INIT", @"Failed to create emulator identity", nil);
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
#else
|
|
443
|
-
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
444
|
-
#endif
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
#pragma mark - Debug
|
|
448
|
-
|
|
449
|
-
RCT_EXPORT_METHOD(initializeSDK:(RCTPromiseResolveBlock)resolve
|
|
450
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
451
|
-
NSDictionary *result = @{
|
|
452
|
-
#if FLIR_SDK_AVAILABLE
|
|
453
|
-
@"initialized": @(YES),
|
|
454
|
-
@"message": @"SDK initialized successfully"
|
|
455
|
-
#else
|
|
456
|
-
@"initialized": @(NO),
|
|
457
|
-
@"message": @"SDK not available - built without FLIR"
|
|
458
|
-
#endif
|
|
459
|
-
};
|
|
460
|
-
resolve(result);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
RCT_EXPORT_METHOD(getDebugInfo:(RCTPromiseResolveBlock)resolve
|
|
464
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
465
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
466
|
-
NSDictionary *info = @{
|
|
467
|
-
#if FLIR_SDK_AVAILABLE
|
|
468
|
-
@"sdkAvailable": @(YES),
|
|
469
|
-
#else
|
|
470
|
-
@"sdkAvailable": @(NO),
|
|
471
|
-
#endif
|
|
472
|
-
@"arch": @"arm64",
|
|
473
|
-
@"discoveredDeviceCount": @(self.discoveredDevices.count),
|
|
474
|
-
@"isConnected": @(self.isConnected),
|
|
475
|
-
@"isStreaming": @(self.isStreaming),
|
|
476
|
-
@"connectedDevice": self.connectedDeviceName ?: @"None"
|
|
477
|
-
};
|
|
478
|
-
resolve(info);
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
RCT_EXPORT_METHOD(getLatestFramePath:(RCTPromiseResolveBlock)resolve
|
|
483
|
-
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
484
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
485
|
-
UIImage *image = [FlirState shared].latestImage;
|
|
486
|
-
if (!image) {
|
|
487
|
-
resolve([NSNull null]);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
NSData *jpegData = UIImageJPEGRepresentation(image, 0.9);
|
|
492
|
-
if (!jpegData) {
|
|
493
|
-
resolve([NSNull null]);
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:
|
|
498
|
-
[NSString stringWithFormat:@"flir_frame_%lld.jpg", (long long)[[NSDate date] timeIntervalSince1970] * 1000]];
|
|
499
|
-
[jpegData writeToFile:tempPath atomically:YES];
|
|
500
|
-
resolve(tempPath);
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
#pragma mark - Helper Methods
|
|
505
|
-
|
|
506
|
-
- (void)emitDeviceConnected {
|
|
507
|
-
BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
|
|
508
|
-
|
|
509
|
-
NSDictionary *body = @{
|
|
510
|
-
@"identity": @{
|
|
511
|
-
@"deviceId": self.connectedDeviceId ?: @"Unknown",
|
|
512
|
-
@"isEmulator": @(isEmu)
|
|
513
|
-
},
|
|
514
|
-
@"deviceType": isEmu ? @"emulator" : @"device",
|
|
515
|
-
@"isEmulator": @(isEmu),
|
|
516
|
-
@"state": @"connected"
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceConnected" body:body];
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
- (void)emitStateChange:(NSString *)state {
|
|
523
|
-
BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
|
|
524
|
-
|
|
525
|
-
NSDictionary *body = @{
|
|
526
|
-
@"state": state,
|
|
527
|
-
@"isConnected": @(self.isConnected),
|
|
528
|
-
@"isStreaming": @(self.isStreaming),
|
|
529
|
-
@"isEmulator": @(isEmu),
|
|
530
|
-
@"deviceName": self.connectedDeviceName ?: @"",
|
|
531
|
-
@"deviceId": self.connectedDeviceId ?: @""
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirStateChanged" body:body];
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
#if FLIR_SDK_AVAILABLE
|
|
538
|
-
- (NSString *)communicationInterfaceName:(FLIRCommunicationInterface)iface {
|
|
539
|
-
if (iface & FLIRCommunicationInterfaceLightning) return @"LIGHTNING";
|
|
540
|
-
if (iface & FLIRCommunicationInterfaceNetwork) return @"NETWORK";
|
|
541
|
-
if (iface & FLIRCommunicationInterfaceFlirOneWireless) return @"WIRELESS";
|
|
542
|
-
if (iface & FLIRCommunicationInterfaceEmulator) return @"EMULATOR";
|
|
543
|
-
if (iface & FLIRCommunicationInterfaceUSB) return @"USB";
|
|
544
|
-
return @"UNKNOWN";
|
|
545
|
-
}
|
|
546
|
-
#endif
|
|
547
|
-
|
|
548
|
-
#pragma mark - FLIRDiscoveryEventDelegate
|
|
549
|
-
|
|
550
|
-
#if FLIR_SDK_AVAILABLE
|
|
551
|
-
- (void)cameraDiscovered:(FLIRDiscoveredCamera *)discoveredCamera {
|
|
552
|
-
FLIRIdentity *identity = discoveredCamera.identity;
|
|
553
|
-
NSString *deviceId = [identity deviceId];
|
|
554
|
-
|
|
555
|
-
RCTLogInfo(@"[FlirModule] Camera discovered: %@", deviceId);
|
|
556
|
-
|
|
557
|
-
// Store identity
|
|
558
|
-
self.identityMap[deviceId] = identity;
|
|
559
|
-
|
|
560
|
-
// Create device info
|
|
561
|
-
NSDictionary *deviceInfo = @{
|
|
562
|
-
@"id": deviceId,
|
|
563
|
-
@"name": discoveredCamera.displayName ?: deviceId,
|
|
564
|
-
@"communicationType": [self communicationInterfaceName:[identity communicationInterface]],
|
|
565
|
-
@"isEmulator": @([identity communicationInterface] == FLIRCommunicationInterfaceEmulator)
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
// Add if not already present
|
|
569
|
-
BOOL found = NO;
|
|
570
|
-
for (NSDictionary *existing in self.discoveredDevices) {
|
|
571
|
-
if ([existing[@"id"] isEqualToString:deviceId]) {
|
|
572
|
-
found = YES;
|
|
573
|
-
break;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (!found) {
|
|
578
|
-
[self.discoveredDevices addObject:deviceInfo];
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Emit devices found event
|
|
582
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
583
|
-
NSDictionary *body = @{
|
|
584
|
-
@"devices": self.discoveredDevices,
|
|
585
|
-
@"count": @(self.discoveredDevices.count)
|
|
586
|
-
};
|
|
587
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:body];
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
- (void)cameraLost:(FLIRIdentity *)cameraIdentity {
|
|
592
|
-
NSString *deviceId = [cameraIdentity deviceId];
|
|
593
|
-
RCTLogInfo(@"[FlirModule] Camera lost: %@", deviceId);
|
|
594
|
-
|
|
595
|
-
[self.identityMap removeObjectForKey:deviceId];
|
|
596
|
-
|
|
597
|
-
NSMutableArray *toRemove = [NSMutableArray new];
|
|
598
|
-
for (NSDictionary *device in self.discoveredDevices) {
|
|
599
|
-
if ([device[@"id"] isEqualToString:deviceId]) {
|
|
600
|
-
[toRemove addObject:device];
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
[self.discoveredDevices removeObjectsInArray:toRemove];
|
|
604
|
-
|
|
605
|
-
// If this was our connected device, handle disconnect
|
|
606
|
-
if ([self.connectedDeviceId isEqualToString:deviceId]) {
|
|
607
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
608
|
-
[self stopStreamInternal];
|
|
609
|
-
self.camera = nil;
|
|
610
|
-
self.connectedIdentity = nil;
|
|
611
|
-
self.connectedDeviceId = nil;
|
|
612
|
-
self.connectedDeviceName = nil;
|
|
613
|
-
self.isConnected = NO;
|
|
614
|
-
self.isStreaming = NO;
|
|
615
|
-
|
|
616
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
617
|
-
[self emitStateChange:@"disconnected"];
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
622
|
-
NSDictionary *body = @{
|
|
623
|
-
@"devices": self.discoveredDevices,
|
|
624
|
-
@"count": @(self.discoveredDevices.count)
|
|
625
|
-
};
|
|
626
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:body];
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
- (void)discoveryError:(NSString *)error netServiceError:(int)nsnetserviceserror on:(FLIRCommunicationInterface)iface {
|
|
631
|
-
RCTLogError(@"[FlirModule] Discovery error: %@ (%d)", error, nsnetserviceserror);
|
|
632
|
-
|
|
633
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
634
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
635
|
-
@"error": error ?: @"Unknown discovery error",
|
|
636
|
-
@"type": @"discovery"
|
|
637
|
-
}];
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
- (void)discoveryFinished:(FLIRCommunicationInterface)iface {
|
|
642
|
-
RCTLogInfo(@"[FlirModule] Discovery finished");
|
|
643
|
-
self.isScanning = NO;
|
|
644
|
-
}
|
|
645
|
-
#endif
|
|
646
|
-
|
|
647
|
-
#pragma mark - FLIRDataReceivedDelegate
|
|
648
|
-
|
|
649
|
-
#if FLIR_SDK_AVAILABLE
|
|
650
|
-
- (void)onDisconnected:(FLIRCamera *)camera withError:(NSError *)error {
|
|
651
|
-
RCTLogInfo(@"[FlirModule] Camera disconnected: %@", error.localizedDescription);
|
|
652
|
-
|
|
653
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
654
|
-
self.isConnected = NO;
|
|
655
|
-
self.isStreaming = NO;
|
|
656
|
-
self.connectedDeviceId = nil;
|
|
657
|
-
self.connectedDeviceName = nil;
|
|
658
|
-
|
|
659
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
660
|
-
[self emitStateChange:@"disconnected"];
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
#endif
|
|
664
|
-
|
|
665
|
-
#pragma mark - FLIRStreamDelegate
|
|
666
|
-
|
|
667
|
-
#if FLIR_SDK_AVAILABLE
|
|
668
|
-
- (void)onError:(NSError *)error {
|
|
669
|
-
RCTLogError(@"[FlirModule] Stream error: %@", error.localizedDescription);
|
|
670
|
-
|
|
671
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
672
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
673
|
-
@"error": error.localizedDescription ?: @"Stream error",
|
|
674
|
-
@"type": @"stream"
|
|
675
|
-
}];
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
- (void)onImageReceived {
|
|
680
|
-
if (!self.streamer) return;
|
|
681
|
-
|
|
682
|
-
NSError *error = nil;
|
|
683
|
-
if ([self.streamer update:&error]) {
|
|
684
|
-
UIImage *image = [self.streamer getImage];
|
|
685
|
-
if (image) {
|
|
686
|
-
// Update shared state
|
|
687
|
-
[[FlirState shared] updateFrame:image];
|
|
688
|
-
|
|
689
|
-
// Get temperature from thermal image if available
|
|
690
|
-
[self.streamer withThermalImage:^(FLIRThermalImage *thermalImage) {
|
|
691
|
-
FLIRImageStatistics *stats = [thermalImage getStatistics];
|
|
692
|
-
if (stats) {
|
|
693
|
-
self.lastTemperature = [[stats getMax] value];
|
|
694
|
-
[FlirState shared].lastTemperature = self.lastTemperature;
|
|
695
|
-
}
|
|
696
|
-
}];
|
|
697
|
-
|
|
698
|
-
// Emit frame received event (rate-limited by RN event queue)
|
|
699
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
700
|
-
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirFrameReceived" body:@{
|
|
701
|
-
@"width": @(image.size.width),
|
|
702
|
-
@"height": @(image.size.height),
|
|
703
|
-
@"timestamp": @([[NSDate date] timeIntervalSince1970] * 1000)
|
|
704
|
-
}];
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
RCTLogError(@"[FlirModule] Streamer update error: %@", error.localizedDescription);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
#endif
|
|
712
|
-
|
|
713
|
-
@end
|
|
1
|
+
//
|
|
2
|
+
// FlirModule.m
|
|
3
|
+
// Flir
|
|
4
|
+
//
|
|
5
|
+
// React Native bridge module for FLIR thermal camera SDK
|
|
6
|
+
// Provides discovery, connection, and streaming functionality
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#import "FlirModule.h"
|
|
10
|
+
#import "FlirEventEmitter.h"
|
|
11
|
+
#import "FlirState.h"
|
|
12
|
+
#import <React/RCTLog.h>
|
|
13
|
+
#import <React/RCTBridge.h>
|
|
14
|
+
|
|
15
|
+
#if __has_include(<ThermalSDK/ThermalSDK.h>)
|
|
16
|
+
#define FLIR_SDK_AVAILABLE 1
|
|
17
|
+
#import <ThermalSDK/ThermalSDK.h>
|
|
18
|
+
#else
|
|
19
|
+
#define FLIR_SDK_AVAILABLE 0
|
|
20
|
+
#endif
|
|
21
|
+
|
|
22
|
+
// Forward declare Swift class
|
|
23
|
+
@class FlirManager;
|
|
24
|
+
|
|
25
|
+
@interface FlirModule()
|
|
26
|
+
#if FLIR_SDK_AVAILABLE
|
|
27
|
+
<FLIRDiscoveryEventDelegate, FLIRDataReceivedDelegate, FLIRStreamDelegate>
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#if FLIR_SDK_AVAILABLE
|
|
31
|
+
@property (nonatomic, strong) FLIRDiscovery *discovery;
|
|
32
|
+
@property (nonatomic, strong) FLIRCamera *camera;
|
|
33
|
+
@property (nonatomic, strong) FLIRStream *stream;
|
|
34
|
+
@property (nonatomic, strong) FLIRThermalStreamer *streamer;
|
|
35
|
+
@property (nonatomic, strong) FLIRIdentity *connectedIdentity;
|
|
36
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLIRIdentity *> *identityMap;
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *discoveredDevices;
|
|
40
|
+
@property (nonatomic, assign) BOOL isScanning;
|
|
41
|
+
@property (nonatomic, assign) BOOL isConnected;
|
|
42
|
+
@property (nonatomic, assign) BOOL isStreaming;
|
|
43
|
+
@property (nonatomic, copy) NSString *connectedDeviceId;
|
|
44
|
+
@property (nonatomic, copy) NSString *connectedDeviceName;
|
|
45
|
+
@property (nonatomic, assign) double lastTemperature;
|
|
46
|
+
@end
|
|
47
|
+
|
|
48
|
+
@implementation FlirModule
|
|
49
|
+
|
|
50
|
+
RCT_EXPORT_MODULE(FlirModule);
|
|
51
|
+
|
|
52
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
53
|
+
return YES;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- (instancetype)init {
|
|
57
|
+
if (self = [super init]) {
|
|
58
|
+
#if FLIR_SDK_AVAILABLE
|
|
59
|
+
_identityMap = [NSMutableDictionary new];
|
|
60
|
+
#endif
|
|
61
|
+
_discoveredDevices = [NSMutableArray new];
|
|
62
|
+
_isScanning = NO;
|
|
63
|
+
_isConnected = NO;
|
|
64
|
+
_isStreaming = NO;
|
|
65
|
+
_lastTemperature = NAN;
|
|
66
|
+
}
|
|
67
|
+
return self;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#pragma mark - Event Emitter Support
|
|
71
|
+
|
|
72
|
+
- (NSArray<NSString *> *)supportedEvents {
|
|
73
|
+
return @[
|
|
74
|
+
@"FlirDeviceConnected",
|
|
75
|
+
@"FlirDeviceDisconnected",
|
|
76
|
+
@"FlirDevicesFound",
|
|
77
|
+
@"FlirFrameReceived",
|
|
78
|
+
@"FlirError",
|
|
79
|
+
@"FlirStateChanged"
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {
|
|
84
|
+
// Required for RCTEventEmitter
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
|
|
88
|
+
// Required for RCTEventEmitter
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#pragma mark - Discovery Methods
|
|
92
|
+
|
|
93
|
+
RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
|
|
94
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
95
|
+
#if FLIR_SDK_AVAILABLE
|
|
96
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
97
|
+
if (self.isScanning) {
|
|
98
|
+
RCTLogInfo(@"[FlirModule] Already scanning");
|
|
99
|
+
resolve(@(YES));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
self.isScanning = YES;
|
|
104
|
+
[self.discoveredDevices removeAllObjects];
|
|
105
|
+
[self.identityMap removeAllObjects];
|
|
106
|
+
|
|
107
|
+
if (!self.discovery) {
|
|
108
|
+
self.discovery = [[FLIRDiscovery alloc] init];
|
|
109
|
+
self.discovery.delegate = self;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Start discovery on all available interfaces
|
|
113
|
+
FLIRCommunicationInterface interfaces = FLIRCommunicationInterfaceLightning |
|
|
114
|
+
FLIRCommunicationInterfaceNetwork |
|
|
115
|
+
FLIRCommunicationInterfaceFlirOneWireless |
|
|
116
|
+
FLIRCommunicationInterfaceEmulator;
|
|
117
|
+
[self.discovery start:interfaces];
|
|
118
|
+
|
|
119
|
+
[self emitStateChange:@"discovering"];
|
|
120
|
+
RCTLogInfo(@"[FlirModule] Discovery started");
|
|
121
|
+
resolve(@(YES));
|
|
122
|
+
});
|
|
123
|
+
#else
|
|
124
|
+
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
125
|
+
#endif
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
RCT_EXPORT_METHOD(stopDiscovery:(RCTPromiseResolveBlock)resolve
|
|
129
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
130
|
+
#if FLIR_SDK_AVAILABLE
|
|
131
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
132
|
+
[self.discovery stop];
|
|
133
|
+
self.isScanning = NO;
|
|
134
|
+
RCTLogInfo(@"[FlirModule] Discovery stopped");
|
|
135
|
+
resolve(@(YES));
|
|
136
|
+
});
|
|
137
|
+
#else
|
|
138
|
+
resolve(@(YES));
|
|
139
|
+
#endif
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
RCT_EXPORT_METHOD(getDiscoveredDevices:(RCTPromiseResolveBlock)resolve
|
|
143
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
144
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
145
|
+
resolve(self.discoveredDevices);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#pragma mark - Connection Methods
|
|
150
|
+
|
|
151
|
+
RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
|
|
152
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
153
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
154
|
+
#if FLIR_SDK_AVAILABLE
|
|
155
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
156
|
+
FLIRIdentity *identity = self.identityMap[deviceId];
|
|
157
|
+
if (!identity) {
|
|
158
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
159
|
+
reject(@"ERR_DEVICE_NOT_FOUND", [NSString stringWithFormat:@"Device not found: %@", deviceId], nil);
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
[self performConnectionWithIdentity:identity completion:^(BOOL success, NSError *error) {
|
|
165
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
166
|
+
if (success) {
|
|
167
|
+
resolve(@(YES));
|
|
168
|
+
} else {
|
|
169
|
+
reject(@"ERR_CONNECTION_FAILED", error.localizedDescription ?: @"Connection failed", error);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}];
|
|
173
|
+
});
|
|
174
|
+
#else
|
|
175
|
+
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
176
|
+
#endif
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#if FLIR_SDK_AVAILABLE
|
|
180
|
+
- (void)performConnectionWithIdentity:(FLIRIdentity *)identity completion:(void(^)(BOOL success, NSError *error))completion {
|
|
181
|
+
if (!self.camera) {
|
|
182
|
+
self.camera = [[FLIRCamera alloc] init];
|
|
183
|
+
self.camera.delegate = self;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
NSError *error = nil;
|
|
187
|
+
|
|
188
|
+
// Handle authentication for generic cameras
|
|
189
|
+
if ([identity cameraType] == FLIRCameraType_generic) {
|
|
190
|
+
NSString *certName = [self getCertificateName];
|
|
191
|
+
FLIRAuthenticationStatus status = pending;
|
|
192
|
+
while (status == pending) {
|
|
193
|
+
status = [self.camera authenticate:identity trustedConnectionName:certName];
|
|
194
|
+
if (status == pending) {
|
|
195
|
+
RCTLogInfo(@"[FlirModule] Waiting for camera authentication...");
|
|
196
|
+
[NSThread sleepForTimeInterval:1.0];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
BOOL connected = [self.camera connect:identity error:&error];
|
|
202
|
+
|
|
203
|
+
if (connected) {
|
|
204
|
+
self.connectedIdentity = identity;
|
|
205
|
+
self.connectedDeviceId = [identity deviceId];
|
|
206
|
+
self.connectedDeviceName = [identity deviceId];
|
|
207
|
+
self.isConnected = YES;
|
|
208
|
+
|
|
209
|
+
RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
|
|
210
|
+
|
|
211
|
+
// Get available streams
|
|
212
|
+
NSArray<FLIRStream *> *streams = [self.camera getStreams];
|
|
213
|
+
if (streams.count > 0) {
|
|
214
|
+
RCTLogInfo(@"[FlirModule] Found %lu streams", (unsigned long)streams.count);
|
|
215
|
+
// Auto-start first stream
|
|
216
|
+
[self startStreamInternal:streams[0]];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
220
|
+
[self emitDeviceConnected];
|
|
221
|
+
[self emitStateChange:@"connected"];
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (completion) completion(YES, nil);
|
|
225
|
+
} else {
|
|
226
|
+
RCTLogError(@"[FlirModule] Connection failed: %@", error.localizedDescription);
|
|
227
|
+
if (completion) completion(NO, error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
- (NSString *)getCertificateName {
|
|
232
|
+
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"com.flir.app";
|
|
233
|
+
NSString *key = [NSString stringWithFormat:@"%@-cert-name", bundleID];
|
|
234
|
+
|
|
235
|
+
NSString *existing = [[NSUserDefaults standardUserDefaults] stringForKey:key];
|
|
236
|
+
if (existing) {
|
|
237
|
+
return existing;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
NSString *newName = [[NSUUID UUID] UUIDString];
|
|
241
|
+
[[NSUserDefaults standardUserDefaults] setObject:newName forKey:key];
|
|
242
|
+
return newName;
|
|
243
|
+
}
|
|
244
|
+
#endif
|
|
245
|
+
|
|
246
|
+
RCT_EXPORT_METHOD(disconnect:(RCTPromiseResolveBlock)resolve
|
|
247
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
248
|
+
#if FLIR_SDK_AVAILABLE
|
|
249
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
250
|
+
[self stopStreamInternal];
|
|
251
|
+
[self.camera disconnect];
|
|
252
|
+
self.camera = nil;
|
|
253
|
+
self.connectedIdentity = nil;
|
|
254
|
+
self.connectedDeviceId = nil;
|
|
255
|
+
self.connectedDeviceName = nil;
|
|
256
|
+
self.isConnected = NO;
|
|
257
|
+
self.isStreaming = NO;
|
|
258
|
+
|
|
259
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
260
|
+
[self emitStateChange:@"disconnected"];
|
|
261
|
+
|
|
262
|
+
RCTLogInfo(@"[FlirModule] Disconnected");
|
|
263
|
+
resolve(@(YES));
|
|
264
|
+
});
|
|
265
|
+
#else
|
|
266
|
+
resolve(@(YES));
|
|
267
|
+
#endif
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
RCT_EXPORT_METHOD(stopFlir:(RCTPromiseResolveBlock)resolve
|
|
271
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
272
|
+
#if FLIR_SDK_AVAILABLE
|
|
273
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
274
|
+
[self stopStreamInternal];
|
|
275
|
+
[self.camera disconnect];
|
|
276
|
+
[self.discovery stop];
|
|
277
|
+
|
|
278
|
+
self.camera = nil;
|
|
279
|
+
self.connectedIdentity = nil;
|
|
280
|
+
self.connectedDeviceId = nil;
|
|
281
|
+
self.connectedDeviceName = nil;
|
|
282
|
+
self.isConnected = NO;
|
|
283
|
+
self.isStreaming = NO;
|
|
284
|
+
self.isScanning = NO;
|
|
285
|
+
|
|
286
|
+
[self emitStateChange:@"stopped"];
|
|
287
|
+
RCTLogInfo(@"[FlirModule] Stopped");
|
|
288
|
+
resolve(@(YES));
|
|
289
|
+
});
|
|
290
|
+
#else
|
|
291
|
+
resolve(@(YES));
|
|
292
|
+
#endif
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#pragma mark - Streaming
|
|
296
|
+
|
|
297
|
+
#if FLIR_SDK_AVAILABLE
|
|
298
|
+
- (void)startStreamInternal:(FLIRStream *)newStream {
|
|
299
|
+
[self stopStreamInternal];
|
|
300
|
+
|
|
301
|
+
self.stream = newStream;
|
|
302
|
+
|
|
303
|
+
if (newStream.isThermal) {
|
|
304
|
+
self.streamer = [[FLIRThermalStreamer alloc] initWithStream:newStream];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
newStream.delegate = self;
|
|
308
|
+
|
|
309
|
+
NSError *error = nil;
|
|
310
|
+
if ([newStream start:&error]) {
|
|
311
|
+
self.isStreaming = YES;
|
|
312
|
+
[self emitStateChange:@"streaming"];
|
|
313
|
+
RCTLogInfo(@"[FlirModule] Stream started (thermal: %@)", newStream.isThermal ? @"YES" : @"NO");
|
|
314
|
+
} else {
|
|
315
|
+
RCTLogError(@"[FlirModule] Stream start failed: %@", error.localizedDescription);
|
|
316
|
+
self.stream = nil;
|
|
317
|
+
self.streamer = nil;
|
|
318
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{@"error": error.localizedDescription ?: @"Stream start failed"}];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
- (void)stopStreamInternal {
|
|
323
|
+
[self.stream stop];
|
|
324
|
+
self.stream = nil;
|
|
325
|
+
self.streamer = nil;
|
|
326
|
+
self.isStreaming = NO;
|
|
327
|
+
}
|
|
328
|
+
#endif
|
|
329
|
+
|
|
330
|
+
#pragma mark - Temperature Methods
|
|
331
|
+
|
|
332
|
+
RCT_EXPORT_METHOD(getTemperatureAt:(nonnull NSNumber *)x
|
|
333
|
+
y:(nonnull NSNumber *)y
|
|
334
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
335
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
336
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
337
|
+
double temp = [FlirState shared].lastTemperature;
|
|
338
|
+
if (isnan(temp)) {
|
|
339
|
+
temp = self.lastTemperature;
|
|
340
|
+
}
|
|
341
|
+
if (isnan(temp)) {
|
|
342
|
+
resolve([NSNull null]);
|
|
343
|
+
} else {
|
|
344
|
+
resolve(@(temp));
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
RCT_EXPORT_METHOD(getTemperatureFromColor:(NSInteger)color
|
|
350
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
351
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
352
|
+
// Placeholder: Convert ARGB color to pseudo-temperature
|
|
353
|
+
int r = (color >> 16) & 0xFF;
|
|
354
|
+
int g = (color >> 8) & 0xFF;
|
|
355
|
+
int b = color & 0xFF;
|
|
356
|
+
double lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
357
|
+
double temp = (lum / 255.0) * 400.0;
|
|
358
|
+
resolve(@(temp));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#pragma mark - Status Methods
|
|
362
|
+
|
|
363
|
+
RCT_EXPORT_METHOD(isEmulator:(RCTPromiseResolveBlock)resolve
|
|
364
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
365
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
366
|
+
BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"] ||
|
|
367
|
+
[self.connectedDeviceName.lowercaseString containsString:@"emulat"];
|
|
368
|
+
resolve(@(isEmu));
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
RCT_EXPORT_METHOD(isDeviceConnected:(RCTPromiseResolveBlock)resolve
|
|
373
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
374
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
375
|
+
resolve(@(self.isConnected));
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
RCT_EXPORT_METHOD(getConnectedDeviceInfo:(RCTPromiseResolveBlock)resolve
|
|
380
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
381
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
382
|
+
resolve(self.connectedDeviceName ?: @"Not connected");
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
RCT_EXPORT_METHOD(isSDKDownloaded:(RCTPromiseResolveBlock)resolve
|
|
387
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
388
|
+
#if FLIR_SDK_AVAILABLE
|
|
389
|
+
resolve(@(YES));
|
|
390
|
+
#else
|
|
391
|
+
resolve(@(NO));
|
|
392
|
+
#endif
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
RCT_EXPORT_METHOD(getSDKStatus:(RCTPromiseResolveBlock)resolve
|
|
396
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
397
|
+
NSDictionary *status = @{
|
|
398
|
+
#if FLIR_SDK_AVAILABLE
|
|
399
|
+
@"available": @(YES),
|
|
400
|
+
#else
|
|
401
|
+
@"available": @(NO),
|
|
402
|
+
#endif
|
|
403
|
+
@"arch": @"arm64",
|
|
404
|
+
@"platform": @"iOS"
|
|
405
|
+
};
|
|
406
|
+
resolve(status);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
#pragma mark - Emulator
|
|
410
|
+
|
|
411
|
+
RCT_EXPORT_METHOD(startEmulator:(NSString *)emulatorType
|
|
412
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
413
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
414
|
+
#if FLIR_SDK_AVAILABLE
|
|
415
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
416
|
+
FLIRCameraType cameraType = FLIRCameraType_flirOne;
|
|
417
|
+
if ([emulatorType.lowercaseString containsString:@"edge"]) {
|
|
418
|
+
cameraType = FLIRCameraType_flirOneEdge;
|
|
419
|
+
} else if ([emulatorType.lowercaseString containsString:@"pro"]) {
|
|
420
|
+
cameraType = FLIRCameraType_flirOneEdgePro;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
FLIRIdentity *emulatorIdentity = [[FLIRIdentity alloc] initWithEmulatorType:cameraType];
|
|
424
|
+
if (emulatorIdentity) {
|
|
425
|
+
self.identityMap[[emulatorIdentity deviceId]] = emulatorIdentity;
|
|
426
|
+
|
|
427
|
+
[self performConnectionWithIdentity:emulatorIdentity completion:^(BOOL success, NSError *error) {
|
|
428
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
429
|
+
if (success) {
|
|
430
|
+
resolve(@(YES));
|
|
431
|
+
} else {
|
|
432
|
+
reject(@"ERR_EMULATOR_FAILED", error.localizedDescription ?: @"Emulator start failed", error);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}];
|
|
436
|
+
} else {
|
|
437
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
438
|
+
reject(@"ERR_EMULATOR_INIT", @"Failed to create emulator identity", nil);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
#else
|
|
443
|
+
reject(@"ERR_FLIR_NOT_AVAILABLE", @"FLIR SDK not available", nil);
|
|
444
|
+
#endif
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#pragma mark - Debug
|
|
448
|
+
|
|
449
|
+
RCT_EXPORT_METHOD(initializeSDK:(RCTPromiseResolveBlock)resolve
|
|
450
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
451
|
+
NSDictionary *result = @{
|
|
452
|
+
#if FLIR_SDK_AVAILABLE
|
|
453
|
+
@"initialized": @(YES),
|
|
454
|
+
@"message": @"SDK initialized successfully"
|
|
455
|
+
#else
|
|
456
|
+
@"initialized": @(NO),
|
|
457
|
+
@"message": @"SDK not available - built without FLIR"
|
|
458
|
+
#endif
|
|
459
|
+
};
|
|
460
|
+
resolve(result);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
RCT_EXPORT_METHOD(getDebugInfo:(RCTPromiseResolveBlock)resolve
|
|
464
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
465
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
466
|
+
NSDictionary *info = @{
|
|
467
|
+
#if FLIR_SDK_AVAILABLE
|
|
468
|
+
@"sdkAvailable": @(YES),
|
|
469
|
+
#else
|
|
470
|
+
@"sdkAvailable": @(NO),
|
|
471
|
+
#endif
|
|
472
|
+
@"arch": @"arm64",
|
|
473
|
+
@"discoveredDeviceCount": @(self.discoveredDevices.count),
|
|
474
|
+
@"isConnected": @(self.isConnected),
|
|
475
|
+
@"isStreaming": @(self.isStreaming),
|
|
476
|
+
@"connectedDevice": self.connectedDeviceName ?: @"None"
|
|
477
|
+
};
|
|
478
|
+
resolve(info);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
RCT_EXPORT_METHOD(getLatestFramePath:(RCTPromiseResolveBlock)resolve
|
|
483
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
484
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
485
|
+
UIImage *image = [FlirState shared].latestImage;
|
|
486
|
+
if (!image) {
|
|
487
|
+
resolve([NSNull null]);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
NSData *jpegData = UIImageJPEGRepresentation(image, 0.9);
|
|
492
|
+
if (!jpegData) {
|
|
493
|
+
resolve([NSNull null]);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:
|
|
498
|
+
[NSString stringWithFormat:@"flir_frame_%lld.jpg", (long long)[[NSDate date] timeIntervalSince1970] * 1000]];
|
|
499
|
+
[jpegData writeToFile:tempPath atomically:YES];
|
|
500
|
+
resolve(tempPath);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#pragma mark - Helper Methods
|
|
505
|
+
|
|
506
|
+
- (void)emitDeviceConnected {
|
|
507
|
+
BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
|
|
508
|
+
|
|
509
|
+
NSDictionary *body = @{
|
|
510
|
+
@"identity": @{
|
|
511
|
+
@"deviceId": self.connectedDeviceId ?: @"Unknown",
|
|
512
|
+
@"isEmulator": @(isEmu)
|
|
513
|
+
},
|
|
514
|
+
@"deviceType": isEmu ? @"emulator" : @"device",
|
|
515
|
+
@"isEmulator": @(isEmu),
|
|
516
|
+
@"state": @"connected"
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceConnected" body:body];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
- (void)emitStateChange:(NSString *)state {
|
|
523
|
+
BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
|
|
524
|
+
|
|
525
|
+
NSDictionary *body = @{
|
|
526
|
+
@"state": state,
|
|
527
|
+
@"isConnected": @(self.isConnected),
|
|
528
|
+
@"isStreaming": @(self.isStreaming),
|
|
529
|
+
@"isEmulator": @(isEmu),
|
|
530
|
+
@"deviceName": self.connectedDeviceName ?: @"",
|
|
531
|
+
@"deviceId": self.connectedDeviceId ?: @""
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirStateChanged" body:body];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#if FLIR_SDK_AVAILABLE
|
|
538
|
+
- (NSString *)communicationInterfaceName:(FLIRCommunicationInterface)iface {
|
|
539
|
+
if (iface & FLIRCommunicationInterfaceLightning) return @"LIGHTNING";
|
|
540
|
+
if (iface & FLIRCommunicationInterfaceNetwork) return @"NETWORK";
|
|
541
|
+
if (iface & FLIRCommunicationInterfaceFlirOneWireless) return @"WIRELESS";
|
|
542
|
+
if (iface & FLIRCommunicationInterfaceEmulator) return @"EMULATOR";
|
|
543
|
+
if (iface & FLIRCommunicationInterfaceUSB) return @"USB";
|
|
544
|
+
return @"UNKNOWN";
|
|
545
|
+
}
|
|
546
|
+
#endif
|
|
547
|
+
|
|
548
|
+
#pragma mark - FLIRDiscoveryEventDelegate
|
|
549
|
+
|
|
550
|
+
#if FLIR_SDK_AVAILABLE
|
|
551
|
+
- (void)cameraDiscovered:(FLIRDiscoveredCamera *)discoveredCamera {
|
|
552
|
+
FLIRIdentity *identity = discoveredCamera.identity;
|
|
553
|
+
NSString *deviceId = [identity deviceId];
|
|
554
|
+
|
|
555
|
+
RCTLogInfo(@"[FlirModule] Camera discovered: %@", deviceId);
|
|
556
|
+
|
|
557
|
+
// Store identity
|
|
558
|
+
self.identityMap[deviceId] = identity;
|
|
559
|
+
|
|
560
|
+
// Create device info
|
|
561
|
+
NSDictionary *deviceInfo = @{
|
|
562
|
+
@"id": deviceId,
|
|
563
|
+
@"name": discoveredCamera.displayName ?: deviceId,
|
|
564
|
+
@"communicationType": [self communicationInterfaceName:[identity communicationInterface]],
|
|
565
|
+
@"isEmulator": @([identity communicationInterface] == FLIRCommunicationInterfaceEmulator)
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// Add if not already present
|
|
569
|
+
BOOL found = NO;
|
|
570
|
+
for (NSDictionary *existing in self.discoveredDevices) {
|
|
571
|
+
if ([existing[@"id"] isEqualToString:deviceId]) {
|
|
572
|
+
found = YES;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!found) {
|
|
578
|
+
[self.discoveredDevices addObject:deviceInfo];
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Emit devices found event
|
|
582
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
583
|
+
NSDictionary *body = @{
|
|
584
|
+
@"devices": self.discoveredDevices,
|
|
585
|
+
@"count": @(self.discoveredDevices.count)
|
|
586
|
+
};
|
|
587
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:body];
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
- (void)cameraLost:(FLIRIdentity *)cameraIdentity {
|
|
592
|
+
NSString *deviceId = [cameraIdentity deviceId];
|
|
593
|
+
RCTLogInfo(@"[FlirModule] Camera lost: %@", deviceId);
|
|
594
|
+
|
|
595
|
+
[self.identityMap removeObjectForKey:deviceId];
|
|
596
|
+
|
|
597
|
+
NSMutableArray *toRemove = [NSMutableArray new];
|
|
598
|
+
for (NSDictionary *device in self.discoveredDevices) {
|
|
599
|
+
if ([device[@"id"] isEqualToString:deviceId]) {
|
|
600
|
+
[toRemove addObject:device];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
[self.discoveredDevices removeObjectsInArray:toRemove];
|
|
604
|
+
|
|
605
|
+
// If this was our connected device, handle disconnect
|
|
606
|
+
if ([self.connectedDeviceId isEqualToString:deviceId]) {
|
|
607
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
608
|
+
[self stopStreamInternal];
|
|
609
|
+
self.camera = nil;
|
|
610
|
+
self.connectedIdentity = nil;
|
|
611
|
+
self.connectedDeviceId = nil;
|
|
612
|
+
self.connectedDeviceName = nil;
|
|
613
|
+
self.isConnected = NO;
|
|
614
|
+
self.isStreaming = NO;
|
|
615
|
+
|
|
616
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
617
|
+
[self emitStateChange:@"disconnected"];
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
622
|
+
NSDictionary *body = @{
|
|
623
|
+
@"devices": self.discoveredDevices,
|
|
624
|
+
@"count": @(self.discoveredDevices.count)
|
|
625
|
+
};
|
|
626
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:body];
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
- (void)discoveryError:(NSString *)error netServiceError:(int)nsnetserviceserror on:(FLIRCommunicationInterface)iface {
|
|
631
|
+
RCTLogError(@"[FlirModule] Discovery error: %@ (%d)", error, nsnetserviceserror);
|
|
632
|
+
|
|
633
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
634
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
635
|
+
@"error": error ?: @"Unknown discovery error",
|
|
636
|
+
@"type": @"discovery"
|
|
637
|
+
}];
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
- (void)discoveryFinished:(FLIRCommunicationInterface)iface {
|
|
642
|
+
RCTLogInfo(@"[FlirModule] Discovery finished");
|
|
643
|
+
self.isScanning = NO;
|
|
644
|
+
}
|
|
645
|
+
#endif
|
|
646
|
+
|
|
647
|
+
#pragma mark - FLIRDataReceivedDelegate
|
|
648
|
+
|
|
649
|
+
#if FLIR_SDK_AVAILABLE
|
|
650
|
+
- (void)onDisconnected:(FLIRCamera *)camera withError:(NSError *)error {
|
|
651
|
+
RCTLogInfo(@"[FlirModule] Camera disconnected: %@", error.localizedDescription);
|
|
652
|
+
|
|
653
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
654
|
+
self.isConnected = NO;
|
|
655
|
+
self.isStreaming = NO;
|
|
656
|
+
self.connectedDeviceId = nil;
|
|
657
|
+
self.connectedDeviceName = nil;
|
|
658
|
+
|
|
659
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceDisconnected" body:@{}];
|
|
660
|
+
[self emitStateChange:@"disconnected"];
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
#endif
|
|
664
|
+
|
|
665
|
+
#pragma mark - FLIRStreamDelegate
|
|
666
|
+
|
|
667
|
+
#if FLIR_SDK_AVAILABLE
|
|
668
|
+
- (void)onError:(NSError *)error {
|
|
669
|
+
RCTLogError(@"[FlirModule] Stream error: %@", error.localizedDescription);
|
|
670
|
+
|
|
671
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
672
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
|
|
673
|
+
@"error": error.localizedDescription ?: @"Stream error",
|
|
674
|
+
@"type": @"stream"
|
|
675
|
+
}];
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
- (void)onImageReceived {
|
|
680
|
+
if (!self.streamer) return;
|
|
681
|
+
|
|
682
|
+
NSError *error = nil;
|
|
683
|
+
if ([self.streamer update:&error]) {
|
|
684
|
+
UIImage *image = [self.streamer getImage];
|
|
685
|
+
if (image) {
|
|
686
|
+
// Update shared state
|
|
687
|
+
[[FlirState shared] updateFrame:image];
|
|
688
|
+
|
|
689
|
+
// Get temperature from thermal image if available
|
|
690
|
+
[self.streamer withThermalImage:^(FLIRThermalImage *thermalImage) {
|
|
691
|
+
FLIRImageStatistics *stats = [thermalImage getStatistics];
|
|
692
|
+
if (stats) {
|
|
693
|
+
self.lastTemperature = [[stats getMax] value];
|
|
694
|
+
[FlirState shared].lastTemperature = self.lastTemperature;
|
|
695
|
+
}
|
|
696
|
+
}];
|
|
697
|
+
|
|
698
|
+
// Emit frame received event (rate-limited by RN event queue)
|
|
699
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
700
|
+
[[FlirEventEmitter shared] sendDeviceEvent:@"FlirFrameReceived" body:@{
|
|
701
|
+
@"width": @(image.size.width),
|
|
702
|
+
@"height": @(image.size.height),
|
|
703
|
+
@"timestamp": @([[NSDate date] timeIntervalSince1970] * 1000)
|
|
704
|
+
}];
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
RCTLogError(@"[FlirModule] Streamer update error: %@", error.localizedDescription);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
#endif
|
|
712
|
+
|
|
713
|
+
@end
|