react-native-polar-bridge 0.2.0 → 0.2.2
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.
|
@@ -17,12 +17,14 @@ import io.reactivex.rxjava3.core.Flowable
|
|
|
17
17
|
import io.reactivex.rxjava3.core.Single
|
|
18
18
|
import java.util.*
|
|
19
19
|
import java.util.concurrent.atomic.AtomicInteger
|
|
20
|
+
import java.util.concurrent.TimeUnit
|
|
20
21
|
import java.time.Instant
|
|
21
22
|
|
|
22
23
|
@ReactModule(name = PolarBridgeModule.NAME)
|
|
23
24
|
class PolarBridgeModule(reactContext: ReactApplicationContext) :
|
|
24
25
|
NativePolarBridgeSpec(reactContext) {
|
|
25
26
|
private val reactContext: ReactApplicationContext = reactContext
|
|
27
|
+
private val SENSOR_BUFFER_SECONDS = 10L
|
|
26
28
|
|
|
27
29
|
override fun getName(): String {
|
|
28
30
|
return NAME
|
|
@@ -617,17 +619,34 @@ class PolarBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
617
619
|
}
|
|
618
620
|
}
|
|
619
621
|
|
|
622
|
+
data class HrSampleWithTimestamp(
|
|
623
|
+
val data: PolarHrData.PolarHrSample,
|
|
624
|
+
val timestampMs: Long
|
|
625
|
+
)
|
|
626
|
+
|
|
620
627
|
override fun fetchHrData(deviceId: String) {
|
|
621
628
|
Log.e(TAG, "Fetch Heart Data called on: $deviceId ")
|
|
622
629
|
val isDisposed = hrDisposable?.isDisposed ?: true
|
|
623
630
|
try{
|
|
624
631
|
if (isDisposed) {
|
|
625
632
|
hrDisposable = api.startHrStreaming(deviceId)
|
|
633
|
+
.flatMapIterable { hrData ->
|
|
634
|
+
hrData.samples.map { sample ->
|
|
635
|
+
HrSampleWithTimestamp(
|
|
636
|
+
data = sample,
|
|
637
|
+
timestampMs = System.currentTimeMillis()
|
|
638
|
+
)
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
.buffer(SENSOR_BUFFER_SECONDS, TimeUnit.SECONDS)
|
|
626
642
|
.observeOn(AndroidSchedulers.mainThread())
|
|
627
643
|
.subscribe(
|
|
628
|
-
{
|
|
629
|
-
|
|
630
|
-
|
|
644
|
+
{ samples ->
|
|
645
|
+
if (samples.isEmpty()) return@subscribe
|
|
646
|
+
|
|
647
|
+
Log.d(TAG, "Flushing HR buffer (${samples.size} samples)")
|
|
648
|
+
for (item in samples) {
|
|
649
|
+
val sample = item.data
|
|
631
650
|
Log.d(TAG, "HR bpm: ${sample.hr} " +
|
|
632
651
|
"rrs: ${sample.rrsMs} " +
|
|
633
652
|
"rrAvailable: ${sample.rrAvailable} " +
|
|
@@ -645,6 +664,7 @@ class PolarBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
645
664
|
event.putBoolean("rrAvailable", sample.rrAvailable)
|
|
646
665
|
event.putBoolean("contactStatus", sample.contactStatus)
|
|
647
666
|
event.putBoolean("contactStatusSupported", sample.contactStatusSupported)
|
|
667
|
+
event.putDouble("timestamp", item.timestampMs.toDouble())
|
|
648
668
|
|
|
649
669
|
sendEvent("PolarHrData", event)
|
|
650
670
|
}
|
|
@@ -719,10 +739,15 @@ class PolarBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
719
739
|
.flatMap { settings: PolarSensorSetting ->
|
|
720
740
|
api.startAccStreaming(deviceId, settings)
|
|
721
741
|
}
|
|
742
|
+
.flatMapIterable { it.samples }
|
|
743
|
+
.buffer(SENSOR_BUFFER_SECONDS, TimeUnit.SECONDS)
|
|
722
744
|
.observeOn(AndroidSchedulers.mainThread())
|
|
723
745
|
.subscribe(
|
|
724
|
-
{
|
|
725
|
-
|
|
746
|
+
{ samples ->
|
|
747
|
+
if (samples.isEmpty()) return@subscribe
|
|
748
|
+
Log.d(TAG, "Flushing ACC buffer (${samples.size} samples)")
|
|
749
|
+
|
|
750
|
+
for (data in samples) {
|
|
726
751
|
Log.d(TAG, "ACC x: ${data.x} y: ${data.y} z: ${data.z} timeStamp: ${data.timeStamp}")
|
|
727
752
|
|
|
728
753
|
val event: WritableMap = Arguments.createMap()
|
|
@@ -770,10 +795,16 @@ class PolarBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
770
795
|
.flatMap { settings: PolarSensorSetting ->
|
|
771
796
|
api.startGyroStreaming(deviceId, settings)
|
|
772
797
|
}
|
|
798
|
+
.flatMapIterable { it.samples }
|
|
799
|
+
.buffer(SENSOR_BUFFER_SECONDS, TimeUnit.SECONDS)
|
|
773
800
|
.observeOn(AndroidSchedulers.mainThread())
|
|
774
801
|
.subscribe(
|
|
775
|
-
{
|
|
776
|
-
|
|
802
|
+
{ samples ->
|
|
803
|
+
if (samples.isEmpty()) return@subscribe
|
|
804
|
+
|
|
805
|
+
Log.d(TAG, "Flushing GYR buffer (${samples.size} samples)")
|
|
806
|
+
|
|
807
|
+
for (data in samples) {
|
|
777
808
|
Log.d(TAG, "GYR x: ${data.x} y: ${data.y} z: ${data.z} timeStamp: ${data.timeStamp}")
|
|
778
809
|
|
|
779
810
|
val event: WritableMap = Arguments.createMap()
|
|
@@ -822,25 +853,31 @@ class PolarBridgeModule(reactContext: ReactApplicationContext) :
|
|
|
822
853
|
.flatMap { settings: PolarSensorSetting ->
|
|
823
854
|
api.startPpgStreaming(deviceId, settings)
|
|
824
855
|
}
|
|
856
|
+
.filter { it.type == PolarPpgData.PpgDataType.PPG3_AMBIENT1 }
|
|
857
|
+
.flatMapIterable { it.samples }
|
|
858
|
+
.buffer(SENSOR_BUFFER_SECONDS, TimeUnit.SECONDS)
|
|
859
|
+
.observeOn(AndroidSchedulers.mainThread())
|
|
825
860
|
.subscribe(
|
|
826
|
-
{
|
|
827
|
-
if (
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
861
|
+
{ samples ->
|
|
862
|
+
if (samples.isEmpty()) return@subscribe
|
|
863
|
+
|
|
864
|
+
Log.d(TAG, "Flushing PPG buffer (${samples.size} samples)")
|
|
865
|
+
|
|
866
|
+
for (data in samples) {
|
|
867
|
+
Log.d(TAG, "PPG ppg0: ${data.channelSamples[0]} ppg1: ${data.channelSamples[1]} ppg2: ${data.channelSamples[2]} ambient: ${data.channelSamples[3]} timeStamp: ${data.timeStamp}")
|
|
868
|
+
|
|
869
|
+
val event: WritableMap = Arguments.createMap()
|
|
870
|
+
// Float not supported
|
|
871
|
+
// See: https://github.com/facebook/react-native/issues/9685
|
|
872
|
+
// See: https://javadoc.io/doc/com.facebook.react/react-native/0.20.1/com/facebook/react/bridge/WritableMap.html
|
|
873
|
+
event.putString("ppg0", "${data.channelSamples[0]}")
|
|
874
|
+
event.putString("ppg1", "${data.channelSamples[1]}")
|
|
875
|
+
event.putString("ppg2", "${data.channelSamples[2]}")
|
|
876
|
+
event.putString("ambient", "${data.channelSamples[3]}")
|
|
877
|
+
// Long not supported, use double as workaround
|
|
878
|
+
event.putDouble("ppgTimestamp", data.timeStamp.toDouble())
|
|
879
|
+
|
|
880
|
+
sendEvent("PolarPpgData", event)
|
|
844
881
|
}
|
|
845
882
|
},
|
|
846
883
|
{ error: Throwable ->
|
package/ios/PolarBridge.swift
CHANGED
|
@@ -46,6 +46,9 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
46
46
|
private var isPpgStreaming = false
|
|
47
47
|
private let disposeBag = DisposeBag()
|
|
48
48
|
|
|
49
|
+
/// Flush interval for all sensor buffers (seconds)
|
|
50
|
+
private let sensorFlushInterval: TimeInterval = 10.0
|
|
51
|
+
|
|
49
52
|
@objc
|
|
50
53
|
func multiply(_ a: NSNumber,withB b: NSNumber) -> NSNumber {
|
|
51
54
|
let result = a.doubleValue * b.doubleValue
|
|
@@ -158,6 +161,53 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
158
161
|
.asObservable()
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
private var hrBuffer: [[String: Any]] = []
|
|
165
|
+
private let hrBufferQueue = DispatchQueue(label: "com.polarbridge.hrBufferQueue")
|
|
166
|
+
private var hrFlushTimer: Timer?
|
|
167
|
+
|
|
168
|
+
private func startHrFlushTimer() {
|
|
169
|
+
stopHrFlushTimer()
|
|
170
|
+
|
|
171
|
+
DispatchQueue.main.async { [weak self] in
|
|
172
|
+
guard let self = self else { return }
|
|
173
|
+
|
|
174
|
+
self.hrFlushTimer = Timer(
|
|
175
|
+
timeInterval: self.sensorFlushInterval,
|
|
176
|
+
repeats: true
|
|
177
|
+
) { [weak self] _ in
|
|
178
|
+
self?.flushHrBuffer()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
RunLoop.main.add(self.hrFlushTimer!, forMode: .common)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func stopHrFlushTimer() {
|
|
186
|
+
DispatchQueue.main.async { [weak self] in
|
|
187
|
+
self?.hrFlushTimer?.invalidate()
|
|
188
|
+
self?.hrFlushTimer = nil
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private func flushHrBuffer() {
|
|
193
|
+
hrBufferQueue.async { [weak self] in
|
|
194
|
+
guard let self = self else { return }
|
|
195
|
+
guard !self.hrBuffer.isEmpty else { return }
|
|
196
|
+
|
|
197
|
+
let events = self.hrBuffer
|
|
198
|
+
self.hrBuffer.removeAll()
|
|
199
|
+
|
|
200
|
+
DispatchQueue.main.async {
|
|
201
|
+
for event in events {
|
|
202
|
+
self.sendEvent(
|
|
203
|
+
withName: PolarEvent.PolarHrData.rawValue,
|
|
204
|
+
body: event
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
161
211
|
@objc(fetchHrData:)
|
|
162
212
|
func fetchHrData(_ deviceId: String) {
|
|
163
213
|
NSLog("PolarBridge: Fetch HR Data called on: \(deviceId)")
|
|
@@ -170,12 +220,17 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
170
220
|
// Dispose previous subscription if running
|
|
171
221
|
if isHrStreaming {
|
|
172
222
|
disposeHrStream()
|
|
223
|
+
stopHrFlushTimer()
|
|
224
|
+
flushHrBuffer()
|
|
225
|
+
isHrStreaming = false
|
|
226
|
+
|
|
173
227
|
NSLog("PolarBridge: HR Stream stopped")
|
|
174
228
|
sendEvent(withName: PolarEvent.PolarHrComplete.rawValue, body: ["message": "HR Stream stopped"])
|
|
175
229
|
return
|
|
176
230
|
}
|
|
177
231
|
|
|
178
232
|
isHrStreaming = true
|
|
233
|
+
startHrFlushTimer()
|
|
179
234
|
|
|
180
235
|
// Start HR streaming
|
|
181
236
|
hrDisposable = api.startHrStreaming(deviceId)
|
|
@@ -185,28 +240,39 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
185
240
|
guard let self = self else { return }
|
|
186
241
|
NSLog("PolarBridge: HR data samples count: \(hrData.count)")
|
|
187
242
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// Create dictionary to send as event
|
|
192
|
-
var event: [String: Any] = [:]
|
|
193
|
-
event["hr"] = sample.hr
|
|
194
|
-
event["rrsMs"] = sample.rrsMs
|
|
195
|
-
event["rrAvailable"] = sample.rrAvailable
|
|
196
|
-
event["contactStatus"] = sample.contactStatus
|
|
197
|
-
event["contactStatusSupported"] = sample.contactStatusSupported
|
|
243
|
+
self.hrBufferQueue.async {
|
|
244
|
+
for sample in hrData {
|
|
245
|
+
NSLog("HR bpm: \(sample.hr), rrs: \(sample.rrsMs), rrAvailable: \(sample.rrAvailable), contactStatus: \(sample.contactStatus), contactStatusSupported: \(sample.contactStatusSupported)")
|
|
198
246
|
|
|
199
|
-
|
|
247
|
+
// Create dictionary to send as event
|
|
248
|
+
var event: [String: Any] = [:]
|
|
249
|
+
event["hr"] = sample.hr
|
|
250
|
+
event["rrsMs"] = sample.rrsMs
|
|
251
|
+
event["rrAvailable"] = sample.rrAvailable
|
|
252
|
+
event["contactStatus"] = sample.contactStatus
|
|
253
|
+
event["contactStatusSupported"] = sample.contactStatusSupported
|
|
254
|
+
event["timestamp"] = Date().timeIntervalSince1970 * 1000
|
|
255
|
+
|
|
256
|
+
// self.sendEvent(withName: PolarEvent.PolarHrData.rawValue, body: event)
|
|
257
|
+
self.hrBuffer.append(event)
|
|
258
|
+
}
|
|
200
259
|
}
|
|
201
260
|
},
|
|
202
261
|
onError: { [weak self] error in
|
|
203
262
|
guard let self = self else { return }
|
|
204
263
|
NSLog("PolarBridge: HR stream failed: \(error.localizedDescription)")
|
|
264
|
+
self.stopHrFlushTimer()
|
|
265
|
+
self.flushHrBuffer()
|
|
266
|
+
self.isHrStreaming = false
|
|
267
|
+
|
|
205
268
|
self.sendEvent(withName: PolarEvent.PolarHrError.rawValue, body: ["error": error.localizedDescription])
|
|
206
269
|
},
|
|
207
270
|
onCompleted: { [weak self] in
|
|
208
271
|
guard let self = self else { return }
|
|
209
272
|
NSLog("PolarBridge: HR stream complete")
|
|
273
|
+
self.stopHrFlushTimer()
|
|
274
|
+
self.flushHrBuffer()
|
|
275
|
+
self.isHrStreaming = false
|
|
210
276
|
self.sendEvent(withName: PolarEvent.PolarHrComplete.rawValue, body: ["message": "HR stream complete"])
|
|
211
277
|
}
|
|
212
278
|
)
|
|
@@ -215,6 +281,64 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
215
281
|
|
|
216
282
|
}
|
|
217
283
|
|
|
284
|
+
// ACC
|
|
285
|
+
private var accBuffer: [[String: Any]] = []
|
|
286
|
+
private let accBufferQueue = DispatchQueue(label: "com.polarbridge.accBufferQueue")
|
|
287
|
+
private var accFlushTimer: Timer?
|
|
288
|
+
|
|
289
|
+
// GYR
|
|
290
|
+
private var gyrBuffer: [[String: Any]] = []
|
|
291
|
+
private let gyrBufferQueue = DispatchQueue(label: "com.polarbridge.gyrBufferQueue")
|
|
292
|
+
private var gyrFlushTimer: Timer?
|
|
293
|
+
|
|
294
|
+
// PPG
|
|
295
|
+
private var ppgBuffer: [[String: Any]] = []
|
|
296
|
+
private let ppgBufferQueue = DispatchQueue(label: "com.polarbridge.ppgBufferQueue")
|
|
297
|
+
private var ppgFlushTimer: Timer?
|
|
298
|
+
|
|
299
|
+
private func startAccFlushTimer() {
|
|
300
|
+
stopAccFlushTimer()
|
|
301
|
+
|
|
302
|
+
DispatchQueue.main.async { [weak self] in
|
|
303
|
+
guard let self = self else { return }
|
|
304
|
+
|
|
305
|
+
self.accFlushTimer = Timer(
|
|
306
|
+
timeInterval: self.sensorFlushInterval,
|
|
307
|
+
repeats: true
|
|
308
|
+
) { [weak self] _ in
|
|
309
|
+
self?.flushAccBuffer()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
RunLoop.main.add(self.accFlushTimer!, forMode: .common)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private func stopAccFlushTimer() {
|
|
317
|
+
DispatchQueue.main.async { [weak self] in
|
|
318
|
+
self?.accFlushTimer?.invalidate()
|
|
319
|
+
self?.accFlushTimer = nil
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private func flushAccBuffer() {
|
|
324
|
+
accBufferQueue.async { [weak self] in
|
|
325
|
+
guard let self = self else { return }
|
|
326
|
+
guard !self.accBuffer.isEmpty else { return }
|
|
327
|
+
|
|
328
|
+
let events = self.accBuffer
|
|
329
|
+
self.accBuffer.removeAll()
|
|
330
|
+
|
|
331
|
+
DispatchQueue.main.async {
|
|
332
|
+
for event in events {
|
|
333
|
+
self.sendEvent(
|
|
334
|
+
withName: PolarEvent.PolarAccData.rawValue,
|
|
335
|
+
body: event
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
218
342
|
@objc(fetchAccData:)
|
|
219
343
|
func fetchAccData(_ deviceId: String) {
|
|
220
344
|
NSLog("PolarBridge: Fetch ACC Data called on: \(deviceId)")
|
|
@@ -227,12 +351,17 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
227
351
|
// Stop existing ACC stream if running
|
|
228
352
|
if isAccStreaming {
|
|
229
353
|
disposeAccStream()
|
|
354
|
+
stopAccFlushTimer()
|
|
355
|
+
flushAccBuffer()
|
|
356
|
+
isAccStreaming = false
|
|
357
|
+
|
|
230
358
|
NSLog("PolarBridge: ACC Stream stopped")
|
|
231
359
|
sendEvent(withName: PolarEvent.PolarAccComplete.rawValue, body: ["message": "ACC Stream stopped"])
|
|
232
360
|
return
|
|
233
361
|
}
|
|
234
362
|
|
|
235
363
|
isAccStreaming = true
|
|
364
|
+
startAccFlushTimer()
|
|
236
365
|
|
|
237
366
|
accDisposable = requestStreamSettings(deviceId, feature: .acc)
|
|
238
367
|
.flatMap { settings in
|
|
@@ -243,21 +372,28 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
243
372
|
onNext: { [weak self] accData in
|
|
244
373
|
guard let self = self else { return }
|
|
245
374
|
|
|
246
|
-
|
|
247
|
-
|
|
375
|
+
self.accBufferQueue.async {
|
|
376
|
+
for sample in accData.samples {
|
|
377
|
+
NSLog("ACC x: \(sample.x) y: \(sample.y) z: \(sample.z) timestamp: \(sample.timeStamp)")
|
|
248
378
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
379
|
+
var event: [String: Any] = [:]
|
|
380
|
+
event["accX"] = sample.x
|
|
381
|
+
event["accY"] = sample.y
|
|
382
|
+
event["accZ"] = sample.z
|
|
383
|
+
event["accTimestamp"] = Double(sample.timeStamp) // RN doesn’t support int64
|
|
254
384
|
|
|
255
|
-
|
|
385
|
+
self.accBuffer.append(event)
|
|
386
|
+
// self.sendEvent(withName: PolarEvent.PolarAccData.rawValue, body: event)
|
|
387
|
+
}
|
|
256
388
|
}
|
|
257
389
|
},
|
|
258
390
|
onError: { [weak self] error in
|
|
259
391
|
guard let self = self else { return }
|
|
260
392
|
|
|
393
|
+
self.stopAccFlushTimer()
|
|
394
|
+
self.flushAccBuffer()
|
|
395
|
+
self.isAccStreaming = false
|
|
396
|
+
|
|
261
397
|
NSLog("PolarBridge: ACC stream failed: \(error.localizedDescription)")
|
|
262
398
|
|
|
263
399
|
self.sendEvent(
|
|
@@ -270,6 +406,10 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
270
406
|
onCompleted: { [weak self] in
|
|
271
407
|
guard let self = self else { return }
|
|
272
408
|
|
|
409
|
+
self.stopAccFlushTimer()
|
|
410
|
+
self.flushAccBuffer()
|
|
411
|
+
self.isAccStreaming = false
|
|
412
|
+
|
|
273
413
|
NSLog("PolarBridge: ACC stream complete")
|
|
274
414
|
|
|
275
415
|
self.sendEvent(
|
|
@@ -284,6 +424,49 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
284
424
|
accDisposable?.disposed(by: disposeBag)
|
|
285
425
|
}
|
|
286
426
|
|
|
427
|
+
private func startGyrFlushTimer() {
|
|
428
|
+
stopGyrFlushTimer()
|
|
429
|
+
|
|
430
|
+
DispatchQueue.main.async { [weak self] in
|
|
431
|
+
guard let self = self else { return }
|
|
432
|
+
|
|
433
|
+
self.gyrFlushTimer = Timer(
|
|
434
|
+
timeInterval: self.sensorFlushInterval,
|
|
435
|
+
repeats: true
|
|
436
|
+
) { [weak self] _ in
|
|
437
|
+
self?.flushGyrBuffer()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
RunLoop.main.add(self.gyrFlushTimer!, forMode: .common)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private func stopGyrFlushTimer() {
|
|
445
|
+
DispatchQueue.main.async { [weak self] in
|
|
446
|
+
self?.gyrFlushTimer?.invalidate()
|
|
447
|
+
self?.gyrFlushTimer = nil
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private func flushGyrBuffer() {
|
|
452
|
+
gyrBufferQueue.async { [weak self] in
|
|
453
|
+
guard let self = self else { return }
|
|
454
|
+
guard !self.gyrBuffer.isEmpty else { return }
|
|
455
|
+
|
|
456
|
+
let events = self.gyrBuffer
|
|
457
|
+
self.gyrBuffer.removeAll()
|
|
458
|
+
|
|
459
|
+
DispatchQueue.main.async {
|
|
460
|
+
for event in events {
|
|
461
|
+
self.sendEvent(
|
|
462
|
+
withName: PolarEvent.PolarGyrData.rawValue,
|
|
463
|
+
body: event
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
287
470
|
@objc(fetchGyrData:)
|
|
288
471
|
func fetchGyrData(_ deviceId: String) {
|
|
289
472
|
NSLog("PolarBridge: Fetch Gyroscope Data called on: \(deviceId)")
|
|
@@ -295,12 +478,17 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
295
478
|
|
|
296
479
|
if isGyrStreaming {
|
|
297
480
|
disposeGyrStream()
|
|
481
|
+
stopGyrFlushTimer()
|
|
482
|
+
flushGyrBuffer()
|
|
483
|
+
isGyrStreaming = false
|
|
484
|
+
|
|
298
485
|
NSLog("PolarBridge: GYR Stream stopped")
|
|
299
486
|
sendEvent(withName: PolarEvent.PolarAccComplete.rawValue, body: ["message": "GYR Stream stopped"])
|
|
300
487
|
return
|
|
301
488
|
}
|
|
302
489
|
|
|
303
490
|
isGyrStreaming = true
|
|
491
|
+
startGyrFlushTimer()
|
|
304
492
|
|
|
305
493
|
gyrDisposable = requestStreamSettings(deviceId, feature: .gyro)
|
|
306
494
|
.flatMap { settings in
|
|
@@ -311,28 +499,35 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
311
499
|
onNext: { [weak self] gyrData in
|
|
312
500
|
guard let self = self else { return }
|
|
313
501
|
|
|
314
|
-
|
|
315
|
-
|
|
502
|
+
self.gyrBufferQueue.async {
|
|
503
|
+
for sample in gyrData.samples {
|
|
504
|
+
NSLog("GYR x: \(sample.x) y: \(sample.y) z: \(sample.z) timestamp: \(sample.timeStamp)")
|
|
316
505
|
|
|
317
|
-
|
|
506
|
+
var event: [String: Any] = [:]
|
|
318
507
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
508
|
+
// JS does NOT support Float — convert to String
|
|
509
|
+
event["gyrX"] = "\(sample.x)"
|
|
510
|
+
event["gyrY"] = "\(sample.y)"
|
|
511
|
+
event["gyrZ"] = "\(sample.z)"
|
|
323
512
|
|
|
324
|
-
|
|
325
|
-
|
|
513
|
+
// JS does NOT support Int64 — convert to Double
|
|
514
|
+
event["gyrTimestamp"] = Double(sample.timeStamp)
|
|
326
515
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
516
|
+
self.gyrBuffer.append(event)
|
|
517
|
+
// self.sendEvent(
|
|
518
|
+
// withName: PolarEvent.PolarGyrData.rawValue,
|
|
519
|
+
// body: event
|
|
520
|
+
// )
|
|
521
|
+
}
|
|
331
522
|
}
|
|
332
523
|
},
|
|
333
524
|
onError: { [weak self] error in
|
|
334
525
|
guard let self = self else { return }
|
|
335
526
|
|
|
527
|
+
stopGyrFlushTimer()
|
|
528
|
+
flushGyrBuffer()
|
|
529
|
+
isGyrStreaming = false
|
|
530
|
+
|
|
336
531
|
NSLog("PolarBridge: GYR stream failed: \(error.localizedDescription)")
|
|
337
532
|
|
|
338
533
|
self.sendEvent(
|
|
@@ -345,6 +540,10 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
345
540
|
onCompleted: { [weak self] in
|
|
346
541
|
guard let self = self else { return }
|
|
347
542
|
|
|
543
|
+
stopGyrFlushTimer()
|
|
544
|
+
flushGyrBuffer()
|
|
545
|
+
isGyrStreaming = false
|
|
546
|
+
|
|
348
547
|
NSLog("PolarBridge: GYR stream complete")
|
|
349
548
|
|
|
350
549
|
self.sendEvent(
|
|
@@ -359,6 +558,49 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
359
558
|
gyrDisposable?.disposed(by: disposeBag)
|
|
360
559
|
}
|
|
361
560
|
|
|
561
|
+
private func startPpgFlushTimer() {
|
|
562
|
+
stopPpgFlushTimer()
|
|
563
|
+
|
|
564
|
+
DispatchQueue.main.async { [weak self] in
|
|
565
|
+
guard let self = self else { return }
|
|
566
|
+
|
|
567
|
+
self.ppgFlushTimer = Timer(
|
|
568
|
+
timeInterval: self.sensorFlushInterval,
|
|
569
|
+
repeats: true
|
|
570
|
+
) { [weak self] _ in
|
|
571
|
+
self?.flushPpgBuffer()
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
RunLoop.main.add(self.ppgFlushTimer!, forMode: .common)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private func stopPpgFlushTimer() {
|
|
579
|
+
DispatchQueue.main.async { [weak self] in
|
|
580
|
+
self?.ppgFlushTimer?.invalidate()
|
|
581
|
+
self?.ppgFlushTimer = nil
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private func flushPpgBuffer() {
|
|
586
|
+
ppgBufferQueue.async { [weak self] in
|
|
587
|
+
guard let self = self else { return }
|
|
588
|
+
guard !self.ppgBuffer.isEmpty else { return }
|
|
589
|
+
|
|
590
|
+
let events = self.ppgBuffer
|
|
591
|
+
self.ppgBuffer.removeAll()
|
|
592
|
+
|
|
593
|
+
DispatchQueue.main.async {
|
|
594
|
+
for event in events {
|
|
595
|
+
self.sendEvent(
|
|
596
|
+
withName: PolarEvent.PolarPpgData.rawValue,
|
|
597
|
+
body: event
|
|
598
|
+
)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
362
604
|
@objc(fetchPpgData:)
|
|
363
605
|
func fetchPpgData(_ deviceId: String) {
|
|
364
606
|
NSLog("PolarBridge: Fetch PPG Data called on: \(deviceId)")
|
|
@@ -370,13 +612,17 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
370
612
|
|
|
371
613
|
if isPpgStreaming {
|
|
372
614
|
disposePpgStream()
|
|
615
|
+
stopPpgFlushTimer()
|
|
616
|
+
flushPpgBuffer()
|
|
617
|
+
isPpgStreaming = false
|
|
618
|
+
|
|
373
619
|
NSLog("PolarBridge: PPG Stream stopped")
|
|
374
620
|
sendEvent(withName: PolarEvent.PolarAccComplete.rawValue, body: ["message": "PPG Stream stopped"])
|
|
375
621
|
return
|
|
376
622
|
}
|
|
377
623
|
|
|
378
624
|
isPpgStreaming = true
|
|
379
|
-
|
|
625
|
+
startPpgFlushTimer()
|
|
380
626
|
|
|
381
627
|
ppgDisposable = requestStreamSettings(deviceId, feature: .ppg)
|
|
382
628
|
.flatMap { settings in
|
|
@@ -389,31 +635,38 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
389
635
|
|
|
390
636
|
// Only handle PPG3_AMBIENT1 type (just like Kotlin)
|
|
391
637
|
if ppgData.type == PpgDataType.ppg3_ambient1 {
|
|
392
|
-
|
|
638
|
+
self.ppgBufferQueue.async {
|
|
639
|
+
for sample in ppgData.samples {
|
|
393
640
|
|
|
394
|
-
|
|
641
|
+
NSLog("PPG ppg0: \(sample.channelSamples[0]) ppg1: \(sample.channelSamples[1]) ppg2: \(sample.channelSamples[2]) ambient: \(sample.channelSamples[3]) ts: \(sample.timeStamp)")
|
|
395
642
|
|
|
396
|
-
|
|
643
|
+
var event: [String: Any] = [:]
|
|
397
644
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
645
|
+
// Float → String (React Native cannot handle Float)
|
|
646
|
+
event["ppg0"] = "\(sample.channelSamples[0])"
|
|
647
|
+
event["ppg1"] = "\(sample.channelSamples[1])"
|
|
648
|
+
event["ppg2"] = "\(sample.channelSamples[2])"
|
|
649
|
+
event["ambient"] = "\(sample.channelSamples[3])"
|
|
403
650
|
|
|
404
|
-
|
|
405
|
-
|
|
651
|
+
// Int64 timestamp → Double
|
|
652
|
+
event["ppgTimestamp"] = Double(sample.timeStamp)
|
|
406
653
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
654
|
+
self.ppgBuffer.append(event)
|
|
655
|
+
// self.sendEvent(
|
|
656
|
+
// withName: PolarEvent.PolarPpgData.rawValue,
|
|
657
|
+
// body: event
|
|
658
|
+
// )
|
|
659
|
+
}
|
|
411
660
|
}
|
|
412
661
|
}
|
|
413
662
|
},
|
|
414
663
|
onError: { [weak self] error in
|
|
415
664
|
guard let self = self else { return }
|
|
416
665
|
|
|
666
|
+
self.stopPpgFlushTimer()
|
|
667
|
+
self.flushPpgBuffer()
|
|
668
|
+
self.isPpgStreaming = false
|
|
669
|
+
|
|
417
670
|
NSLog("PolarBridge: PPG stream failed: \(error.localizedDescription)")
|
|
418
671
|
|
|
419
672
|
self.sendEvent(
|
|
@@ -426,6 +679,10 @@ class PolarBridge: RCTEventEmitter, ObservableObject
|
|
|
426
679
|
onCompleted: { [weak self] in
|
|
427
680
|
guard let self = self else { return }
|
|
428
681
|
|
|
682
|
+
self.stopPpgFlushTimer()
|
|
683
|
+
self.flushPpgBuffer()
|
|
684
|
+
self.isPpgStreaming = false
|
|
685
|
+
|
|
429
686
|
NSLog("PolarBridge: PPG stream complete")
|
|
430
687
|
|
|
431
688
|
self.sendEvent(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PolarDataModel.d.ts","sourceRoot":"","sources":["../../../src/PolarDataModel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"PolarDataModel.d.ts","sourceRoot":"","sources":["../../../src/PolarDataModel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,sBAAsB,EAAE,OAAO,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAG,MAAM,CAAC;CACvB,CAAC"}
|
package/package.json
CHANGED