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;
44 import java.util.UUID;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.atomic.AtomicInteger;
48 import fi.polar.remote.representation.protobuf.ExerciseSamples;
49 import fi.polar.remote.representation.protobuf.Types;
50 import io.reactivex.Completable;
51 import io.reactivex.CompletableEmitter;
52 import io.reactivex.CompletableOnSubscribe;
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;
86 import protocol.PftpNotification;
87 import protocol.PftpRequest;
88 import protocol.PftpResponse;
101 BleDeviceListener.BleSearchPreFilter filter =
new BleDeviceListener.BleSearchPreFilter() {
103 public boolean process(BleAdvertisementContent content) {
104 return content.getPolarDeviceId().length() != 0 && !content.getPolarDeviceType().equals(
"mobile");
108 @SuppressLint({
"NewApi",
"CheckResult"})
111 Set<Class<? extends BleGattBase>> clients =
new HashSet<>();
113 clients.add(BleHrClient.class);
116 clients.add(BleDisClient.class);
119 clients.add(BleBattClient.class);
122 clients.add(BlePMDClient.class);
125 clients.add(BlePsFtpClient.class);
127 listener =
new BDDeviceListenerImpl(context, clients);
129 scheduler = AndroidSchedulers.from(context.getMainLooper());
131 new Consumer<Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState>>() {
133 public void accept(Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState> pair)
throws Exception {
135 pair.first.getPolarDeviceId().length() != 0 ?
136 pair.first.getPolarDeviceId() : pair.first.getAddress(),
137 pair.first.getAddress(),
138 pair.first.getRssi(),pair.first.getName(),
true);
139 switch (pair.second){
148 if (pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
149 pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSING){
154 case SESSION_OPENING:
162 new Consumer<Throwable>() {
164 public void accept(Throwable throwable)
throws Exception {
170 public void run()
throws Exception {
176 new Consumer<Boolean>() {
178 public void accept(Boolean aBoolean)
throws Exception {
184 new Consumer<Throwable>() {
186 public void accept(Throwable throwable)
throws Exception {
192 public void run()
throws Exception {
197 BleLogger.setLoggerInterface(
new BleLogger.BleLoggerInterface() {
199 public void d(String tag, String msg) {
204 public void e(String tag, String msg) {
209 public void w(String tag, String msg) {
213 public void i(String tag, String msg) {
218 @SuppressLint(
"NewApi")
221 List<ScanFilter> filter =
new ArrayList<>();
222 filter.add(
new ScanFilter.Builder().setServiceUuid(
223 ParcelUuid.fromString(BleHrClient.HR_SERVICE.toString())).build());
224 filter.add(
new ScanFilter.Builder().setServiceUuid(
225 ParcelUuid.fromString(BlePsFtpUtils.RFC77_PFTP_SERVICE.toString())).build());
258 }
catch (Throwable ignored) {
276 listener.setAutomaticReconnection(disable);
283 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
284 PftpRequest.PbPFtpSetLocalTimeParams.Builder builder = PftpRequest.PbPFtpSetLocalTimeParams.newBuilder();
285 Types.PbDate date = Types.PbDate.newBuilder()
286 .setYear(cal.get(Calendar.YEAR))
287 .setMonth(cal.get(Calendar.MONTH) + 1)
288 .setDay(cal.get(Calendar.DAY_OF_MONTH)).build();
289 Types.PbTime time = Types.PbTime.newBuilder()
290 .setHour(cal.get(Calendar.HOUR_OF_DAY))
291 .setMinute(cal.get(Calendar.MINUTE))
292 .setSeconds(cal.get(Calendar.SECOND))
293 .setMillis(cal.get(Calendar.MILLISECOND)).build();
294 builder.setDate(date).setTime(time).setTzOffset((
int) TimeUnit.MINUTES.convert(cal.get(Calendar.ZONE_OFFSET), TimeUnit.MILLISECONDS));
295 return client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE,builder.build().toByteArray()).toObservable().ignoreElements();
296 }
catch (Throwable error){
297 return Completable.error(error);
303 return querySettings(identifier,BlePMDClient.PmdMeasurementType.ACC);
308 return querySettings(identifier,BlePMDClient.PmdMeasurementType.ECG);
313 return querySettings(identifier,BlePMDClient.PmdMeasurementType.PPG);
318 return querySettings(identifier,BlePMDClient.PmdMeasurementType.BIOZ);
321 protected Single<PolarSensorSetting>
querySettings(
final String identifier,
final BlePMDClient.PmdMeasurementType type) {
324 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
325 return client.querySettings(type).map(
new Function<BlePMDClient.PmdSetting, PolarSensorSetting>() {
331 }
catch (Throwable e){
332 return Single.error(e);
347 public Completable
autoConnectToDevice(
final int rssiLimit,
final String service,
final int timeout,
final TimeUnit unit,
final String polarDeviceType) {
348 final long[] start = {0};
349 return Completable.create(
new CompletableOnSubscribe() {
351 public void subscribe(CompletableEmitter emitter)
throws Exception {
352 if( service ==
null || service.matches(
"([0-9a-fA-F]{4})") ) {
353 emitter.onComplete();
358 }).andThen(
listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
360 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
361 if( bleDeviceSession.getMedianRssi() >= rssiLimit &&
362 bleDeviceSession.isConnectableAdvertisement() &&
363 (polarDeviceType ==
null || polarDeviceType.equals(bleDeviceSession.getPolarDeviceType())) &&
364 (service ==
null || bleDeviceSession.getAdvertisementContent().containsService(service)) ) {
366 start[0] = System.currentTimeMillis();
372 }).timestamp().takeUntil(
new Predicate<Timed<BleDeviceSession>>() {
374 public boolean test(Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
375 long diff = bleDeviceSessionTimed.time(TimeUnit.MILLISECONDS) - start[0];
376 return (diff >= unit.toMillis(timeout));
378 }).reduce(
new HashSet<BleDeviceSession>(),
new BiFunction<Set<BleDeviceSession>, Timed<BleDeviceSession>, Set<BleDeviceSession>>() {
380 public Set<BleDeviceSession> apply(Set<BleDeviceSession> objects, Timed<BleDeviceSession> bleDeviceSessionTimed)
throws Exception {
381 objects.add(bleDeviceSessionTimed.value());
384 }).doOnSuccess(
new Consumer<Set<BleDeviceSession>>() {
386 public void accept(Set<BleDeviceSession>
set)
throws Exception {
387 List<BleDeviceSession> list =
new ArrayList<>(
set);
388 Collections.sort(list,
new Comparator<BleDeviceSession>() {
390 public int compare(BleDeviceSession o1, BleDeviceSession o2) {
391 return o1.getRssi() > o2.getRssi() ? -1 : 1;
394 listener.openSessionDirect(list.get(0));
395 log(
"auto connect search complete");
397 }).toObservable().ignoreElements());
401 public Completable
autoConnectToDevice(
final int rssiLimit,
final String service,
final String polarDeviceType) {
408 if( session ==
null || session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSED ){
413 if( session !=
null ){
414 listener.openSessionDirect(session);
418 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
419 return identifier.contains(
":") ?
420 bleDeviceSession.getAddress().equals(identifier) :
421 bleDeviceSession.getPolarDeviceId().equals(identifier);
423 }).take(1).observeOn(
scheduler).subscribe(
424 new Consumer<BleDeviceSession>() {
426 public void accept(BleDeviceSession bleDeviceSession)
throws Exception {
427 listener.openSessionDirect(bleDeviceSession);
430 new Consumer<Throwable>() {
432 public void accept(Throwable throwable)
throws Exception {
438 public void run()
throws Exception {
439 log(
"connect search complete");
450 if( session !=
null ){
451 if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
452 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPENING ||
453 session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN_PARK ) {
454 listener.closeSessionDirect(session);
467 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
468 if(session.getPolarDeviceType().equals(
"H10")) {
470 Types.PbSampleType.SAMPLE_TYPE_HEART_RATE :
471 Types.PbSampleType.SAMPLE_TYPE_RR_INTERVAL;
472 Types.PbDuration duration = Types.PbDuration.newBuilder().setSeconds(interval.
getValue()).build();
473 PftpRequest.PbPFtpRequestStartRecordingParams params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().
474 setSampleDataIdentifier(exerciseId).setSampleType(t).setRecordingInterval(duration).build();
475 return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
477 public CompletableSource apply(Throwable throwable)
throws Exception {
478 return Completable.error(throwable);
483 }
catch (Throwable error){
484 return Completable.error(error);
492 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
493 if(session.getPolarDeviceType().equals(
"H10")) {
494 return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE,
null).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
496 public CompletableSource apply(Throwable throwable)
throws Exception {
502 }
catch (Throwable error){
503 return Completable.error(error);
511 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
512 if(session.getPolarDeviceType().equals(
"H10")) {
513 return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE,
null).map(
new Function<ByteArrayOutputStream, Pair<Boolean,String>>() {
515 public Pair<Boolean,String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
516 PftpResponse.PbRequestRecordingStatusResult result = PftpResponse.PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray());
517 return new Pair<>(result.getRecordingOn(),result.hasSampleDataIdentifier() ? result.getSampleDataIdentifier() :
"");
519 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends Pair<Boolean, String>>>() {
521 public SingleSource<? extends Pair<Boolean, String>> apply(Throwable throwable)
throws Exception {
527 }
catch (Throwable error){
528 return Single.error(error);
536 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
537 switch (session.getPolarDeviceType()) {
541 public boolean include(String entry) {
542 return entry.matches(
"^([0-9]{8})(\\/)") ||
543 entry.matches(
"^([0-9]{6})(\\/)") ||
544 entry.equals(
"E/") ||
545 entry.equals(
"SAMPLES.BPB") ||
548 }).map(
new Function<String, PolarExerciseEntry>() {
551 String components[] = p.split(
"/");
552 SimpleDateFormat format =
new SimpleDateFormat(
"yyyyMMdd HHmmss", Locale.getDefault());
553 Date date = format.parse(components[3] +
" " + components[5]);
556 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
558 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
565 public boolean include(String entry) {
566 return entry.endsWith(
"/") || entry.equals(
"SAMPLES.BPB");
568 }).map(
new Function<String, PolarExerciseEntry>() {
571 String components[] = p.split(
"/");
574 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
576 public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable)
throws Exception {
583 }
catch (Throwable error){
584 return Flowable.error(error);
592 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
593 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
594 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
595 builder.setPath(entry.
path);
596 if(session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10")) {
597 return client.request(builder.build().toByteArray()).map(
new Function<ByteArrayOutputStream, PolarExerciseData>() {
599 public PolarExerciseData apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
600 ExerciseSamples.PbExerciseSamples samples = ExerciseSamples.PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray());
601 if(samples.hasRrSamples()){
602 return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getRrSamples().getRrIntervalsList());
604 return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getHeartRateSamplesList());
607 }).onErrorResumeNext(
new Function<Throwable, SingleSource<? extends PolarExerciseData>>() {
609 public SingleSource<? extends PolarExerciseData> apply(Throwable throwable)
throws Exception {
615 }
catch (Throwable error){
616 return Single.error(error);
624 final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
625 if(session.getPolarDeviceType().equals(
"OH1")){
626 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
627 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
628 final String[] components = entry.
path.split(
"/");
629 final String exerciseParent =
"/U/0/" + components[3] +
"/E/";
630 builder.setPath(exerciseParent);
631 return client.request(builder.build().toByteArray()).flatMap(
new Function<ByteArrayOutputStream, SingleSource<?>>() {
633 public SingleSource<?> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
634 PftpResponse.PbPFtpDirectory directory = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
635 protocol.PftpRequest.PbPFtpOperation.Builder removeBuilder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
636 removeBuilder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
637 if( directory.getEntriesCount() <= 1 ){
639 removeBuilder.setPath(
"/U/0/" + components[3] +
"/");
642 removeBuilder.setPath(
"/U/0/" + components[3] +
"/E/" + components[5] +
"/");
644 return client.request(removeBuilder.build().toByteArray());
646 }).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
648 public CompletableSource apply(Throwable throwable)
throws Exception {
652 }
else if(session.getPolarDeviceType().equals(
"H10")){
653 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
654 builder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
655 builder.setPath(entry.
path);
656 return client.request(builder.build().toByteArray()).toObservable().ignoreElements().onErrorResumeNext(
new Function<Throwable, CompletableSource>() {
658 public CompletableSource apply(Throwable throwable)
throws Exception {
664 }
catch (Throwable error){
665 return Completable.error(error);
671 return listener.search(
false).distinct().map(
new Function<BleDeviceSession, PolarDeviceInfo>() {
673 public PolarDeviceInfo apply(BleDeviceSession bleDeviceSession)
throws Exception {
675 bleDeviceSession.getAddress(),
676 bleDeviceSession.getRssi(),
677 bleDeviceSession.getName(),
678 bleDeviceSession.isConnectableAdvertisement());
686 return listener.search(
false).filter(
new Predicate<BleDeviceSession>() {
688 public boolean test(BleDeviceSession bleDeviceSession)
throws Exception {
689 return (deviceIds ==
null || deviceIds.contains(bleDeviceSession.getPolarDeviceId())) &&
690 bleDeviceSession.getAdvertisementContent().getPolarHrAdvertisement().isPresent();
692 }).map(
new Function<BleDeviceSession, PolarHrBroadcastData>() {
695 BlePolarHrAdvertisement advertisement = bleDeviceSession.getBlePolarHrAdvertisement();
697 bleDeviceSession.getAddress(),
698 bleDeviceSession.getRssi(),
699 bleDeviceSession.getName(),
700 bleDeviceSession.isConnectableAdvertisement()),
701 advertisement.getHrForDisplay(),
702 advertisement.getBatteryStatus() != 0);
712 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
713 return client.startMeasurement(BlePMDClient.PmdMeasurementType.ECG, setting.
map2PmdSettings()).andThen(
714 client.monitorEcgNotifications(
true).map(
new Function<BlePMDClient.EcgData, PolarEcgData>() {
716 public PolarEcgData apply(BlePMDClient.EcgData ecgData)
throws Exception {
717 List<Integer> samples =
new ArrayList<>();
718 for( BlePMDClient.EcgData.EcgSample s : ecgData.ecgSamples ){
719 samples.add(s.microVolts);
723 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarEcgData>>() {
725 public Publisher<? extends PolarEcgData> apply(Throwable throwable)
throws Exception {
728 }).doFinally(
new Action() {
730 public void run()
throws Exception {
734 }
catch (Throwable t){
735 return Flowable.error(t);
744 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
745 return client.startMeasurement(BlePMDClient.PmdMeasurementType.ACC, setting.
map2PmdSettings()).andThen(
746 client.monitorAccNotifications(
true).map(
new Function<BlePMDClient.AccData, PolarAccelerometerData>() {
750 for( BlePMDClient.AccData.AccSample s : accData.accSamples ){
755 }).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarAccelerometerData>>() {
757 public Publisher<? extends PolarAccelerometerData> apply(Throwable throwable)
throws Exception {
760 }).doFinally(
new Action() {
762 public void run()
throws Exception {
766 }
catch (Throwable t){
767 return Flowable.error(t);
776 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
777 return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPG, setting.
map2PmdSettings()).andThen(
778 client.monitorPpgNotifications(
true).map(
new Function<BlePMDClient.PpgData, PolarOhrPPGData>() {
780 public PolarOhrPPGData apply(BlePMDClient.PpgData ppgData)
throws Exception {
782 for( BlePMDClient.PpgData.PpgSample s : ppgData.ppgSamples ){
787 }).doFinally(
new Action() {
789 public void run()
throws Exception {
792 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarOhrPPGData>>() {
794 public Publisher<? extends PolarOhrPPGData> apply(Throwable throwable)
throws Exception {
798 }
catch (Throwable t){
799 return Flowable.error(t);
807 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
808 return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPI,
new BlePMDClient.PmdSetting(
new HashMap<BlePMDClient.PmdSetting.PmdSettingType, Integer>())).andThen(
809 client.monitorPpiNotifications(
true).map(
new Function<BlePMDClient.PpiData, PolarOhrPPIData>() {
811 public PolarOhrPPIData apply(BlePMDClient.PpiData ppiData)
throws Exception {
813 for(BlePMDClient.PpiData.PPSample ppSample : ppiData.ppSamples){
815 ppSample.ppErrorEstimate,
817 ppSample.blockerBit != 0,
818 ppSample.skinContactStatus != 0,
819 ppSample.skinContactSupported != 0));
823 }).doFinally(
new Action() {
825 public void run()
throws Exception {
828 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarOhrPPIData>>() {
830 public Publisher<? extends PolarOhrPPIData> apply(Throwable throwable)
throws Exception {
834 }
catch (Throwable t){
835 return Flowable.error(t);
843 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
844 return client.startMeasurement(BlePMDClient.PmdMeasurementType.BIOZ, setting.
map2PmdSettings()).andThen(
845 client.monitorBiozNotifications(
true).map(
new Function<BlePMDClient.BiozData, PolarBiozData>() {
847 public PolarBiozData apply(BlePMDClient.BiozData biozData)
throws Exception {
848 return new PolarBiozData(biozData.timeStamp,biozData.samples,biozData.status);
850 }).doFinally(
new Action() {
852 public void run()
throws Exception {
855 })).onErrorResumeNext(
new Function<Throwable, Publisher<? extends PolarBiozData>>() {
857 public Publisher<? extends PolarBiozData> apply(Throwable throwable)
throws Exception {
861 }
catch (Throwable t){
862 return Flowable.error(t);
867 if(identifier.matches(
"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")){
869 }
else if(identifier.matches(
"([0-9a-fA-F]){6,8}")) {
876 for ( BleDeviceSession session :
listener.deviceSessions() ){
877 if( session.getAddress().equals(address) ){
885 for ( BleDeviceSession session :
listener.deviceSessions() ){
886 if( session.getAdvertisementContent().getPolarDeviceId().equals(deviceId) ){
896 if(session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN) {
897 BleGattBase client = session.fetchClient(service);
898 if (client.isServiceDiscovered()) {
910 BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
911 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePMDClient.PMD_CP);
912 final AtomicInteger pairData = client.getNotificationAtomicInteger(BlePMDClient.PMD_DATA);
913 if (pair !=
null && pairData !=
null &&
914 pair.get() == BleGattBase.ATT_SUCCESS &&
915 pairData.get() == BleGattBase.ATT_SUCCESS) {
922 BleDeviceSession session =
sessionServiceReady(identifier, BlePsFtpUtils.RFC77_PFTP_SERVICE);
923 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
924 final AtomicInteger pair = client.getNotificationAtomicInteger(BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC);
925 if (pair !=
null && pair.get() == BleGattBase.ATT_SUCCESS ) {
931 @SuppressLint(
"CheckResult")
932 protected
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type) {
933 if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ){
935 client.stopMeasurement(type).subscribe(
938 public void run()
throws Exception {
942 new Consumer<Throwable>() {
944 public void accept(Throwable throwable)
throws Exception {
945 logError(
"failed to stop pmd stream: " + throwable.getLocalizedMessage());
952 @SuppressLint(
"CheckResult")
954 final String deviceId = session.getPolarDeviceId().length() != 0 ? session.getPolarDeviceId() : session.getAddress();
955 session.monitorServicesDiscovered(
true).observeOn(
scheduler).toFlowable().flatMapIterable(
956 new Function<List<UUID>, Iterable<UUID>>() {
958 public Iterable<UUID> apply(List<UUID> uuids)
throws Exception {
962 ).flatMap(
new Function<UUID, Publisher<?>>() {
964 public Publisher<?> apply(UUID uuid)
throws Exception {
965 if(session.fetchClient(uuid) !=
null) {
966 if (uuid.equals(BleHrClient.HR_SERVICE)) {
970 final BleHrClient client = (BleHrClient) session.fetchClient(BleHrClient.HR_SERVICE);
971 client.observeHrNotifications(
true).observeOn(
scheduler).subscribe(
972 new Consumer<BleHrClient.HrNotificationData>() {
974 public void accept(BleHrClient.HrNotificationData hrNotificationData)
throws Exception {
978 hrNotificationData.rrs,
979 hrNotificationData.sensorContact,
980 hrNotificationData.sensorContactSupported,
981 hrNotificationData.rrPresent));
985 new Consumer<Throwable>() {
987 public void accept(Throwable throwable)
throws Exception {
993 public void run()
throws Exception {
998 }
else if (uuid.equals(BleBattClient.BATTERY_SERVICE)) {
999 BleBattClient client = (BleBattClient) session.fetchClient(BleBattClient.BATTERY_SERVICE);
1000 client.monitorBatteryLevelUpdate(
true).observeOn(
scheduler).subscribe(
1001 new Consumer<Integer>() {
1003 public void accept(Integer integer)
throws Exception {
1009 new Consumer<Throwable>() {
1011 public void accept(Throwable throwable)
throws Exception {
1017 public void run()
throws Exception {
1021 }
else if (uuid.equals(BlePMDClient.PMD_SERVICE)) {
1022 final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
1023 return client.waitNotificationEnabled(BlePMDClient.PMD_CP,
true).
1024 concatWith(client.waitNotificationEnabled(BlePMDClient.PMD_DATA,
true)).andThen(client.readFeature(
true).doOnSuccess(
new Consumer<BlePMDClient.PmdFeature>() {
1026 public void accept(BlePMDClient.PmdFeature pmdFeature) {
1028 if (pmdFeature.ecgSupported) {
1031 if (pmdFeature.accSupported) {
1034 if (pmdFeature.ppgSupported) {
1037 if (pmdFeature.ppiSupported) {
1040 if (pmdFeature.bioZSupported) {
1046 }
else if (uuid.equals(BleDisClient.DIS_SERVICE)) {
1047 BleDisClient client = (BleDisClient) session.fetchClient(BleDisClient.DIS_SERVICE);
1048 return client.observeDisInfo(
true).observeOn(
scheduler).doOnNext(
new Consumer<Pair<UUID, String>>() {
1050 public void accept(Pair<UUID, String> pair) {
1056 }
else if (uuid.equals(BlePsFtpUtils.RFC77_PFTP_SERVICE)) {
1057 BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
1058 return client.waitPsFtpClientReady(
true).observeOn(
scheduler).doOnComplete(
new Action() {
1060 public void run()
throws Exception {
1062 (session.getPolarDeviceType().equals(
"OH1") || session.getPolarDeviceType().equals(
"H10"))) {
1069 return Flowable.empty();
1072 new Consumer<Object>() {
1074 public void accept(Object o)
throws Exception {
1078 new Consumer<Throwable>() {
1080 public void accept(Throwable throwable)
throws Exception {
1086 public void run()
throws Exception {
1093 if( throwable instanceof BleDisconnected ){
1096 return new Exception(
"Unknown Error: " + throwable.getLocalizedMessage());
1100 interface FetchRecursiveCondition {
1101 boolean include(String entry);
1104 protected Flowable<String>
fetchRecursively(
final BlePsFtpClient client,
final String path,
final FetchRecursiveCondition condition) {
1105 protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
1106 builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
1107 builder.setPath(path);
1108 return client.request(builder.build().toByteArray()).toFlowable().flatMap(
new Function<ByteArrayOutputStream, Publisher<String>>() {
1110 public Publisher<String> apply(ByteArrayOutputStream byteArrayOutputStream)
throws Exception {
1111 PftpResponse.PbPFtpDirectory dir = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
1112 Set<String> entrys =
new HashSet<>();
1113 for(
int i=0; i < dir.getEntriesCount(); ++i ){
1114 PftpResponse.PbPFtpEntry entry = dir.getEntries(i);
1115 if( condition.include(entry.getName()) ){
1116 BleUtils.validate(entrys.add(path + entry.getName()),
"duplicate entry");
1119 if(entrys.size()!=0) {
1120 return Flowable.fromIterable(entrys).flatMap(
new Function<String, Publisher<String>>() {
1122 public Publisher<String> apply(String s) {
1123 if (s.endsWith(
"/")) {
1126 return Flowable.just(s);
1131 return Flowable.empty();
1136 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)
@@ -80,7 +80,7 @@
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)
void hrFeatureReady(@NonNull final String identifier)
@@ -131,7 +131,7 @@
BleDeviceSession sessionPmdClientReady(final String identifier)
-
Flowable< String > fetchRecursively(final BlePsFtpClient client, final String path, final FetchRecursiveCondition condition)
+
Flowable< String > fetchRecursively(final BlePsFtpClient client, final String path, final FetchRecursiveCondition condition)
Completable stopRecording(String identifier)
void biozFeatureReady(@NonNull final String identifier)
@@ -172,7 +172,7 @@
Completable setLocalTime(String identifier, Calendar cal)
BleDeviceSession sessionPsFtpClientReady(final String identifier)
-
void logError(final String message)
+
void logError(final String message)
diff --git a/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1model_1_1PolarOhrPPIData.html b/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1model_1_1PolarOhrPPIData.html
index 27cedd25..2e817902 100644
--- a/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1model_1_1PolarOhrPPIData.html
+++ b/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1model_1_1PolarOhrPPIData.html
@@ -160,7 +160,7 @@