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.Log;
11 import android.util.Pair;
13 import com.androidcommunications.polar.api.ble.BleDeviceListener;
14 import com.androidcommunications.polar.api.ble.BleLogger;
15 import com.androidcommunications.polar.api.ble.exceptions.BleDisconnected;
16 import com.androidcommunications.polar.api.ble.model.BleDeviceSession;
17 import com.androidcommunications.polar.api.ble.model.advertisement.BleAdvertisementContent;
18 import com.androidcommunications.polar.api.ble.model.advertisement.BlePolarHrAdvertisement;
19 import com.androidcommunications.polar.api.ble.model.gatt.BleGattBase;
20 import com.androidcommunications.polar.api.ble.model.gatt.client.BleBattClient;
21 import com.androidcommunications.polar.api.ble.model.gatt.client.BleDisClient;
22 import com.androidcommunications.polar.api.ble.model.gatt.client.BleHrClient;
23 import com.androidcommunications.polar.api.ble.model.gatt.client.BlePMDClient;
24 import com.androidcommunications.polar.api.ble.model.gatt.client.psftp.BlePsFtpClient;
25 import com.androidcommunications.polar.api.ble.model.gatt.client.psftp.BlePsFtpUtils;
26 import com.androidcommunications.polar.common.ble.BleUtils;
27 import com.androidcommunications.polar.enpoints.ble.bluedroid.host.BDDeviceListenerImpl;
29 import org.reactivestreams.Publisher;
31 import java.io.ByteArrayOutputStream;
32 import java.text.SimpleDateFormat;
33 import java.util.ArrayList;
34 import java.util.Calendar;
35 import java.util.Collections;
36 import java.util.Comparator;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Locale;
43 import java.util.Observable;
45 import java.util.TreeSet;
46 import java.util.UUID;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.atomic.AtomicInteger;
50 import fi.polar.remote.representation.protobuf.ExerciseSamples;
51 import fi.polar.remote.representation.protobuf.Types;
52 import io.reactivex.Completable;
53 import io.reactivex.CompletableSource;
54 import io.reactivex.Flowable;
55 import io.reactivex.Scheduler;
56 import io.reactivex.Single;
57 import io.reactivex.SingleSource;
58 import io.reactivex.android.schedulers.AndroidSchedulers;
59 import io.reactivex.disposables.Disposable;
60 import io.reactivex.functions.Action;
61 import io.reactivex.functions.BiFunction;
62 import io.reactivex.functions.Consumer;
63 import io.reactivex.functions.Function;
64 import io.reactivex.functions.Predicate;
65 import io.reactivex.schedulers.Timed;
84 import protocol.PftpNotification;
85 import protocol.PftpRequest;
86 import protocol.PftpResponse;
100 @SuppressLint({
"NewApi",
"CheckResult"})
103 Set<Class<? extends BleGattBase>> clients =
new HashSet<>();
105 clients.add(BleHrClient.class);
108 clients.add(BleDisClient.class);
111 clients.add(BleBattClient.class);
114 clients.add(BlePMDClient.class);
117 clients.add(BlePsFtpClient.class);
119 listener =
new BDDeviceListenerImpl(context, clients);
120 BleDeviceListener.BleSearchPreFilter filter =
new BleDeviceListener.BleSearchPreFilter() {
122 public boolean process(BleAdvertisementContent content) {
123 return content.getPolarDeviceId().length() != 0 && !content.getPolarDeviceType().equals(
"mobile");
127 scheduler = AndroidSchedulers.from(context.getMainLooper());
129 new Consumer<Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState>>() {
131 public void accept(Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState> pair)
throws Exception {
133 pair.first.getRssi(),pair.first.getName(),
true);
134 switch (pair.second){
143 if (pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
144 pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSING){
149 case SESSION_OPENING:
157 new Consumer<Throwable>() {
159 public void accept(Throwable throwable)
throws Exception {
165 public void run()
throws Exception {
171 new Consumer<Boolean>() {
173 public void accept(Boolean aBoolean)
throws Exception {
179 new Consumer<Throwable>() {
181 public void accept(Throwable throwable)
throws Exception {
187 public void run()
throws Exception {
192 BleLogger.setLoggerInterface(
new BleLogger.BleLoggerInterface() {
194 public void d(String tag, String msg) {
199 public void e(String tag, String msg) {
204 public void w(String tag, String msg) {
208 public void i(String tag, String msg) {
213 @SuppressLint(
"NewApi")
216 List<ScanFilter> filter =
new ArrayList<>();
217 filter.add(
new ScanFilter.Builder().setServiceUuid(
218 ParcelUuid.fromString(BleHrClient.HR_SERVICE.toString())).build());
219 filter.add(
new ScanFilter.Builder().setServiceUuid(
220 ParcelUuid.fromString(BlePsFtpUtils.RFC77_PFTP_SERVICE.toString())).build());
244 }
catch (Throwable ignored) {
262 listener.setAutomaticReconnection(disable);
269 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
270 PftpRequest.PbPFtpSetLocalTimeParams.Builder builder = PftpRequest.PbPFtpSetLocalTimeParams.newBuilder();
271 Calendar cal = Calendar.getInstance();
273 Types.PbDate date = Types.PbDate.newBuilder()
274 .setYear(cal.get(Calendar.YEAR))
275 .setMonth(cal.get(Calendar.MONTH) + 1)
276 .setDay(cal.get(Calendar.DAY_OF_MONTH)).build();
277 Types.PbTime time = Types.PbTime.newBuilder()
278 .setHour(cal.get(Calendar.HOUR_OF_DAY))
279 .setMinute(cal.get(Calendar.MINUTE))
280 .setSeconds(cal.get(Calendar.SECOND))
281 .setMillis(cal.get(Calendar.MILLISECOND)).build();
282 builder.setDate(date).setTime(time).setTzOffset(cal.get(Calendar.ZONE_OFFSET));
283 return client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE,builder.build().toByteArray()).toObservable().ignoreElements();
284 }
catch (Throwable error){
285 return Completable.error(error);
291 return querySettings(identifier,BlePMDClient.PmdMeasurementType.ACC);
296 return querySettings(identifier,BlePMDClient.PmdMeasurementType.ECG);
301 return querySettings(identifier,BlePMDClient.PmdMeasurementType.PPG);
306 return querySettings(identifier,BlePMDClient.PmdMeasurementType.BIOZ);
309 private Single<PolarSensorSetting>
querySettings(
final String identifier,
final BlePMDClient.PmdMeasurementType type) {
312 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
313 return client.querySettings(type).map(
new Function<BlePMDClient.PmdSetting, PolarSensorSetting>() {
319 }
catch (Throwable e){
320 return Single.error(e);
335 public Completable
autoConnectToPolarDevice(
final int rssiLimit,
final int timeout,
final TimeUnit unit,
final String polarDeviceType) {
336 final long[] start = {0};
337 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
339 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
340 if( bleDeviceSession.getMedianRssi() >= rssiLimit &&
341 bleDeviceSession.getPolarDeviceId().length() != 0 &&
342 bleDeviceSession.isConnectableAdvertisement() &&
343 (polarDeviceType ==
null || polarDeviceType.equals(bleDeviceSession.getPolarDeviceType())) ) {
345 start[0] = System.currentTimeMillis();
351 }).timestamp().takeUntil(
new Predicate<Timed<BleDeviceSession>>() {
353 public boolean test(Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
354 long diff = bleDeviceSessionTimed.time(TimeUnit.MILLISECONDS) - start[0];
355 return (diff >= unit.toMillis(timeout));
357 }).reduce(
new HashSet<BleDeviceSession>(),
new BiFunction<Set<BleDeviceSession>, Timed<BleDeviceSession>, Set<BleDeviceSession>>() {
359 public Set<BleDeviceSession> apply(Set<BleDeviceSession> objects, Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
360 objects.add(bleDeviceSessionTimed.value());
363 }).doOnSuccess(
new Consumer<Set<BleDeviceSession>>() {
365 public void accept(Set<BleDeviceSession>
set)
throws Exception {
366 List<BleDeviceSession> list =
new ArrayList<>(
set);
367 Collections.sort(list,
new Comparator<BleDeviceSession>() {
369 public int compare(BleDeviceSession o1, BleDeviceSession o2) {
370 return o1.getRssi() > o2.getRssi() ? -1 : 1;
373 listener.openSessionDirect(list.get(0));
374 log(
"auto connect search complete");
376 }).toObservable().ignoreElements();
387 if( session ==
null || session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSED ){
393 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
394 return bleDeviceSession.getPolarDeviceId().equals(identifier);
396 }).take(1).observeOn(
scheduler).subscribe(
397 new Consumer<BleDeviceSession>() {
399 public void accept(BleDeviceSession bleDeviceSession)
throws Exception {
400 listener.openSessionDirect(bleDeviceSession);
403 new Consumer<Throwable>() {
405 public void accept(Throwable throwable)
throws Exception {
411 public void run()
throws Exception {
412 log(
"connect search complete");
422 if( session !=
null ){
423 if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
424 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPENING ||
425 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN_PARK ) {
426 listener.closeSessionDirect(session);
439 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
440 if(session.getPolarDeviceType().equals(
"H10")) {
442 Types.PbSampleType.SAMPLE_TYPE_HEART_RATE :
443 Types.PbSampleType.SAMPLE_TYPE_RR_INTERVAL;
444 Types.PbDuration duration = Types.PbDuration.newBuilder().setSeconds(interval.
getValue()).build();
445 PftpRequest.PbPFtpRequestStartRecordingParams params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().
446 setSampleDataIdentifier(exerciseId).setSampleType(t).setRecordingInterval(duration).build();
447 return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
449 public CompletableSource apply(Throwable throwable)
throws Exception {
450 return Completable.error(throwable);
455 }
catch (Throwable error){
456 return Completable.error(error);
464 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
465 if(session.getPolarDeviceType().equals(
"H10")) {
466 return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE,
null).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
468 public CompletableSource apply(Throwable throwable)
throws Exception {
474 }
catch (Throwable error){
475 return Completable.error(error);
483 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
484 if(session.getPolarDeviceType().equals(
"H10")) {
485 return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE,
null).map(
new Function<ByteArrayOutputStream, Pair<Boolean,String>>() {
487 public Pair<Boolean,String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
488 PftpResponse.PbRequestRecordingStatusResult result = PftpResponse.PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray());
489 return new Pair<>(result.getRecordingOn(),result.hasSampleDataIdentifier() ? result.getSampleDataIdentifier() :
"");
491 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends Pair<Boolean, String>>>() {
493 public SingleSource<? extends Pair<Boolean, String>> apply(Throwable throwable)
throws Exception {
499 }
catch (Throwable error){
500 return Single.error(error);
508 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
509 switch (session.getPolarDeviceType()) {
513 public boolean include(String entry) {
514 return entry.matches(
"^([0-9]{8})(\\/)") ||
515 entry.matches(
"^([0-9]{6})(\\/)") ||
516 entry.equals(
"E/") ||
517 entry.equals(
"SAMPLES.BPB") ||
520 }).map(
new Function<String, PolarExerciseEntry>() {
523 String components[] = p.split(
"/");
524 SimpleDateFormat format =
new SimpleDateFormat(
"yyyyMMdd HHmmss", Locale.getDefault());
525 Date date = format.parse(components[3] +
" " + components[5]);
528 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
530 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
537 public boolean include(String entry) {
538 return entry.endsWith(
"/") || entry.equals(
"SAMPLES.BPB");
540 }).map(
new Function<String, PolarExerciseEntry>() {
543 String components[] = p.split(
"/");
546 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
548 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
555 }
catch (Throwable error){
556 return Flowable.error(error);
564 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
565 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
566 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
567 builder.setPath(entry.
path);
568 if(session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10")) {
569 return client.request(builder.build().toByteArray()).map(
new Function<ByteArrayOutputStream, PolarExerciseData>() {
571 public PolarExerciseData apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
572 ExerciseSamples.PbExerciseSamples samples = ExerciseSamples.PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray());
573 return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getHeartRateSamplesList());
575 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends PolarExerciseData>>() {
577 public SingleSource<? extends PolarExerciseData> apply(Throwable throwable)
throws Exception {
583 }
catch (Throwable error){
584 return Single.error(error);
592 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
593 if(session.getPolarDeviceType().equals(
"OH1")){
594 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
595 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
596 final String components[] = entry.
path.split(
"/");
597 final String exerciseParent =
"/U/0/" + components[3] +
"/E/";
598 builder.setPath(exerciseParent);
599 return client.request(builder.build().toByteArray()).flatMap(
new Function<ByteArrayOutputStream, SingleSource<?>>() {
601 public SingleSource<?> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
602 PftpResponse.PbPFtpDirectory directory = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
603 protocol.PftpRequest.PbPFtpOperation.Builder removeBuilder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
604 removeBuilder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
605 if( directory.getEntriesCount() <= 1 ){
607 removeBuilder.setPath(
"/U/0/" + components[3] +
"/");
610 removeBuilder.setPath(
"/U/0/" + components[3] +
"/E/" + components[5] +
"/");
612 return client.request(removeBuilder.build().toByteArray());
614 }).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
616 public CompletableSource apply(Throwable throwable)
throws Exception {
620 }
else if(session.getPolarDeviceType().equals(
"H10")){
621 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
622 builder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
623 builder.setPath(entry.
path);
624 return client.request(builder.build().toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
626 public CompletableSource apply(Throwable throwable)
throws Exception {
632 }
catch (Throwable error){
633 return Completable.error(error);
639 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
641 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
642 return bleDeviceSession.getAdvertisementContent().getPolarDeviceId().length() != 0;
644 }).distinct().map(
new Function<BleDeviceSession, PolarDeviceInfo>() {
646 public PolarDeviceInfo apply(BleDeviceSession bleDeviceSession)
throws Exception {
647 return new PolarDeviceInfo(bleDeviceSession.getPolarDeviceId(),bleDeviceSession.getRssi(),bleDeviceSession.getName(), bleDeviceSession.isConnectableAdvertisement());
655 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
657 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
658 return (deviceIds ==
null || deviceIds.contains(bleDeviceSession.getPolarDeviceId())) &&
659 bleDeviceSession.getAdvertisementContent().getPolarHrAdvertisement().isPresent();
661 }).map(
new Function<BleDeviceSession, PolarHrBroadcastData>() {
664 BlePolarHrAdvertisement advertisement = bleDeviceSession.getBlePolarHrAdvertisement();
666 bleDeviceSession.getRssi(), bleDeviceSession.getName(), bleDeviceSession.isConnectableAdvertisement()),
667 advertisement.getHrForDisplay(), advertisement.getBatteryStatus() != 0);
677 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
678 return client.startMeasurement(BlePMDClient.PmdMeasurementType.ECG, setting.
map2PmdSettings()).andThen(
679 client.monitorEcgNotifications(
true).map(
new Function<BlePMDClient.EcgData, PolarEcgData>() {
681 public PolarEcgData apply(BlePMDClient.EcgData ecgData)
throws Exception {
682 List<Integer> samples =
new ArrayList<>();
683 for( BlePMDClient.EcgData.EcgSample s : ecgData.ecgSamples ){
684 samples.add(s.microVolts);
688 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarEcgData>>() {
690 public Publisher<? extends PolarEcgData> apply(Throwable throwable)
throws Exception {
693 }).doFinally(
new Action() {
695 public void run()
throws Exception {
699 }
catch (Throwable t){
700 return Flowable.error(t);
709 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
710 return client.startMeasurement(BlePMDClient.PmdMeasurementType.ACC, setting.
map2PmdSettings()).andThen(
711 client.monitorAccNotifications(
true).map(
new Function<BlePMDClient.AccData, PolarAccelerometerData>() {
715 for( BlePMDClient.AccData.AccSample s : accData.accSamples ){
720 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarAccelerometerData>>() {
722 public Publisher<? extends PolarAccelerometerData> apply(Throwable throwable)
throws Exception {
725 }).doFinally(
new Action() {
727 public void run()
throws Exception {
731 }
catch (Throwable t){
732 return Flowable.error(t);
741 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
742 return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPG, setting.
map2PmdSettings()).andThen(
743 client.monitorPpgNotifications(
true).map(
new Function<BlePMDClient.PpgData, PolarOhrPPGData>() {
745 public PolarOhrPPGData apply(BlePMDClient.PpgData ppgData)
throws Exception {
747 for( BlePMDClient.PpgData.PpgSample s : ppgData.ppgSamples ){
752 }).doFinally(
new Action() {
754 public void run()
throws Exception {
757 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarOhrPPGData>>() {
759 public Publisher<? extends PolarOhrPPGData> apply(Throwable throwable)
throws Exception {
763 }
catch (Throwable t){
764 return Flowable.error(t);
772 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
773 return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPI,
new BlePMDClient.PmdSetting(
new HashMap<BlePMDClient.PmdSetting.PmdSettingType, Integer>())).andThen(
774 client.monitorPpiNotifications(
true).map(
new Function<BlePMDClient.PpiData, PolarOhrPPIData>() {
776 public PolarOhrPPIData apply(BlePMDClient.PpiData ppiData)
throws Exception {
778 for(BlePMDClient.PpiData.PPSample ppSample : ppiData.ppSamples){
780 ppSample.ppErrorEstimate,
782 ppSample.blockerBit != 0,
783 ppSample.skinContactStatus != 0,
784 ppSample.skinContactSupported != 0));
788 }).doFinally(
new Action() {
790 public void run()
throws Exception {
793 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarOhrPPIData>>() {
795 public Publisher<? extends PolarOhrPPIData> apply(Throwable throwable)
throws Exception {
799 }
catch (Throwable t){
800 return Flowable.error(t);
808 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
809 return client.startMeasurement(BlePMDClient.PmdMeasurementType.BIOZ, setting.
map2PmdSettings()).andThen(
810 client.monitorBiozNotifications(
true).map(
new Function<BlePMDClient.BiozData, PolarBiozData>() {
812 public PolarBiozData apply(BlePMDClient.BiozData biozData)
throws Exception {
813 return new PolarBiozData(biozData.timeStamp,biozData.samples);
815 }).doFinally(
new Action() {
817 public void run()
throws Exception {
820 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarBiozData>>() {
822 public Publisher<? extends PolarBiozData> apply(Throwable throwable)
throws Exception {
826 }
catch (Throwable t){
827 return Flowable.error(t);
832 for ( BleDeviceSession session :
listener.deviceSessions() ){
833 if( session.getAdvertisementContent().getPolarDeviceId().equals(deviceId) ){
843 if(session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN) {
844 BleGattBase client = session.fetchClient(service);
845 if (client.isServiceDiscovered()) {
857 BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
858 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePMDClient.PMD_CP);
859 final AtomicInteger pairData = client.getNotificationAtomicInteger(BlePMDClient.PMD_DATA);
860 if (pair !=
null && pairData !=
null &&
861 pair.get() == BleGattBase.ATT_SUCCESS &&
862 pairData.get() == BleGattBase.ATT_SUCCESS) {
869 BleDeviceSession session =
sessionServiceReady(identifier, BlePsFtpUtils.RFC77_PFTP_SERVICE);
870 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
871 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC);
872 if (pair !=
null && pair.get() == BleGattBase.ATT_SUCCESS ) {
878 @SuppressLint(
"CheckResult")
879 private
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type) {
880 if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ){
882 client.stopMeasurement(type).subscribe(
885 public void run()
throws Exception {
889 new Consumer<Throwable>() {
891 public void accept(Throwable throwable)
throws Exception {
892 logError(
"failed to stop pmd stream: " + throwable.getLocalizedMessage());
899 @SuppressLint(
"CheckResult")
901 final String deviceId = session.getPolarDeviceId();
902 session.monitorServicesDiscovered(
true).observeOn(
scheduler).toFlowable().flatMapIterable(
903 new Function<List<UUID>, Iterable<UUID>>() {
905 public Iterable<UUID> apply(List<UUID> uuids)
throws Exception {
909 ).flatMap(
new Function<UUID, Publisher<?>>() {
911 public Publisher<?> apply(UUID uuid)
throws Exception {
912 if(session.fetchClient(uuid) !=
null) {
913 if (uuid.equals(BleHrClient.HR_SERVICE)) {
917 final BleHrClient client = (BleHrClient) session.fetchClient(BleHrClient.HR_SERVICE);
918 client.observeHrNotifications(
true).observeOn(
scheduler).subscribe(
919 new Consumer<BleHrClient.HrNotificationData>() {
921 public void accept(BleHrClient.HrNotificationData hrNotificationData)
throws Exception {
925 hrNotificationData.rrs,
926 hrNotificationData.sensorContact,
927 hrNotificationData.sensorContactSupported,
928 hrNotificationData.rrPresent));
932 new Consumer<Throwable>() {
934 public void accept(Throwable throwable)
throws Exception {
940 public void run()
throws Exception {
945 }
else if (uuid.equals(BleBattClient.BATTERY_SERVICE)) {
946 BleBattClient client = (BleBattClient) session.fetchClient(BleBattClient.BATTERY_SERVICE);
947 return client.waitBatteryLevelUpdate(
true).observeOn(
scheduler).doOnSuccess(
new Consumer<Integer>() {
949 public void accept(Integer integer)
throws Exception {
955 }
else if (uuid.equals(BlePMDClient.PMD_SERVICE)) {
956 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
957 return client.waitNotificationEnabled(BlePMDClient.PMD_CP,
true).
958 concatWith(client.waitNotificationEnabled(BlePMDClient.PMD_DATA,
true)).andThen(client.readFeature(
true).doOnSuccess(
new Consumer<BlePMDClient.PmdFeature>() {
960 public void accept(BlePMDClient.PmdFeature pmdFeature) {
962 if (pmdFeature.ecgSupported) {
965 if (pmdFeature.accSupported) {
968 if (pmdFeature.ppgSupported) {
971 if (pmdFeature.ppiSupported) {
974 if (pmdFeature.bioZSupported) {
980 }
else if (uuid.equals(BleDisClient.DIS_SERVICE)) {
981 BleDisClient client = (BleDisClient) session.fetchClient(BleDisClient.DIS_SERVICE);
982 return client.observeDisInfo(
true).takeUntil(
new Predicate<HashMap<UUID, String>>() {
984 public boolean test(HashMap<UUID, String> map)
throws Exception {
985 return map.containsKey(BleDisClient.SOFTWARE_REVISION_STRING);
987 }).observeOn(
scheduler).doOnNext(
new Consumer<HashMap<UUID, String>>() {
989 public void accept(HashMap<UUID, String> uuidStringHashMap)
throws Exception {
990 if (
callback !=
null && uuidStringHashMap.containsKey(BleDisClient.SOFTWARE_REVISION_STRING)) {
995 }
else if (uuid.equals(BlePsFtpUtils.RFC77_PFTP_SERVICE)) {
996 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
997 return client.waitPsFtpClientReady(
true).observeOn(
scheduler).doOnComplete(
new Action() {
999 public void run()
throws Exception {
1001 (session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10"))) {
1008 return Flowable.empty();
1011 new Consumer<Object>() {
1013 public void accept(Object o)
throws Exception {
1017 new Consumer<Throwable>() {
1019 public void accept(Throwable throwable)
throws Exception {
1025 public void run()
throws Exception {
1032 if( throwable instanceof BleDisconnected ){
1035 return new Exception(
"Unknown Error: " + throwable.getLocalizedMessage());
1039 interface FetchRecursiveCondition {
1040 boolean include(String entry);
1043 private Flowable<String>
fetchRecursively(
final BlePsFtpClient client,
final String path,
final FetchRecursiveCondition condition) {
1044 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
1045 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
1046 builder.setPath(path);
1047 return client.request(builder.build().toByteArray()).toFlowable().flatMap(
new Function<ByteArrayOutputStream, Publisher<String>>() {
1049 public Publisher<String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
1050 PftpResponse.PbPFtpDirectory dir = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
1051 Set<String> entrys =
new HashSet<>();
1052 for(
int i=0; i < dir.getEntriesCount(); ++i ){
1053 PftpResponse.PbPFtpEntry entry = dir.getEntries(i);
1054 if( condition.include(entry.getName()) ){
1055 BleUtils.validate(entrys.add(path + entry.getName()),
"duplicate entry");
1058 if(entrys.size()!=0) {
1059 return Flowable.fromIterable(entrys).flatMap(
new Function<String, Publisher<String>>() {
1061 public Publisher<String> apply(String s) {
1062 if (s.endsWith(
"/")) {
1065 return Flowable.just(s);
1070 return Flowable.empty();
1075 private void log(
final String message) {
+
void log(final String message)
+
Flowable< PolarDeviceInfo > searchForPolarDevice()
+
+
static final int ANDROID_VERSION_O
+
Single< PolarExerciseData > fetchExercise(String identifier, PolarExerciseEntry entry)
+
+
Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type)
-
void setAutomaticReconnection(boolean disable)
-
Single< PolarSensorSetting > requestAccSettings(String identifier)
-
PolarBleApiCallback callback
-
Exception handleError(Throwable throwable)
+
void setAutomaticReconnection(boolean disable)
+
Single< PolarSensorSetting > requestAccSettings(String identifier)
+
PolarBleApiCallback callback
+
Exception handleError(Throwable throwable)
void accelerometerFeatureReady(@NonNull final String identifier)
-
Flowable< PolarOhrPPIData > startOhrPPIStreaming(String identifier)
+
Flowable< PolarOhrPPIData > startOhrPPIStreaming(String identifier)
-
+
void hrFeatureReady(@NonNull final String identifier)
-
+
void fwInformationReceived(@NonNull final String identifier, @NonNull final String fwVersion)
-
Flowable< PolarHrBroadcastData > startListenForPolarHrBroadcasts(final Set< String > deviceIds)
-
BleDeviceListener listener
+
Flowable< PolarHrBroadcastData > startListenForPolarHrBroadcasts(final Set< String > deviceIds)
+
BleDeviceListener listener
void ecgFeatureReady(@NonNull final String identifier)
-
-
-
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
+
static final int FEATURE_POLAR_SENSOR_STREAMING
void message(final String str)
-
Map< String, Disposable > connectSubscriptions
+
Map< String, Disposable > connectSubscriptions
void ppgFeatureReady(@NonNull final String identifier)
-
Completable autoConnectToPolarDevice(final int rssiLimit, final String polarDeviceType)
-
Single< PolarSensorSetting > requestBiozSettings(final String identifier)
-
void disconnectFromPolarDevice(String identifier)
-
-
Single< PolarSensorSetting > requestEcgSettings(String identifier)
-
BleDeviceSession sessionServiceReady(final String identifier, UUID service)
-
Single< Pair< Boolean, String > > requestRecordingStatus(String identifier)
+
Completable autoConnectToPolarDevice(final int rssiLimit, final String polarDeviceType)
+
Single< PolarSensorSetting > requestBiozSettings(final String identifier)
+
+
void disconnectFromPolarDevice(String identifier)
+
+
Single< PolarSensorSetting > requestEcgSettings(String identifier)
+
BleDeviceSession sessionServiceReady(final String identifier, UUID service)
+
Single< Pair< Boolean, String > > requestRecordingStatus(String identifier)
-
+
-
static final int FEATURE_BATTERY_INFO
+
static final int FEATURE_BATTERY_INFO
-
boolean isFeatureReady(final String deviceId, int feature)
+
boolean isFeatureReady(final String deviceId, int feature)
void polarDeviceConnecting(@NonNull final PolarDeviceInfo polarDeviceInfo)
-
Flowable< PolarAccelerometerData > startAccStreaming(String identifier, PolarSensorSetting setting)
-
-
BDBleApiImpl(final Context context, int features)
-
Single< PolarSensorSetting > requestPpgSettings(String identifier)
+
Flowable< PolarAccelerometerData > startAccStreaming(String identifier, PolarSensorSetting setting)
+
+
BDBleApiImpl(final Context context, int features)
+
Single< PolarSensorSetting > requestPpgSettings(String identifier)
-
static final int FEATURE_HR
+
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 ppiFeatureReady(@NonNull final String identifier)
-
Completable setLocalTime(String identifier, Date local)
+
Completable setLocalTime(String identifier, Date local)
void polarDeviceDisconnected(@NonNull final PolarDeviceInfo polarDeviceInfo)
void batteryLevelReceived(@NonNull final String identifier, final int level)
-
void sendNotification(BlePsFtpClient client, int notification)
-
static final int FEATURE_DEVICE_INFO
-
void setupDevice(final BleDeviceSession session)
+
static final int FEATURE_DEVICE_INFO
+
void setupDevice(final BleDeviceSession session)
-
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
-
Flowable< PolarExerciseEntry > listExercises(String identifier)
-
+
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
+
Flowable< PolarExerciseEntry > listExercises(String identifier)
+
-
BleDeviceSession sessionByDeviceId(final String deviceId)
+
BleDeviceSession sessionByDeviceId(final String deviceId)
void hrNotificationReceived(@NonNull final String identifier, @NonNull final PolarHrData data)
void blePowerStateChanged(final boolean powered)
-
static final int FEATURE_POLAR_FILE_TRANSFER
-
Completable removeExercise(String identifier, PolarExerciseEntry entry)
+
static final int FEATURE_POLAR_FILE_TRANSFER
+
Completable removeExercise(String identifier, PolarExerciseEntry entry)
void polarFtpFeatureReady(@NonNull final String identifier)
void polarDeviceConnected(@NonNull final PolarDeviceInfo polarDeviceInfo)
-
void enableAndroidScanFilter()
-
-
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type)
-
-
void setApiLogger(@Nullable PolarBleApiLogger logger)
-
Completable autoConnectToPolarDevice(final int rssiLimit, final int timeout, final TimeUnit unit, final String polarDeviceType)
-
Flowable< PolarOhrPPGData > startOhrPPGStreaming(String identifier, PolarSensorSetting setting)
-
Flowable< PolarEcgData > startEcgStreaming(String identifier, PolarSensorSetting setting)
+
void enableAndroidScanFilter()
+
+
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type)
+
+
void setApiLogger(@Nullable PolarBleApiLogger logger)
+
Completable autoConnectToPolarDevice(final int rssiLimit, final int timeout, final TimeUnit unit, final String polarDeviceType)
+
Flowable< PolarOhrPPGData > startOhrPPGStreaming(String identifier, PolarSensorSetting setting)
+
Flowable< PolarEcgData > startEcgStreaming(String identifier, PolarSensorSetting setting)
void biozFeatureReady(@NonNull final String identifier)
-
+
BlePMDClient.PmdSetting map2PmdSettings()
-
void setApiCallback(PolarBleApiCallback callback)
+
void setApiCallback(PolarBleApiCallback callback)
-
BleDeviceSession sessionPsFtpClientReady(final String identifier)
-
void logError(final String message)
+
BleDeviceSession sessionPsFtpClientReady(final String identifier)
+
void logError(final String message)
-
-
void connectToPolarDevice(final String identifier)
+
+
void connectToPolarDevice(final String identifier)