diff --git a/README.md b/README.md index 6b1ac120..afedf89c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This SDK uses ReactiveX. You can read more about ReactiveX from their website [r By exploiting the SDK, you indicate your acceptance of [License](Polar_SDK_License.txt). -If you wish to collaborate with Polar commercially, [click here](http://polar.com/developers) +If you wish to collaborate with Polar commercially, [click here](https://polar.com/developers) ### Quick License Summary / Your rights to use the SDK You may use, copy and modify the SDK as long as you @@ -26,11 +26,35 @@ Most accurate Heart rate sensor in the markets. The H10 is used in the Getting s #### H10 heart rate sensor available data types * From version 3.0.35 onwards. * Heart rate as beats per minute. RR Interval in ms and 1/1024 format. +* Heart rate broadcast. * Electrocardiography (ECG) data in µV. Default epoch for timestamp is 1.1.2000 * Accelerometer data with sample rates of 25Hz, 50Hz, 100Hz and 200Hz and range of 2G, 4G and 8G. Axis specific acceleration data in mG. Default epoch for timestamp is 1.1.2000 * Start and stop of internal recording and request for internal recording status. Recording supports RR, HR with one second sampletime or HR with five second sampletime. * List, read and remove for stored internal recording (sensor supports only one recording at the time). +### H9 Heart rate sensor +Reliable high quality heart rate chest strap. +[Store page](https://www.polar.com/en/products/accessories/H9_heart_rate_sensor) + +#### H9 heart rate sensor available data types +* From version 1.0.0 onwards. +* Heart rate as beats per minute. RR Interval in ms and 1/1024 format. +* Heart rate broadcast. + +### Polar Verity Sense Optical heart rate sensor +Optical heart rate sensor is a rechargeable device that measures user’s heart rate with LED technology. +[Store page](https://www.polar.com/en/products/accessories/polar-verity-sense) + +#### Polar Verity Sense Optical heart rate sensor available data types +* From version 1.0.0 onwards. +* Heart rate as beats per minute. +* Heart rate broadcast. +* Photoplethysmograpy (PPG) values. +* PP interval (milliseconds) representing cardiac pulse-to-pulse interval extracted from PPG signal. +* Accelerometer data with sample rate of 52Hz and range of 8G. Axis specific acceleration data in mG. +* Gyroscope data with sample rate of 52Hz and ranges of 250dps, 500dps, 1000dps and 2000dps. Axis specific gyroscope data in dps. +* Magnetometer data with sample rates of 10Hz, 20Hz, 50HZ and 100Hz and range of +/-50 Gauss. Axis specific magnetometer data in Gauss. +* List, read and remove stored exercise. Recording of exercise requires that sensor is registered to Polar Flow account. ### OH1 Optical heart rate sensor Optical heart rate sensor is a rechargeable device that measures user’s heart rate with LED technology. @@ -39,6 +63,7 @@ Optical heart rate sensor is a rechargeable device that measures user’s heart #### OH1 Optical heart rate sensor available data types * From version 2.0.8 onwards. * Heart rate as beats per minute. +* Heart rate broadcast. * Photoplethysmograpy (PPG) values. * PP interval (milliseconds) representing cardiac pulse-to-pulse interval extracted from PPG signal. * Accelerometer data with samplerate of 50Hz and range of 8G. Axis specific acceleration data in mG. @@ -52,17 +77,6 @@ Optical heart rate sensor is a rechargeable device that measures user’s heart * [gatt specification](technical_documentation/Polar_Measurement_Data_Specification.pdf) contains gatt specification for polar measurement data streaming * [H10 ecg technical document](technical_documentation/H10_ECG_Explained.docx) -### Android proguard-rules -``` --dontwarn rx.internal.util.** --dontwarn com.google.protobuf.** --keep class fi.polar.remote.representation.protobuf.** {public private protected *;} --keep class protocol.** {public private protected *;} --keep class data.** {public private protected *;} --keep class com.androidcommunications.polar.api.ble.model.** {public private protected *;} --keep class com.androidcommunications.polar.enpoints.ble.bluedroid.host.** -``` - # Android: Getting started Detailed documentation [Full Documentation](polar-sdk-android/docs/html/). ## Installation @@ -106,6 +120,16 @@ dependencies { ``` +## Android proguard-rules +``` +-dontwarn rx.internal.util.** +-dontwarn com.google.protobuf.** +-keep class fi.polar.remote.representation.protobuf.** {public private protected *;} +-keep class protocol.** {public private protected *;} +-keep class data.** {public private protected *;} +-keep class com.androidcommunications.polar.api.ble.model.** {public private protected *;} +-keep class com.androidcommunications.polar.enpoints.ble.bluedroid.host.** +``` ## Code example: Heart rate See the [example](examples/example-android) folder for the full project. @@ -116,34 +140,33 @@ This is not required if you are using automatic connection. 1. Import following packages. ``` -import io.reactivex.CompletableObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.functions.Function; + import polar.com.sdk.api.PolarBleApi; import polar.com.sdk.api.PolarBleApiCallback; import polar.com.sdk.api.PolarBleApiDefaultImpl; +import polar.com.sdk.api.errors.PolarInvalidArgument; import polar.com.sdk.api.model.PolarAccelerometerData; import polar.com.sdk.api.model.PolarDeviceInfo; import polar.com.sdk.api.model.PolarEcgData; -import polar.com.sdk.api.model.PolarExerciseData; import polar.com.sdk.api.model.PolarExerciseEntry; -import polar.com.sdk.api.model.PolarHrBroadcastData; +import polar.com.sdk.api.model.PolarGyroData; import polar.com.sdk.api.model.PolarHrData; -import polar.com.sdk.api.model.PolarOhrPPGData; +import polar.com.sdk.api.model.PolarMagnetometerData; +import polar.com.sdk.api.model.PolarOhrData; import polar.com.sdk.api.model.PolarOhrPPIData; import polar.com.sdk.api.model.PolarSensorSetting; ``` 2. Load the default api implementation and add callback. ``` -// NOTICE only FEATURE_HR is enabled, to enable more features like battery info +// NOTICE all features are enabled, if only interested on particular feature(s) like info Heart rate and Battery info then // e.g. PolarBleApiDefaultImpl.defaultImplementation(this, PolarBleApi.FEATURE_HR | // PolarBleApi.FEATURE_BATTERY_INFO); // batteryLevelReceived callback is invoked after connection -PolarBleApi api = PolarBleApiDefaultImpl.defaultImplementation(this, PolarBleApi.FEATURE_HR); +PolarBleApi api = PolarBleApiDefaultImpl.defaultImplementation(this, PolarBleApi.ALL_FEATURES); api.setApiCallback(new PolarBleApiCallback() { @Override @@ -152,39 +175,27 @@ api.setApiCallback(new PolarBleApiCallback() { } @Override - public void polarDeviceConnected(PolarDeviceInfo polarDeviceInfo) { + public void deviceConnected(PolarDeviceInfo polarDeviceInfo) { Log.d("MyApp","CONNECTED: " + polarDeviceInfo.deviceId); } @Override - public void polarDeviceConnecting(PolarDeviceInfo polarDeviceInfo) { + public void deviceConnecting(PolarDeviceInfo polarDeviceInfo) { Log.d("MyApp","CONNECTING: " + polarDeviceInfo.deviceId); } @Override - public void polarDeviceDisconnected(PolarDeviceInfo polarDeviceInfo) { + public void deviceDisconnected(PolarDeviceInfo polarDeviceInfo) { Log.d("MyApp","DISCONNECTED: " + polarDeviceInfo.deviceId); } @Override - public void ecgFeatureReady(String identifier) { - } - - @Override - public void accelerometerFeatureReady(String identifier) { - } - - @Override - public void ppgFeatureReady(String identifier) { - } - - @Override - public void ppiFeatureReady(String identifier) { - } - - @Override - public void biozFeatureReady(String identifier) { - } + public void streamingFeaturesReady(@NonNull final String identifier, + @NonNull final Set features) { + for(PolarBleApi.DeviceStreamingFeature feature : features) { + Log.d("MyApp", "Streaming feature " + feature.toString() + " is ready"); + } + } @Override public void hrFeatureReady(String identifier) { @@ -192,7 +203,7 @@ api.setApiCallback(new PolarBleApiCallback() { } @Override - public void fwInformationReceived(String identifier, String fwVersion) { + public void disInformationReceived(String identifier, UUID uuid, String value) { } @Override @@ -317,13 +328,10 @@ class MyController: UIViewController, print("HR READY") } - func ecgFeatureReady(_ identifier: String) { - } - - func accFeatureReady(_ identifier: String) { - } - - func ohrPPGFeatureReady(_ identifier: String) { + func streamingFeaturesReady(_ identifier: String, streamingFeatures: Set) { + for feature in streamingFeatures { + print("Feature \(feature) is ready.") + } } func blePowerOn() { @@ -333,10 +341,7 @@ class MyController: UIViewController, func blePowerOff() { print("BLE OFF") } - - func ohrPPIFeatureReady(_ identifier: String) { - } - + func ftpFeatureReady(_ identifier: String) { } } diff --git a/demos/Android-Demos/PolarSDK-ECG-HR-Demo/build.gradle b/demos/Android-Demos/PolarSDK-ECG-HR-Demo/build.gradle index fb8c2e23..d9f7d2f2 100644 --- a/demos/Android-Demos/PolarSDK-ECG-HR-Demo/build.gradle +++ b/demos/Android-Demos/PolarSDK-ECG-HR-Demo/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' } } diff --git a/examples/example-android/androidBleSdkTestApp/app/build.gradle b/examples/example-android/androidBleSdkTestApp/app/build.gradle index ba39bf85..e54c72f6 100644 --- a/examples/example-android/androidBleSdkTestApp/app/build.gradle +++ b/examples/example-android/androidBleSdkTestApp/app/build.gradle @@ -30,10 +30,10 @@ android { dependencies { implementation files('libs/polar-ble-sdk.aar') implementation files('libs/polar-protobuf-release.aar') - implementation 'com.google.protobuf:protobuf-java:3.1.0' + implementation 'com.google.protobuf:protobuf-java:3.14.0' implementation 'io.reactivex.rxjava3:rxjava:3.0.0' - implementation 'commons-io:commons-io:2.8.0' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' + implementation 'commons-io:commons-io:2.8.0' implementation 'androidx.appcompat:appcompat:1.2.0' testImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2' diff --git a/examples/example-android/androidBleSdkTestApp/app/src/main/java/polar/com/androidblesdk/MainActivity.java b/examples/example-android/androidBleSdkTestApp/app/src/main/java/polar/com/androidblesdk/MainActivity.java index 5cfa51a3..7bf7e496 100644 --- a/examples/example-android/androidBleSdkTestApp/app/src/main/java/polar/com/androidblesdk/MainActivity.java +++ b/examples/example-android/androidBleSdkTestApp/app/src/main/java/polar/com/androidblesdk/MainActivity.java @@ -3,17 +3,17 @@ import android.Manifest; import android.os.Build; import android.os.Bundle; +import android.util.Log; +import android.widget.Button; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; -import android.widget.Button; - import org.reactivestreams.Publisher; import java.util.Calendar; import java.util.Date; +import java.util.Set; import java.util.UUID; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -27,11 +27,15 @@ import polar.com.sdk.api.model.PolarDeviceInfo; import polar.com.sdk.api.model.PolarEcgData; import polar.com.sdk.api.model.PolarExerciseEntry; +import polar.com.sdk.api.model.PolarGyroData; import polar.com.sdk.api.model.PolarHrData; -import polar.com.sdk.api.model.PolarOhrPPGData; +import polar.com.sdk.api.model.PolarMagnetometerData; +import polar.com.sdk.api.model.PolarOhrData; import polar.com.sdk.api.model.PolarOhrPPIData; import polar.com.sdk.api.model.PolarSensorSetting; +import static polar.com.sdk.api.model.PolarOhrData.OHR_DATA_TYPE.PPG3_AMBIENT1; + public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private static final String API_LOGGER_TAG = "API LOGGER"; @@ -39,11 +43,13 @@ public class MainActivity extends AppCompatActivity { Disposable broadcastDisposable; Disposable ecgDisposable; Disposable accDisposable; + Disposable gyrDisposable; + Disposable magDisposable; Disposable ppgDisposable; Disposable ppiDisposable; Disposable scanDisposable; - String DEVICE_ID = "0A34442D"; // or bt address like F5:A7:B8:EF:7A:D1 // TODO replace with your device id Disposable autoConnectDisposable; + String DEVICE_ID = "8C4CAD2D"; //TODO replace with your device id PolarExerciseEntry exerciseEntry; @Override @@ -60,6 +66,8 @@ protected void onCreate(Bundle savedInstanceState) { final Button autoConnect = this.findViewById(R.id.auto_connect_button); final Button ecg = this.findViewById(R.id.ecg_button); final Button acc = this.findViewById(R.id.acc_button); + final Button gyr = this.findViewById(R.id.gyr_button); + final Button mag = this.findViewById(R.id.mag_button); final Button ppg = this.findViewById(R.id.ohr_ppg_button); final Button ppi = this.findViewById(R.id.ohr_ppi_button); final Button scan = this.findViewById(R.id.scan_button); @@ -68,7 +76,7 @@ protected void onCreate(Bundle savedInstanceState) { final Button remove = this.findViewById(R.id.remove_exercise); final Button startH10Recording = this.findViewById(R.id.start_h10_recording); final Button stopH10Recording = this.findViewById(R.id.stop_h10_recording); - final Button H10RecordingStatus = this.findViewById(R.id.h10_recording_status); + final Button readH10RecordingStatus = this.findViewById(R.id.h10_recording_status); final Button setTime = this.findViewById(R.id.set_time); api.setApiLogger(s -> Log.d(API_LOGGER_TAG, s)); @@ -98,38 +106,18 @@ public void deviceDisconnected(@NonNull PolarDeviceInfo polarDeviceInfo) { Log.d(TAG, "DISCONNECTED: " + polarDeviceInfo.deviceId); ecgDisposable = null; accDisposable = null; + gyrDisposable = null; + magDisposable = null; ppgDisposable = null; ppiDisposable = null; } @Override - public void ecgFeatureReady(@NonNull String identifier) { - Log.d(TAG, "ECG READY: " + identifier); - // ecg streaming can be started now if needed - } - - @Override - public void accelerometerFeatureReady(@NonNull String identifier) { - Log.d(TAG, "ACC READY: " + identifier); - // acc streaming can be started now if needed - } - - @Override - public void ppgFeatureReady(@NonNull String identifier) { - Log.d(TAG, "PPG READY: " + identifier); - // ohr ppg can be started - } - - @Override - public void ppiFeatureReady(@NonNull String identifier) { - Log.d(TAG, "PPI READY: " + identifier); - // ohr ppi can be started - } - - @Override - public void biozFeatureReady(@NonNull String identifier) { - Log.d(TAG, "BIOZ READY: " + identifier); - // ohr ppi can be started + public void streamingFeaturesReady(@NonNull final String identifier, + @NonNull final Set features) { + for(PolarBleApi.DeviceStreamingFeature feature : features) { + Log.d(TAG, "Streaming feature " + feature.toString() + " is ready"); + } } @Override @@ -141,19 +129,17 @@ public void hrFeatureReady(@NonNull String identifier) { @Override public void disInformationReceived(@NonNull String identifier, @NonNull UUID uuid, @NonNull String value) { Log.d(TAG, "uuid: " + uuid + " value: " + value); - } @Override public void batteryLevelReceived(@NonNull String identifier, int level) { Log.d(TAG, "BATTERY LEVEL: " + level); - } @Override public void hrNotificationReceived(@NonNull String identifier, @NonNull PolarHrData data) { Log.d(TAG, "HR value: " + data.hr + " rrsMs: " + data.rrsMs + " rr: " + data.rrs + " contact: " + data.contactStatus + "," + data.contactStatusSupported); - } + } @Override public void polarFtpFeatureReady(@NonNull String s) { @@ -161,43 +147,16 @@ public void polarFtpFeatureReady(@NonNull String s) { } }); - list.setOnClickListener(v -> api.listExercises(DEVICE_ID).observeOn(AndroidSchedulers.mainThread()).subscribe( - polarExerciseEntry -> { - Log.d(TAG, "next: " + polarExerciseEntry.date + " path: " + polarExerciseEntry.path + " id: " + polarExerciseEntry.identifier); - exerciseEntry = polarExerciseEntry; - }, - throwable -> Log.e(TAG, "fetch exercises failed: " + throwable.getLocalizedMessage()), - () -> Log.d(TAG, "complete") - )); - - read.setOnClickListener(v -> { - if (exerciseEntry != null) { - api.fetchExercise(DEVICE_ID, exerciseEntry).observeOn(AndroidSchedulers.mainThread()).subscribe( - polarExerciseData -> Log.d(TAG, "exercise data count: " + polarExerciseData.hrSamples.size() + " samples: " + polarExerciseData.hrSamples), - throwable -> Log.e(TAG, "Failed to read exercise: " + throwable.getLocalizedMessage()) - ); - } - }); - - remove.setOnClickListener(v -> { - if (exerciseEntry != null) { - api.removeExercise(DEVICE_ID, exerciseEntry).observeOn(AndroidSchedulers.mainThread()).subscribe( - () -> Log.d(TAG, "ex removed ok"), - throwable -> Log.d(TAG, "ex remove failed: " + throwable.getLocalizedMessage()) - ); - } - }); - broadcast.setOnClickListener(v -> { if (broadcastDisposable == null) { - broadcastDisposable = api.startListenForPolarHrBroadcasts(null).subscribe( - polarBroadcastData -> Log.d(TAG, "HR BROADCAST " + - polarBroadcastData.polarDeviceInfo.deviceId + " HR: " + - polarBroadcastData.hr + " batt: " + - polarBroadcastData.batteryStatus), - throwable -> Log.e(TAG, "" + throwable.getLocalizedMessage()), - () -> Log.d(TAG, "complete") - ); + broadcastDisposable = api.startListenForPolarHrBroadcasts(null) + .subscribe(polarBroadcastData -> Log.d(TAG, "HR BROADCAST " + + polarBroadcastData.polarDeviceInfo.deviceId + " HR: " + + polarBroadcastData.hr + " batt: " + + polarBroadcastData.batteryStatus), + error -> Log.e(TAG, "Broadcast listener failed. Reason " + error), + () -> Log.d(TAG, "complete") + ); } else { broadcastDisposable.dispose(); broadcastDisposable = null; @@ -225,15 +184,16 @@ public void polarFtpFeatureReady(@NonNull String s) { autoConnectDisposable.dispose(); autoConnectDisposable = null; } - autoConnectDisposable = api.autoConnectToDevice(-50, "180D", null).subscribe( - () -> Log.d(TAG, "auto connect search complete"), - throwable -> Log.e(TAG, "" + throwable.toString()) - ); + autoConnectDisposable = api.autoConnectToDevice(-50, "180D", null) + .subscribe( + () -> Log.d(TAG, "auto connect search complete"), + throwable -> Log.e(TAG, "" + throwable.toString()) + ); }); ecg.setOnClickListener(v -> { if (ecgDisposable == null) { - ecgDisposable = api.requestEcgSettings(DEVICE_ID) + ecgDisposable = api.requestStreamSettings(DEVICE_ID, PolarBleApi.DeviceStreamingFeature.ECG) .toFlowable() .flatMap((Function>) polarEcgSettings -> { PolarSensorSetting sensorSetting = polarEcgSettings.maxSettings(); @@ -244,7 +204,7 @@ public void polarFtpFeatureReady(@NonNull String s) { Log.d(TAG, " yV: " + microVolts); } }, - throwable -> Log.e(TAG, "" + throwable.toString()), + throwable -> Log.e(TAG, "" + throwable), () -> Log.d(TAG, "complete") ); } else { @@ -256,7 +216,7 @@ public void polarFtpFeatureReady(@NonNull String s) { acc.setOnClickListener(v -> { if (accDisposable == null) { - accDisposable = api.requestAccSettings(DEVICE_ID) + accDisposable = api.requestStreamSettings(DEVICE_ID, PolarBleApi.DeviceStreamingFeature.ACC) .toFlowable() .flatMap((Function>) settings -> { PolarSensorSetting sensorSetting = settings.maxSettings(); @@ -264,11 +224,11 @@ public void polarFtpFeatureReady(@NonNull String s) { }).observeOn(AndroidSchedulers.mainThread()) .subscribe( polarAccelerometerData -> { - for (PolarAccelerometerData.PolarAccelerometerSample data : polarAccelerometerData.samples) { + for (PolarAccelerometerData.PolarAccelerometerDataSample data : polarAccelerometerData.samples) { Log.d(TAG, " x: " + data.x + " y: " + data.y + " z: " + data.z); } }, - throwable -> Log.e(TAG, "" + throwable.getLocalizedMessage()), + throwable -> Log.e(TAG, "" + throwable), () -> Log.d(TAG, "complete") ); } else { @@ -278,15 +238,70 @@ public void polarFtpFeatureReady(@NonNull String s) { } }); + gyr.setOnClickListener(v -> { + if (gyrDisposable == null) { + gyrDisposable = api.requestStreamSettings(DEVICE_ID, PolarBleApi.DeviceStreamingFeature.GYRO) + .toFlowable() + .flatMap((Function>) settings -> { + PolarSensorSetting sensorSetting = settings.maxSettings(); + return api.startGyroStreaming(DEVICE_ID, sensorSetting); + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + polarGyroData -> { + for (PolarGyroData.PolarGyroDataSample data : polarGyroData.samples) { + Log.d(TAG, " x: " + data.x + " y: " + data.y + " z: " + data.z); + } + }, + throwable -> Log.e(TAG, "" + throwable), + () -> Log.d(TAG, "complete") + ); + } else { + // NOTE dispose will stop streaming if it is "running" + gyrDisposable.dispose(); + gyrDisposable = null; + } + }); + + mag.setOnClickListener(v -> { + if (magDisposable == null) { + magDisposable = api.requestStreamSettings(DEVICE_ID, PolarBleApi.DeviceStreamingFeature.MAGNETOMETER) + .toFlowable() + .flatMap((Function>) settings -> { + PolarSensorSetting sensorSetting = settings.maxSettings(); + return api.startMagnetometerStreaming(DEVICE_ID, sensorSetting); + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + polarMagData -> { + for (PolarMagnetometerData.PolarMagnetometerDataSample data : polarMagData.samples) { + Log.d(TAG, " x: " + data.x + " y: " + data.y + " z: " + data.z); + } + }, + throwable -> Log.e(TAG, "" + throwable), + () -> Log.d(TAG, "complete") + ); + } else { + // NOTE dispose will stop streaming if it is "running" + magDisposable.dispose(); + magDisposable = null; + } + }); + ppg.setOnClickListener(v -> { if (ppgDisposable == null) { - ppgDisposable = api.requestPpgSettings(DEVICE_ID) + ppgDisposable = api.requestStreamSettings(DEVICE_ID, PolarBleApi.DeviceStreamingFeature.PPG) .toFlowable() - .flatMap((Function>) polarPPGSettings -> api.startOhrPPGStreaming(DEVICE_ID, polarPPGSettings.maxSettings())) + .flatMap((Function>) polarPPGSettings -> api.startOhrStreaming(DEVICE_ID, polarPPGSettings.maxSettings())) .subscribe( polarOhrPPGData -> { - for (PolarOhrPPGData.PolarOhrPPGSample data : polarOhrPPGData.samples) { - Log.d(TAG, " ppg0: " + data.ppg0 + " ppg1: " + data.ppg1 + " ppg2: " + data.ppg2 + "ambient: " + data.ambient); + if (polarOhrPPGData.type == PPG3_AMBIENT1) { + for (PolarOhrData.PolarOhrSample data : polarOhrPPGData.samples) { + Log.d(TAG, " ppg0: " + data.channelSamples.get(0) + + " ppg1: " + data.channelSamples.get(0) + + " ppg2: " + data.channelSamples.get(0) + + " ambient: " + data.channelSamples.get(0)); + } } }, throwable -> Log.e(TAG, "" + throwable.getLocalizedMessage()), @@ -305,7 +320,7 @@ public void polarFtpFeatureReady(@NonNull String s) { .subscribe( ppiData -> { for (PolarOhrPPIData.PolarOhrPPISample sample : ppiData.samples) { - Log.d(TAG, "ppi: " + sample.ppi + Log.d(TAG, " ppi: " + sample.ppi + " blocker: " + sample.blockerBit + " errorEstimate: " + sample.errorEstimate); } }, @@ -333,31 +348,71 @@ public void polarFtpFeatureReady(@NonNull String s) { } }); - startH10Recording.setOnClickListener(view -> api.startRecording(DEVICE_ID, "TEST_APP_ID", PolarBleApi.RecordingInterval.INTERVAL_1S, PolarBleApi.SampleType.HR).subscribe( - () -> Log.d(TAG, "recording started"), - throwable -> Log.e(TAG, "recording start failed: " + throwable.getLocalizedMessage()) - )); + list.setOnClickListener(v -> api.listExercises(DEVICE_ID) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + polarExerciseEntry -> { + Log.d(TAG, "next: " + polarExerciseEntry.date + " path: " + polarExerciseEntry.path + " id: " + polarExerciseEntry.identifier); + exerciseEntry = polarExerciseEntry; + }, + throwable -> Log.e(TAG, "Failed to fetch exercises: " + throwable), + () -> Log.d(TAG, "fetch exercises complete") + )); - stopH10Recording.setOnClickListener(view -> api.stopRecording(DEVICE_ID).subscribe( - () -> Log.d(TAG, "recording stopped"), - throwable -> Log.e(TAG, "recording stop failed: " + throwable.getLocalizedMessage()) - )); + read.setOnClickListener(v -> { + if (exerciseEntry != null) { + api.fetchExercise(DEVICE_ID, exerciseEntry) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + polarExerciseData -> Log.d(TAG, "exercise data count: " + polarExerciseData.hrSamples.size() + " samples: " + polarExerciseData.hrSamples), + throwable -> Log.e(TAG, "Failed to read exercise: " + throwable.getLocalizedMessage()) + ); + } + }); - H10RecordingStatus.setOnClickListener(view -> api.requestRecordingStatus(DEVICE_ID).subscribe( - pair -> Log.d(TAG, "recording on: " + pair.first + " ID: " + pair.second), - throwable -> Log.e(TAG, "recording status failed: " + throwable.getLocalizedMessage()) - )); + remove.setOnClickListener(v -> { + if (exerciseEntry != null) { + api.removeExercise(DEVICE_ID, exerciseEntry) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + () -> Log.d(TAG, "ex removed ok"), + throwable -> Log.d(TAG, "ex remove failed: " + throwable.getLocalizedMessage()) + ); + } + }); + + startH10Recording.setOnClickListener(view -> + api.startRecording(DEVICE_ID, "TEST_APP_ID", PolarBleApi.RecordingInterval.INTERVAL_1S, PolarBleApi.SampleType.HR) + .subscribe( + () -> Log.d(TAG, "recording started"), + throwable -> Log.e(TAG, "recording start failed: " + throwable.getLocalizedMessage()) + )); + + stopH10Recording.setOnClickListener(view -> + api.stopRecording(DEVICE_ID) + .subscribe( + () -> Log.d(TAG, "recording stopped"), + throwable -> Log.e(TAG, "recording stop failed: " + throwable.getLocalizedMessage()) + )); + + readH10RecordingStatus.setOnClickListener(view -> + api.requestRecordingStatus(DEVICE_ID) + .subscribe( + pair -> Log.d(TAG, "recording on: " + pair.first + " ID: " + pair.second), + throwable -> Log.e(TAG, "recording status failed: " + throwable.getLocalizedMessage()) + )); setTime.setOnClickListener(v -> { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); - api.setLocalTime(DEVICE_ID, calendar).subscribe( - () -> Log.d(TAG, "time set to device"), - throwable -> Log.d(TAG, "set time failed: " + throwable.getLocalizedMessage())); + api.setLocalTime(DEVICE_ID, calendar) + .subscribe( + () -> Log.d(TAG, "time set to device"), + error -> Log.d(TAG, "set time failed: " + error)); }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) { - this.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 1); + this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1); } } diff --git a/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml b/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml index 03087e45..17b6a045 100644 --- a/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml +++ b/examples/example-android/androidBleSdkTestApp/app/src/main/res/layout/activity_main.xml @@ -46,6 +46,18 @@ android:layout_height="wrap_content" android:text="acc_stream" /> + - - - + - - - - - + - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + - diff --git a/examples/example-ios/polarBleSdkTestApp/ViewController.swift b/examples/example-ios/polarBleSdkTestApp/ViewController.swift index f575211f..b7e79e60 100644 --- a/examples/example-ios/polarBleSdkTestApp/ViewController.swift +++ b/examples/example-ios/polarBleSdkTestApp/ViewController.swift @@ -18,12 +18,14 @@ class ViewController: UIViewController, var broadcast: Disposable? var ecgToggle: Disposable? var accToggle: Disposable? + var gyroToggle: Disposable? + var magnetometerToggle: Disposable? var ppgToggle: Disposable? var ppiToggle: Disposable? var searchToggle: Disposable? var autoConnect: Disposable? - var entry: PolarExerciseEntry? - var deviceId = "4F443C26" // TODO replace this with your device id + var exerciseEntry: PolarExerciseEntry? + var deviceId = "89647C2E" //TODO replace this with your device id override func viewDidLoad() { super.viewDidLoad() @@ -43,21 +45,6 @@ class ViewController: UIViewController, // Dispose of any resources that can be recreated. } - @IBAction func autoConnect(_ sender: Any) { - autoConnect?.dispose() - autoConnect = api.startAutoConnectToDevice(-55, service: nil, polarDeviceType: nil) - .subscribe{ e in - switch e { - case .completed: - NSLog("auto connect search complete") - case .error(let err): - NSLog("auto connect failed: \(err)") - @unknown default: - fatalError() - } - } - } - @IBAction func broadcastToggle(_ sender: Any) { if broadcast == nil { broadcast = api.startListenForPolarHrBroadcasts(nil) @@ -94,16 +81,57 @@ class ViewController: UIViewController, } } + @IBAction func autoConnect(_ sender: Any) { + autoConnect?.dispose() + autoConnect = api.startAutoConnectToDevice(-55, service: nil, polarDeviceType: nil) + .subscribe{ e in + switch e { + case .completed: + NSLog("auto connect search complete") + case .error(let err): + NSLog("auto connect failed: \(err)") + @unknown default: + fatalError() + } + } + } + + @IBAction func ecgToggle(_ sender: Any) { + if ecgToggle == nil { + ecgToggle = api.requestStreamSettings(deviceId, feature: DeviceStreamingFeature.ecg) + .asObservable() + .flatMap({ (settings) -> Observable in + return self.api.startEcgStreaming(self.deviceId, settings: settings.maxSettings()) + }) + .observe(on: MainScheduler.instance) + .subscribe{ e in + switch e { + case .next(let data): + for µv in data.samples { + NSLog(" µV: \(µv)") + } + case .error(let err): + NSLog("ECG error: \(err)") + self.ecgToggle = nil + case .completed: + break + } + } + } else { + ecgToggle?.dispose() + ecgToggle = nil + } + } + @IBAction func accToggle(_ sender: Any) { if accToggle == nil { - accToggle = api.requestAccSettings(deviceId) + accToggle = api.requestStreamSettings(deviceId, feature: DeviceStreamingFeature.acc) .asObservable() .flatMap({ (settings) -> Observable in NSLog("settings: \(settings.settings)") return self.api.startAccStreaming(self.deviceId, settings: settings.maxSettings()) }) - .observe(on: MainScheduler.instance) - .subscribe{ e in + .observe(on: MainScheduler.instance).subscribe{ e in switch e { case .next(let data): for item in data.samples { @@ -122,69 +150,81 @@ class ViewController: UIViewController, } } - @IBAction func ecgToggle(_ sender: Any) { - if ecgToggle == nil { - ecgToggle = api.requestEcgSettings(deviceId) + @IBAction func gyroToggle(_ sender: Any) { + if gyroToggle == nil { + gyroToggle = api.requestStreamSettings(deviceId, feature:DeviceStreamingFeature.gyro) .asObservable() - .flatMap({ (settings) -> Observable in - return self.api.startEcgStreaming(self.deviceId, settings: settings.maxSettings()) + .flatMap({ (settings) -> Observable in + return self.api.startGyroStreaming(self.deviceId, settings: settings.maxSettings()) }) .observe(on: MainScheduler.instance) .subscribe{ e in switch e { case .next(let data): - for µv in data.samples { - NSLog(" µV: \(µv)") + for item in data.samples { + NSLog(" x: \(item.x) y: \(item.y) z: \(item.z)") } case .error(let err): - NSLog("start ecg error: \(err)") - self.ecgToggle = nil + NSLog("Gyro error: \(err)") + self.gyroToggle = nil case .completed: break } } } else { - ecgToggle?.dispose() - ecgToggle = nil + gyroToggle?.dispose() + gyroToggle = nil } } - @IBAction func listFilesToggle(_ sender: Any) { - _ = api.fetchStoredExerciseList(deviceId) - .observe(on: MainScheduler.instance) - .subscribe{ e in - switch e { - case .completed: - NSLog("list files done") - case .error(let err): - NSLog("list files: \(err)") - case .next(let e): - NSLog("entry: \(e.date.description) id: \(e.entryId)") - self.entry = e + @IBAction func magnetometerToggle(_ sender: Any) { + if magnetometerToggle == nil { + magnetometerToggle = api.requestStreamSettings(deviceId, feature:DeviceStreamingFeature.magnetometer) + .asObservable() + .flatMap({ (settings) -> Observable in + return self.api.startMagnetometerStreaming(self.deviceId, settings: settings.maxSettings()) + }) + .observe(on: MainScheduler.instance) + .subscribe{ e in + switch e { + case .next(let data): + for item in data.samples { + NSLog(" x: \(item.x) y: \(item.y) z: \(item.z)") + } + case .error(let err): + NSLog("Magnetometer error: \(err)") + self.magnetometerToggle = nil + case .completed: + break + } } - } - + } else { + magnetometerToggle?.dispose() + magnetometerToggle = nil + } } @IBAction func ppgToggle(_ sender: Any) { if ppgToggle == nil { - ppgToggle = api.requestPpgSettings(deviceId) + ppgToggle = api.requestStreamSettings(deviceId, feature: DeviceStreamingFeature.ppg) .asObservable() - .flatMap({ (settings) -> Observable in - return self.api.startOhrPPGStreaming(self.deviceId, settings: settings.maxSettings()) + .flatMap({ (settings) -> Observable in + return self.api.startOhrStreaming(self.deviceId, settings: settings.maxSettings()) }) .observe(on: MainScheduler.instance) .subscribe{ e in switch e { - case .completed: - NSLog("ppg finished") - case .error(let err): - NSLog("start ppg error: \(err)") - self.ppgToggle = nil case .next(let data): - for item in data.samples { - NSLog(" ppg0: \(item.ppg0) ppg1: \(item.ppg1) ppg2: \(item.ppg2)") + if(data.type == OhrDataType.ppg3_ambient1) { + for item in data.samples { + NSLog(" ppg0: \(item[0]) ppg1: \(item[1]) ppg2: \(item[2]) ambient: \(item[3])") + } } + case .error(let err): + NSLog("PPG error: \(err)") + self.ppgToggle = nil + case .completed: + NSLog("ppg complete") } } } else { @@ -199,15 +239,15 @@ class ViewController: UIViewController, .observe(on: MainScheduler.instance) .subscribe{ e in switch e { - case .completed: - NSLog("ppi complete") - case .error(let err): - NSLog("start ppi error: \(err)") - self.ppiToggle = nil case .next(let data): for item in data.samples { - NSLog("PPI: \(item.ppInMs)") + NSLog(" PPI: \(item.ppInMs) sample.blockerBit: \(item.blockerBit) errorEstimate: \(item.ppErrorEstimate)") } + case .error(let err): + NSLog("PPI error: \(err)") + self.ppiToggle = nil + case .completed: + NSLog("ppi complete") } } } else { @@ -236,8 +276,24 @@ class ViewController: UIViewController, } } + @IBAction func listExercises(_ sender: Any) { + _ = api.fetchStoredExerciseList(deviceId) + .observe(on: MainScheduler.instance) + .subscribe{ e in + switch e { + case .completed: + NSLog("list exercises done") + case .error(let err): + NSLog("failed to list exercises: \(err)") + case .next(let polarExerciseEntry): + NSLog("entry: \(polarExerciseEntry.date.description) path: \(polarExerciseEntry.path) id: \(polarExerciseEntry.entryId)"); + self.exerciseEntry = polarExerciseEntry + } + } + } + @IBAction func readExercise(_ sender: Any) { - guard let e = entry else { + guard let e = exerciseEntry else { return } _ = api.fetchExercise(deviceId, entry: e) @@ -245,15 +301,15 @@ class ViewController: UIViewController, .subscribe{ e in switch e { case .failure(let err): - NSLog("read ex error: \(err)") + NSLog("failed to read exercises: \(err)") case .success(let data): - NSLog("\(data.samples)") + NSLog("exercise data count: \(data.samples.count) samples: \(data.samples)") } } } @IBAction func removeExercise(_ sender: Any) { - guard let e = entry else { + guard let e = exerciseEntry else { return } _ = api.removeExercise(deviceId, entry: e) @@ -313,6 +369,23 @@ class ViewController: UIViewController, } } + @IBAction func setTime(_ sender: Any) { + let time = Date() + let timeZone = TimeZone.current + _ = api.setLocalTime(deviceId, time: time, zone: timeZone) + .observe(on: MainScheduler.instance) + .subscribe{ e in + switch e { + case .completed: + NSLog("time set to device") + case .error(let err): + NSLog("set time failed: \(err)") + @unknown default: + fatalError() + } + } + } + // PolarBleApiObserver func deviceConnecting(_ polarDeviceInfo: PolarDeviceInfo) { NSLog("DEVICE CONNECTING: \(polarDeviceInfo)") @@ -338,25 +411,17 @@ class ViewController: UIViewController, // PolarBleApiDeviceHrObserver func hrValueReceived(_ identifier: String, data: PolarHrData) { - NSLog("(\(identifier)) HR notification: \(data.hr) rrs: \(data.rrs) rrsMs: \(data.rrsMs) c: \(data.contact) s: \(data.contactSupported)") + NSLog("(\(identifier)) HR notification: \(data.hr) rrs: \(data.rrs) rrsMs: \(data.rrsMs) contact: \(data.contact) contact supported: \(data.contactSupported)") } func hrFeatureReady(_ identifier: String) { NSLog("HR READY") } - // PolarBleApiDeviceEcgObserver - func ecgFeatureReady(_ identifier: String) { - NSLog("ECG READY \(identifier)") - } - - // PolarBleApiDeviceAccelerometerObserver - func accFeatureReady(_ identifier: String) { - NSLog("ACC READY") - } - - func ohrPPGFeatureReady(_ identifier: String) { - NSLog("OHR PPG ready") + func streamingFeaturesReady(_ identifier: String, streamingFeatures: Set) { + for feature in streamingFeatures { + NSLog("Feature \(feature) is ready.") + } } // PolarBleApiPowerStateObserver @@ -378,7 +443,7 @@ class ViewController: UIViewController, } func message(_ str: String) { - NSLog(str) + NSLog("Polar SDK log: \(str)") } /// ccc write observer diff --git a/polar-sdk-ios/PolarBleSdk.xcframework/Info.plist b/polar-sdk-ios/PolarBleSdk.xcframework/Info.plist index 5761dcf7..5f91df25 100644 --- a/polar-sdk-ios/PolarBleSdk.xcframework/Info.plist +++ b/polar-sdk-ios/PolarBleSdk.xcframework/Info.plist @@ -6,59 +6,59 @@ LibraryIdentifier - watchos-arm64_i386_x86_64-simulator + ios-arm64_x86_64-simulator LibraryPath - PolarBleSdkWatchOs.framework + PolarBleSdk.framework SupportedArchitectures arm64 - i386 x86_64 SupportedPlatform - watchos + ios SupportedPlatformVariant simulator LibraryIdentifier - ios-arm64_x86_64-simulator + watchos-arm64_i386_x86_64-simulator LibraryPath - PolarBleSdk.framework + PolarBleSdkWatchOs.framework SupportedArchitectures arm64 + i386 x86_64 SupportedPlatform - ios + watchos SupportedPlatformVariant simulator LibraryIdentifier - watchos-arm64_32_armv7k + ios-arm64 LibraryPath - PolarBleSdkWatchOs.framework + PolarBleSdk.framework SupportedArchitectures - arm64_32 - armv7k + arm64 SupportedPlatform - watchos + ios LibraryIdentifier - ios-arm64 + watchos-arm64_32_armv7k LibraryPath - PolarBleSdk.framework + PolarBleSdkWatchOs.framework SupportedArchitectures - arm64 + arm64_32 + armv7k SupportedPlatform - ios + watchos CFBundlePackageType diff --git a/polar-sdk-ios/docs/Classes.html b/polar-sdk-ios/docs/Classes.html index 30f4d719..2f2ea4c6 100644 --- a/polar-sdk-ios/docs/Classes.html +++ b/polar-sdk-ios/docs/Classes.html @@ -17,7 +17,7 @@
-

Docs (90% documented)

+

Docs (89% documented)

@@ -76,9 +76,15 @@