2 package polar.com.sdk.impl;
4 import android.annotation.SuppressLint;
5 import android.bluetooth.le.ScanFilter;
6 import android.content.Context;
7 import android.os.Build;
8 import android.os.ParcelUuid;
9 import android.support.annotation.Nullable;
10 import android.util.Pair;
12 import com.androidcommunications.polar.api.ble.BleDeviceListener;
13 import com.androidcommunications.polar.api.ble.BleLogger;
14 import com.androidcommunications.polar.api.ble.exceptions.BleDisconnected;
15 import com.androidcommunications.polar.api.ble.model.BleDeviceSession;
16 import com.androidcommunications.polar.api.ble.model.advertisement.BleAdvertisementContent;
17 import com.androidcommunications.polar.api.ble.model.advertisement.BlePolarHrAdvertisement;
18 import com.androidcommunications.polar.api.ble.model.gatt.BleGattBase;
19 import com.androidcommunications.polar.api.ble.model.gatt.client.BleBattClient;
20 import com.androidcommunications.polar.api.ble.model.gatt.client.BleDisClient;
21 import com.androidcommunications.polar.api.ble.model.gatt.client.BleHrClient;
22 import com.androidcommunications.polar.api.ble.model.gatt.client.BlePMDClient;
23 import com.androidcommunications.polar.api.ble.model.gatt.client.psftp.BlePsFtpClient;
24 import com.androidcommunications.polar.api.ble.model.gatt.client.psftp.BlePsFtpUtils;
25 import com.androidcommunications.polar.common.ble.BleUtils;
26 import com.androidcommunications.polar.enpoints.ble.bluedroid.host.BDDeviceListenerImpl;
28 import org.reactivestreams.Publisher;
30 import java.io.ByteArrayOutputStream;
31 import java.text.SimpleDateFormat;
32 import java.util.ArrayList;
33 import java.util.Calendar;
34 import java.util.Collections;
35 import java.util.Comparator;
36 import java.util.Date;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Locale;
43 import java.util.UUID;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.atomic.AtomicInteger;
47 import fi.polar.remote.representation.protobuf.ExerciseSamples;
48 import fi.polar.remote.representation.protobuf.Types;
49 import io.reactivex.Completable;
50 import io.reactivex.CompletableEmitter;
51 import io.reactivex.CompletableOnSubscribe;
52 import io.reactivex.CompletableSource;
53 import io.reactivex.Flowable;
54 import io.reactivex.Scheduler;
55 import io.reactivex.Single;
56 import io.reactivex.SingleSource;
57 import io.reactivex.android.schedulers.AndroidSchedulers;
58 import io.reactivex.disposables.Disposable;
59 import io.reactivex.functions.Action;
60 import io.reactivex.functions.BiFunction;
61 import io.reactivex.functions.Consumer;
62 import io.reactivex.functions.Function;
63 import io.reactivex.functions.Predicate;
64 import io.reactivex.schedulers.Timed;
84 import protocol.PftpRequest;
85 import protocol.PftpResponse;
87 import static com.androidcommunications.polar.api.ble.model.BleDeviceSession.DeviceSessionState.SESSION_CLOSED;
88 import static com.androidcommunications.polar.api.ble.model.BleDeviceSession.DeviceSessionState.SESSION_OPEN;
89 import static com.androidcommunications.polar.api.ble.model.BleDeviceSession.DeviceSessionState.SESSION_OPENING;
95 BleDeviceListener.BleDeviceSessionStateChangedCallback,
96 BleDeviceListener.BlePowerStateChangedCallback {
104 BleDeviceListener.BleSearchPreFilter filter =
new BleDeviceListener.BleSearchPreFilter() {
106 public boolean process(BleAdvertisementContent content) {
107 return content.getPolarDeviceId().length() != 0 && !content.getPolarDeviceType().equals(
"mobile");
111 @SuppressLint({
"NewApi",
"CheckResult"})
114 Set<Class<? extends BleGattBase>> clients =
new HashSet<>();
116 clients.add(BleHrClient.class);
119 clients.add(BleDisClient.class);
122 clients.add(BleBattClient.class);
125 clients.add(BlePMDClient.class);
128 clients.add(BlePsFtpClient.class);
130 listener =
new BDDeviceListenerImpl(context, clients);
132 listener.setDeviceSessionStateChangedCallback(
this);
133 listener.setBlePowerStateCallback(
this);
134 scheduler = AndroidSchedulers.from(context.getMainLooper());
135 BleLogger.setLoggerInterface(
new BleLogger.BleLoggerInterface() {
137 public void d(String tag, String msg) {
138 log(tag +
"/" + msg);
142 public void e(String tag, String msg) {
147 public void w(String tag, String msg) {
151 public void i(String tag, String msg) {
156 @SuppressLint(
"NewApi")
159 List<ScanFilter> filter =
new ArrayList<>();
160 filter.add(
new ScanFilter.Builder().setServiceUuid(
161 ParcelUuid.fromString(BleHrClient.HR_SERVICE.toString())).build());
162 filter.add(
new ScanFilter.Builder().setServiceUuid(
163 ParcelUuid.fromString(BlePsFtpUtils.RFC77_PFTP_SERVICE.toString())).build());
201 }
catch (Throwable ignored) {
219 listener.setAutomaticReconnection(disable);
226 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
227 PftpRequest.PbPFtpSetLocalTimeParams.Builder builder = PftpRequest.PbPFtpSetLocalTimeParams.newBuilder();
228 Types.PbDate date = Types.PbDate.newBuilder()
229 .setYear(cal.get(Calendar.YEAR))
230 .setMonth(cal.get(Calendar.MONTH) + 1)
231 .setDay(cal.get(Calendar.DAY_OF_MONTH)).build();
232 Types.PbTime time = Types.PbTime.newBuilder()
233 .setHour(cal.get(Calendar.HOUR_OF_DAY))
234 .setMinute(cal.get(Calendar.MINUTE))
235 .setSeconds(cal.get(Calendar.SECOND))
236 .setMillis(cal.get(Calendar.MILLISECOND)).build();
237 builder.setDate(date).setTime(time).setTzOffset((
int) TimeUnit.MINUTES.convert(cal.get(Calendar.ZONE_OFFSET), TimeUnit.MILLISECONDS));
238 return client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE, builder.build().toByteArray()).toObservable().ignoreElements();
239 }
catch (Throwable error) {
240 return Completable.error(error);
246 return querySettings(identifier, BlePMDClient.PmdMeasurementType.ACC);
251 return querySettings(identifier, BlePMDClient.PmdMeasurementType.ECG);
256 return querySettings(identifier, BlePMDClient.PmdMeasurementType.PPG);
261 return querySettings(identifier, BlePMDClient.PmdMeasurementType.BIOZ);
264 protected Single<PolarSensorSetting>
querySettings(
final String identifier,
final BlePMDClient.PmdMeasurementType type) {
267 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
268 return client.querySettings(type).map(
new Function<BlePMDClient.PmdSetting, PolarSensorSetting>() {
274 }
catch (Throwable e) {
275 return Single.error(e);
290 public Completable
autoConnectToDevice(
final int rssiLimit,
final String service,
final int timeout,
final TimeUnit unit,
final String polarDeviceType) {
291 final long[] start = {0};
292 return Completable.create(
new CompletableOnSubscribe() {
294 public void subscribe(CompletableEmitter emitter)
throws Exception {
295 if (service ==
null || service.matches(
"([0-9a-fA-F]{4})")) {
296 emitter.onComplete();
301 }).andThen(
listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
303 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
304 if (bleDeviceSession.getMedianRssi() >= rssiLimit &&
305 bleDeviceSession.isConnectableAdvertisement() &&
306 (polarDeviceType ==
null || polarDeviceType.equals(bleDeviceSession.getPolarDeviceType())) &&
307 (service ==
null || bleDeviceSession.getAdvertisementContent().containsService(service))) {
309 start[0] = System.currentTimeMillis();
315 }).timestamp().takeUntil(
new Predicate<Timed<BleDeviceSession>>() {
317 public boolean test(Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
318 long diff = bleDeviceSessionTimed.time(TimeUnit.MILLISECONDS) - start[0];
319 return (diff >= unit.toMillis(timeout));
321 }).reduce(
new HashSet<BleDeviceSession>(),
new BiFunction<Set<BleDeviceSession>, Timed<BleDeviceSession>, Set<BleDeviceSession>>() {
323 public Set<BleDeviceSession> apply(Set<BleDeviceSession> objects, Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
324 objects.add(bleDeviceSessionTimed.value());
327 }).doOnSuccess(
new Consumer<Set<BleDeviceSession>>() {
329 public void accept(Set<BleDeviceSession>
set)
throws Exception {
330 List<BleDeviceSession> list =
new ArrayList<>(
set);
331 Collections.sort(list,
new Comparator<BleDeviceSession>() {
333 public int compare(BleDeviceSession o1, BleDeviceSession o2) {
334 return o1.getRssi() > o2.getRssi() ? -1 : 1;
337 listener.openSessionDirect(list.get(0));
338 log(
"auto connect search complete");
340 }).toObservable().ignoreElements());
344 public Completable
autoConnectToDevice(
final int rssiLimit,
final String service,
final String polarDeviceType) {
351 if (session ==
null || session.getSessionState() == SESSION_CLOSED) {
356 if (session !=
null) {
357 listener.openSessionDirect(session);
361 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
362 return identifier.contains(
":") ?
363 bleDeviceSession.getAddress().equals(identifier) :
364 bleDeviceSession.getPolarDeviceId().equals(identifier);
366 }).take(1).observeOn(
scheduler).subscribe(
367 new Consumer<BleDeviceSession>() {
369 public void accept(BleDeviceSession bleDeviceSession)
throws Exception {
370 listener.openSessionDirect(bleDeviceSession);
373 new Consumer<Throwable>() {
375 public void accept(Throwable throwable)
throws Exception {
381 public void run()
throws Exception {
382 log(
"connect search complete");
393 if (session !=
null) {
394 if (session.getSessionState() == SESSION_OPEN ||
395 session.getSessionState() == SESSION_OPENING ||
396 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN_PARK) {
397 listener.closeSessionDirect(session);
410 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
411 if (session.getPolarDeviceType().equals(
"H10")) {
413 Types.PbSampleType.SAMPLE_TYPE_HEART_RATE :
414 Types.PbSampleType.SAMPLE_TYPE_RR_INTERVAL;
415 Types.PbDuration duration = Types.PbDuration.newBuilder().setSeconds(interval.
getValue()).build();
416 PftpRequest.PbPFtpRequestStartRecordingParams params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().
417 setSampleDataIdentifier(exerciseId).setSampleType(t).setRecordingInterval(duration).build();
418 return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
420 public CompletableSource apply(Throwable throwable)
throws Exception {
421 return Completable.error(throwable);
426 }
catch (Throwable error) {
427 return Completable.error(error);
435 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
436 if (session.getPolarDeviceType().equals(
"H10")) {
437 return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE,
null).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
439 public CompletableSource apply(Throwable throwable)
throws Exception {
445 }
catch (Throwable error) {
446 return Completable.error(error);
454 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
455 if (session.getPolarDeviceType().equals(
"H10")) {
456 return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE,
null).map(
new Function<ByteArrayOutputStream, Pair<Boolean, String>>() {
458 public Pair<Boolean, String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
459 PftpResponse.PbRequestRecordingStatusResult result = PftpResponse.PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray());
460 return new Pair<>(result.getRecordingOn(), result.hasSampleDataIdentifier() ? result.getSampleDataIdentifier() :
"");
462 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends Pair<Boolean, String>>>() {
464 public SingleSource<? extends Pair<Boolean, String>> apply(Throwable throwable)
throws Exception {
470 }
catch (Throwable error) {
471 return Single.error(error);
479 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
480 switch (session.getPolarDeviceType()) {
484 public boolean include(String entry) {
485 return entry.matches(
"^([0-9]{8})(\\/)") ||
486 entry.matches(
"^([0-9]{6})(\\/)") ||
487 entry.equals(
"E/") ||
488 entry.equals(
"SAMPLES.BPB") ||
491 }).map(
new Function<String, PolarExerciseEntry>() {
494 String[] components = p.split(
"/");
495 SimpleDateFormat format =
new SimpleDateFormat(
"yyyyMMdd HHmmss", Locale.getDefault());
496 Date date = format.parse(components[3] +
" " + components[5]);
499 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
501 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
508 public boolean include(String entry) {
509 return entry.endsWith(
"/") || entry.equals(
"SAMPLES.BPB");
511 }).map(
new Function<String, PolarExerciseEntry>() {
514 String[] components = p.split(
"/");
517 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
519 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
526 }
catch (Throwable error) {
527 return Flowable.error(error);
535 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
536 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
537 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
538 builder.setPath(entry.
path);
539 if (session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10")) {
556 return client.request(builder.build().toByteArray()).map(
new Function<ByteArrayOutputStream, PolarExerciseData>() {
558 public PolarExerciseData apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
559 ExerciseSamples.PbExerciseSamples samples = ExerciseSamples.PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray());
560 if (samples.hasRrSamples()) {
561 return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getRrSamples().getRrIntervalsList());
563 return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getHeartRateSamplesList());
566 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends PolarExerciseData>>() {
568 public SingleSource<? extends PolarExerciseData> apply(Throwable throwable)
throws Exception {
574 }
catch (Throwable error) {
575 return Single.error(error);
583 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
584 if (session.getPolarDeviceType().equals(
"OH1")) {
585 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
586 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
587 final String[] components = entry.
path.split(
"/");
588 final String exerciseParent =
"/U/0/" + components[3] +
"/E/";
589 builder.setPath(exerciseParent);
590 return client.request(builder.build().toByteArray()).flatMap(
new Function<ByteArrayOutputStream, SingleSource<?>>() {
592 public SingleSource<?> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
593 PftpResponse.PbPFtpDirectory directory = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
594 protocol.PftpRequest.PbPFtpOperation.Builder removeBuilder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
595 removeBuilder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
596 if (directory.getEntriesCount() <= 1) {
598 removeBuilder.setPath(
"/U/0/" + components[3] +
"/");
601 removeBuilder.setPath(
"/U/0/" + components[3] +
"/E/" + components[5] +
"/");
603 return client.request(removeBuilder.build().toByteArray());
605 }).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
607 public CompletableSource apply(Throwable throwable)
throws Exception {
611 }
else if (session.getPolarDeviceType().equals(
"H10")) {
612 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
613 builder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
614 builder.setPath(entry.
path);
615 return client.request(builder.build().toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
617 public CompletableSource apply(Throwable throwable)
throws Exception {
623 }
catch (Throwable error) {
624 return Completable.error(error);
630 return listener.search(
false).distinct().map(
new Function<BleDeviceSession, PolarDeviceInfo>() {
632 public PolarDeviceInfo apply(BleDeviceSession bleDeviceSession)
throws Exception {
634 bleDeviceSession.getAddress(),
635 bleDeviceSession.getRssi(),
636 bleDeviceSession.getName(),
637 bleDeviceSession.isConnectableAdvertisement());
645 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
647 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
648 return (deviceIds ==
null || deviceIds.contains(bleDeviceSession.getPolarDeviceId())) &&
649 bleDeviceSession.getAdvertisementContent().getPolarHrAdvertisement().isPresent();
651 }).map(
new Function<BleDeviceSession, PolarHrBroadcastData>() {
654 BlePolarHrAdvertisement advertisement = bleDeviceSession.getBlePolarHrAdvertisement();
656 bleDeviceSession.getAddress(),
657 bleDeviceSession.getRssi(),
658 bleDeviceSession.getName(),
659 bleDeviceSession.isConnectableAdvertisement()),
660 advertisement.getHrForDisplay(),
661 advertisement.getBatteryStatus() != 0);
671 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
672 return client.startMeasurement(BlePMDClient.PmdMeasurementType.ECG, setting.
map2PmdSettings()).andThen(
673 client.monitorEcgNotifications(
true).map(
new Function<BlePMDClient.EcgData, PolarEcgData>() {
675 public PolarEcgData apply(BlePMDClient.EcgData ecgData)
throws Exception {
676 List<Integer> samples =
new ArrayList<>();
677 for (BlePMDClient.EcgData.EcgSample s : ecgData.ecgSamples) {
678 samples.add(s.microVolts);
682 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarEcgData>>() {
684 public Publisher<? extends PolarEcgData> apply(Throwable throwable)
throws Exception {
687 }).doFinally(
new Action() {
689 public void run()
throws Exception {
693 }
catch (Throwable t) {
694 return Flowable.error(t);
703 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
704 return client.startMeasurement(BlePMDClient.PmdMeasurementType.ACC, setting.
map2PmdSettings()).andThen(
705 client.monitorAccNotifications(
true).map(
new Function<BlePMDClient.AccData, PolarAccelerometerData>() {
709 for (BlePMDClient.AccData.AccSample s : accData.accSamples) {
714 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarAccelerometerData>>() {
716 public Publisher<? extends PolarAccelerometerData> apply(Throwable throwable)
throws Exception {
719 }).doFinally(
new Action() {
721 public void run()
throws Exception {
725 }
catch (Throwable t) {
726 return Flowable.error(t);
735 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
736 return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPG, setting.
map2PmdSettings()).andThen(
737 client.monitorPpgNotifications(
true).map(
new Function<BlePMDClient.PpgData, PolarOhrPPGData>() {
739 public PolarOhrPPGData apply(BlePMDClient.PpgData ppgData)
throws Exception {
741 for (BlePMDClient.PpgData.PpgSample s : ppgData.ppgSamples) {
746 }).doFinally(
new Action() {
748 public void run()
throws Exception {
751 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarOhrPPGData>>() {
753 public Publisher<? extends PolarOhrPPGData> apply(Throwable throwable)
throws Exception {
757 }
catch (Throwable t) {
758 return Flowable.error(t);
766 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
767 return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPI,
new BlePMDClient.PmdSetting(
new HashMap<BlePMDClient.PmdSetting.PmdSettingType, Integer>())).andThen(
768 client.monitorPpiNotifications(
true).map(
new Function<BlePMDClient.PpiData, PolarOhrPPIData>() {
770 public PolarOhrPPIData apply(BlePMDClient.PpiData ppiData)
throws Exception {
772 for (BlePMDClient.PpiData.PPSample ppSample : ppiData.ppSamples) {
774 ppSample.ppErrorEstimate,
776 ppSample.blockerBit != 0,
777 ppSample.skinContactStatus != 0,
778 ppSample.skinContactSupported != 0));
782 }).doFinally(
new Action() {
784 public void run()
throws Exception {
787 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarOhrPPIData>>() {
789 public Publisher<? extends PolarOhrPPIData> apply(Throwable throwable)
throws Exception {
793 }
catch (Throwable t) {
794 return Flowable.error(t);
802 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
803 return client.startMeasurement(BlePMDClient.PmdMeasurementType.BIOZ, setting.
map2PmdSettings()).andThen(
804 client.monitorBiozNotifications(
true).map(
new Function<BlePMDClient.BiozData, PolarBiozData>() {
806 public PolarBiozData apply(BlePMDClient.BiozData biozData)
throws Exception {
807 return new PolarBiozData(biozData.timeStamp, biozData.samples, biozData.status, biozData.type);
809 }).doFinally(
new Action() {
811 public void run()
throws Exception {
814 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarBiozData>>() {
816 public Publisher<? extends PolarBiozData> apply(Throwable throwable)
throws Exception {
820 }
catch (Throwable t) {
821 return Flowable.error(t);
826 if (identifier.matches(
"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")) {
828 }
else if (identifier.matches(
"([0-9a-fA-F]){6,8}")) {
835 for (BleDeviceSession session :
listener.deviceSessions()) {
836 if (session.getAddress().equals(address)) {
844 for (BleDeviceSession session :
listener.deviceSessions()) {
845 if (session.getAdvertisementContent().getPolarDeviceId().equals(deviceId)) {
854 if (session !=
null) {
855 if (session.getSessionState() == SESSION_OPEN) {
856 BleGattBase client = session.fetchClient(service);
857 if (client.isServiceDiscovered()) {
869 BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
870 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePMDClient.PMD_CP);
871 final AtomicInteger pairData = client.getNotificationAtomicInteger(BlePMDClient.PMD_DATA);
872 if (pair !=
null && pairData !=
null &&
873 pair.get() == BleGattBase.ATT_SUCCESS &&
874 pairData.get() == BleGattBase.ATT_SUCCESS) {
881 BleDeviceSession session =
sessionServiceReady(identifier, BlePsFtpUtils.RFC77_PFTP_SERVICE);
882 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
883 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC);
884 if (pair !=
null && pair.get() == BleGattBase.ATT_SUCCESS) {
890 @SuppressLint(
"CheckResult")
891 protected
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type) {
892 if (session.getSessionState() == SESSION_OPEN) {
894 client.stopMeasurement(type).subscribe(
897 public void run()
throws Exception {
901 new Consumer<Throwable>() {
903 public void accept(Throwable throwable)
throws Exception {
904 logError(
"failed to stop pmd stream: " + throwable.getLocalizedMessage());
911 @SuppressLint(
"CheckResult")
913 final String deviceId = session.getPolarDeviceId().length() != 0 ? session.getPolarDeviceId() : session.getAddress();
914 session.monitorServicesDiscovered(
true).observeOn(
scheduler).toFlowable().flatMapIterable(
915 new Function<List<UUID>, Iterable<UUID>>() {
917 public Iterable<UUID> apply(List<UUID> uuids)
throws Exception {
921 ).flatMap(
new Function<UUID, Publisher<?>>() {
923 public Publisher<?> apply(UUID uuid)
throws Exception {
924 if (session.fetchClient(uuid) !=
null) {
925 if (uuid.equals(BleHrClient.HR_SERVICE)) {
929 final BleHrClient client = (BleHrClient) session.fetchClient(BleHrClient.HR_SERVICE);
930 client.observeHrNotifications(
true).observeOn(
scheduler).subscribe(
931 new Consumer<BleHrClient.HrNotificationData>() {
933 public void accept(BleHrClient.HrNotificationData hrNotificationData)
throws Exception {
937 hrNotificationData.rrs,
938 hrNotificationData.sensorContact,
939 hrNotificationData.sensorContactSupported,
940 hrNotificationData.rrPresent));
944 new Consumer<Throwable>() {
946 public void accept(Throwable throwable)
throws Exception {
952 public void run()
throws Exception {
957 }
else if (uuid.equals(BleBattClient.BATTERY_SERVICE)) {
958 BleBattClient client = (BleBattClient) session.fetchClient(BleBattClient.BATTERY_SERVICE);
959 return client.waitBatteryLevelUpdate(
true).observeOn(
scheduler).doOnSuccess(
new Consumer<Integer>() {
961 public void accept(Integer integer)
throws Exception {
967 }
else if (uuid.equals(BlePMDClient.PMD_SERVICE)) {
968 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
969 return client.waitNotificationEnabled(BlePMDClient.PMD_CP,
true).
970 concatWith(client.waitNotificationEnabled(BlePMDClient.PMD_DATA,
true)).andThen(client.readFeature(
true).doOnSuccess(
new Consumer<BlePMDClient.PmdFeature>() {
972 public void accept(BlePMDClient.PmdFeature pmdFeature) {
974 if (pmdFeature.ecgSupported) {
977 if (pmdFeature.accSupported) {
980 if (pmdFeature.ppgSupported) {
983 if (pmdFeature.ppiSupported) {
986 if (pmdFeature.bioZSupported) {
992 }
else if (uuid.equals(BleDisClient.DIS_SERVICE)) {
993 BleDisClient client = (BleDisClient) session.fetchClient(BleDisClient.DIS_SERVICE);
994 return client.observeDisInfo(
true).observeOn(
scheduler).doOnNext(
new Consumer<Pair<UUID, String>>() {
996 public void accept(Pair<UUID, String> pair) {
1002 }
else if (uuid.equals(BlePsFtpUtils.RFC77_PFTP_SERVICE)) {
1003 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
1004 return client.waitPsFtpClientReady(
true).observeOn(
scheduler).doOnComplete(
new Action() {
1006 public void run()
throws Exception {
1008 (session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10"))) {
1015 return Flowable.empty();
1018 new Consumer<Object>() {
1020 public void accept(Object o)
throws Exception {
1024 new Consumer<Throwable>() {
1026 public void accept(Throwable throwable)
throws Exception {
1032 public void run()
throws Exception {
1039 if (throwable instanceof BleDisconnected) {
1042 return new Exception(
"Unknown Error: " + throwable.getLocalizedMessage());
1047 public void stateChanged(BleDeviceSession session, BleDeviceSession.DeviceSessionState sessionState) {
1049 session.getPolarDeviceId().length() != 0 ?
1050 session.getPolarDeviceId() : session.getAddress(),
1051 session.getAddress(),
1052 session.getRssi(), session.getName(),
true);
1053 switch (sessionState) {
1060 case SESSION_CLOSED:
1062 if (session.getPreviousState() == SESSION_OPEN ||
1063 session.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSING) {
1068 case SESSION_OPENING:
1083 interface FetchRecursiveCondition {
1084 boolean include(String entry);
1087 protected Flowable<String>
fetchRecursively(
final BlePsFtpClient client,
final String path,
final FetchRecursiveCondition condition) {
1088 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
1089 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
1090 builder.setPath(path);
1091 return client.request(builder.build().toByteArray()).toFlowable().flatMap(
new Function<ByteArrayOutputStream, Publisher<String>>() {
1093 public Publisher<String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
1094 PftpResponse.PbPFtpDirectory dir = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
1095 Set<String> entrys =
new HashSet<>();
1096 for (
int i = 0; i < dir.getEntriesCount(); ++i) {
1097 PftpResponse.PbPFtpEntry entry = dir.getEntries(i);
1098 if (condition.include(entry.getName())) {
1099 BleUtils.validate(entrys.add(path + entry.getName()),
"duplicate entry");
1102 if (entrys.size() != 0) {
1103 return Flowable.fromIterable(entrys).flatMap(
new Function<String, Publisher<String>>() {
1105 public Publisher<String> apply(String s) {
1106 if (s.endsWith(
"/")) {
1109 return Flowable.just(s);
1114 return Flowable.empty();
1119 protected void log(
final String message) {
+
void log(final String message)
void polarFtpFeatureReady(@NonNull final String identifier)
void batteryLevelReceived(@NonNull final String identifier, final int level)
static final int ANDROID_VERSION_O
-
Single< PolarExerciseData > fetchExercise(String identifier, PolarExerciseEntry entry)
-
-
Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type)
-
Completable autoConnectToDevice(final int rssiLimit, final String service, final int timeout, final TimeUnit unit, final String polarDeviceType)
+
Single< PolarExerciseData > fetchExercise(String identifier, PolarExerciseEntry entry)
+
+
Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type)
+
Completable autoConnectToDevice(final int rssiLimit, final String service, final int timeout, final TimeUnit unit, final String polarDeviceType)
-
void setAutomaticReconnection(boolean disable)
-
Single< PolarSensorSetting > requestAccSettings(String identifier)
+
void setAutomaticReconnection(boolean disable)
+
Single< PolarSensorSetting > requestAccSettings(String identifier)
void deviceConnected(@NonNull final PolarDeviceInfo polarDeviceInfo)
-
Exception handleError(Throwable throwable)
+
Exception handleError(Throwable throwable)
-
Flowable< PolarOhrPPIData > startOhrPPIStreaming(String identifier)
+
Flowable< PolarOhrPPIData > startOhrPPIStreaming(String identifier)
void hrFeatureReady(@NonNull final String identifier)
-
Flowable< PolarHrBroadcastData > startListenForPolarHrBroadcasts(final Set< String > deviceIds)
+
Flowable< PolarHrBroadcastData > startListenForPolarHrBroadcasts(final Set< String > deviceIds)
BleDeviceListener listener
-
Flowable< PolarDeviceInfo > searchForDevice()
+
Flowable< PolarDeviceInfo > searchForDevice()
-
-
Single< PolarSensorSetting > querySettings(final String identifier, final BlePMDClient.PmdMeasurementType type)
+
+
Single< PolarSensorSetting > querySettings(final String identifier, final BlePMDClient.PmdMeasurementType type)
static final int FEATURE_POLAR_SENSOR_STREAMING
void message(final String str)
-
Single< PolarSensorSetting > requestBiozSettings(final String identifier)
-
+
Single< PolarSensorSetting > requestBiozSettings(final String identifier)
+
-
Single< PolarSensorSetting > requestEcgSettings(String identifier)
-
BleDeviceSession sessionServiceReady(final String identifier, UUID service)
+
Single< PolarSensorSetting > requestEcgSettings(String identifier)
+
BleDeviceSession sessionServiceReady(final String identifier, UUID service)
void deviceDisconnected(@NonNull final PolarDeviceInfo polarDeviceInfo)
@@ -111,56 +111,57 @@
static final int FEATURE_BATTERY_INFO
-
BleDeviceSession fetchSession(final String identifier)
-
void stateChanged(Boolean power)
+
BleDeviceSession fetchSession(final String identifier)
+
void stateChanged(Boolean power)
void ppiFeatureReady(@NonNull final String identifier)
void ppgFeatureReady(@NonNull final String identifier)
-
boolean isFeatureReady(final String deviceId, int feature)
-
Single< Pair< Boolean, String > > requestRecordingStatus(String identifier)
+
boolean isFeatureReady(final String deviceId, int feature)
+
Single< Pair< Boolean, String > > requestRecordingStatus(String identifier)
Map< String, Disposable > connectSubscriptions
-
BleDeviceSession sessionByAddress(final String address)
+
BleDeviceSession sessionByAddress(final String address)
void blePowerStateChanged(final boolean powered)
-
Flowable< PolarAccelerometerData > startAccStreaming(String identifier, PolarSensorSetting setting)
+
Flowable< PolarAccelerometerData > startAccStreaming(String identifier, PolarSensorSetting setting)
BDBleApiImpl(final Context context, int features)
-
Single< PolarSensorSetting > requestPpgSettings(String identifier)
+
Single< PolarSensorSetting > requestPpgSettings(String identifier)
static final int FEATURE_HR
-
BleDeviceSession sessionPmdClientReady(final String identifier)
+
BleDeviceSession sessionPmdClientReady(final String identifier)
-
Flowable< String > fetchRecursively(final BlePsFtpClient client, final String path, final FetchRecursiveCondition condition)
-
Completable stopRecording(String identifier)
+
Flowable< String > fetchRecursively(final BlePsFtpClient client, final String path, final FetchRecursiveCondition condition)
+
Completable stopRecording(String identifier)
void biozFeatureReady(@NonNull final String identifier)
PolarBleApiCallbackProvider callback
static final int FEATURE_DEVICE_INFO
void accelerometerFeatureReady(@NonNull final String identifier)
-
void setupDevice(final BleDeviceSession session)
+
void setupDevice(final BleDeviceSession session)
-
void connectToDevice(final String identifier)
-
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
-
Flowable< PolarExerciseEntry > listExercises(String identifier)
+
void connectToDevice(final String identifier)
+
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
+
Flowable< PolarExerciseEntry > listExercises(String identifier)
-
Completable autoConnectToDevice(final int rssiLimit, final String service, final String polarDeviceType)
+
Completable autoConnectToDevice(final int rssiLimit, final String service, final String polarDeviceType)
-
BleDeviceSession sessionByDeviceId(final String deviceId)
-
void setPolarFilter(boolean enable)
-
void setApiCallback(PolarBleApiCallbackProvider callback)
+
BleDeviceSession sessionByDeviceId(final String deviceId)
+
void setPolarFilter(boolean enable)
+
void setApiCallback(PolarBleApiCallbackProvider callback)
static final int FEATURE_POLAR_FILE_TRANSFER
-
Completable removeExercise(String identifier, PolarExerciseEntry entry)
+
Completable removeExercise(String identifier, PolarExerciseEntry entry)
void disInformationReceived(@NonNull final String identifier, @NonNull UUID uuid, @NonNull final String value)
void enableAndroidScanFilter()
-
void stateChanged(BleDeviceSession session, BleDeviceSession.DeviceSessionState sessionState)
+
void stateChanged(BleDeviceSession session, BleDeviceSession.DeviceSessionState sessionState)
void deviceConnecting(@NonNull final PolarDeviceInfo polarDeviceInfo)
-
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type)
-
-
void setApiLogger(@Nullable PolarBleApiLogger logger)
-
Flowable< PolarOhrPPGData > startOhrPPGStreaming(String identifier, PolarSensorSetting setting)
-
Flowable< PolarEcgData > startEcgStreaming(String identifier, PolarSensorSetting setting)
+
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type)
+
+
void setApiLogger(@Nullable PolarBleApiLogger logger)
+
+
Flowable< PolarOhrPPGData > startOhrPPGStreaming(String identifier, PolarSensorSetting setting)
+
Flowable< PolarEcgData > startEcgStreaming(String identifier, PolarSensorSetting setting)
void hrNotificationReceived(@NonNull final String identifier, @NonNull final PolarHrData data)
void ecgFeatureReady(@NonNull final String identifier)
@@ -170,14 +171,14 @@
-
Completable setLocalTime(String identifier, Calendar cal)
+
Completable setLocalTime(String identifier, Calendar cal)
-
BleDeviceSession sessionPsFtpClientReady(final String identifier)
-
void logError(final String message)
+
BleDeviceSession sessionPsFtpClientReady(final String identifier)
+
void logError(final String message)
-
void disconnectFromDevice(String identifier)
+
void disconnectFromDevice(String identifier)