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.UUID;
46 import java.util.concurrent.TimeUnit;
47 import java.util.concurrent.atomic.AtomicInteger;
49 import fi.polar.remote.representation.protobuf.ExerciseSamples;
50 import fi.polar.remote.representation.protobuf.Types;
51 import io.reactivex.Completable;
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;
83 import protocol.PftpNotification;
84 import protocol.PftpRequest;
85 import protocol.PftpResponse;
99 @SuppressLint({
"NewApi",
"CheckResult"})
102 List<Class<? extends BleGattBase>> clients =
new ArrayList<>();
104 clients.add(BleHrClient.class);
107 clients.add(BleDisClient.class);
110 clients.add(BleBattClient.class);
113 clients.add(BlePMDClient.class);
116 clients.add(BlePsFtpClient.class);
118 listener =
new BDDeviceListenerImpl(context, clients);
119 BleDeviceListener.BleSearchPreFilter filter =
new BleDeviceListener.BleSearchPreFilter() {
121 public boolean process(BleAdvertisementContent content) {
122 return content.getPolarDeviceId().length() != 0 && !content.getPolarDeviceType().equals(
"mobile");
126 scheduler = AndroidSchedulers.from(context.getMainLooper());
128 new Consumer<Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState>>() {
130 public void accept(Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState> pair)
throws Exception {
132 pair.first.getRssi(),pair.first.getName(),
true);
133 switch (pair.second){
142 if (pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
143 pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSING){
148 case SESSION_OPENING:
156 new Consumer<Throwable>() {
158 public void accept(Throwable throwable)
throws Exception {
164 public void run()
throws Exception {
170 new Consumer<Boolean>() {
172 public void accept(Boolean aBoolean)
throws Exception {
178 new Consumer<Throwable>() {
180 public void accept(Throwable throwable)
throws Exception {
186 public void run()
throws Exception {
191 BleLogger.setLoggerInterface(
new BleLogger.BleLoggerInterface() {
193 public void d(String tag, String msg) {
198 public void e(String tag, String msg) {
203 public void w(String tag, String msg) {
207 public void i(String tag, String msg) {
212 @SuppressLint(
"NewApi")
215 List<ScanFilter> filter =
new ArrayList<>();
216 filter.add(
new ScanFilter.Builder().setServiceUuid(
217 ParcelUuid.fromString(BleHrClient.HR_SERVICE.toString())).build());
218 filter.add(
new ScanFilter.Builder().setServiceUuid(
219 ParcelUuid.fromString(BlePsFtpUtils.RFC77_PFTP_SERVICE.toString())).build());
238 }
catch (Throwable ignored) {
256 listener.setAutomaticReconnection(disable);
263 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
264 PftpRequest.PbPFtpSetLocalTimeParams.Builder builder = PftpRequest.PbPFtpSetLocalTimeParams.newBuilder();
265 Calendar cal = Calendar.getInstance();
267 Types.PbDate date = Types.PbDate.newBuilder()
268 .setYear(cal.get(Calendar.YEAR))
269 .setMonth(cal.get(Calendar.MONTH) + 1)
270 .setDay(cal.get(Calendar.DAY_OF_MONTH)).build();
271 Types.PbTime time = Types.PbTime.newBuilder()
272 .setHour(cal.get(Calendar.HOUR))
273 .setMinute(cal.get(Calendar.MINUTE))
274 .setSeconds(cal.get(Calendar.SECOND))
275 .setMillis(cal.get(Calendar.MILLISECOND)).build();
276 builder.setDate(date).setTime(time).setTzOffset(cal.get(Calendar.ZONE_OFFSET));
277 return client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE,builder.build().toByteArray()).toObservable().ignoreElements();
278 }
catch (Throwable error){
279 return Completable.error(error);
285 return querySettings(identifier,BlePMDClient.PmdMeasurementType.ACC);
290 return querySettings(identifier,BlePMDClient.PmdMeasurementType.ECG);
295 return querySettings(identifier,BlePMDClient.PmdMeasurementType.PPG);
300 return querySettings(identifier,BlePMDClient.PmdMeasurementType.BIOZ);
303 private Single<PolarSensorSetting>
querySettings(
final String identifier,
final BlePMDClient.PmdMeasurementType type) {
306 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
307 return client.querySettings(type).map(
new Function<BlePMDClient.PmdSetting, PolarSensorSetting>() {
313 }
catch (Throwable e){
314 return Single.error(e);
329 public Completable
autoConnectToPolarDevice(
final int rssiLimit,
final int timeout,
final TimeUnit unit,
final String polarDeviceType) {
330 final long[] start = {0};
331 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
333 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
334 if( bleDeviceSession.getMedianRssi() >= rssiLimit &&
335 bleDeviceSession.getPolarDeviceId().length() != 0 &&
336 bleDeviceSession.isConnectableAdvertisement() &&
337 (polarDeviceType ==
null || polarDeviceType.equals(bleDeviceSession.getPolarDeviceType())) ) {
339 start[0] = System.currentTimeMillis();
345 }).timestamp().takeUntil(
new Predicate<Timed<BleDeviceSession>>() {
347 public boolean test(Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
348 long diff = bleDeviceSessionTimed.time(TimeUnit.MILLISECONDS) - start[0];
349 return (diff >= unit.toMillis(timeout));
351 }).reduce(
new HashSet<BleDeviceSession>(),
new BiFunction<Set<BleDeviceSession>, Timed<BleDeviceSession>, Set<BleDeviceSession>>() {
353 public Set<BleDeviceSession> apply(Set<BleDeviceSession> objects, Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
354 objects.add(bleDeviceSessionTimed.value());
357 }).doOnSuccess(
new Consumer<Set<BleDeviceSession>>() {
359 public void accept(Set<BleDeviceSession>
set)
throws Exception {
360 List<BleDeviceSession> list =
new ArrayList<>(
set);
361 Collections.sort(list,
new Comparator<BleDeviceSession>() {
363 public int compare(BleDeviceSession o1, BleDeviceSession o2) {
364 return o1.getRssi() > o2.getRssi() ? -1 : 1;
367 listener.openSessionDirect(list.get(0));
368 log(
"auto connect search complete");
370 }).toObservable().ignoreElements();
381 if( session ==
null || session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSED ){
387 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
388 return bleDeviceSession.getPolarDeviceId().equals(identifier);
390 }).take(1).observeOn(
scheduler).subscribe(
391 new Consumer<BleDeviceSession>() {
393 public void accept(BleDeviceSession bleDeviceSession)
throws Exception {
394 listener.openSessionDirect(bleDeviceSession);
397 new Consumer<Throwable>() {
399 public void accept(Throwable throwable)
throws Exception {
405 public void run()
throws Exception {
406 log(
"connect search complete");
416 if( session !=
null ){
417 if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
418 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPENING ||
419 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN_PARK ) {
420 listener.closeSessionDirect(session);
433 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
434 if(session.getPolarDeviceType().equals(
"H10")) {
436 Types.PbSampleType.SAMPLE_TYPE_HEART_RATE :
437 Types.PbSampleType.SAMPLE_TYPE_RR_INTERVAL;
438 Types.PbDuration duration = Types.PbDuration.newBuilder().setSeconds(interval.
getValue()).build();
439 PftpRequest.PbPFtpRequestStartRecordingParams params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().
440 setSampleDataIdentifier(exerciseId).setSampleType(t).setRecordingInterval(duration).build();
441 return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
443 public CompletableSource apply(Throwable throwable)
throws Exception {
444 return Completable.error(throwable);
449 }
catch (Throwable error){
450 return Completable.error(error);
458 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
459 if(session.getPolarDeviceType().equals(
"H10")) {
460 return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE,
null).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
462 public CompletableSource apply(Throwable throwable)
throws Exception {
468 }
catch (Throwable error){
469 return Completable.error(error);
477 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
478 if(session.getPolarDeviceType().equals(
"H10")) {
479 return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE,
null).map(
new Function<ByteArrayOutputStream, Pair<Boolean,String>>() {
481 public Pair<Boolean,String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
482 PftpResponse.PbRequestRecordingStatusResult result = PftpResponse.PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray());
483 return new Pair<>(result.getRecordingOn(),result.hasSampleDataIdentifier() ? result.getSampleDataIdentifier() :
"");
485 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends Pair<Boolean, String>>>() {
487 public SingleSource<? extends Pair<Boolean, String>> apply(Throwable throwable)
throws Exception {
493 }
catch (Throwable error){
494 return Single.error(error);
502 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
503 switch (session.getPolarDeviceType()) {
507 public boolean include(String entry) {
508 return entry.matches(
"^([0-9]{8})(\\/)") ||
509 entry.matches(
"^([0-9]{6})(\\/)") ||
510 entry.equals(
"E/") ||
511 entry.equals(
"SAMPLES.BPB") ||
514 }).map(
new Function<String, PolarExerciseEntry>() {
517 String components[] = p.split(
"/");
518 SimpleDateFormat format =
new SimpleDateFormat(
"yyyyMMdd HHmmss", Locale.getDefault());
519 Date date = format.parse(components[3] +
" " + components[5]);
522 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
524 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
531 public boolean include(String entry) {
532 return entry.endsWith(
"/") || entry.equals(
"SAMPLES.BPB");
534 }).map(
new Function<String, PolarExerciseEntry>() {
537 String components[] = p.split(
"/");
540 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
542 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
549 }
catch (Throwable error){
550 return Flowable.error(error);
558 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
559 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
560 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
561 builder.setPath(entry.
path);
562 if(session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10")) {
563 return client.request(builder.build().toByteArray()).map(
new Function<ByteArrayOutputStream, PolarExerciseData>() {
565 public PolarExerciseData apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
566 ExerciseSamples.PbExerciseSamples samples = ExerciseSamples.PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray());
567 return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getHeartRateSamplesList());
569 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends PolarExerciseData>>() {
571 public SingleSource<? extends PolarExerciseData> apply(Throwable throwable)
throws Exception {
577 }
catch (Throwable error){
578 return Single.error(error);
586 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
587 if(session.getPolarDeviceType().equals(
"OH1")){
588 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
589 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
590 final String components[] = entry.
path.split(
"/");
591 final String exerciseParent =
"/U/0/" + components[3] +
"/E/";
592 builder.setPath(exerciseParent);
593 return client.request(builder.build().toByteArray()).flatMap(
new Function<ByteArrayOutputStream, SingleSource<?>>() {
595 public SingleSource<?> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
596 PftpResponse.PbPFtpDirectory directory = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
597 protocol.PftpRequest.PbPFtpOperation.Builder removeBuilder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
598 removeBuilder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
599 if( directory.getEntriesCount() <= 1 ){
601 removeBuilder.setPath(
"/U/0/" + components[3] +
"/");
604 removeBuilder.setPath(
"/U/0/" + components[3] +
"/E/" + components[5] +
"/");
606 return client.request(removeBuilder.build().toByteArray());
608 }).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
610 public CompletableSource apply(Throwable throwable)
throws Exception {
614 }
else if(session.getPolarDeviceType().equals(
"H10")){
615 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
616 builder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
617 builder.setPath(entry.
path);
618 return client.request(builder.build().toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
620 public CompletableSource apply(Throwable throwable)
throws Exception {
626 }
catch (Throwable error){
627 return Completable.error(error);
633 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
635 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
636 return bleDeviceSession.getAdvertisementContent().getPolarDeviceId().length() != 0;
638 }).distinct().map(
new Function<BleDeviceSession, PolarDeviceInfo>() {
640 public PolarDeviceInfo apply(BleDeviceSession bleDeviceSession)
throws Exception {
641 return new PolarDeviceInfo(bleDeviceSession.getPolarDeviceId(),bleDeviceSession.getRssi(),bleDeviceSession.getName(), bleDeviceSession.isConnectableAdvertisement());
649 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
651 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
652 return (deviceIds ==
null || deviceIds.contains(bleDeviceSession.getPolarDeviceId())) &&
653 bleDeviceSession.getAdvertisementContent().getPolarHrAdvertisement().isPresent();
655 }).map(
new Function<BleDeviceSession, PolarHrBroadcastData>() {
658 BlePolarHrAdvertisement advertisement = bleDeviceSession.getBlePolarHrAdvertisement();
660 bleDeviceSession.getRssi(), bleDeviceSession.getName(), bleDeviceSession.isConnectableAdvertisement()),
661 advertisement.getHrForDisplay(), 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);
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 for ( BleDeviceSession session :
listener.deviceSessions() ){
827 if( session.getAdvertisementContent().getPolarDeviceId().equals(deviceId) ){
837 if(session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN) {
838 BleGattBase client = session.fetchClient(service);
839 if (client.isServiceDiscovered()) {
851 BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
852 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePMDClient.PMD_CP);
853 final AtomicInteger pairData = client.getNotificationAtomicInteger(BlePMDClient.PMD_DATA);
854 if (pair !=
null && pairData !=
null &&
855 pair.get() == BleGattBase.ATT_SUCCESS &&
856 pairData.get() == BleGattBase.ATT_SUCCESS) {
863 BleDeviceSession session =
sessionServiceReady(identifier, BlePsFtpUtils.RFC77_PFTP_SERVICE);
864 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
865 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC);
866 if (pair !=
null && pair.get() == BleGattBase.ATT_SUCCESS ) {
872 @SuppressLint(
"CheckResult")
873 private
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type) {
874 if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ){
876 client.stopMeasurement(type).subscribe(
879 public void run()
throws Exception {
883 new Consumer<Throwable>() {
885 public void accept(Throwable throwable)
throws Exception {
886 logError(
"failed to stop pmd stream: " + throwable.getLocalizedMessage());
893 @SuppressLint(
"CheckResult")
895 final String deviceId = session.getPolarDeviceId();
896 session.monitorServicesDiscovered(
true).observeOn(
scheduler).toFlowable().flatMapIterable(
897 new Function<List<UUID>, Iterable<UUID>>() {
899 public Iterable<UUID> apply(List<UUID> uuids)
throws Exception {
903 ).flatMap(
new Function<UUID, Publisher<?>>() {
905 public Publisher<?> apply(UUID uuid)
throws Exception {
906 if(session.fetchClient(uuid) !=
null) {
907 if (uuid.equals(BleHrClient.HR_SERVICE)) {
911 final BleHrClient client = (BleHrClient) session.fetchClient(BleHrClient.HR_SERVICE);
912 client.observeHrNotifications(
true).observeOn(
scheduler).subscribe(
913 new Consumer<BleHrClient.HrNotificationData>() {
915 public void accept(BleHrClient.HrNotificationData hrNotificationData)
throws Exception {
919 hrNotificationData.rrs,
920 hrNotificationData.sensorContact,
921 hrNotificationData.sensorContactSupported,
922 hrNotificationData.rrPresent));
926 new Consumer<Throwable>() {
928 public void accept(Throwable throwable)
throws Exception {
934 public void run()
throws Exception {
939 }
else if (uuid.equals(BleBattClient.BATTERY_SERVICE)) {
940 BleBattClient client = (BleBattClient) session.fetchClient(BleBattClient.BATTERY_SERVICE);
941 return client.waitBatteryLevelUpdate(
true).observeOn(
scheduler).doOnSuccess(
new Consumer<Integer>() {
943 public void accept(Integer integer)
throws Exception {
949 }
else if (uuid.equals(BlePMDClient.PMD_SERVICE)) {
950 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
951 return client.waitNotificationEnabled(BlePMDClient.PMD_CP,
true).
952 concatWith(client.waitNotificationEnabled(BlePMDClient.PMD_DATA,
true)).andThen(client.readFeature(
true).doOnSuccess(
new Consumer<BlePMDClient.PmdFeature>() {
954 public void accept(BlePMDClient.PmdFeature pmdFeature) {
956 if (pmdFeature.ecgSupported) {
959 if (pmdFeature.accSupported) {
962 if (pmdFeature.ppgSupported) {
965 if (pmdFeature.ppiSupported) {
968 if (pmdFeature.bioZSupported) {
974 }
else if (uuid.equals(BleDisClient.DIS_SERVICE)) {
975 BleDisClient client = (BleDisClient) session.fetchClient(BleDisClient.DIS_SERVICE);
976 return client.observeDisInfo(
true).takeUntil(
new Predicate<HashMap<UUID, String>>() {
978 public boolean test(HashMap<UUID, String> map)
throws Exception {
979 return map.containsKey(BleDisClient.SOFTWARE_REVISION_STRING);
981 }).observeOn(
scheduler).doOnNext(
new Consumer<HashMap<UUID, String>>() {
983 public void accept(HashMap<UUID, String> uuidStringHashMap)
throws Exception {
984 if (
callback !=
null && uuidStringHashMap.containsKey(BleDisClient.SOFTWARE_REVISION_STRING)) {
989 }
else if (uuid.equals(BlePsFtpUtils.RFC77_PFTP_SERVICE)) {
990 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
991 return client.waitPsFtpClientReady(
true).observeOn(
scheduler).doOnComplete(
new Action() {
993 public void run()
throws Exception {
995 (session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10"))) {
1002 return Flowable.empty();
1005 new Consumer<Object>() {
1007 public void accept(Object o)
throws Exception {
1011 new Consumer<Throwable>() {
1013 public void accept(Throwable throwable)
throws Exception {
1019 public void run()
throws Exception {
1026 if( throwable instanceof BleDisconnected ){
1029 return new Exception(
"Unknown Error: " + throwable.getLocalizedMessage());
1033 interface FetchRecursiveCondition {
1034 boolean include(String entry);
1037 private Flowable<String>
fetchRecursively(
final BlePsFtpClient client,
final String path,
final FetchRecursiveCondition condition) {
1038 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
1039 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
1040 builder.setPath(path);
1041 return client.request(builder.build().toByteArray()).toFlowable().flatMap(
new Function<ByteArrayOutputStream, Publisher<String>>() {
1043 public Publisher<String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
1044 PftpResponse.PbPFtpDirectory dir = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
1045 Set<String> entrys =
new HashSet<>();
1046 for(
int i=0; i < dir.getEntriesCount(); ++i ){
1047 PftpResponse.PbPFtpEntry entry = dir.getEntries(i);
1048 if( condition.include(entry.getName()) ){
1049 BleUtils.validate(entrys.add(path + entry.getName()),
"duplicate entry");
1052 if(entrys.size()!=0) {
1053 return Flowable.fromIterable(entrys).flatMap(
new Function<String, Publisher<String>>() {
1055 public Publisher<String> apply(String s) {
1056 if (s.endsWith(
"/")) {
1059 return Flowable.just(s);
1064 return Flowable.empty();
1069 @SuppressLint(
"CheckResult")
1071 client.sendNotification(notification,
null).subscribe(
1074 public void run()
throws Exception {
1077 new Consumer<Throwable>() {
1079 public void accept(Throwable throwable)
throws Exception {
1080 logError(
"failed to send notification: " + throwable.getLocalizedMessage());
1086 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 accelerometerFeatureReady(@NonNull final 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
+
+
void ecgFeatureReady(@NonNull final String identifier)
+
+
+
Single< PolarSensorSetting > querySettings(final String identifier, final BlePMDClient.PmdMeasurementType type)
+
+
static final int FEATURE_POLAR_SENSOR_STREAMING
+
void message(final String str)
+
+
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)
+
+
+
+
static final int FEATURE_BATTERY_INFO
+
+
+
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)
+
+
static final int FEATURE_HR
+
+
BleDeviceSession sessionPmdClientReady(final 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)
+
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)
+
+
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
+
Flowable< PolarExerciseEntry > listExercises(String identifier)
+
+
+
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)
+
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 biozFeatureReady(@NonNull final String identifier)
+
+
+
+
BlePMDClient.PmdSetting map2PmdSettings()
+
+
+
+
void setApiCallback(PolarBleApiCallback callback)
+
+
BleDeviceSession sessionPsFtpClientReady(final String identifier)
+
void logError(final String message)
+
+
+
+
void connectToPolarDevice(final String identifier)
+