From eaadf6172bc168680858ca544344fd1ce1031917 Mon Sep 17 00:00:00 2001 From: Erkki Silvola Date: Wed, 24 Apr 2019 10:30:53 +0300 Subject: [PATCH] docs updated --- .../docs/html/BDBleApiImpl_8java_source.html | 112 +++++++++--------- .../PolarBleApiDefaultImpl_8java_source.html | 2 +- .../docs/html/PolarBleApi_8java_source.html | 2 +- .../PolarInvalidArgument_8java_source.html | 4 +- ...r_1_1com_1_1sdk_1_1api_1_1PolarBleApi.html | 5 +- ...api_1_1errors_1_1PolarInvalidArgument.html | 68 ++++++++++- ..._1api_1_1errors_1_1PolarInvalidArgument.js | 5 + ...1_1com_1_1sdk_1_1impl_1_1BDBleApiImpl.html | 110 ++++++++--------- polar-sdk-android/docs/html/files_dup.js | 2 +- polar-sdk-android/docs/html/functions.html | 3 + .../docs/html/functions_func.html | 3 + ...acepolar_1_1com_1_1sdk_1_1api_1_1errors.js | 2 +- polar-sdk-android/docs/html/navtreedata.js | 2 +- polar-sdk-android/docs/html/navtreeindex0.js | 6 +- polar-sdk-android/docs/html/navtreeindex1.js | 2 + .../docs/API Default Implementation.html | 2 +- polar-sdk-ios/docs/API.html | 2 +- .../docs/Classes/PolarBleApiDefaultImpl.html | 2 +- polar-sdk-ios/docs/Enums/Features.html | 2 +- polar-sdk-ios/docs/Enums/SampleType.html | 2 +- polar-sdk-ios/docs/Other Enums.html | 2 +- polar-sdk-ios/docs/Other Typealiases.html | 2 +- polar-sdk-ios/docs/PolarErrors.html | 2 +- polar-sdk-ios/docs/Protocols/PolarBleApi.html | 16 ++- .../PolarBleApiDeviceFeaturesObserver.html | 2 +- .../PolarBleApiDeviceHrObserver.html | 2 +- .../PolarBleApiDeviceInfoObserver.html | 2 +- .../docs/Protocols/PolarBleApiLogger.html | 2 +- .../docs/Protocols/PolarBleApiObserver.html | 2 +- .../PolarBleApiPowerStateObserver.html | 2 +- .../docs/Structs/PolarSensorSetting.html | 2 +- .../PolarSensorSetting/SettingType.html | 2 +- .../Documents/API Default Implementation.html | 2 +- .../Contents/Resources/Documents/API.html | 2 +- .../Classes/PolarBleApiDefaultImpl.html | 2 +- .../Resources/Documents/Enums/Features.html | 2 +- .../Resources/Documents/Enums/SampleType.html | 2 +- .../Resources/Documents/Other Enums.html | 2 +- .../Documents/Other Typealiases.html | 2 +- .../Resources/Documents/PolarErrors.html | 2 +- .../Documents/Protocols/PolarBleApi.html | 16 ++- .../PolarBleApiDeviceFeaturesObserver.html | 2 +- .../PolarBleApiDeviceHrObserver.html | 2 +- .../PolarBleApiDeviceInfoObserver.html | 2 +- .../Protocols/PolarBleApiLogger.html | 2 +- .../Protocols/PolarBleApiObserver.html | 2 +- .../PolarBleApiPowerStateObserver.html | 2 +- .../Documents/Structs/PolarSensorSetting.html | 2 +- .../PolarSensorSetting/SettingType.html | 2 +- .../Contents/Resources/Documents/index.html | 2 +- polar-sdk-ios/docs/index.html | 2 +- 51 files changed, 267 insertions(+), 161 deletions(-) create mode 100644 polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1errors_1_1PolarInvalidArgument.js diff --git a/polar-sdk-android/docs/html/BDBleApiImpl_8java_source.html b/polar-sdk-android/docs/html/BDBleApiImpl_8java_source.html index dfddbb81..391d3720 100644 --- a/polar-sdk-android/docs/html/BDBleApiImpl_8java_source.html +++ b/polar-sdk-android/docs/html/BDBleApiImpl_8java_source.html @@ -66,48 +66,48 @@
BDBleApiImpl.java
-Go to the documentation of this file.
1 // Copyright © 2019 Polar Electro Oy. All rights reserved.
2 package polar.com.sdk.impl;
3 
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;
12 
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;
28 
29 import org.reactivestreams.Publisher;
30 
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;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.UUID;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.atomic.AtomicInteger;
47 
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.CompletableSource;
52 import io.reactivex.Flowable;
53 import io.reactivex.Scheduler;
54 import io.reactivex.Single;
55 import io.reactivex.SingleSource;
56 import io.reactivex.android.schedulers.AndroidSchedulers;
57 import io.reactivex.disposables.Disposable;
58 import io.reactivex.functions.Action;
59 import io.reactivex.functions.BiFunction;
60 import io.reactivex.functions.Consumer;
61 import io.reactivex.functions.Function;
62 import io.reactivex.functions.Predicate;
63 import io.reactivex.schedulers.Timed;
64 import polar.com.sdk.api.PolarBleApi;
83 import protocol.PftpNotification;
84 import protocol.PftpRequest;
85 import protocol.PftpResponse;
86 
90 public class BDBleApiImpl extends PolarBleApi {
91  protected final static String TAG = BDBleApiImpl.class.getSimpleName();
92  protected BleDeviceListener listener;
93  protected Map<String,Disposable> connectSubscriptions = new HashMap<>();
94  protected Scheduler scheduler;
97  protected static final int ANDROID_VERSION_O = 26;
98  BleDeviceListener.BleSearchPreFilter filter = new BleDeviceListener.BleSearchPreFilter() {
99  @Override
100  public boolean process(BleAdvertisementContent content) {
101  return content.getPolarDeviceId().length() != 0 && !content.getPolarDeviceType().equals("mobile");
102  }
103  };
104 
105  @SuppressLint({"NewApi", "CheckResult"})
106  public BDBleApiImpl(final Context context, int features) {
107  super(features);
108  Set<Class<? extends BleGattBase>> clients = new HashSet<>();
109  if((this.features & PolarBleApi.FEATURE_HR)!=0){
110  clients.add(BleHrClient.class);
111  }
112  if((this.features & PolarBleApi.FEATURE_DEVICE_INFO)!=0){
113  clients.add(BleDisClient.class);
114  }
115  if((this.features & PolarBleApi.FEATURE_BATTERY_INFO)!=0){
116  clients.add(BleBattClient.class);
117  }
118  if((this.features & PolarBleApi.FEATURE_POLAR_SENSOR_STREAMING)!=0){
119  clients.add(BlePMDClient.class);
120  }
121  if((this.features & PolarBleApi.FEATURE_POLAR_FILE_TRANSFER)!=0){
122  clients.add(BlePsFtpClient.class);
123  }
124  listener = new BDDeviceListenerImpl(context, clients);
125  listener.setScanPreFilter(filter);
126  scheduler = AndroidSchedulers.from(context.getMainLooper());
127  listener.monitorDeviceSessionState(null).observeOn(scheduler).subscribe(
128  new Consumer<Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState>>() {
129  @Override
130  public void accept(Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState> pair) throws Exception {
131  PolarDeviceInfo info = new PolarDeviceInfo(
132  pair.first.getPolarDeviceId().length() != 0 ?
133  pair.first.getPolarDeviceId() : pair.first.getAddress(),
134  pair.first.getAddress(),
135  pair.first.getRssi(),pair.first.getName(),true);
136  switch (pair.second){
137  case SESSION_OPEN:
138  if(callback!=null){
140  }
141  setupDevice(pair.first);
142  break;
143  case SESSION_CLOSED:
144  if( callback != null ) {
145  if (pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
146  pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSING){
148  }
149  }
150  break;
151  case SESSION_OPENING:
152  if(callback != null){
154  }
155  break;
156  }
157  }
158  },
159  new Consumer<Throwable>() {
160  @Override
161  public void accept(Throwable throwable) throws Exception {
162  logError(throwable.getMessage());
163  }
164  },
165  new Action() {
166  @Override
167  public void run() throws Exception {
168 
169  }
170  }
171  );
172  listener.monitorBleState().observeOn(scheduler).subscribe(
173  new Consumer<Boolean>() {
174  @Override
175  public void accept(Boolean aBoolean) throws Exception {
176  if(callback != null){
177  callback.blePowerStateChanged(aBoolean);
178  }
179  }
180  },
181  new Consumer<Throwable>() {
182  @Override
183  public void accept(Throwable throwable) throws Exception {
184  logError(throwable.getMessage());
185  }
186  },
187  new Action() {
188  @Override
189  public void run() throws Exception {
190 
191  }
192  }
193  );
194  BleLogger.setLoggerInterface(new BleLogger.BleLoggerInterface() {
195  @Override
196  public void d(String tag, String msg) {
197  log(tag+"/"+msg);
198  }
199 
200  @Override
201  public void e(String tag, String msg) {
202  logError(tag+"/"+msg);
203  }
204 
205  @Override
206  public void w(String tag, String msg) {
207  }
208 
209  @Override
210  public void i(String tag, String msg) {
211  }
212  });
213  }
214 
215  @SuppressLint("NewApi")
216  protected void enableAndroidScanFilter() {
217  if (Build.VERSION.SDK_INT >= ANDROID_VERSION_O) {
218  List<ScanFilter> filter = new ArrayList<>();
219  filter.add(new ScanFilter.Builder().setServiceUuid(
220  ParcelUuid.fromString(BleHrClient.HR_SERVICE.toString())).build());
221  filter.add(new ScanFilter.Builder().setServiceUuid(
222  ParcelUuid.fromString(BlePsFtpUtils.RFC77_PFTP_SERVICE.toString())).build());
223  listener.setScanFilters(filter);
224  }
225  }
226 
227  @Override
228  public void shutDown() {
229  listener.shutDown();
230  }
231 
232  @Override
233  public void cleanup() {
234  listener.removeAllSessions();
235  }
236 
237  @Override
238  public void setPolarFilter(boolean enable) {
239  if(!enable) {
240  listener.setScanPreFilter(null);
241  } else {
242  listener.setScanPreFilter(filter);
243  }
244  }
245 
246  @Override
247  public boolean isFeatureReady(final String deviceId, int feature) {
248  try {
249  switch (feature) {
251  return sessionPsFtpClientReady(deviceId) != null;
253  return sessionPmdClientReady(deviceId) != null;
254  }
255  } catch (Throwable ignored) {
256  }
257  return false;
258  }
259 
260  @Override
262  this.callback = callback;
264  }
265 
266  @Override
267  public void setApiLogger(@Nullable PolarBleApiLogger logger) {
268  this.logger = logger;
269  }
270 
271  @Override
272  public void setAutomaticReconnection(boolean disable) {
273  listener.setAutomaticReconnection(disable);
274  }
275 
276  @Override
277  public Completable setLocalTime(String identifier, Calendar cal) {
278  try {
279  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
280  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
281  PftpRequest.PbPFtpSetLocalTimeParams.Builder builder = PftpRequest.PbPFtpSetLocalTimeParams.newBuilder();
282  Types.PbDate date = Types.PbDate.newBuilder()
283  .setYear(cal.get(Calendar.YEAR))
284  .setMonth(cal.get(Calendar.MONTH) + 1)
285  .setDay(cal.get(Calendar.DAY_OF_MONTH)).build();
286  Types.PbTime time = Types.PbTime.newBuilder()
287  .setHour(cal.get(Calendar.HOUR_OF_DAY))
288  .setMinute(cal.get(Calendar.MINUTE))
289  .setSeconds(cal.get(Calendar.SECOND))
290  .setMillis(cal.get(Calendar.MILLISECOND)).build();
291  builder.setDate(date).setTime(time).setTzOffset((int) TimeUnit.MINUTES.convert(cal.get(Calendar.ZONE_OFFSET), TimeUnit.MILLISECONDS));
292  return client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE,builder.build().toByteArray()).toObservable().ignoreElements();
293  } catch (Throwable error){
294  return Completable.error(error);
295  }
296  }
297 
298  @Override
299  public Single<PolarSensorSetting> requestAccSettings(String identifier) {
300  return querySettings(identifier,BlePMDClient.PmdMeasurementType.ACC);
301  }
302 
303  @Override
304  public Single<PolarSensorSetting> requestEcgSettings(String identifier) {
305  return querySettings(identifier,BlePMDClient.PmdMeasurementType.ECG);
306  }
307 
308  @Override
309  public Single<PolarSensorSetting> requestPpgSettings(String identifier) {
310  return querySettings(identifier,BlePMDClient.PmdMeasurementType.PPG);
311  }
312 
313  @Override
314  public Single<PolarSensorSetting> requestBiozSettings(final String identifier){
315  return querySettings(identifier,BlePMDClient.PmdMeasurementType.BIOZ);
316  }
317 
318  protected Single<PolarSensorSetting> querySettings(final String identifier, final BlePMDClient.PmdMeasurementType type) {
319  try {
320  final BleDeviceSession session = sessionPmdClientReady(identifier);
321  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
322  return client.querySettings(type).map(new Function<BlePMDClient.PmdSetting, PolarSensorSetting>() {
323  @Override
324  public PolarSensorSetting apply(BlePMDClient.PmdSetting setting) throws Exception {
325  return new PolarSensorSetting(setting.settings, type);
326  }
327  });
328  } catch (Throwable e){
329  return Single.error(e);
330  }
331  }
332 
333  @Override
334  public void backgroundEntered() {
336  }
337 
338  @Override
339  public void foregroundEntered() {
340  listener.setScanFilters(null);
341  }
342 
343  @Override
344  public Completable autoConnectToDevice(final int rssiLimit, final String service, final int timeout, final TimeUnit unit, final String polarDeviceType) {
345  final long[] start = {0};
346  return listener.search(false).filter(new Predicate<BleDeviceSession>() {
347  @Override
348  public boolean test(BleDeviceSession bleDeviceSession) throws Exception {
349  if( bleDeviceSession.getMedianRssi() >= rssiLimit &&
350  bleDeviceSession.isConnectableAdvertisement() &&
351  (polarDeviceType == null || polarDeviceType.equals(bleDeviceSession.getPolarDeviceType())) &&
352  (service == null || bleDeviceSession.getAdvertisementContent().containsService(service)) ) {
353  if(start[0] == 0){
354  start[0] = System.currentTimeMillis();
355  }
356  return true;
357  }
358  return false;
359  }
360  }).timestamp().takeUntil(new Predicate<Timed<BleDeviceSession>>() {
361  @Override
362  public boolean test(Timed<BleDeviceSession> bleDeviceSessionTimed) throws Exception {
363  long diff = bleDeviceSessionTimed.time(TimeUnit.MILLISECONDS) - start[0];
364  return (diff >= unit.toMillis(timeout));
365  }
366  }).reduce(new HashSet<BleDeviceSession>(), new BiFunction<Set<BleDeviceSession>, Timed<BleDeviceSession>, Set<BleDeviceSession>>() {
367  @Override
368  public Set<BleDeviceSession> apply(Set<BleDeviceSession> objects, Timed<BleDeviceSession> bleDeviceSessionTimed) throws Exception {
369  objects.add(bleDeviceSessionTimed.value());
370  return objects;
371  }
372  }).doOnSuccess(new Consumer<Set<BleDeviceSession>>() {
373  @Override
374  public void accept(Set<BleDeviceSession> set) throws Exception {
375  List<BleDeviceSession> list = new ArrayList<>(set);
376  Collections.sort(list, new Comparator<BleDeviceSession>() {
377  @Override
378  public int compare(BleDeviceSession o1, BleDeviceSession o2) {
379  return o1.getRssi() > o2.getRssi() ? -1 : 1;
380  }
381  });
382  listener.openSessionDirect(list.get(0));
383  log("auto connect search complete");
384  }
385  }).toObservable().ignoreElements();
386  }
387 
388  @Override
389  public Completable autoConnectToDevice(final int rssiLimit, final String service, final String polarDeviceType) {
390  return autoConnectToDevice(rssiLimit, service, 2, TimeUnit.SECONDS, polarDeviceType);
391  }
392 
393  @Override
394  public void connectToDevice(final String identifier) throws PolarInvalidArgument {
395  BleDeviceSession session = fetchSession(identifier);
396  if( session == null || session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSED ){
397  if( connectSubscriptions.containsKey(identifier) ){
398  connectSubscriptions.get(identifier).dispose();
399  connectSubscriptions.remove(identifier);
400  }
401  if( session != null ){
402  listener.openSessionDirect(session);
403  } else {
404  connectSubscriptions.put(identifier, listener.search(false).filter(new Predicate<BleDeviceSession>() {
405  @Override
406  public boolean test(BleDeviceSession bleDeviceSession) throws Exception {
407  return identifier.contains(":") ?
408  bleDeviceSession.getAddress().equals(identifier) :
409  bleDeviceSession.getPolarDeviceId().equals(identifier);
410  }
411  }).take(1).observeOn(scheduler).subscribe(
412  new Consumer<BleDeviceSession>() {
413  @Override
414  public void accept(BleDeviceSession bleDeviceSession) throws Exception {
415  listener.openSessionDirect(bleDeviceSession);
416  }
417  },
418  new Consumer<Throwable>() {
419  @Override
420  public void accept(Throwable throwable) throws Exception {
421  logError(throwable.getMessage());
422  }
423  },
424  new Action() {
425  @Override
426  public void run() throws Exception {
427  log("connect search complete");
428  }
429  }
430  ));
431  }
432  }
433  }
434 
435  @Override
436  public void disconnectFromDevice(String identifier) throws PolarInvalidArgument {
437  BleDeviceSession session = fetchSession(identifier);
438  if( session != null ){
439  if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
440  session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPENING ||
441  session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN_PARK ) {
442  listener.closeSessionDirect(session);
443  }
444  }
445  if (connectSubscriptions.containsKey(identifier)){
446  connectSubscriptions.get(identifier).dispose();
447  connectSubscriptions.remove(identifier);
448  }
449  }
450 
451  @Override
452  public Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type) {
453  try {
454  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
455  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
456  if(session.getPolarDeviceType().equals("H10")) {
457  Types.PbSampleType t = type == SampleType.HR ?
458  Types.PbSampleType.SAMPLE_TYPE_HEART_RATE :
459  Types.PbSampleType.SAMPLE_TYPE_RR_INTERVAL;
460  Types.PbDuration duration = Types.PbDuration.newBuilder().setSeconds(interval.getValue()).build();
461  PftpRequest.PbPFtpRequestStartRecordingParams params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().
462  setSampleDataIdentifier(exerciseId).setSampleType(t).setRecordingInterval(duration).build();
463  return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
464  @Override
465  public CompletableSource apply(Throwable throwable) throws Exception {
466  return Completable.error(throwable);
467  }
468  });
469  }
470  return Completable.error(new PolarOperationNotSupported());
471  } catch (Throwable error){
472  return Completable.error(error);
473  }
474  }
475 
476  @Override
477  public Completable stopRecording(String identifier) {
478  try {
479  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
480  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
481  if(session.getPolarDeviceType().equals("H10")) {
482  return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE, null).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
483  @Override
484  public CompletableSource apply(Throwable throwable) throws Exception {
485  return Completable.error(handleError(throwable));
486  }
487  });
488  }
489  return Completable.error(new PolarOperationNotSupported());
490  } catch (Throwable error){
491  return Completable.error(error);
492  }
493  }
494 
495  @Override
496  public Single<Pair<Boolean,String>> requestRecordingStatus(String identifier) {
497  try {
498  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
499  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
500  if(session.getPolarDeviceType().equals("H10")) {
501  return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE, null).map(new Function<ByteArrayOutputStream, Pair<Boolean,String>>() {
502  @Override
503  public Pair<Boolean,String> apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
504  PftpResponse.PbRequestRecordingStatusResult result = PftpResponse.PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray());
505  return new Pair<>(result.getRecordingOn(),result.hasSampleDataIdentifier() ? result.getSampleDataIdentifier() : "");
506  }
507  }).onErrorResumeNext(new Function<Throwable, SingleSource<? extends Pair<Boolean, String>>>() {
508  @Override
509  public SingleSource<? extends Pair<Boolean, String>> apply(Throwable throwable) throws Exception {
510  return Single.error(handleError(throwable));
511  }
512  });
513  }
514  return Single.error(new PolarOperationNotSupported());
515  } catch (Throwable error){
516  return Single.error(error);
517  }
518  }
519 
520  @Override
521  public Flowable<PolarExerciseEntry> listExercises(String identifier) {
522  try{
523  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
524  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
525  switch (session.getPolarDeviceType()) {
526  case "OH1":
527  return fetchRecursively(client, "/U/0/", new FetchRecursiveCondition() {
528  @Override
529  public boolean include(String entry) {
530  return entry.matches("^([0-9]{8})(\\/)") ||
531  entry.matches("^([0-9]{6})(\\/)") ||
532  entry.equals("E/") ||
533  entry.equals("SAMPLES.BPB") ||
534  entry.equals("00/");
535  }
536  }).map(new Function<String, PolarExerciseEntry>() {
537  @Override
538  public PolarExerciseEntry apply(String p) throws Exception {
539  String components[] = p.split("/");
540  SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault());
541  Date date = format.parse(components[3] + " " + components[5]);
542  return new PolarExerciseEntry(p, date, components[3] + components[5]);
543  }
544  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
545  @Override
546  public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable) throws Exception {
547  return Flowable.error(handleError(throwable));
548  }
549  });
550  case "H10":
551  return fetchRecursively(client, "/", new FetchRecursiveCondition() {
552  @Override
553  public boolean include(String entry) {
554  return entry.endsWith("/") || entry.equals("SAMPLES.BPB");
555  }
556  }).map(new Function<String, PolarExerciseEntry>() {
557  @Override
558  public PolarExerciseEntry apply(String p) throws Exception {
559  String components[] = p.split("/");
560  return new PolarExerciseEntry(p, new Date(), components[1]);
561  }
562  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
563  @Override
564  public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable) throws Exception {
565  return Flowable.error(handleError(throwable));
566  }
567  });
568  default:
569  return Flowable.error(new PolarOperationNotSupported());
570  }
571  } catch (Throwable error){
572  return Flowable.error(error);
573  }
574  }
575 
576  @Override
577  public Single<PolarExerciseData> fetchExercise(String identifier, PolarExerciseEntry entry) {
578  try{
579  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
580  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
581  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
582  builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
583  builder.setPath(entry.path);
584  if(session.getPolarDeviceType().equals("OH1") || session.getPolarDeviceType().equals("H10")) {
585  return client.request(builder.build().toByteArray()).map(new Function<ByteArrayOutputStream, PolarExerciseData>() {
586  @Override
587  public PolarExerciseData apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
588  ExerciseSamples.PbExerciseSamples samples = ExerciseSamples.PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray());
589  return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getHeartRateSamplesList());
590  }
591  }).onErrorResumeNext(new Function<Throwable, SingleSource<? extends PolarExerciseData>>() {
592  @Override
593  public SingleSource<? extends PolarExerciseData> apply(Throwable throwable) throws Exception {
594  return Single.error(handleError(throwable));
595  }
596  });
597  }
598  return Single.error(new PolarOperationNotSupported());
599  } catch (Throwable error){
600  return Single.error(error);
601  }
602  }
603 
604  @Override
605  public Completable removeExercise(String identifier, PolarExerciseEntry entry) {
606  try{
607  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
608  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
609  if(session.getPolarDeviceType().equals("OH1")){
610  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
611  builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
612  final String components[] = entry.path.split("/");
613  final String exerciseParent = "/U/0/" + components[3] + "/E/";
614  builder.setPath(exerciseParent);
615  return client.request(builder.build().toByteArray()).flatMap(new Function<ByteArrayOutputStream, SingleSource<?>>() {
616  @Override
617  public SingleSource<?> apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
618  PftpResponse.PbPFtpDirectory directory = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
619  protocol.PftpRequest.PbPFtpOperation.Builder removeBuilder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
620  removeBuilder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
621  if( directory.getEntriesCount() <= 1 ){
622  // remove entire directory
623  removeBuilder.setPath("/U/0/" + components[3] + "/");
624  } else {
625  // remove only exercise
626  removeBuilder.setPath("/U/0/" + components[3] + "/E/" + components[5] + "/");
627  }
628  return client.request(removeBuilder.build().toByteArray());
629  }
630  }).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
631  @Override
632  public CompletableSource apply(Throwable throwable) throws Exception {
633  return Completable.error(handleError(throwable));
634  }
635  });
636  } else if(session.getPolarDeviceType().equals("H10")){
637  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
638  builder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
639  builder.setPath(entry.path);
640  return client.request(builder.build().toByteArray()).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
641  @Override
642  public CompletableSource apply(Throwable throwable) throws Exception {
643  return Completable.error(handleError(throwable));
644  }
645  });
646  }
647  return Completable.error(new PolarOperationNotSupported());
648  } catch (Throwable error){
649  return Completable.error(error);
650  }
651  }
652 
653  @Override
654  public Flowable<PolarDeviceInfo> searchForDevice() {
655  return listener.search(false).distinct().map(new Function<BleDeviceSession, PolarDeviceInfo>() {
656  @Override
657  public PolarDeviceInfo apply(BleDeviceSession bleDeviceSession) throws Exception {
658  return new PolarDeviceInfo(bleDeviceSession.getPolarDeviceId(),
659  bleDeviceSession.getAddress(),
660  bleDeviceSession.getRssi(),
661  bleDeviceSession.getName(),
662  bleDeviceSession.isConnectableAdvertisement());
663  }
664  });
665  }
666 
667  @Override
668  public Flowable<PolarHrBroadcastData> startListenForPolarHrBroadcasts(final Set<String> deviceIds) {
669  // set filter to null, NOTE this disables reconnection in background
670  return listener.search(false).filter(new Predicate<BleDeviceSession>() {
671  @Override
672  public boolean test(BleDeviceSession bleDeviceSession) throws Exception {
673  return (deviceIds == null || deviceIds.contains(bleDeviceSession.getPolarDeviceId())) &&
674  bleDeviceSession.getAdvertisementContent().getPolarHrAdvertisement().isPresent();
675  }
676  }).map(new Function<BleDeviceSession, PolarHrBroadcastData>() {
677  @Override
678  public PolarHrBroadcastData apply(BleDeviceSession bleDeviceSession) throws Exception {
679  BlePolarHrAdvertisement advertisement = bleDeviceSession.getBlePolarHrAdvertisement();
680  return new PolarHrBroadcastData( new PolarDeviceInfo(bleDeviceSession.getPolarDeviceId(),
681  bleDeviceSession.getAddress(),
682  bleDeviceSession.getRssi(),
683  bleDeviceSession.getName(),
684  bleDeviceSession.isConnectableAdvertisement()),
685  advertisement.getHrForDisplay(),
686  advertisement.getBatteryStatus() != 0);
687  }
688  });
689  }
690 
691  @Override
692  public Flowable<PolarEcgData> startEcgStreaming(String identifier,
693  PolarSensorSetting setting) {
694  try {
695  final BleDeviceSession session = sessionPmdClientReady(identifier);
696  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
697  return client.startMeasurement(BlePMDClient.PmdMeasurementType.ECG, setting.map2PmdSettings()).andThen(
698  client.monitorEcgNotifications(true).map(new Function<BlePMDClient.EcgData, PolarEcgData>() {
699  @Override
700  public PolarEcgData apply(BlePMDClient.EcgData ecgData) throws Exception {
701  List<Integer> samples = new ArrayList<>();
702  for( BlePMDClient.EcgData.EcgSample s : ecgData.ecgSamples ){
703  samples.add(s.microVolts);
704  }
705  return new PolarEcgData(samples,ecgData.timeStamp);
706  }
707  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarEcgData>>() {
708  @Override
709  public Publisher<? extends PolarEcgData> apply(Throwable throwable) throws Exception {
710  return Flowable.error(handleError(throwable));
711  }
712  }).doFinally(new Action() {
713  @Override
714  public void run() throws Exception {
715  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.ECG);
716  }
717  }));
718  } catch (Throwable t){
719  return Flowable.error(t);
720  }
721  }
722 
723  @Override
724  public Flowable<PolarAccelerometerData> startAccStreaming(String identifier,
725  PolarSensorSetting setting) {
726  try {
727  final BleDeviceSession session = sessionPmdClientReady(identifier);
728  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
729  return client.startMeasurement(BlePMDClient.PmdMeasurementType.ACC, setting.map2PmdSettings()).andThen(
730  client.monitorAccNotifications(true).map(new Function<BlePMDClient.AccData, PolarAccelerometerData>() {
731  @Override
732  public PolarAccelerometerData apply(BlePMDClient.AccData accData) throws Exception {
733  List<PolarAccelerometerData.PolarAccelerometerSample> samples = new ArrayList<>();
734  for( BlePMDClient.AccData.AccSample s : accData.accSamples ){
735  samples.add(new PolarAccelerometerData.PolarAccelerometerSample(s.x,s.y,s.z));
736  }
737  return new PolarAccelerometerData(samples,accData.timeStamp);
738  }
739  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarAccelerometerData>>() {
740  @Override
741  public Publisher<? extends PolarAccelerometerData> apply(Throwable throwable) throws Exception {
742  return Flowable.error(handleError(throwable));
743  }
744  }).doFinally(new Action() {
745  @Override
746  public void run() throws Exception {
747  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.ACC);
748  }
749  }));
750  } catch (Throwable t){
751  return Flowable.error(t);
752  }
753  }
754 
755  @Override
756  public Flowable<PolarOhrPPGData> startOhrPPGStreaming(String identifier,
757  PolarSensorSetting setting) {
758  try {
759  final BleDeviceSession session = sessionPmdClientReady(identifier);
760  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
761  return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPG, setting.map2PmdSettings()).andThen(
762  client.monitorPpgNotifications(true).map(new Function<BlePMDClient.PpgData, PolarOhrPPGData>() {
763  @Override
764  public PolarOhrPPGData apply(BlePMDClient.PpgData ppgData) throws Exception {
765  List<PolarOhrPPGData.PolarOhrPPGSample> samples = new ArrayList<>();
766  for( BlePMDClient.PpgData.PpgSample s : ppgData.ppgSamples ){
767  samples.add(new PolarOhrPPGData.PolarOhrPPGSample(s.ppg0,s.ppg1,s.ppg2,s.ambient));
768  }
769  return new PolarOhrPPGData(samples,ppgData.timeStamp);
770  }
771  }).doFinally(new Action() {
772  @Override
773  public void run() throws Exception {
774  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.PPG);
775  }
776  })).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarOhrPPGData>>() {
777  @Override
778  public Publisher<? extends PolarOhrPPGData> apply(Throwable throwable) throws Exception {
779  return Flowable.error(handleError(throwable));
780  }
781  });
782  } catch (Throwable t){
783  return Flowable.error(t);
784  }
785  }
786 
787  @Override
788  public Flowable<PolarOhrPPIData> startOhrPPIStreaming(String identifier) {
789  try {
790  final BleDeviceSession session = sessionPmdClientReady(identifier);
791  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
792  return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPI, new BlePMDClient.PmdSetting(new HashMap<BlePMDClient.PmdSetting.PmdSettingType, Integer>())).andThen(
793  client.monitorPpiNotifications(true).map(new Function<BlePMDClient.PpiData, PolarOhrPPIData>() {
794  @Override
795  public PolarOhrPPIData apply(BlePMDClient.PpiData ppiData) throws Exception {
796  List<PolarOhrPPIData.PolarOhrPPISample> samples = new ArrayList<>();
797  for(BlePMDClient.PpiData.PPSample ppSample : ppiData.ppSamples){
798  samples.add(new PolarOhrPPIData.PolarOhrPPISample(ppSample.ppInMs,
799  ppSample.ppErrorEstimate,
800  ppSample.hr,
801  ppSample.blockerBit != 0,
802  ppSample.skinContactStatus != 0,
803  ppSample.skinContactSupported != 0));
804  }
805  return new PolarOhrPPIData(ppiData.timestamp,samples);
806  }
807  }).doFinally(new Action() {
808  @Override
809  public void run() throws Exception {
810  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.PPI);
811  }
812  })).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarOhrPPIData>>() {
813  @Override
814  public Publisher<? extends PolarOhrPPIData> apply(Throwable throwable) throws Exception {
815  return Flowable.error(handleError(throwable));
816  }
817  });
818  } catch (Throwable t){
819  return Flowable.error(t);
820  }
821  }
822 
823  @Override
824  public Flowable<PolarBiozData> startBiozStreaming(final String identifier, PolarSensorSetting setting){
825  try {
826  final BleDeviceSession session = sessionPmdClientReady(identifier);
827  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
828  return client.startMeasurement(BlePMDClient.PmdMeasurementType.BIOZ, setting.map2PmdSettings()).andThen(
829  client.monitorBiozNotifications(true).map(new Function<BlePMDClient.BiozData, PolarBiozData>() {
830  @Override
831  public PolarBiozData apply(BlePMDClient.BiozData biozData) throws Exception {
832  return new PolarBiozData(biozData.timeStamp,biozData.samples);
833  }
834  }).doFinally(new Action() {
835  @Override
836  public void run() throws Exception {
837  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.PPG);
838  }
839  })).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarBiozData>>() {
840  @Override
841  public Publisher<? extends PolarBiozData> apply(Throwable throwable) throws Exception {
842  return Flowable.error(handleError(throwable));
843  }
844  });
845  } catch (Throwable t){
846  return Flowable.error(t);
847  }
848  }
849 
850  protected BleDeviceSession fetchSession(final String identifier) throws PolarInvalidArgument {
851  return identifier.contains(":") ?
852  sessionByAddress(identifier) : sessionByDeviceId(identifier);
853  }
854 
855  protected BleDeviceSession sessionByAddress(final String address) throws PolarInvalidArgument {
856  validate(address.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"));
857  for ( BleDeviceSession session : listener.deviceSessions() ){
858  if( session.getAddress().equals(address) ){
859  return session;
860  }
861  }
862  return null;
863  }
864 
865  protected BleDeviceSession sessionByDeviceId(final String deviceId) throws PolarInvalidArgument {
866  validate(deviceId.matches("([0-9a-fA-F]){6,8}"));
867  for ( BleDeviceSession session : listener.deviceSessions() ){
868  if( session.getAdvertisementContent().getPolarDeviceId().equals(deviceId) ){
869  return session;
870  }
871  }
872  return null;
873  }
874 
875  private void validate(boolean a) throws PolarInvalidArgument {
876  if(!a) {
877  throw new PolarInvalidArgument();
878  }
879  }
880 
881  protected BleDeviceSession sessionServiceReady(final String identifier, UUID service) throws Throwable {
882  BleDeviceSession session = fetchSession(identifier);
883  if(session != null){
884  if(session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN) {
885  BleGattBase client = session.fetchClient(service);
886  if (client.isServiceDiscovered()) {
887  return session;
888  }
889  throw new PolarServiceNotAvailable();
890  }
891  throw new PolarDeviceDisconnected();
892  }
893  throw new PolarDeviceNotFound();
894  }
895 
896  public BleDeviceSession sessionPmdClientReady(final String identifier) throws Throwable {
897  BleDeviceSession session = sessionServiceReady(identifier, BlePMDClient.PMD_SERVICE);
898  BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
899  final AtomicInteger pair = client.getNotificationAtomicInteger(BlePMDClient.PMD_CP);
900  final AtomicInteger pairData = client.getNotificationAtomicInteger(BlePMDClient.PMD_DATA);
901  if (pair != null && pairData != null &&
902  pair.get() == BleGattBase.ATT_SUCCESS &&
903  pairData.get() == BleGattBase.ATT_SUCCESS) {
904  return session;
905  }
906  throw new PolarNotificationNotEnabled();
907  }
908 
909  protected BleDeviceSession sessionPsFtpClientReady(final String identifier) throws Throwable {
910  BleDeviceSession session = sessionServiceReady(identifier, BlePsFtpUtils.RFC77_PFTP_SERVICE);
911  BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
912  final AtomicInteger pair = client.getNotificationAtomicInteger(BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC);
913  if (pair != null && pair.get() == BleGattBase.ATT_SUCCESS ) {
914  return session;
915  }
916  throw new PolarNotificationNotEnabled();
917  }
918 
919  @SuppressLint("CheckResult")
920  protected void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type) {
921  if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ){
922  // stop streaming
923  client.stopMeasurement(type).subscribe(
924  new Action() {
925  @Override
926  public void run() throws Exception {
927 
928  }
929  },
930  new Consumer<Throwable>() {
931  @Override
932  public void accept(Throwable throwable) throws Exception {
933  logError("failed to stop pmd stream: " + throwable.getLocalizedMessage());
934  }
935  }
936  );
937  }
938  }
939 
940  @SuppressLint("CheckResult")
941  protected void setupDevice(final BleDeviceSession session){
942  final String deviceId = session.getPolarDeviceId().length() != 0 ? session.getPolarDeviceId() : session.getAddress();
943  session.monitorServicesDiscovered(true).observeOn(scheduler).toFlowable().flatMapIterable(
944  new Function<List<UUID>, Iterable<UUID>>() {
945  @Override
946  public Iterable<UUID> apply(List<UUID> uuids) throws Exception {
947  return uuids;
948  }
949  }
950  ).flatMap(new Function<UUID, Publisher<?>>() {
951  @Override
952  public Publisher<?> apply(UUID uuid) throws Exception {
953  if(session.fetchClient(uuid) != null) {
954  if (uuid.equals(BleHrClient.HR_SERVICE)) {
955  if (callback != null) {
956  callback.hrFeatureReady(deviceId);
957  }
958  final BleHrClient client = (BleHrClient) session.fetchClient(BleHrClient.HR_SERVICE);
959  client.observeHrNotifications(true).observeOn(scheduler).subscribe(
960  new Consumer<BleHrClient.HrNotificationData>() {
961  @Override
962  public void accept(BleHrClient.HrNotificationData hrNotificationData) throws Exception {
963  if (callback != null) {
965  new PolarHrData(hrNotificationData.hrValue,
966  hrNotificationData.rrs,
967  hrNotificationData.sensorContact,
968  hrNotificationData.sensorContactSupported,
969  hrNotificationData.rrPresent));
970  }
971  }
972  },
973  new Consumer<Throwable>() {
974  @Override
975  public void accept(Throwable throwable) throws Exception {
976  logError(throwable.getMessage());
977  }
978  },
979  new Action() {
980  @Override
981  public void run() throws Exception {
982 
983  }
984  }
985  );
986  } else if (uuid.equals(BleBattClient.BATTERY_SERVICE)) {
987  BleBattClient client = (BleBattClient) session.fetchClient(BleBattClient.BATTERY_SERVICE);
988  return client.waitBatteryLevelUpdate(true).observeOn(scheduler).doOnSuccess(new Consumer<Integer>() {
989  @Override
990  public void accept(Integer integer) throws Exception {
991  if (callback != null) {
992  callback.batteryLevelReceived(deviceId, integer);
993  }
994  }
995  }).toFlowable();
996  } else if (uuid.equals(BlePMDClient.PMD_SERVICE)) {
997  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
998  return client.waitNotificationEnabled(BlePMDClient.PMD_CP, true).
999  concatWith(client.waitNotificationEnabled(BlePMDClient.PMD_DATA, true)).andThen(client.readFeature(true).doOnSuccess(new Consumer<BlePMDClient.PmdFeature>() {
1000  @Override
1001  public void accept(BlePMDClient.PmdFeature pmdFeature) {
1002  if (callback != null) {
1003  if (pmdFeature.ecgSupported) {
1004  callback.ecgFeatureReady(deviceId);
1005  }
1006  if (pmdFeature.accSupported) {
1008  }
1009  if (pmdFeature.ppgSupported) {
1010  callback.ppgFeatureReady(deviceId);
1011  }
1012  if (pmdFeature.ppiSupported) {
1013  callback.ppiFeatureReady(deviceId);
1014  }
1015  if (pmdFeature.bioZSupported) {
1016  callback.biozFeatureReady(deviceId);
1017  }
1018  }
1019  }
1020  })).toFlowable();
1021  } else if (uuid.equals(BleDisClient.DIS_SERVICE)) {
1022  BleDisClient client = (BleDisClient) session.fetchClient(BleDisClient.DIS_SERVICE);
1023  return client.observeDisInfo(true).observeOn(scheduler).doOnNext(new Consumer<Pair<UUID, String>>() {
1024  @Override
1025  public void accept(Pair<UUID, String> pair) {
1026  if (callback != null) {
1027  callback.disInformationReceived(deviceId, pair.first , pair.second);
1028  }
1029  }
1030  });
1031  } else if (uuid.equals(BlePsFtpUtils.RFC77_PFTP_SERVICE)) {
1032  BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
1033  return client.waitPsFtpClientReady(true).observeOn(scheduler).doOnComplete(new Action() {
1034  @Override
1035  public void run() throws Exception {
1036  if (callback != null &&
1037  (session.getPolarDeviceType().equals("OH1") || session.getPolarDeviceType().equals("H10"))) {
1038  callback.polarFtpFeatureReady(deviceId);
1039  }
1040  }
1041  }).toFlowable();
1042  }
1043  }
1044  return Flowable.empty();
1045  }
1046  }).subscribe(
1047  new Consumer<Object>() {
1048  @Override
1049  public void accept(Object o) throws Exception {
1050 
1051  }
1052  },
1053  new Consumer<Throwable>() {
1054  @Override
1055  public void accept(Throwable throwable) throws Exception {
1056  logError(throwable.getMessage());
1057  }
1058  },
1059  new Action() {
1060  @Override
1061  public void run() throws Exception {
1062  log("complete");
1063  }
1064  });
1065  }
1066 
1067  protected Exception handleError(Throwable throwable) {
1068  if( throwable instanceof BleDisconnected ){
1069  return new PolarDeviceDisconnected();
1070  } else {
1071  return new Exception("Unknown Error: " + throwable.getLocalizedMessage());
1072  }
1073  }
1074 
1075  interface FetchRecursiveCondition {
1076  boolean include(String entry);
1077  }
1078 
1079  protected Flowable<String> fetchRecursively(final BlePsFtpClient client, final String path, final FetchRecursiveCondition condition) {
1080  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
1081  builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
1082  builder.setPath(path);
1083  return client.request(builder.build().toByteArray()).toFlowable().flatMap(new Function<ByteArrayOutputStream, Publisher<String>>() {
1084  @Override
1085  public Publisher<String> apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
1086  PftpResponse.PbPFtpDirectory dir = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
1087  Set<String> entrys = new HashSet<>();
1088  for( int i=0; i < dir.getEntriesCount(); ++i ){
1089  PftpResponse.PbPFtpEntry entry = dir.getEntries(i);
1090  if( condition.include(entry.getName()) ){
1091  BleUtils.validate(entrys.add(path + entry.getName()),"duplicate entry");
1092  }
1093  }
1094  if(entrys.size()!=0) {
1095  return Flowable.fromIterable(entrys).flatMap(new Function<String, Publisher<String>>() {
1096  @Override
1097  public Publisher<String> apply(String s) {
1098  if (s.endsWith("/")) {
1099  return fetchRecursively(client, s, condition);
1100  } else {
1101  return Flowable.just(s);
1102  }
1103  }
1104  });
1105  }
1106  return Flowable.empty();
1107  }
1108  });
1109  }
1110 
1111  protected void log(final String message) {
1112  if(logger != null){
1113  logger.message("" + message);
1114  }
1115  }
1116 
1117  protected void logError(final String message) {
1118  if(logger != null){
1119  logger.message("Error: "+message);
1120  }
1121  }
1122 }
-
void log(final String message)
- -
static final int ANDROID_VERSION_O
-
Single< PolarExerciseData > fetchExercise(String identifier, PolarExerciseEntry entry)
- -
Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type)
-
Completable autoConnectToDevice(final int rssiLimit, final String service, final int timeout, final TimeUnit unit, final String polarDeviceType)
+Go to the documentation of this file.
1 // Copyright © 2019 Polar Electro Oy. All rights reserved.
2 package polar.com.sdk.impl;
3 
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;
12 
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;
28 
29 import org.reactivestreams.Publisher;
30 
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;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.UUID;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.atomic.AtomicInteger;
47 
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;
66 import polar.com.sdk.api.PolarBleApi;
85 import protocol.PftpNotification;
86 import protocol.PftpRequest;
87 import protocol.PftpResponse;
88 
92 public class BDBleApiImpl extends PolarBleApi {
93  protected final static String TAG = BDBleApiImpl.class.getSimpleName();
94  protected BleDeviceListener listener;
95  protected Map<String,Disposable> connectSubscriptions = new HashMap<>();
96  protected Scheduler scheduler;
99  protected static final int ANDROID_VERSION_O = 26;
100  BleDeviceListener.BleSearchPreFilter filter = new BleDeviceListener.BleSearchPreFilter() {
101  @Override
102  public boolean process(BleAdvertisementContent content) {
103  return content.getPolarDeviceId().length() != 0 && !content.getPolarDeviceType().equals("mobile");
104  }
105  };
106 
107  @SuppressLint({"NewApi", "CheckResult"})
108  public BDBleApiImpl(final Context context, int features) {
109  super(features);
110  Set<Class<? extends BleGattBase>> clients = new HashSet<>();
111  if((this.features & PolarBleApi.FEATURE_HR)!=0){
112  clients.add(BleHrClient.class);
113  }
114  if((this.features & PolarBleApi.FEATURE_DEVICE_INFO)!=0){
115  clients.add(BleDisClient.class);
116  }
117  if((this.features & PolarBleApi.FEATURE_BATTERY_INFO)!=0){
118  clients.add(BleBattClient.class);
119  }
120  if((this.features & PolarBleApi.FEATURE_POLAR_SENSOR_STREAMING)!=0){
121  clients.add(BlePMDClient.class);
122  }
123  if((this.features & PolarBleApi.FEATURE_POLAR_FILE_TRANSFER)!=0){
124  clients.add(BlePsFtpClient.class);
125  }
126  listener = new BDDeviceListenerImpl(context, clients);
127  listener.setScanPreFilter(filter);
128  scheduler = AndroidSchedulers.from(context.getMainLooper());
129  listener.monitorDeviceSessionState(null).observeOn(scheduler).subscribe(
130  new Consumer<Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState>>() {
131  @Override
132  public void accept(Pair<BleDeviceSession, BleDeviceSession.DeviceSessionState> pair) throws Exception {
133  PolarDeviceInfo info = new PolarDeviceInfo(
134  pair.first.getPolarDeviceId().length() != 0 ?
135  pair.first.getPolarDeviceId() : pair.first.getAddress(),
136  pair.first.getAddress(),
137  pair.first.getRssi(),pair.first.getName(),true);
138  switch (pair.second){
139  case SESSION_OPEN:
140  if(callback!=null){
142  }
143  setupDevice(pair.first);
144  break;
145  case SESSION_CLOSED:
146  if( callback != null ) {
147  if (pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
148  pair.first.getPreviousState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSING){
150  }
151  }
152  break;
153  case SESSION_OPENING:
154  if(callback != null){
156  }
157  break;
158  }
159  }
160  },
161  new Consumer<Throwable>() {
162  @Override
163  public void accept(Throwable throwable) throws Exception {
164  logError(throwable.getMessage());
165  }
166  },
167  new Action() {
168  @Override
169  public void run() throws Exception {
170 
171  }
172  }
173  );
174  listener.monitorBleState().observeOn(scheduler).subscribe(
175  new Consumer<Boolean>() {
176  @Override
177  public void accept(Boolean aBoolean) throws Exception {
178  if(callback != null){
179  callback.blePowerStateChanged(aBoolean);
180  }
181  }
182  },
183  new Consumer<Throwable>() {
184  @Override
185  public void accept(Throwable throwable) throws Exception {
186  logError(throwable.getMessage());
187  }
188  },
189  new Action() {
190  @Override
191  public void run() throws Exception {
192 
193  }
194  }
195  );
196  BleLogger.setLoggerInterface(new BleLogger.BleLoggerInterface() {
197  @Override
198  public void d(String tag, String msg) {
199  log(tag+"/"+msg);
200  }
201 
202  @Override
203  public void e(String tag, String msg) {
204  logError(tag+"/"+msg);
205  }
206 
207  @Override
208  public void w(String tag, String msg) {
209  }
210 
211  @Override
212  public void i(String tag, String msg) {
213  }
214  });
215  }
216 
217  @SuppressLint("NewApi")
218  protected void enableAndroidScanFilter() {
219  if (Build.VERSION.SDK_INT >= ANDROID_VERSION_O) {
220  List<ScanFilter> filter = new ArrayList<>();
221  filter.add(new ScanFilter.Builder().setServiceUuid(
222  ParcelUuid.fromString(BleHrClient.HR_SERVICE.toString())).build());
223  filter.add(new ScanFilter.Builder().setServiceUuid(
224  ParcelUuid.fromString(BlePsFtpUtils.RFC77_PFTP_SERVICE.toString())).build());
225  listener.setScanFilters(filter);
226  }
227  }
228 
229  @Override
230  public void shutDown() {
231  listener.shutDown();
232  }
233 
234  @Override
235  public void cleanup() {
236  listener.removeAllSessions();
237  }
238 
239  @Override
240  public void setPolarFilter(boolean enable) {
241  if(!enable) {
242  listener.setScanPreFilter(null);
243  } else {
244  listener.setScanPreFilter(filter);
245  }
246  }
247 
248  @Override
249  public boolean isFeatureReady(final String deviceId, int feature) {
250  try {
251  switch (feature) {
253  return sessionPsFtpClientReady(deviceId) != null;
255  return sessionPmdClientReady(deviceId) != null;
256  }
257  } catch (Throwable ignored) {
258  }
259  return false;
260  }
261 
262  @Override
264  this.callback = callback;
266  }
267 
268  @Override
269  public void setApiLogger(@Nullable PolarBleApiLogger logger) {
270  this.logger = logger;
271  }
272 
273  @Override
274  public void setAutomaticReconnection(boolean disable) {
275  listener.setAutomaticReconnection(disable);
276  }
277 
278  @Override
279  public Completable setLocalTime(String identifier, Calendar cal) {
280  try {
281  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
282  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
283  PftpRequest.PbPFtpSetLocalTimeParams.Builder builder = PftpRequest.PbPFtpSetLocalTimeParams.newBuilder();
284  Types.PbDate date = Types.PbDate.newBuilder()
285  .setYear(cal.get(Calendar.YEAR))
286  .setMonth(cal.get(Calendar.MONTH) + 1)
287  .setDay(cal.get(Calendar.DAY_OF_MONTH)).build();
288  Types.PbTime time = Types.PbTime.newBuilder()
289  .setHour(cal.get(Calendar.HOUR_OF_DAY))
290  .setMinute(cal.get(Calendar.MINUTE))
291  .setSeconds(cal.get(Calendar.SECOND))
292  .setMillis(cal.get(Calendar.MILLISECOND)).build();
293  builder.setDate(date).setTime(time).setTzOffset((int) TimeUnit.MINUTES.convert(cal.get(Calendar.ZONE_OFFSET), TimeUnit.MILLISECONDS));
294  return client.query(PftpRequest.PbPFtpQuery.SET_LOCAL_TIME_VALUE,builder.build().toByteArray()).toObservable().ignoreElements();
295  } catch (Throwable error){
296  return Completable.error(error);
297  }
298  }
299 
300  @Override
301  public Single<PolarSensorSetting> requestAccSettings(String identifier) {
302  return querySettings(identifier,BlePMDClient.PmdMeasurementType.ACC);
303  }
304 
305  @Override
306  public Single<PolarSensorSetting> requestEcgSettings(String identifier) {
307  return querySettings(identifier,BlePMDClient.PmdMeasurementType.ECG);
308  }
309 
310  @Override
311  public Single<PolarSensorSetting> requestPpgSettings(String identifier) {
312  return querySettings(identifier,BlePMDClient.PmdMeasurementType.PPG);
313  }
314 
315  @Override
316  public Single<PolarSensorSetting> requestBiozSettings(final String identifier){
317  return querySettings(identifier,BlePMDClient.PmdMeasurementType.BIOZ);
318  }
319 
320  protected Single<PolarSensorSetting> querySettings(final String identifier, final BlePMDClient.PmdMeasurementType type) {
321  try {
322  final BleDeviceSession session = sessionPmdClientReady(identifier);
323  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
324  return client.querySettings(type).map(new Function<BlePMDClient.PmdSetting, PolarSensorSetting>() {
325  @Override
326  public PolarSensorSetting apply(BlePMDClient.PmdSetting setting) throws Exception {
327  return new PolarSensorSetting(setting.settings, type);
328  }
329  });
330  } catch (Throwable e){
331  return Single.error(e);
332  }
333  }
334 
335  @Override
336  public void backgroundEntered() {
338  }
339 
340  @Override
341  public void foregroundEntered() {
342  listener.setScanFilters(null);
343  }
344 
345  @Override
346  public Completable autoConnectToDevice(final int rssiLimit, final String service, final int timeout, final TimeUnit unit, final String polarDeviceType) {
347  final long[] start = {0};
348  return Completable.create(new CompletableOnSubscribe() {
349  @Override
350  public void subscribe(CompletableEmitter emitter) throws Exception {
351  if( service == null || service.matches("([0-9a-fA-F]{4})") ) {
352  emitter.onComplete();
353  } else {
354  emitter.tryOnError(new PolarInvalidArgument("Invalid service string format"));
355  }
356  }
357  }).andThen(listener.search(false).filter(new Predicate<BleDeviceSession>() {
358  @Override
359  public boolean test(BleDeviceSession bleDeviceSession) throws Exception {
360  if( bleDeviceSession.getMedianRssi() >= rssiLimit &&
361  bleDeviceSession.isConnectableAdvertisement() &&
362  (polarDeviceType == null || polarDeviceType.equals(bleDeviceSession.getPolarDeviceType())) &&
363  (service == null || bleDeviceSession.getAdvertisementContent().containsService(service)) ) {
364  if(start[0] == 0){
365  start[0] = System.currentTimeMillis();
366  }
367  return true;
368  }
369  return false;
370  }
371  }).timestamp().takeUntil(new Predicate<Timed<BleDeviceSession>>() {
372  @Override
373  public boolean test(Timed<BleDeviceSession> bleDeviceSessionTimed) throws Exception {
374  long diff = bleDeviceSessionTimed.time(TimeUnit.MILLISECONDS) - start[0];
375  return (diff >= unit.toMillis(timeout));
376  }
377  }).reduce(new HashSet<BleDeviceSession>(), new BiFunction<Set<BleDeviceSession>, Timed<BleDeviceSession>, Set<BleDeviceSession>>() {
378  @Override
379  public Set<BleDeviceSession> apply(Set<BleDeviceSession> objects, Timed<BleDeviceSession> bleDeviceSessionTimed) throws Exception {
380  objects.add(bleDeviceSessionTimed.value());
381  return objects;
382  }
383  }).doOnSuccess(new Consumer<Set<BleDeviceSession>>() {
384  @Override
385  public void accept(Set<BleDeviceSession> set) throws Exception {
386  List<BleDeviceSession> list = new ArrayList<>(set);
387  Collections.sort(list, new Comparator<BleDeviceSession>() {
388  @Override
389  public int compare(BleDeviceSession o1, BleDeviceSession o2) {
390  return o1.getRssi() > o2.getRssi() ? -1 : 1;
391  }
392  });
393  listener.openSessionDirect(list.get(0));
394  log("auto connect search complete");
395  }
396  }).toObservable().ignoreElements());
397  }
398 
399  @Override
400  public Completable autoConnectToDevice(final int rssiLimit, final String service, final String polarDeviceType) {
401  return autoConnectToDevice(rssiLimit, service, 2, TimeUnit.SECONDS, polarDeviceType);
402  }
403 
404  @Override
405  public void connectToDevice(final String identifier) throws PolarInvalidArgument {
406  BleDeviceSession session = fetchSession(identifier);
407  if( session == null || session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_CLOSED ){
408  if( connectSubscriptions.containsKey(identifier) ){
409  connectSubscriptions.get(identifier).dispose();
410  connectSubscriptions.remove(identifier);
411  }
412  if( session != null ){
413  listener.openSessionDirect(session);
414  } else {
415  connectSubscriptions.put(identifier, listener.search(false).filter(new Predicate<BleDeviceSession>() {
416  @Override
417  public boolean test(BleDeviceSession bleDeviceSession) throws Exception {
418  return identifier.contains(":") ?
419  bleDeviceSession.getAddress().equals(identifier) :
420  bleDeviceSession.getPolarDeviceId().equals(identifier);
421  }
422  }).take(1).observeOn(scheduler).subscribe(
423  new Consumer<BleDeviceSession>() {
424  @Override
425  public void accept(BleDeviceSession bleDeviceSession) throws Exception {
426  listener.openSessionDirect(bleDeviceSession);
427  }
428  },
429  new Consumer<Throwable>() {
430  @Override
431  public void accept(Throwable throwable) throws Exception {
432  logError(throwable.getMessage());
433  }
434  },
435  new Action() {
436  @Override
437  public void run() throws Exception {
438  log("connect search complete");
439  }
440  }
441  ));
442  }
443  }
444  }
445 
446  @Override
447  public void disconnectFromDevice(String identifier) throws PolarInvalidArgument {
448  BleDeviceSession session = fetchSession(identifier);
449  if( session != null ){
450  if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ||
451  session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPENING ||
452  session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN_PARK ) {
453  listener.closeSessionDirect(session);
454  }
455  }
456  if (connectSubscriptions.containsKey(identifier)){
457  connectSubscriptions.get(identifier).dispose();
458  connectSubscriptions.remove(identifier);
459  }
460  }
461 
462  @Override
463  public Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type) {
464  try {
465  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
466  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
467  if(session.getPolarDeviceType().equals("H10")) {
468  Types.PbSampleType t = type == SampleType.HR ?
469  Types.PbSampleType.SAMPLE_TYPE_HEART_RATE :
470  Types.PbSampleType.SAMPLE_TYPE_RR_INTERVAL;
471  Types.PbDuration duration = Types.PbDuration.newBuilder().setSeconds(interval.getValue()).build();
472  PftpRequest.PbPFtpRequestStartRecordingParams params = PftpRequest.PbPFtpRequestStartRecordingParams.newBuilder().
473  setSampleDataIdentifier(exerciseId).setSampleType(t).setRecordingInterval(duration).build();
474  return client.query(PftpRequest.PbPFtpQuery.REQUEST_START_RECORDING_VALUE, params.toByteArray()).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
475  @Override
476  public CompletableSource apply(Throwable throwable) throws Exception {
477  return Completable.error(throwable);
478  }
479  });
480  }
481  return Completable.error(new PolarOperationNotSupported());
482  } catch (Throwable error){
483  return Completable.error(error);
484  }
485  }
486 
487  @Override
488  public Completable stopRecording(String identifier) {
489  try {
490  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
491  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
492  if(session.getPolarDeviceType().equals("H10")) {
493  return client.query(PftpRequest.PbPFtpQuery.REQUEST_STOP_RECORDING_VALUE, null).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
494  @Override
495  public CompletableSource apply(Throwable throwable) throws Exception {
496  return Completable.error(handleError(throwable));
497  }
498  });
499  }
500  return Completable.error(new PolarOperationNotSupported());
501  } catch (Throwable error){
502  return Completable.error(error);
503  }
504  }
505 
506  @Override
507  public Single<Pair<Boolean,String>> requestRecordingStatus(String identifier) {
508  try {
509  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
510  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
511  if(session.getPolarDeviceType().equals("H10")) {
512  return client.query(PftpRequest.PbPFtpQuery.REQUEST_RECORDING_STATUS_VALUE, null).map(new Function<ByteArrayOutputStream, Pair<Boolean,String>>() {
513  @Override
514  public Pair<Boolean,String> apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
515  PftpResponse.PbRequestRecordingStatusResult result = PftpResponse.PbRequestRecordingStatusResult.parseFrom(byteArrayOutputStream.toByteArray());
516  return new Pair<>(result.getRecordingOn(),result.hasSampleDataIdentifier() ? result.getSampleDataIdentifier() : "");
517  }
518  }).onErrorResumeNext(new Function<Throwable, SingleSource<? extends Pair<Boolean, String>>>() {
519  @Override
520  public SingleSource<? extends Pair<Boolean, String>> apply(Throwable throwable) throws Exception {
521  return Single.error(handleError(throwable));
522  }
523  });
524  }
525  return Single.error(new PolarOperationNotSupported());
526  } catch (Throwable error){
527  return Single.error(error);
528  }
529  }
530 
531  @Override
532  public Flowable<PolarExerciseEntry> listExercises(String identifier) {
533  try{
534  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
535  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
536  switch (session.getPolarDeviceType()) {
537  case "OH1":
538  return fetchRecursively(client, "/U/0/", new FetchRecursiveCondition() {
539  @Override
540  public boolean include(String entry) {
541  return entry.matches("^([0-9]{8})(\\/)") ||
542  entry.matches("^([0-9]{6})(\\/)") ||
543  entry.equals("E/") ||
544  entry.equals("SAMPLES.BPB") ||
545  entry.equals("00/");
546  }
547  }).map(new Function<String, PolarExerciseEntry>() {
548  @Override
549  public PolarExerciseEntry apply(String p) throws Exception {
550  String components[] = p.split("/");
551  SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd HHmmss", Locale.getDefault());
552  Date date = format.parse(components[3] + " " + components[5]);
553  return new PolarExerciseEntry(p, date, components[3] + components[5]);
554  }
555  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
556  @Override
557  public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable) throws Exception {
558  return Flowable.error(handleError(throwable));
559  }
560  });
561  case "H10":
562  return fetchRecursively(client, "/", new FetchRecursiveCondition() {
563  @Override
564  public boolean include(String entry) {
565  return entry.endsWith("/") || entry.equals("SAMPLES.BPB");
566  }
567  }).map(new Function<String, PolarExerciseEntry>() {
568  @Override
569  public PolarExerciseEntry apply(String p) throws Exception {
570  String components[] = p.split("/");
571  return new PolarExerciseEntry(p, new Date(), components[1]);
572  }
573  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarExerciseEntry>>() {
574  @Override
575  public Publisher<? extends PolarExerciseEntry> apply(Throwable throwable) throws Exception {
576  return Flowable.error(handleError(throwable));
577  }
578  });
579  default:
580  return Flowable.error(new PolarOperationNotSupported());
581  }
582  } catch (Throwable error){
583  return Flowable.error(error);
584  }
585  }
586 
587  @Override
588  public Single<PolarExerciseData> fetchExercise(String identifier, PolarExerciseEntry entry) {
589  try{
590  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
591  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
592  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
593  builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
594  builder.setPath(entry.path);
595  if(session.getPolarDeviceType().equals("OH1") || session.getPolarDeviceType().equals("H10")) {
596  return client.request(builder.build().toByteArray()).map(new Function<ByteArrayOutputStream, PolarExerciseData>() {
597  @Override
598  public PolarExerciseData apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
599  ExerciseSamples.PbExerciseSamples samples = ExerciseSamples.PbExerciseSamples.parseFrom(byteArrayOutputStream.toByteArray());
600  return new PolarExerciseData(samples.getRecordingInterval().getSeconds(), samples.getHeartRateSamplesList());
601  }
602  }).onErrorResumeNext(new Function<Throwable, SingleSource<? extends PolarExerciseData>>() {
603  @Override
604  public SingleSource<? extends PolarExerciseData> apply(Throwable throwable) throws Exception {
605  return Single.error(handleError(throwable));
606  }
607  });
608  }
609  return Single.error(new PolarOperationNotSupported());
610  } catch (Throwable error){
611  return Single.error(error);
612  }
613  }
614 
615  @Override
616  public Completable removeExercise(String identifier, PolarExerciseEntry entry) {
617  try{
618  final BleDeviceSession session = sessionPsFtpClientReady(identifier);
619  final BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
620  if(session.getPolarDeviceType().equals("OH1")){
621  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
622  builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
623  final String components[] = entry.path.split("/");
624  final String exerciseParent = "/U/0/" + components[3] + "/E/";
625  builder.setPath(exerciseParent);
626  return client.request(builder.build().toByteArray()).flatMap(new Function<ByteArrayOutputStream, SingleSource<?>>() {
627  @Override
628  public SingleSource<?> apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
629  PftpResponse.PbPFtpDirectory directory = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
630  protocol.PftpRequest.PbPFtpOperation.Builder removeBuilder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
631  removeBuilder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
632  if( directory.getEntriesCount() <= 1 ){
633  // remove entire directory
634  removeBuilder.setPath("/U/0/" + components[3] + "/");
635  } else {
636  // remove only exercise
637  removeBuilder.setPath("/U/0/" + components[3] + "/E/" + components[5] + "/");
638  }
639  return client.request(removeBuilder.build().toByteArray());
640  }
641  }).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
642  @Override
643  public CompletableSource apply(Throwable throwable) throws Exception {
644  return Completable.error(handleError(throwable));
645  }
646  });
647  } else if(session.getPolarDeviceType().equals("H10")){
648  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
649  builder.setCommand(PftpRequest.PbPFtpOperation.Command.REMOVE);
650  builder.setPath(entry.path);
651  return client.request(builder.build().toByteArray()).toObservable().ignoreElements().onErrorResumeNext(new Function<Throwable, CompletableSource>() {
652  @Override
653  public CompletableSource apply(Throwable throwable) throws Exception {
654  return Completable.error(handleError(throwable));
655  }
656  });
657  }
658  return Completable.error(new PolarOperationNotSupported());
659  } catch (Throwable error){
660  return Completable.error(error);
661  }
662  }
663 
664  @Override
665  public Flowable<PolarDeviceInfo> searchForDevice() {
666  return listener.search(false).distinct().map(new Function<BleDeviceSession, PolarDeviceInfo>() {
667  @Override
668  public PolarDeviceInfo apply(BleDeviceSession bleDeviceSession) throws Exception {
669  return new PolarDeviceInfo(bleDeviceSession.getPolarDeviceId(),
670  bleDeviceSession.getAddress(),
671  bleDeviceSession.getRssi(),
672  bleDeviceSession.getName(),
673  bleDeviceSession.isConnectableAdvertisement());
674  }
675  });
676  }
677 
678  @Override
679  public Flowable<PolarHrBroadcastData> startListenForPolarHrBroadcasts(final Set<String> deviceIds) {
680  // set filter to null, NOTE this disables reconnection in background
681  return listener.search(false).filter(new Predicate<BleDeviceSession>() {
682  @Override
683  public boolean test(BleDeviceSession bleDeviceSession) throws Exception {
684  return (deviceIds == null || deviceIds.contains(bleDeviceSession.getPolarDeviceId())) &&
685  bleDeviceSession.getAdvertisementContent().getPolarHrAdvertisement().isPresent();
686  }
687  }).map(new Function<BleDeviceSession, PolarHrBroadcastData>() {
688  @Override
689  public PolarHrBroadcastData apply(BleDeviceSession bleDeviceSession) throws Exception {
690  BlePolarHrAdvertisement advertisement = bleDeviceSession.getBlePolarHrAdvertisement();
691  return new PolarHrBroadcastData( new PolarDeviceInfo(bleDeviceSession.getPolarDeviceId(),
692  bleDeviceSession.getAddress(),
693  bleDeviceSession.getRssi(),
694  bleDeviceSession.getName(),
695  bleDeviceSession.isConnectableAdvertisement()),
696  advertisement.getHrForDisplay(),
697  advertisement.getBatteryStatus() != 0);
698  }
699  });
700  }
701 
702  @Override
703  public Flowable<PolarEcgData> startEcgStreaming(String identifier,
704  PolarSensorSetting setting) {
705  try {
706  final BleDeviceSession session = sessionPmdClientReady(identifier);
707  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
708  return client.startMeasurement(BlePMDClient.PmdMeasurementType.ECG, setting.map2PmdSettings()).andThen(
709  client.monitorEcgNotifications(true).map(new Function<BlePMDClient.EcgData, PolarEcgData>() {
710  @Override
711  public PolarEcgData apply(BlePMDClient.EcgData ecgData) throws Exception {
712  List<Integer> samples = new ArrayList<>();
713  for( BlePMDClient.EcgData.EcgSample s : ecgData.ecgSamples ){
714  samples.add(s.microVolts);
715  }
716  return new PolarEcgData(samples,ecgData.timeStamp);
717  }
718  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarEcgData>>() {
719  @Override
720  public Publisher<? extends PolarEcgData> apply(Throwable throwable) throws Exception {
721  return Flowable.error(handleError(throwable));
722  }
723  }).doFinally(new Action() {
724  @Override
725  public void run() throws Exception {
726  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.ECG);
727  }
728  }));
729  } catch (Throwable t){
730  return Flowable.error(t);
731  }
732  }
733 
734  @Override
735  public Flowable<PolarAccelerometerData> startAccStreaming(String identifier,
736  PolarSensorSetting setting) {
737  try {
738  final BleDeviceSession session = sessionPmdClientReady(identifier);
739  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
740  return client.startMeasurement(BlePMDClient.PmdMeasurementType.ACC, setting.map2PmdSettings()).andThen(
741  client.monitorAccNotifications(true).map(new Function<BlePMDClient.AccData, PolarAccelerometerData>() {
742  @Override
743  public PolarAccelerometerData apply(BlePMDClient.AccData accData) throws Exception {
744  List<PolarAccelerometerData.PolarAccelerometerSample> samples = new ArrayList<>();
745  for( BlePMDClient.AccData.AccSample s : accData.accSamples ){
746  samples.add(new PolarAccelerometerData.PolarAccelerometerSample(s.x,s.y,s.z));
747  }
748  return new PolarAccelerometerData(samples,accData.timeStamp);
749  }
750  }).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarAccelerometerData>>() {
751  @Override
752  public Publisher<? extends PolarAccelerometerData> apply(Throwable throwable) throws Exception {
753  return Flowable.error(handleError(throwable));
754  }
755  }).doFinally(new Action() {
756  @Override
757  public void run() throws Exception {
758  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.ACC);
759  }
760  }));
761  } catch (Throwable t){
762  return Flowable.error(t);
763  }
764  }
765 
766  @Override
767  public Flowable<PolarOhrPPGData> startOhrPPGStreaming(String identifier,
768  PolarSensorSetting setting) {
769  try {
770  final BleDeviceSession session = sessionPmdClientReady(identifier);
771  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
772  return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPG, setting.map2PmdSettings()).andThen(
773  client.monitorPpgNotifications(true).map(new Function<BlePMDClient.PpgData, PolarOhrPPGData>() {
774  @Override
775  public PolarOhrPPGData apply(BlePMDClient.PpgData ppgData) throws Exception {
776  List<PolarOhrPPGData.PolarOhrPPGSample> samples = new ArrayList<>();
777  for( BlePMDClient.PpgData.PpgSample s : ppgData.ppgSamples ){
778  samples.add(new PolarOhrPPGData.PolarOhrPPGSample(s.ppg0,s.ppg1,s.ppg2,s.ambient));
779  }
780  return new PolarOhrPPGData(samples,ppgData.timeStamp);
781  }
782  }).doFinally(new Action() {
783  @Override
784  public void run() throws Exception {
785  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.PPG);
786  }
787  })).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarOhrPPGData>>() {
788  @Override
789  public Publisher<? extends PolarOhrPPGData> apply(Throwable throwable) throws Exception {
790  return Flowable.error(handleError(throwable));
791  }
792  });
793  } catch (Throwable t){
794  return Flowable.error(t);
795  }
796  }
797 
798  @Override
799  public Flowable<PolarOhrPPIData> startOhrPPIStreaming(String identifier) {
800  try {
801  final BleDeviceSession session = sessionPmdClientReady(identifier);
802  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
803  return client.startMeasurement(BlePMDClient.PmdMeasurementType.PPI, new BlePMDClient.PmdSetting(new HashMap<BlePMDClient.PmdSetting.PmdSettingType, Integer>())).andThen(
804  client.monitorPpiNotifications(true).map(new Function<BlePMDClient.PpiData, PolarOhrPPIData>() {
805  @Override
806  public PolarOhrPPIData apply(BlePMDClient.PpiData ppiData) throws Exception {
807  List<PolarOhrPPIData.PolarOhrPPISample> samples = new ArrayList<>();
808  for(BlePMDClient.PpiData.PPSample ppSample : ppiData.ppSamples){
809  samples.add(new PolarOhrPPIData.PolarOhrPPISample(ppSample.ppInMs,
810  ppSample.ppErrorEstimate,
811  ppSample.hr,
812  ppSample.blockerBit != 0,
813  ppSample.skinContactStatus != 0,
814  ppSample.skinContactSupported != 0));
815  }
816  return new PolarOhrPPIData(ppiData.timestamp,samples);
817  }
818  }).doFinally(new Action() {
819  @Override
820  public void run() throws Exception {
821  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.PPI);
822  }
823  })).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarOhrPPIData>>() {
824  @Override
825  public Publisher<? extends PolarOhrPPIData> apply(Throwable throwable) throws Exception {
826  return Flowable.error(handleError(throwable));
827  }
828  });
829  } catch (Throwable t){
830  return Flowable.error(t);
831  }
832  }
833 
834  @Override
835  public Flowable<PolarBiozData> startBiozStreaming(final String identifier, PolarSensorSetting setting){
836  try {
837  final BleDeviceSession session = sessionPmdClientReady(identifier);
838  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
839  return client.startMeasurement(BlePMDClient.PmdMeasurementType.BIOZ, setting.map2PmdSettings()).andThen(
840  client.monitorBiozNotifications(true).map(new Function<BlePMDClient.BiozData, PolarBiozData>() {
841  @Override
842  public PolarBiozData apply(BlePMDClient.BiozData biozData) throws Exception {
843  return new PolarBiozData(biozData.timeStamp,biozData.samples);
844  }
845  }).doFinally(new Action() {
846  @Override
847  public void run() throws Exception {
848  stopPmdStreaming(session,client, BlePMDClient.PmdMeasurementType.PPG);
849  }
850  })).onErrorResumeNext(new Function<Throwable, Publisher<? extends PolarBiozData>>() {
851  @Override
852  public Publisher<? extends PolarBiozData> apply(Throwable throwable) throws Exception {
853  return Flowable.error(handleError(throwable));
854  }
855  });
856  } catch (Throwable t){
857  return Flowable.error(t);
858  }
859  }
860 
861  protected BleDeviceSession fetchSession(final String identifier) throws PolarInvalidArgument {
862  return identifier.contains(":") ?
863  sessionByAddress(identifier) : sessionByDeviceId(identifier);
864  }
865 
866  protected BleDeviceSession sessionByAddress(final String address) throws PolarInvalidArgument {
867  validate(address.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"));
868  for ( BleDeviceSession session : listener.deviceSessions() ){
869  if( session.getAddress().equals(address) ){
870  return session;
871  }
872  }
873  return null;
874  }
875 
876  protected BleDeviceSession sessionByDeviceId(final String deviceId) throws PolarInvalidArgument {
877  validate(deviceId.matches("([0-9a-fA-F]){6,8}"));
878  for ( BleDeviceSession session : listener.deviceSessions() ){
879  if( session.getAdvertisementContent().getPolarDeviceId().equals(deviceId) ){
880  return session;
881  }
882  }
883  return null;
884  }
885 
886  private void validate(boolean a) throws PolarInvalidArgument {
887  if(!a) {
888  throw new PolarInvalidArgument();
889  }
890  }
891 
892  protected BleDeviceSession sessionServiceReady(final String identifier, UUID service) throws Throwable {
893  BleDeviceSession session = fetchSession(identifier);
894  if(session != null){
895  if(session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN) {
896  BleGattBase client = session.fetchClient(service);
897  if (client.isServiceDiscovered()) {
898  return session;
899  }
900  throw new PolarServiceNotAvailable();
901  }
902  throw new PolarDeviceDisconnected();
903  }
904  throw new PolarDeviceNotFound();
905  }
906 
907  public BleDeviceSession sessionPmdClientReady(final String identifier) throws Throwable {
908  BleDeviceSession session = sessionServiceReady(identifier, BlePMDClient.PMD_SERVICE);
909  BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
910  final AtomicInteger pair = client.getNotificationAtomicInteger(BlePMDClient.PMD_CP);
911  final AtomicInteger pairData = client.getNotificationAtomicInteger(BlePMDClient.PMD_DATA);
912  if (pair != null && pairData != null &&
913  pair.get() == BleGattBase.ATT_SUCCESS &&
914  pairData.get() == BleGattBase.ATT_SUCCESS) {
915  return session;
916  }
917  throw new PolarNotificationNotEnabled();
918  }
919 
920  protected BleDeviceSession sessionPsFtpClientReady(final String identifier) throws Throwable {
921  BleDeviceSession session = sessionServiceReady(identifier, BlePsFtpUtils.RFC77_PFTP_SERVICE);
922  BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
923  final AtomicInteger pair = client.getNotificationAtomicInteger(BlePsFtpUtils.RFC77_PFTP_MTU_CHARACTERISTIC);
924  if (pair != null && pair.get() == BleGattBase.ATT_SUCCESS ) {
925  return session;
926  }
927  throw new PolarNotificationNotEnabled();
928  }
929 
930  @SuppressLint("CheckResult")
931  protected void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type) {
932  if( session.getSessionState() == BleDeviceSession.DeviceSessionState.SESSION_OPEN ){
933  // stop streaming
934  client.stopMeasurement(type).subscribe(
935  new Action() {
936  @Override
937  public void run() throws Exception {
938 
939  }
940  },
941  new Consumer<Throwable>() {
942  @Override
943  public void accept(Throwable throwable) throws Exception {
944  logError("failed to stop pmd stream: " + throwable.getLocalizedMessage());
945  }
946  }
947  );
948  }
949  }
950 
951  @SuppressLint("CheckResult")
952  protected void setupDevice(final BleDeviceSession session){
953  final String deviceId = session.getPolarDeviceId().length() != 0 ? session.getPolarDeviceId() : session.getAddress();
954  session.monitorServicesDiscovered(true).observeOn(scheduler).toFlowable().flatMapIterable(
955  new Function<List<UUID>, Iterable<UUID>>() {
956  @Override
957  public Iterable<UUID> apply(List<UUID> uuids) throws Exception {
958  return uuids;
959  }
960  }
961  ).flatMap(new Function<UUID, Publisher<?>>() {
962  @Override
963  public Publisher<?> apply(UUID uuid) throws Exception {
964  if(session.fetchClient(uuid) != null) {
965  if (uuid.equals(BleHrClient.HR_SERVICE)) {
966  if (callback != null) {
967  callback.hrFeatureReady(deviceId);
968  }
969  final BleHrClient client = (BleHrClient) session.fetchClient(BleHrClient.HR_SERVICE);
970  client.observeHrNotifications(true).observeOn(scheduler).subscribe(
971  new Consumer<BleHrClient.HrNotificationData>() {
972  @Override
973  public void accept(BleHrClient.HrNotificationData hrNotificationData) throws Exception {
974  if (callback != null) {
976  new PolarHrData(hrNotificationData.hrValue,
977  hrNotificationData.rrs,
978  hrNotificationData.sensorContact,
979  hrNotificationData.sensorContactSupported,
980  hrNotificationData.rrPresent));
981  }
982  }
983  },
984  new Consumer<Throwable>() {
985  @Override
986  public void accept(Throwable throwable) throws Exception {
987  logError(throwable.getMessage());
988  }
989  },
990  new Action() {
991  @Override
992  public void run() throws Exception {
993 
994  }
995  }
996  );
997  } else if (uuid.equals(BleBattClient.BATTERY_SERVICE)) {
998  BleBattClient client = (BleBattClient) session.fetchClient(BleBattClient.BATTERY_SERVICE);
999  return client.waitBatteryLevelUpdate(true).observeOn(scheduler).doOnSuccess(new Consumer<Integer>() {
1000  @Override
1001  public void accept(Integer integer) throws Exception {
1002  if (callback != null) {
1003  callback.batteryLevelReceived(deviceId, integer);
1004  }
1005  }
1006  }).toFlowable();
1007  } else if (uuid.equals(BlePMDClient.PMD_SERVICE)) {
1008  final BlePMDClient client = (BlePMDClient) session.fetchClient(BlePMDClient.PMD_SERVICE);
1009  return client.waitNotificationEnabled(BlePMDClient.PMD_CP, true).
1010  concatWith(client.waitNotificationEnabled(BlePMDClient.PMD_DATA, true)).andThen(client.readFeature(true).doOnSuccess(new Consumer<BlePMDClient.PmdFeature>() {
1011  @Override
1012  public void accept(BlePMDClient.PmdFeature pmdFeature) {
1013  if (callback != null) {
1014  if (pmdFeature.ecgSupported) {
1015  callback.ecgFeatureReady(deviceId);
1016  }
1017  if (pmdFeature.accSupported) {
1019  }
1020  if (pmdFeature.ppgSupported) {
1021  callback.ppgFeatureReady(deviceId);
1022  }
1023  if (pmdFeature.ppiSupported) {
1024  callback.ppiFeatureReady(deviceId);
1025  }
1026  if (pmdFeature.bioZSupported) {
1027  callback.biozFeatureReady(deviceId);
1028  }
1029  }
1030  }
1031  })).toFlowable();
1032  } else if (uuid.equals(BleDisClient.DIS_SERVICE)) {
1033  BleDisClient client = (BleDisClient) session.fetchClient(BleDisClient.DIS_SERVICE);
1034  return client.observeDisInfo(true).observeOn(scheduler).doOnNext(new Consumer<Pair<UUID, String>>() {
1035  @Override
1036  public void accept(Pair<UUID, String> pair) {
1037  if (callback != null) {
1038  callback.disInformationReceived(deviceId, pair.first , pair.second);
1039  }
1040  }
1041  });
1042  } else if (uuid.equals(BlePsFtpUtils.RFC77_PFTP_SERVICE)) {
1043  BlePsFtpClient client = (BlePsFtpClient) session.fetchClient(BlePsFtpUtils.RFC77_PFTP_SERVICE);
1044  return client.waitPsFtpClientReady(true).observeOn(scheduler).doOnComplete(new Action() {
1045  @Override
1046  public void run() throws Exception {
1047  if (callback != null &&
1048  (session.getPolarDeviceType().equals("OH1") || session.getPolarDeviceType().equals("H10"))) {
1049  callback.polarFtpFeatureReady(deviceId);
1050  }
1051  }
1052  }).toFlowable();
1053  }
1054  }
1055  return Flowable.empty();
1056  }
1057  }).subscribe(
1058  new Consumer<Object>() {
1059  @Override
1060  public void accept(Object o) throws Exception {
1061 
1062  }
1063  },
1064  new Consumer<Throwable>() {
1065  @Override
1066  public void accept(Throwable throwable) throws Exception {
1067  logError(throwable.getMessage());
1068  }
1069  },
1070  new Action() {
1071  @Override
1072  public void run() throws Exception {
1073  log("complete");
1074  }
1075  });
1076  }
1077 
1078  protected Exception handleError(Throwable throwable) {
1079  if( throwable instanceof BleDisconnected ){
1080  return new PolarDeviceDisconnected();
1081  } else {
1082  return new Exception("Unknown Error: " + throwable.getLocalizedMessage());
1083  }
1084  }
1085 
1086  interface FetchRecursiveCondition {
1087  boolean include(String entry);
1088  }
1089 
1090  protected Flowable<String> fetchRecursively(final BlePsFtpClient client, final String path, final FetchRecursiveCondition condition) {
1091  protocol.PftpRequest.PbPFtpOperation.Builder builder = protocol.PftpRequest.PbPFtpOperation.newBuilder();
1092  builder.setCommand(PftpRequest.PbPFtpOperation.Command.GET);
1093  builder.setPath(path);
1094  return client.request(builder.build().toByteArray()).toFlowable().flatMap(new Function<ByteArrayOutputStream, Publisher<String>>() {
1095  @Override
1096  public Publisher<String> apply(ByteArrayOutputStream byteArrayOutputStream) throws Exception {
1097  PftpResponse.PbPFtpDirectory dir = PftpResponse.PbPFtpDirectory.parseFrom(byteArrayOutputStream.toByteArray());
1098  Set<String> entrys = new HashSet<>();
1099  for( int i=0; i < dir.getEntriesCount(); ++i ){
1100  PftpResponse.PbPFtpEntry entry = dir.getEntries(i);
1101  if( condition.include(entry.getName()) ){
1102  BleUtils.validate(entrys.add(path + entry.getName()),"duplicate entry");
1103  }
1104  }
1105  if(entrys.size()!=0) {
1106  return Flowable.fromIterable(entrys).flatMap(new Function<String, Publisher<String>>() {
1107  @Override
1108  public Publisher<String> apply(String s) {
1109  if (s.endsWith("/")) {
1110  return fetchRecursively(client, s, condition);
1111  } else {
1112  return Flowable.just(s);
1113  }
1114  }
1115  });
1116  }
1117  return Flowable.empty();
1118  }
1119  });
1120  }
1121 
1122  protected void log(final String message) {
1123  if(logger != null){
1124  logger.message("" + message);
1125  }
1126  }
1127 
1128  protected void logError(final String message) {
1129  if(logger != null){
1130  logger.message("Error: "+message);
1131  }
1132  }
1133 }
+
void log(final String message)
+ +
static final int ANDROID_VERSION_O
+
Single< PolarExerciseData > fetchExercise(String identifier, PolarExerciseEntry entry)
+ +
Completable startRecording(String identifier, String exerciseId, RecordingInterval interval, SampleType type)
+
Completable autoConnectToDevice(final int rssiLimit, final String service, final int timeout, final TimeUnit unit, final String polarDeviceType)
-
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 deviceConnecting(@NonNull final PolarDeviceInfo polarDeviceInfo)
- +
void hrFeatureReady(@NonNull final String identifier)
-
Flowable< PolarHrBroadcastData > startListenForPolarHrBroadcasts(final Set< String > deviceIds)
- +
Flowable< PolarHrBroadcastData > startListenForPolarHrBroadcasts(final Set< String > deviceIds)
+ -
Flowable< PolarDeviceInfo > searchForDevice()
+
Flowable< PolarDeviceInfo > searchForDevice()
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
-
Map< String, Disposable > connectSubscriptions
+
Map< String, Disposable > connectSubscriptions
void ppgFeatureReady(@NonNull final String identifier)
-
Single< PolarSensorSetting > requestBiozSettings(final String identifier)
- +
Single< PolarSensorSetting > requestBiozSettings(final String identifier)
+ -
Single< PolarSensorSetting > requestEcgSettings(String identifier)
-
BleDeviceSession sessionServiceReady(final String identifier, UUID service)
-
Single< Pair< Boolean, String > > requestRecordingStatus(String identifier)
+
Single< PolarSensorSetting > requestEcgSettings(String identifier)
+
BleDeviceSession sessionServiceReady(final String identifier, UUID service)
+
Single< Pair< Boolean, String > > requestRecordingStatus(String identifier)
@@ -115,51 +115,51 @@
void disInformationReceived(@NonNull final String identifier, @NonNull UUID uuid, @NonNull final String value)
-
BleDeviceSession fetchSession(final String identifier)
-
boolean isFeatureReady(final String deviceId, int feature)
-
BleDeviceSession sessionByAddress(final String address)
+
BleDeviceSession fetchSession(final String identifier)
+
boolean isFeatureReady(final String deviceId, int feature)
+
BleDeviceSession sessionByAddress(final String address)
void deviceConnected(@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)
-
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)
void batteryLevelReceived(@NonNull final String identifier, final int level)
static final int FEATURE_DEVICE_INFO
-
void setupDevice(final BleDeviceSession session)
+
void setupDevice(final BleDeviceSession session)
-
void connectToDevice(final String identifier)
-
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
-
Flowable< PolarExerciseEntry > listExercises(String identifier)
+
void connectToDevice(final String identifier)
+
Flowable< PolarBiozData > startBiozStreaming(final String identifier, PolarSensorSetting setting)
+
Flowable< PolarExerciseEntry > listExercises(String identifier)
-
Completable autoConnectToDevice(final int rssiLimit, final String service, final String polarDeviceType)
+
Completable autoConnectToDevice(final int rssiLimit, final String service, final String polarDeviceType)
-
BleDeviceSession sessionByDeviceId(final String deviceId)
+
BleDeviceSession sessionByDeviceId(final String deviceId)
void hrNotificationReceived(@NonNull final String identifier, @NonNull final PolarHrData data)
-
void setPolarFilter(boolean enable)
+
void setPolarFilter(boolean enable)
void blePowerStateChanged(final boolean powered)
static final int FEATURE_POLAR_FILE_TRANSFER
-
Completable removeExercise(String identifier, PolarExerciseEntry entry)
+
Completable removeExercise(String identifier, PolarExerciseEntry entry)
void polarFtpFeatureReady(@NonNull final String identifier)
void deviceDisconnected(@NonNull final PolarDeviceInfo polarDeviceInfo)
- - -
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type)
- -
void setApiLogger(@Nullable PolarBleApiLogger logger)
-
Flowable< PolarOhrPPGData > startOhrPPGStreaming(String identifier, PolarSensorSetting setting)
-
Flowable< PolarEcgData > startEcgStreaming(String identifier, PolarSensorSetting setting)
+ + +
void stopPmdStreaming(BleDeviceSession session, BlePMDClient client, BlePMDClient.PmdMeasurementType type)
+ +
void setApiLogger(@Nullable PolarBleApiLogger logger)
+
Flowable< PolarOhrPPGData > startOhrPPGStreaming(String identifier, PolarSensorSetting setting)
+
Flowable< PolarEcgData > startEcgStreaming(String identifier, PolarSensorSetting setting)
void biozFeatureReady(@NonNull final String identifier)
@@ -168,15 +168,15 @@ -
void setApiCallback(PolarBleApiCallback callback)
-
Completable setLocalTime(String identifier, Calendar cal)
+
void setApiCallback(PolarBleApiCallback callback)
+
Completable setLocalTime(String identifier, Calendar cal)
-
BleDeviceSession sessionPsFtpClientReady(final String identifier)
-
void logError(final String message)
+
BleDeviceSession sessionPsFtpClientReady(final String identifier)
+
void logError(final String message)
-
void disconnectFromDevice(String identifier)
+
void disconnectFromDevice(String identifier)
diff --git a/polar-sdk-android/docs/html/PolarBleApiDefaultImpl_8java_source.html b/polar-sdk-android/docs/html/PolarBleApiDefaultImpl_8java_source.html index 117490c1..b7fbdd22 100644 --- a/polar-sdk-android/docs/html/PolarBleApiDefaultImpl_8java_source.html +++ b/polar-sdk-android/docs/html/PolarBleApiDefaultImpl_8java_source.html @@ -66,7 +66,7 @@
PolarBleApiDefaultImpl.java
-Go to the documentation of this file.
1 // Copyright © 2019 Polar Electro Oy. All rights reserved.
2 package polar.com.sdk.api;
3 
4 import android.content.Context;
5 
6 import com.androidcommunications.polar.api.ble.BleRefApiVersion;
7 
9 
13 public class PolarBleApiDefaultImpl {
20  public static PolarBleApi defaultImplementation(final Context context, int features){
21  return new BDBleApiImpl(context,features);
22  }
23 
27  public static String versionInfo(){
28  return "2.1.0";
29  }
30 }
+Go to the documentation of this file.
1 // Copyright © 2019 Polar Electro Oy. All rights reserved.
2 package polar.com.sdk.api;
3 
4 import android.content.Context;
5 
6 import com.androidcommunications.polar.api.ble.BleRefApiVersion;
7 
9 
13 public class PolarBleApiDefaultImpl {
20  public static PolarBleApi defaultImplementation(final Context context, int features){
21  return new BDBleApiImpl(context,features);
22  }
23 
27  public static String versionInfo(){
28  return "2.1.0";
29  }
30 }
diff --git a/polar-sdk-android/docs/html/PolarBleApi_8java_source.html b/polar-sdk-android/docs/html/PolarBleApi_8java_source.html index 195b7df0..fb3c9082 100644 --- a/polar-sdk-android/docs/html/PolarBleApi_8java_source.html +++ b/polar-sdk-android/docs/html/PolarBleApi_8java_source.html @@ -66,7 +66,7 @@
PolarBleApi.java
-Go to the documentation of this file.
1 // Copyright © 2019 Polar Electro Oy. All rights reserved.
2 package polar.com.sdk.api;
3 
4 import android.support.annotation.Nullable;
5 import android.support.annotation.Size;
6 import android.util.Pair;
7 
8 import java.util.Calendar;
9 import java.util.Date;
10 import java.util.Set;
11 import java.util.concurrent.TimeUnit;
12 
13 import io.reactivex.Completable;
14 import io.reactivex.Flowable;
15 import io.reactivex.Single;
16 import io.reactivex.annotations.NonNull;
28 
32 public abstract class PolarBleApi {
33 
37  public interface PolarBleApiLogger {
42  void message(final String str);
43  }
44 
48  public enum RecordingInterval {
52  private int value;
53 
54  RecordingInterval(int value) {
55  this.value = value;
56  }
57 
58  public int getValue() {
59  return value;
60  }
61  }
62 
66  public enum SampleType {
67  HR,
68  RR;
69  }
70 
74  public static final int FEATURE_HR = 1;
78  public static final int FEATURE_DEVICE_INFO = 2;
82  public static final int FEATURE_BATTERY_INFO = 4;
86  public static final int FEATURE_POLAR_SENSOR_STREAMING = 8;
90  public static final int FEATURE_POLAR_FILE_TRANSFER = 16;
94  public static final int ALL_FEATURES = 0xff;
95 
96  protected int features;
97 
102  protected PolarBleApi(final int features) {
103  this.features = features;
104  }
105 
109  public abstract void shutDown();
110 
114  public abstract void cleanup();
115 
120  public abstract void setPolarFilter(boolean enable);
121 
128  public abstract boolean isFeatureReady(@NonNull final String deviceId, final int feature);
129 
133  public abstract void backgroundEntered();
134 
138  public abstract void foregroundEntered();
139 
143  public abstract void setApiCallback(@Nullable PolarBleApiCallback callback);
144 
148  public abstract void setApiLogger(@Nullable PolarBleApiLogger logger);
149 
154  public abstract void setAutomaticReconnection(boolean enable);
155 
163  public abstract Completable setLocalTime(@NonNull final String identifier, @NonNull Calendar calendar);
164 
170  public abstract Single<PolarSensorSetting> requestAccSettings(@NonNull final String identifier);
171 
177  public abstract Single<PolarSensorSetting> requestEcgSettings(@NonNull final String identifier);
178 
184  public abstract Single<PolarSensorSetting> requestPpgSettings(@NonNull final String identifier);
185 
186  public abstract Single<PolarSensorSetting> requestBiozSettings(@NonNull final String identifier);
187 
198  public abstract Completable autoConnectToDevice(int rssiLimit, @Nullable String service, int timeout,@NonNull TimeUnit unit,@Nullable final String polarDeviceType);
199  public abstract Completable autoConnectToDevice(int rssiLimit, @Nullable String service, final String polarDeviceType);
200 
206  public abstract void connectToDevice(@NonNull final String identifier) throws PolarInvalidArgument;
207 
213  public abstract void disconnectFromDevice(@NonNull final String identifier) throws PolarInvalidArgument;
214 
223  public abstract Completable startRecording(@NonNull final String identifier,
224  @NonNull @Size(min = 1, max = 64) final String exerciseId,
225  @NonNull RecordingInterval interval,
226  @NonNull SampleType type);
227 
233  public abstract Completable stopRecording(@NonNull final String identifier);
234 
240  public abstract Single<Pair<Boolean,String>> requestRecordingStatus(@NonNull final String identifier);
241 
247  public abstract Flowable<PolarExerciseEntry> listExercises(@NonNull final String identifier);
248 
255  public abstract Single<PolarExerciseData> fetchExercise(@NonNull final String identifier, @NonNull final PolarExerciseEntry entry);
256 
263  public abstract Completable removeExercise(@NonNull final String identifier, @NonNull final PolarExerciseEntry entry);
264 
273  public abstract Flowable<PolarDeviceInfo> searchForDevice();
274 
284  public abstract Flowable<PolarHrBroadcastData> startListenForPolarHrBroadcasts(@Nullable final Set<String> deviceIds);
285 
298  public abstract Flowable<PolarEcgData> startEcgStreaming(@NonNull final String identifier,
299  @NonNull PolarSensorSetting sensorSetting);
300 
313  public abstract Flowable<PolarAccelerometerData> startAccStreaming(@NonNull final String identifier,
314  @NonNull PolarSensorSetting sensorSetting);
315 
328  public abstract Flowable<PolarOhrPPGData> startOhrPPGStreaming(@NonNull final String identifier,
329  @NonNull PolarSensorSetting sensorSetting);
330 
331  public abstract Flowable<PolarBiozData> startBiozStreaming(@NonNull final String identifier,
332  @NonNull PolarSensorSetting sensorSetting);
333 
344  public abstract Flowable<PolarOhrPPIData> startOhrPPIStreaming(@NonNull final String identifier);
345 }
+Go to the documentation of this file.
1 // Copyright © 2019 Polar Electro Oy. All rights reserved.
2 package polar.com.sdk.api;
3 
4 import android.support.annotation.Nullable;
5 import android.support.annotation.Size;
6 import android.util.Pair;
7 
8 import java.util.Calendar;
9 import java.util.Date;
10 import java.util.Set;
11 import java.util.concurrent.TimeUnit;
12 
13 import io.reactivex.Completable;
14 import io.reactivex.Flowable;
15 import io.reactivex.Single;
16 import io.reactivex.annotations.NonNull;
28 
32 public abstract class PolarBleApi {
33 
37  public interface PolarBleApiLogger {
42  void message(final String str);
43  }
44 
48  public enum RecordingInterval {
52  private int value;
53 
54  RecordingInterval(int value) {
55  this.value = value;
56  }
57 
58  public int getValue() {
59  return value;
60  }
61  }
62 
66  public enum SampleType {
67  HR,
68  RR;
69  }
70 
74  public static final int FEATURE_HR = 1;
78  public static final int FEATURE_DEVICE_INFO = 2;
82  public static final int FEATURE_BATTERY_INFO = 4;
86  public static final int FEATURE_POLAR_SENSOR_STREAMING = 8;
90  public static final int FEATURE_POLAR_FILE_TRANSFER = 16;
94  public static final int ALL_FEATURES = 0xff;
95 
96  protected int features;
97 
102  protected PolarBleApi(final int features) {
103  this.features = features;
104  }
105 
109  public abstract void shutDown();
110 
114  public abstract void cleanup();
115 
120  public abstract void setPolarFilter(boolean enable);
121 
128  public abstract boolean isFeatureReady(@NonNull final String deviceId, final int feature);
129 
133  public abstract void backgroundEntered();
134 
138  public abstract void foregroundEntered();
139 
143  public abstract void setApiCallback(@Nullable PolarBleApiCallback callback);
144 
148  public abstract void setApiLogger(@Nullable PolarBleApiLogger logger);
149 
154  public abstract void setAutomaticReconnection(boolean enable);
155 
163  public abstract Completable setLocalTime(@NonNull final String identifier, @NonNull Calendar calendar);
164 
170  public abstract Single<PolarSensorSetting> requestAccSettings(@NonNull final String identifier);
171 
177  public abstract Single<PolarSensorSetting> requestEcgSettings(@NonNull final String identifier);
178 
184  public abstract Single<PolarSensorSetting> requestPpgSettings(@NonNull final String identifier);
185 
186  public abstract Single<PolarSensorSetting> requestBiozSettings(@NonNull final String identifier);
187 
199  public abstract Completable autoConnectToDevice(int rssiLimit, @Nullable String service, int timeout,@NonNull TimeUnit unit,@Nullable final String polarDeviceType);
200  public abstract Completable autoConnectToDevice(int rssiLimit, @Nullable String service, final String polarDeviceType);
201 
207  public abstract void connectToDevice(@NonNull final String identifier) throws PolarInvalidArgument;
208 
214  public abstract void disconnectFromDevice(@NonNull final String identifier) throws PolarInvalidArgument;
215 
224  public abstract Completable startRecording(@NonNull final String identifier,
225  @NonNull @Size(min = 1, max = 64) final String exerciseId,
226  @NonNull RecordingInterval interval,
227  @NonNull SampleType type);
228 
234  public abstract Completable stopRecording(@NonNull final String identifier);
235 
241  public abstract Single<Pair<Boolean,String>> requestRecordingStatus(@NonNull final String identifier);
242 
248  public abstract Flowable<PolarExerciseEntry> listExercises(@NonNull final String identifier);
249 
256  public abstract Single<PolarExerciseData> fetchExercise(@NonNull final String identifier, @NonNull final PolarExerciseEntry entry);
257 
264  public abstract Completable removeExercise(@NonNull final String identifier, @NonNull final PolarExerciseEntry entry);
265 
274  public abstract Flowable<PolarDeviceInfo> searchForDevice();
275 
285  public abstract Flowable<PolarHrBroadcastData> startListenForPolarHrBroadcasts(@Nullable final Set<String> deviceIds);
286 
299  public abstract Flowable<PolarEcgData> startEcgStreaming(@NonNull final String identifier,
300  @NonNull PolarSensorSetting sensorSetting);
301 
314  public abstract Flowable<PolarAccelerometerData> startAccStreaming(@NonNull final String identifier,
315  @NonNull PolarSensorSetting sensorSetting);
316 
329  public abstract Flowable<PolarOhrPPGData> startOhrPPGStreaming(@NonNull final String identifier,
330  @NonNull PolarSensorSetting sensorSetting);
331 
332  public abstract Flowable<PolarBiozData> startBiozStreaming(@NonNull final String identifier,
333  @NonNull PolarSensorSetting sensorSetting);
334 
345  public abstract Flowable<PolarOhrPPIData> startOhrPPIStreaming(@NonNull final String identifier);
346 }
abstract Single< PolarSensorSetting > requestEcgSettings(@NonNull final String identifier)
abstract Flowable< PolarOhrPPIData > startOhrPPIStreaming(@NonNull final String identifier)
diff --git a/polar-sdk-android/docs/html/PolarInvalidArgument_8java_source.html b/polar-sdk-android/docs/html/PolarInvalidArgument_8java_source.html index 7fdfe6f1..65a23a56 100644 --- a/polar-sdk-android/docs/html/PolarInvalidArgument_8java_source.html +++ b/polar-sdk-android/docs/html/PolarInvalidArgument_8java_source.html @@ -66,7 +66,9 @@
PolarInvalidArgument.java
-Go to the documentation of this file.
1 package polar.com.sdk.api.errors;
2 
3 public class PolarInvalidArgument extends Exception {
4 }
+Go to the documentation of this file.
1 package polar.com.sdk.api.errors;
2 
3 public class PolarInvalidArgument extends Exception {
4  public PolarInvalidArgument(String s) {
5  super(s);
6  }
7 
9  }
10 }
+ +
diff --git a/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1PolarBleApi.html b/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1PolarBleApi.html index 017e43c6..8c2a0449 100644 --- a/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1PolarBleApi.html +++ b/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1PolarBleApi.html @@ -275,13 +275,14 @@

Parameters
+
rssiLimitRSSI (Received Signal Strength Indication) value is typically from -40 to -60 (dBm), depends on the used Bluetooth chipset and/or antenna tuning
servicein hex string format like "180D" PolarInvalidArgument invoked if not in correct format
timeoutmin time to search nearby device default = 2s
unittime unit to be used
polarDeviceTypelike H10, OH1 etc... or null for any polar device
-
Returns
rx Completable, complete invoked when nearby device found, and connection attempt started. polarDeviceConnectionAttemptStarted callback invoked to inform connection attempt
+
Returns
rx Completable, complete invoked when nearby device found, and connection attempt started. deviceConnecting callback invoked to inform connection attempt

@@ -404,7 +405,7 @@

Request a connection to a Polar device. Invokes PolarBleApiCallback::polarDeviceConnected callback.

Parameters
- +
identifierPolar device id found printed on the sensor/device or bt address
identifierPolar device id found printed on the sensor/device (in format "12345678") or bt address (in format "00:11:22:33:44:55")
diff --git a/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1errors_1_1PolarInvalidArgument.html b/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1errors_1_1PolarInvalidArgument.html index 63ae1609..96fb1964 100644 --- a/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1errors_1_1PolarInvalidArgument.html +++ b/polar-sdk-android/docs/html/classpolar_1_1com_1_1sdk_1_1api_1_1errors_1_1PolarInvalidArgument.html @@ -62,6 +62,8 @@
+
polar.com.sdk.api.errors.PolarInvalidArgument Class Reference
@@ -72,10 +74,74 @@
+ + + + + + +

+Public Member Functions

 PolarInvalidArgument (String s)
 
 PolarInvalidArgument ()
 

Detailed Description

Definition at line 3 of file PolarInvalidArgument.java.

-

The documentation for this class was generated from the following file: