ilabs-flir 2.2.31 → 2.3.0

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.
@@ -95,6 +95,7 @@ object FlirManager {
95
95
  /**
96
96
  * Start scanning
97
97
  */
98
+ @Synchronized
98
99
  fun startDiscovery(retry: Boolean = false) {
99
100
  if (!isInitialized && reactContext != null) {
100
101
  init(reactContext!!)
@@ -102,6 +103,7 @@ object FlirManager {
102
103
 
103
104
  if (isScanning && !retry) return
104
105
 
106
+ Log.i(TAG, "Starting FlirManager discovery...")
105
107
  isScanning = true
106
108
  emitDeviceState("discovering")
107
109
  sdkManager?.scan()
@@ -118,7 +120,9 @@ object FlirManager {
118
120
  /**
119
121
  * Stop scanning
120
122
  */
123
+ @Synchronized
121
124
  fun stopDiscovery() {
125
+ Log.i(TAG, "Stopping FlirManager discovery...")
122
126
  sdkManager?.stopScan()
123
127
  isScanning = false
124
128
  }
@@ -126,7 +130,12 @@ object FlirManager {
126
130
  /**
127
131
  * Connect to a device
128
132
  */
129
- fun connectToDevice(deviceId: String) {
133
+ @Synchronized
134
+ fun connectToDevice(deviceId: String?) {
135
+ if (deviceId == null) {
136
+ Log.e(TAG, "connectToDevice: deviceId is null")
137
+ return
138
+ }
130
139
  Log.i(TAG, "connectToDevice: $deviceId")
131
140
 
132
141
  val devices = sdkManager?.discoveredDevices ?: emptyList()
@@ -148,7 +157,9 @@ object FlirManager {
148
157
  /**
149
158
  * Disconnect
150
159
  */
160
+ @Synchronized
151
161
  fun disconnect() {
162
+ Log.i(TAG, "Disconnecting FlirManager...")
152
163
  shouldProcessFrames.set(false)
153
164
  sdkManager?.disconnect()
154
165
  isConnected = false
@@ -160,11 +171,19 @@ object FlirManager {
160
171
  /**
161
172
  * Stop everything
162
173
  */
174
+ @Synchronized
163
175
  fun stop() {
176
+ Log.i(TAG, "Stopping FlirManager completely...")
164
177
  shouldProcessFrames.set(false)
178
+
179
+ // Clear callbacks first to prevent any more frames/updates from hitting Java/RN
180
+ textureCallback = null
181
+ temperatureCallback = null
182
+
165
183
  disconnect()
166
184
  stopDiscovery()
167
185
  latestBitmap = null
186
+ Log.i(TAG, "FlirManager stopped")
168
187
  }
169
188
 
170
189
  // Stub legacy methods
@@ -34,7 +34,7 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
34
34
  // Simple placeholder conversion: converts an ARGB color to a pseudo-temperature value.
35
35
  // Replace with SDK call when integrating thermalsdk APIs.
36
36
  @ReactMethod
37
- fun getTemperatureFromColor(color: Int, promise: Promise) {
37
+ fun getTemperatureFromColor(color: Int, promise: Promise?) {
38
38
  try {
39
39
  val r = (color shr 16) and 0xFF
40
40
  val g = (color shr 8) and 0xFF
@@ -42,81 +42,81 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
42
42
  // Luminance-like value scaled to a plausible temperature range (0°C - 400°C)
43
43
  val lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
44
44
  val temp = 0.0 + (lum / 255.0) * 400.0
45
- promise.resolve(temp)
45
+ promise?.resolve(temp)
46
46
  } catch (e: Exception) {
47
- promise.reject("ERR_FLIR_CONVERT", e)
47
+ promise?.reject("ERR_FLIR_CONVERT", e)
48
48
  }
49
49
  }
50
50
 
51
51
  @ReactMethod
52
- fun getLatestFramePath(promise: Promise) {
52
+ fun getLatestFramePath(promise: Promise?) {
53
53
  try {
54
54
  val path = FlirFrameCache.latestFramePath
55
- if (path != null) promise.resolve(path) else promise.resolve(null)
55
+ if (path != null) promise?.resolve(path) else promise?.resolve(null)
56
56
  } catch (e: Exception) {
57
- promise.reject("ERR_FLIR_PATH", e)
57
+ promise?.reject("ERR_FLIR_PATH", e)
58
58
  }
59
59
  }
60
60
 
61
61
  @ReactMethod
62
- fun getTemperatureAt(x: Int, y: Int, promise: Promise) {
62
+ fun getTemperatureAt(x: Int, y: Int, promise: Promise?) {
63
63
  try {
64
64
  val temp = FlirManager.getTemperatureAt(x, y)
65
- if (temp != null) promise.resolve(temp) else promise.reject("ERR_NO_DATA", "No temperature data available")
65
+ if (temp != null) promise?.resolve(temp) else promise?.reject("ERR_NO_DATA", "No temperature data available")
66
66
  } catch (e: Exception) {
67
- promise.reject("ERR_FLIR_SAMPLE", e)
67
+ promise?.reject("ERR_FLIR_SAMPLE", e)
68
68
  }
69
69
  }
70
70
 
71
71
  @ReactMethod
72
- fun getTemperatureAtNormalized(nx: Double, ny: Double, promise: Promise) {
72
+ fun getTemperatureAtNormalized(nx: Double, ny: Double, promise: Promise?) {
73
73
  try {
74
74
  val temp = FlirManager.getTemperatureAtNormalized(nx, ny)
75
- if (temp != null) promise.resolve(temp) else promise.resolve(null)
75
+ if (temp != null) promise?.resolve(temp) else promise?.resolve(null)
76
76
  } catch (e: Exception) {
77
- promise.reject("ERR_FLIR_TEMP_NORM", e)
77
+ promise?.reject("ERR_FLIR_TEMP_NORM", e)
78
78
  }
79
79
  }
80
80
 
81
81
  @ReactMethod
82
- fun isEmulator(promise: Promise) {
82
+ fun isEmulator(promise: Promise?) {
83
83
  try {
84
- promise.resolve(FlirManager.isEmulator())
84
+ promise?.resolve(FlirManager.isEmulator())
85
85
  } catch (e: Exception) {
86
- promise.reject("ERR_FLIR_EMULATOR_CHECK", e)
86
+ promise?.reject("ERR_FLIR_EMULATOR_CHECK", e)
87
87
  }
88
88
  }
89
89
 
90
90
  @ReactMethod
91
- fun isDeviceConnected(promise: Promise) {
91
+ fun isDeviceConnected(promise: Promise?) {
92
92
  try {
93
- promise.resolve(FlirManager.isDeviceConnected())
93
+ promise?.resolve(FlirManager.isDeviceConnected())
94
94
  } catch (e: Exception) {
95
- promise.reject("ERR_FLIR_DEVICE_CHECK", e)
95
+ promise?.reject("ERR_FLIR_DEVICE_CHECK", e)
96
96
  }
97
97
  }
98
98
 
99
99
  @ReactMethod
100
- fun getConnectedDeviceInfo(promise: Promise) {
100
+ fun getConnectedDeviceInfo(promise: Promise?) {
101
101
  try {
102
- promise.resolve(FlirManager.getConnectedDeviceInfo())
102
+ promise?.resolve(FlirManager.getConnectedDeviceInfo())
103
103
  } catch (e: Exception) {
104
- promise.reject("ERR_FLIR_DEVICE_INFO", e)
104
+ promise?.reject("ERR_FLIR_DEVICE_INFO", e)
105
105
  }
106
106
  }
107
107
 
108
108
  @ReactMethod
109
- fun isSDKDownloaded(promise: Promise) {
109
+ fun isSDKDownloaded(promise: Promise?) {
110
110
  try {
111
111
  val available = FlirSDKLoader.isSDKAvailable(reactContext)
112
- promise.resolve(available)
112
+ promise?.resolve(available)
113
113
  } catch (e: Exception) {
114
- promise.reject("ERR_FLIR_SDK_CHECK", e)
114
+ promise?.reject("ERR_FLIR_SDK_CHECK", e)
115
115
  }
116
116
  }
117
117
 
118
118
  @ReactMethod
119
- fun getSDKStatus(promise: Promise) {
119
+ fun getSDKStatus(promise: Promise?) {
120
120
  try {
121
121
  val available = FlirSDKLoader.isSDKAvailable(reactContext)
122
122
  val arch = FlirSDKLoader.getDeviceArch()
@@ -131,14 +131,14 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
131
131
  result.putBoolean("dexExists", dexPath?.exists() == true)
132
132
  result.putBoolean("nativeLibsExist", nativeLibDir?.exists() == true)
133
133
 
134
- promise.resolve(result)
134
+ promise?.resolve(result)
135
135
  } catch (e: Exception) {
136
- promise.reject("ERR_FLIR_SDK_STATUS", e)
136
+ promise?.reject("ERR_FLIR_SDK_STATUS", e)
137
137
  }
138
138
  }
139
139
 
140
140
  @ReactMethod
141
- fun getDiscoveredDevices(promise: Promise) {
141
+ fun getDiscoveredDevices(promise: Promise?) {
142
142
  try {
143
143
  val devices = FlirManager.getDiscoveredDevices()
144
144
  val result = com.facebook.react.bridge.Arguments.createArray()
@@ -152,129 +152,131 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
152
152
  result.pushMap(deviceMap)
153
153
  }
154
154
 
155
- promise.resolve(result)
155
+ promise?.resolve(result)
156
156
  } catch (e: Exception) {
157
- promise.reject("ERR_FLIR_DEVICES", e)
157
+ promise?.reject("ERR_FLIR_DEVICES", e)
158
158
  }
159
159
  }
160
160
 
161
161
  @ReactMethod
162
- fun setPreferSdkRotation(prefer: Boolean, promise: Promise) {
162
+ fun setPreferSdkRotation(prefer: Boolean, promise: Promise?) {
163
163
  try {
164
164
  FlirManager.setPreferSdkRotation(prefer)
165
- promise.resolve(true)
165
+ promise?.resolve(true)
166
166
  } catch (e: Exception) {
167
- promise.reject("ERR_FLIR_SET_ROTATION_PREF", e)
167
+ promise?.reject("ERR_FLIR_SET_ROTATION_PREF", e)
168
168
  }
169
169
  }
170
170
 
171
171
  @ReactMethod
172
- fun isPreferSdkRotation(promise: Promise) {
172
+ fun isPreferSdkRotation(promise: Promise?) {
173
173
  try {
174
174
  val v = FlirManager.isPreferSdkRotation()
175
- promise.resolve(v)
175
+ promise?.resolve(v)
176
176
  } catch (e: Exception) {
177
- promise.reject("ERR_FLIR_GET_ROTATION_PREF", e)
177
+ promise?.reject("ERR_FLIR_GET_ROTATION_PREF", e)
178
178
  }
179
179
  }
180
180
 
181
181
  @ReactMethod
182
- fun getBatteryLevel(promise: Promise) {
182
+ fun getBatteryLevel(promise: Promise?) {
183
183
  try {
184
184
  val level = FlirManager.getBatteryLevel()
185
- promise.resolve(level)
185
+ promise?.resolve(level)
186
186
  } catch (e: Exception) {
187
- promise.reject("ERR_FLIR_GET_BATTERY", e)
187
+ promise?.reject("ERR_FLIR_GET_BATTERY", e)
188
188
  }
189
189
  }
190
190
 
191
191
  @ReactMethod
192
- fun isBatteryCharging(promise: Promise) {
192
+ fun isBatteryCharging(promise: Promise?) {
193
193
  try {
194
194
  val v = FlirManager.isBatteryCharging()
195
- promise.resolve(v)
195
+ promise?.resolve(v)
196
196
  } catch (e: Exception) {
197
- promise.reject("ERR_FLIR_CHARGING", e)
197
+ promise?.reject("ERR_FLIR_CHARGING", e)
198
198
  }
199
199
  }
200
200
 
201
201
  @ReactMethod
202
- fun startEmulator(emulatorType: String, promise: Promise) {
202
+ fun startEmulator(emulatorType: String?, promise: Promise?) {
203
203
  try {
204
204
  // Ensure SDK is initialized with context before starting discovery
205
205
  FlirManager.init(reactContext)
206
206
  // With simplified API, just start discovery - emulators are discovered like any device
207
207
  FlirManager.startDiscovery(true)
208
- promise.resolve(true)
208
+ promise?.resolve(true)
209
209
  } catch (e: Exception) {
210
- promise.reject("ERR_FLIR_EMULATOR", e)
210
+ promise?.reject("ERR_FLIR_EMULATOR", e)
211
211
  }
212
212
  }
213
213
 
214
214
  @ReactMethod
215
- fun connectToDevice(deviceId: String, promise: Promise) {
215
+ fun connectToDevice(deviceId: String?, promise: Promise?) {
216
216
  try {
217
217
  // Ensure SDK is initialized with context before connecting
218
218
  FlirManager.init(reactContext)
219
- FlirManager.connectToDevice(deviceId)
220
- promise.resolve(true)
219
+ if (deviceId != null) {
220
+ FlirManager.connectToDevice(deviceId)
221
+ }
222
+ promise?.resolve(true)
221
223
  } catch (e: Exception) {
222
- promise.reject("ERR_FLIR_CONNECT", e)
224
+ promise?.reject("ERR_FLIR_CONNECT", e)
223
225
  }
224
226
  }
225
227
 
226
228
  @ReactMethod
227
- fun startDiscovery(promise: Promise) {
229
+ fun startDiscovery(promise: Promise?) {
228
230
  try {
229
231
  // Ensure SDK is initialized with context before starting discovery
230
232
  FlirManager.init(reactContext)
231
233
  FlirManager.startDiscovery(true)
232
- promise.resolve(true)
234
+ promise?.resolve(true)
233
235
  } catch (e: Exception) {
234
- promise.reject("ERR_FLIR_DISCOVERY", e)
236
+ promise?.reject("ERR_FLIR_DISCOVERY", e)
235
237
  }
236
238
  }
237
239
 
238
240
  @ReactMethod
239
- fun stopDiscovery(promise: Promise) {
241
+ fun stopDiscovery(promise: Promise?) {
240
242
  try {
241
243
  FlirManager.stopDiscovery()
242
- promise.resolve(true)
244
+ promise?.resolve(true)
243
245
  } catch (e: Exception) {
244
- promise.reject("ERR_FLIR_STOP_DISCOVERY", e)
246
+ promise?.reject("ERR_FLIR_STOP_DISCOVERY", e)
245
247
  }
246
248
  }
247
249
 
248
250
  @ReactMethod
249
- fun stopFlir(promise: Promise) {
251
+ fun stopFlir(promise: Promise?) {
250
252
  try {
251
253
  FlirManager.stop()
252
- promise.resolve(true)
254
+ promise?.resolve(true)
253
255
  } catch (e: Exception) {
254
- promise.reject("ERR_FLIR_STOP", e)
256
+ promise?.reject("ERR_FLIR_STOP", e)
255
257
  }
256
258
  }
257
259
 
258
260
  @ReactMethod
259
- fun initializeSDK(promise: Promise) {
261
+ fun initializeSDK(promise: Promise?) {
260
262
  try {
261
263
  FlirManager.init(reactContext)
262
264
 
263
265
  val result = com.facebook.react.bridge.Arguments.createMap()
264
266
  result.putBoolean("initialized", true)
265
267
  result.putString("message", "SDK initialized successfully")
266
- promise.resolve(result)
268
+ promise?.resolve(result)
267
269
  } catch (e: Exception) {
268
270
  val result = com.facebook.react.bridge.Arguments.createMap()
269
271
  result.putBoolean("initialized", false)
270
272
  result.putString("error", e.message ?: "Unknown error")
271
273
  result.putString("errorType", e.javaClass.simpleName)
272
- promise.resolve(result)
274
+ promise?.resolve(result)
273
275
  }
274
276
  }
275
277
 
276
278
  @ReactMethod
277
- fun getDebugInfo(promise: Promise) {
279
+ fun getDebugInfo(promise: Promise?) {
278
280
  try {
279
281
  val result = com.facebook.react.bridge.Arguments.createMap()
280
282
 
@@ -299,9 +301,9 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
299
301
  result.putBoolean("isStreaming", FlirManager.isStreaming())
300
302
  result.putString("connectedDevice", FlirManager.getConnectedDeviceInfo())
301
303
 
302
- promise.resolve(result)
304
+ promise?.resolve(result)
303
305
  } catch (e: Exception) {
304
- promise.reject("ERR_DEBUG_INFO", e)
306
+ promise?.reject("ERR_DEBUG_INFO", e)
305
307
  }
306
308
  }
307
309
  }
@@ -63,6 +63,9 @@ import ThermalSDK
63
63
  private var connectedDeviceId: String?
64
64
  private var connectedDeviceName: String?
65
65
 
66
+ // Internal synchronization
67
+ private let stateLock = NSObject()
68
+
66
69
  // Dedicated render queue for frame processing (matches sample app pattern)
67
70
  private let renderQueue = DispatchQueue(label: "com.flir.render")
68
71
 
@@ -122,6 +125,7 @@ import ThermalSDK
122
125
  }
123
126
 
124
127
  @objc public func stopDiscovery() {
128
+ objc_sync_enter(stateLock); defer { objc_sync_exit(stateLock) }
125
129
  NSLog("[FlirManager] stopDiscovery")
126
130
 
127
131
  #if FLIR_ENABLED
@@ -133,6 +137,7 @@ import ThermalSDK
133
137
  // MARK: - Connection
134
138
 
135
139
  @objc public func connectToDevice(_ deviceId: String) {
140
+ objc_sync_enter(stateLock); defer { objc_sync_exit(stateLock) }
136
141
  NSLog("[FlirManager] connectToDevice: \(deviceId)")
137
142
 
138
143
  #if FLIR_ENABLED
@@ -270,10 +275,11 @@ import ThermalSDK
270
275
  }
271
276
 
272
277
  @objc public func disconnect() {
278
+ objc_sync_enter(stateLock); defer { objc_sync_exit(stateLock) }
273
279
  NSLog("[FlirManager] disconnect")
274
280
 
275
281
  #if FLIR_ENABLED
276
- stopStream()
282
+ stopStreamInternalSync()
277
283
  camera?.disconnect()
278
284
  camera = nil
279
285
  _isConnected = false
@@ -289,9 +295,36 @@ import ThermalSDK
289
295
  }
290
296
 
291
297
  @objc public func stop() {
292
- stopStream()
293
- disconnect()
294
- stopDiscovery()
298
+ objc_sync_enter(stateLock); defer { objc_sync_exit(stateLock) }
299
+ stopStreamInternalSync()
300
+ disconnectInternalSync()
301
+ stopDiscoveryInternalSync()
302
+ }
303
+
304
+ private func stopDiscoveryInternalSync() {
305
+ NSLog("[FlirManager] stopDiscovery")
306
+ #if FLIR_ENABLED
307
+ discovery?.stop()
308
+ emitStateChange("idle")
309
+ #endif
310
+ }
311
+
312
+ private func disconnectInternalSync() {
313
+ NSLog("[FlirManager] disconnect")
314
+ #if FLIR_ENABLED
315
+ stopStreamInternalSync()
316
+ camera?.disconnect()
317
+ camera = nil
318
+ _isConnected = false
319
+ connectedDeviceId = nil
320
+ connectedDeviceName = nil
321
+ _latestImage = nil
322
+
323
+ DispatchQueue.main.async { [weak self] in
324
+ self?.delegate?.onDeviceDisconnected()
325
+ self?.emitStateChange("disconnected")
326
+ }
327
+ #endif
295
328
  }
296
329
 
297
330
  // MARK: - Streaming
@@ -345,13 +378,18 @@ import ThermalSDK
345
378
  #endif
346
379
 
347
380
  @objc public func stopStream() {
381
+ objc_sync_enter(stateLock); defer { objc_sync_exit(stateLock) }
382
+ stopStreamInternalSync()
383
+ }
384
+
385
+ private func stopStreamInternalSync() {
348
386
  NSLog("[FlirManager] stopStream")
349
387
 
350
388
  #if FLIR_ENABLED
389
+ _isStreaming = false
351
390
  stream?.stop()
352
391
  stream = nil
353
392
  streamer = nil
354
- _isStreaming = false
355
393
  _latestImage = nil
356
394
 
357
395
  if _isConnected {
@@ -364,14 +402,22 @@ import ThermalSDK
364
402
 
365
403
  @objc public func getTemperatureAt(x: Int, y: Int) -> Double {
366
404
  #if FLIR_ENABLED
367
- guard let streamer = streamer else { return Double.nan }
405
+ guard let streamer = streamer, _isStreaming else { return Double.nan }
368
406
 
369
407
  var result = Double.nan
370
408
  streamer.withThermalImage { thermalImage in
371
- let w = thermalImage.getWidth()
372
- let h = thermalImage.getHeight()
373
- let cx = max(0, min(Int(w) - 1, x))
374
- let cy = max(0, min(Int(h) - 1, y))
409
+ let w = Double(thermalImage.getWidth())
410
+ let h = Double(thermalImage.getHeight())
411
+
412
+ // Map incoming integer coordinates (from latestImage) to normalized, then to sensor
413
+ // This assumes x,y are in latestImage coordinate space.
414
+ guard let img = self.latestImage, img.size.width > 0, img.size.height > 0 else { return }
415
+
416
+ let nx = Double(x) / Double(img.size.width)
417
+ let ny = Double(y) / Double(img.size.height)
418
+
419
+ let cx = max(0, min(Int(w) - 1, Int(nx * w)))
420
+ let cy = max(0, min(Int(h) - 1, Int(ny * h)))
375
421
 
376
422
  if let measurements = thermalImage.measurements,
377
423
  let spot = try? measurements.addSpot(CGPoint(x: cx, y: cy)) {
@@ -393,10 +439,28 @@ import ThermalSDK
393
439
 
394
440
 
395
441
  @objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
396
- guard let img = latestImage else { return Double.nan }
397
- let px = Int(nx * Double(img.size.width))
398
- let py = Int(y * Double(img.size.height))
399
- return getTemperatureAt(x: px, y: py)
442
+ #if FLIR_ENABLED
443
+ guard let streamer = streamer, _isStreaming else { return Double.nan }
444
+
445
+ var result = Double.nan
446
+ streamer.withThermalImage { thermalImage in
447
+ let w = Double(thermalImage.getWidth())
448
+ let h = Double(thermalImage.getHeight())
449
+
450
+ // Map normalized (0.0 - 1.0) to actual sensor pixels
451
+ let cx = max(0, min(Int(w) - 1, Int(nx * w)))
452
+ let cy = max(0, min(Int(h) - 1, Int(y * h)))
453
+
454
+ if let measurements = thermalImage.measurements,
455
+ let spot = try? measurements.addSpot(CGPoint(x: cx, y: cy)) {
456
+ result = spot.getValue().value
457
+ try? measurements.remove(spot)
458
+ }
459
+ }
460
+ return result
461
+ #else
462
+ return Double.nan
463
+ #endif
400
464
  }
401
465
 
402
466
  // MARK: - Legacy / Compatibility Methods
@@ -604,13 +668,22 @@ extension FlirManager: FLIRStreamDelegate {
604
668
  _isProcessingFrame = true
605
669
  renderQueue.async { [weak self] in
606
670
  defer { self?._isProcessingFrame = false }
607
- guard let self = self, let streamer = self.streamer else {
608
- NSLog("[FLIR-TRACE ❌] No self or streamer in renderQueue")
671
+ guard let self = self, self._isStreaming, let streamer = self.streamer else {
672
+ NSLog("[FLIR-TRACE ❌] No self, streamer or not streaming in renderQueue")
609
673
  return
610
674
  }
611
675
 
612
676
  NSLog("[FLIR-TRACE 2️⃣] Processing on renderQueue")
613
677
 
678
+ objc_sync_enter(self.stateLock)
679
+ let currentStreamer = self.streamer
680
+ let streaming = self._isStreaming
681
+ objc_sync_exit(self.stateLock)
682
+
683
+ guard streaming, let streamer = currentStreamer else {
684
+ return
685
+ }
686
+
614
687
  do {
615
688
  try streamer.update()
616
689
  NSLog("[FLIR-TRACE 3️⃣] Streamer updated successfully")
@@ -161,7 +161,7 @@ RCT_EXPORT_METHOD(setNetworkDiscoveryEnabled : (BOOL)enabled resolver : (
161
161
  setBool:enabled
162
162
  forKey:@"ilabsFlir.networkDiscoveryEnabled"];
163
163
  }
164
- resolve(@(YES));
164
+ if (resolve) resolve(@(YES));
165
165
  }
166
166
 
167
167
  RCT_EXPORT_METHOD(startDiscovery : (RCTPromiseResolveBlock)
@@ -178,7 +178,7 @@ RCT_EXPORT_METHOD(startDiscovery : (RCTPromiseResolveBlock)
178
178
  NSLog(@"[FlirModule] [%@] ⏱ FlirManager.startDiscovery returned",
179
179
  [NSDate date]);
180
180
  }
181
- resolve(@(YES));
181
+ if (resolve) resolve(@(YES));
182
182
  });
183
183
  }
184
184
 
@@ -191,7 +191,7 @@ RCT_EXPORT_METHOD(stopDiscovery : (RCTPromiseResolveBlock)
191
191
  ((void (*)(id, SEL))objc_msgSend)(manager,
192
192
  sel_registerName("stopDiscovery"));
193
193
  }
194
- resolve(@(YES));
194
+ if (resolve) resolve(@(YES));
195
195
  });
196
196
  }
197
197
 
@@ -211,12 +211,16 @@ RCT_EXPORT_METHOD(getDiscoveredDevices : (RCTPromiseResolveBlock)
211
211
  }
212
212
  }
213
213
  }
214
- resolve(arr);
214
+ if (resolve) resolve(arr);
215
215
  });
216
216
  }
217
217
 
218
218
  RCT_EXPORT_METHOD(connectToDevice : (NSString *)deviceId resolver : (
219
219
  RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
220
+ if (!deviceId) {
221
+ if (reject) reject(@"ERR_INVALID_ARGS", @"deviceId is required", nil);
222
+ return;
223
+ }
220
224
  NSLog(@"[FlirModule] [%@] ⏱ RN->connectToDevice called for: %@",
221
225
  [NSDate date], deviceId);
222
226
  dispatch_async(dispatch_get_main_queue(), ^{
@@ -242,10 +246,10 @@ RCT_EXPORT_METHOD(connectToDevice : (NSString *)deviceId resolver : (
242
246
  [NSDate date]);
243
247
 
244
248
  // Resolve immediately - connection status will come via events
245
- resolve(@(YES));
249
+ if (resolve) resolve(@(YES));
246
250
  } else {
247
251
  NSLog(@"[FlirModule] [%@] ❌ FlirManager not found", [NSDate date]);
248
- reject(@"ERR_NO_MANAGER", @"FlirManager not found", nil);
252
+ if (reject) reject(@"ERR_NO_MANAGER", @"FlirManager not found", nil);
249
253
  }
250
254
  });
251
255
  }
@@ -261,7 +265,7 @@ RCT_EXPORT_METHOD(disconnect : (RCTPromiseResolveBlock)
261
265
  ((void (*)(id, SEL))objc_msgSend)(manager,
262
266
  sel_registerName("disconnect"));
263
267
  }
264
- resolve(@(YES));
268
+ if (resolve) resolve(@(YES));
265
269
  });
266
270
  }
267
271
 
@@ -274,7 +278,7 @@ RCT_EXPORT_METHOD(stopFlir : (RCTPromiseResolveBlock)
274
278
  [[FlirState shared] reset];
275
279
  ((void (*)(id, SEL))objc_msgSend)(manager, sel_registerName("stop"));
276
280
  }
277
- resolve(@(YES));
281
+ if (resolve) resolve(@(YES));
278
282
  });
279
283
  }
280
284
 
@@ -295,13 +299,13 @@ RCT_EXPORT_METHOD(startEmulator : (NSString *)emulatorType resolver : (
295
299
 
296
300
  // Initiate emulator start asynchronously
297
301
  ((void (*)(id, SEL, id))objc_msgSend)(
298
- manager, sel_registerName("startEmulatorWithType:"), emulatorType);
302
+ manager, sel_registerName("startEmulatorWithType:"), emulatorType ?: @"FLIR_ONE_EDGE");
299
303
 
300
304
  // Resolve immediately - connection status will come via events
301
- resolve(@(YES));
305
+ if (resolve) resolve(@(YES));
302
306
  } else {
303
307
  // Fallback if selector assumption wrong/mismatch
304
- reject(@"ERR_NOT_IMPL",
308
+ if (reject) reject(@"ERR_NOT_IMPL",
305
309
  @"startEmulator not implemented or signature mismatch", nil);
306
310
  self.connectResolve = nil;
307
311
  self.connectReject = nil;
@@ -323,9 +327,9 @@ RCT_EXPORT_METHOD(getTemperatureAt : (nonnull NSNumber *)x y : (
323
327
  [y intValue]);
324
328
  }
325
329
  if (isnan(temp)) {
326
- resolve([NSNull null]);
330
+ if (resolve) resolve([NSNull null]);
327
331
  } else {
328
- resolve(@(temp));
332
+ if (resolve) resolve(@(temp));
329
333
  }
330
334
  });
331
335
  }
@@ -346,9 +350,9 @@ RCT_EXPORT_METHOD(getTemperatureAtNormalized : (nonnull NSNumber *)nx y : (
346
350
  [nx doubleValue], [ny doubleValue]);
347
351
  }
348
352
  if (isnan(temp)) {
349
- resolve([NSNull null]);
353
+ if (resolve) resolve([NSNull null]);
350
354
  } else {
351
- resolve(@(temp));
355
+ if (resolve) resolve(@(temp));
352
356
  }
353
357
  });
354
358
  }
@@ -360,7 +364,7 @@ RCT_EXPORT_METHOD(getTemperatureFromColor : (NSInteger)color resolver : (
360
364
  int b = color & 0xFF;
361
365
  double lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
362
366
  double temp = (lum / 255.0) * 400.0;
363
- resolve(@(temp));
367
+ if (resolve) resolve(@(temp));
364
368
  }
365
369
 
366
370
  RCT_EXPORT_METHOD(isEmulator : (RCTPromiseResolveBlock)
@@ -373,7 +377,7 @@ RCT_EXPORT_METHOD(isEmulator : (RCTPromiseResolveBlock)
373
377
  isEm = ((BOOL (*)(id, SEL))objc_msgSend)(manager,
374
378
  sel_registerName("isEmulator"));
375
379
  }
376
- resolve(@(isEm));
380
+ if (resolve) resolve(@(isEm));
377
381
  });
378
382
  }
379
383
 
@@ -384,15 +388,15 @@ RCT_EXPORT_METHOD(getLatestFrameBitmap : (RCTPromiseResolveBlock)
384
388
  if (!manager ||
385
389
  ![manager
386
390
  respondsToSelector:sel_registerName("latestFrameBitmapBase64")]) {
387
- resolve([NSNull null]);
391
+ if (resolve) resolve([NSNull null]);
388
392
  return;
389
393
  }
390
394
  NSDictionary *dict = ((NSDictionary * (*)(id, SEL)) objc_msgSend)(
391
395
  manager, sel_registerName("latestFrameBitmapBase64"));
392
396
  if (!dict) {
393
- resolve([NSNull null]);
397
+ if (resolve) resolve([NSNull null]);
394
398
  } else {
395
- resolve(dict);
399
+ if (resolve) resolve(dict);
396
400
  }
397
401
  });
398
402
  }
@@ -407,7 +411,7 @@ RCT_EXPORT_METHOD(isDeviceConnected : (RCTPromiseResolveBlock)
407
411
  isC = ((BOOL (*)(id, SEL))objc_msgSend)(manager,
408
412
  sel_registerName("isConnected"));
409
413
  }
410
- resolve(@(isC));
414
+ if (resolve) resolve(@(isC));
411
415
  });
412
416
  }
413
417
 
@@ -421,19 +425,17 @@ RCT_EXPORT_METHOD(getConnectedDeviceInfo : (RCTPromiseResolveBlock)
421
425
  info = ((NSString * (*)(id, SEL)) objc_msgSend)(
422
426
  manager, sel_registerName("getConnectedDeviceInfo"));
423
427
  }
424
- resolve(info);
428
+ if (resolve) resolve(info);
425
429
  });
426
430
  }
427
431
 
428
- RCT_EXPORT_METHOD(isSDKDownloaded : (RCTPromiseResolveBlock)
429
- resolve rejecter : (RCTPromiseRejectBlock)reject) {
430
432
  // Assuming integrated SDK
431
- resolve(@(YES));
433
+ if (resolve) resolve(@(YES));
432
434
  }
433
435
 
434
436
  RCT_EXPORT_METHOD(getSDKStatus : (RCTPromiseResolveBlock)
435
437
  resolve rejecter : (RCTPromiseRejectBlock)reject) {
436
- resolve(@{@"available" : @(YES), @"arch" : @"arm64", @"platform" : @"iOS"});
438
+ if (resolve) resolve(@{@"available" : @(YES), @"arch" : @"arm64", @"platform" : @"iOS"});
437
439
  }
438
440
 
439
441
  RCT_EXPORT_METHOD(getBatteryLevel : (RCTPromiseResolveBlock)
@@ -446,7 +448,7 @@ RCT_EXPORT_METHOD(getBatteryLevel : (RCTPromiseResolveBlock)
446
448
  level = ((int (*)(id, SEL))objc_msgSend)(
447
449
  manager, sel_registerName("getBatteryLevel"));
448
450
  }
449
- resolve(@(level));
451
+ if (resolve) resolve(@(level));
450
452
  });
451
453
  }
452
454
 
@@ -460,7 +462,7 @@ RCT_EXPORT_METHOD(isBatteryCharging : (RCTPromiseResolveBlock)
460
462
  ch = ((BOOL (*)(id, SEL))objc_msgSend)(
461
463
  manager, sel_registerName("isBatteryCharging"));
462
464
  }
463
- resolve(@(ch));
465
+ if (resolve) resolve(@(ch));
464
466
  });
465
467
  }
466
468
 
@@ -473,7 +475,7 @@ RCT_EXPORT_METHOD(setPreferSdkRotation : (BOOL)prefer resolver : (
473
475
  ((void (*)(id, SEL, BOOL))objc_msgSend)(
474
476
  manager, sel_registerName("setPreferSdkRotation:"), prefer);
475
477
  }
476
- resolve(@(YES));
478
+ if (resolve) resolve(@(YES));
477
479
  });
478
480
  }
479
481
 
@@ -487,7 +489,7 @@ RCT_EXPORT_METHOD(isPreferSdkRotation : (RCTPromiseResolveBlock)
487
489
  v = ((BOOL (*)(id, SEL))objc_msgSend)(
488
490
  manager, sel_registerName("isPreferSdkRotation"));
489
491
  }
490
- resolve(@(v));
492
+ if (resolve) resolve(@(v));
491
493
  });
492
494
  }
493
495
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.31",
3
+ "version": "2.3.0",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",