From 2ccb1d8d6d0dfcd63beacb410ad3735cf8c1279e Mon Sep 17 00:00:00 2001 From: yatharthranjan Date: Thu, 5 Sep 2024 12:59:32 +0100 Subject: [PATCH 1/5] Auto connect with Polar devices --- gradle/android.gradle | 4 +- .../radarbase/passive/polar/PolarManager.kt | 133 ++++++++++++++---- .../radarbase/passive/polar/PolarProvider.kt | 14 ++ .../radarbase/passive/polar/PolarService.kt | 28 +++- 4 files changed, 147 insertions(+), 32 deletions(-) diff --git a/gradle/android.gradle b/gradle/android.gradle index 2aa7f96de..9a8ec4cc9 100644 --- a/gradle/android.gradle +++ b/gradle/android.gradle @@ -1,6 +1,6 @@ android { - compileSdkVersion 33 - buildToolsVersion '32.0.0' + compileSdk 34 + buildToolsVersion '34.0.0' defaultConfig { minSdkVersion 26 diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt index 56951d500..7341f9d23 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt @@ -14,35 +14,39 @@ import com.polar.sdk.api.model.* import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.core.Flowable -import io.reactivex.rxjava3.core.Single import org.radarbase.android.data.DataCache import org.radarbase.android.source.AbstractSourceManager import org.radarbase.android.source.SourceStatusListener import org.radarbase.android.util.SafeHandler import org.radarcns.kafka.ObservationKey import org.radarcns.passive.polar.* -import java.time.LocalDateTime -import java.time.ZoneOffset import java.util.* +import java.util.concurrent.TimeUnit class PolarManager( polarService: PolarService, private val applicationContext: Context ) : AbstractSourceManager(polarService) { - private val accelerationTopic: DataCache = createCache("android_polar_acceleration", PolarAcceleration()) - private val batteryLevelTopic: DataCache = createCache("android_polar_battery_level", PolarBatteryLevel()) - private val ecgTopic: DataCache = createCache("android_polar_ecg", PolarEcg()) - private val heartRateTopic: DataCache = createCache("android_polar_heart_rate", PolarHeartRate()) - private val ppIntervalTopic: DataCache = createCache("android_polar_pulse_to_pulse_interval", PolarPpInterval()) - private val ppgTopic: DataCache = createCache("android_polar_ppg", PolarPpg()) + private val accelerationTopic: DataCache = + createCache("android_polar_acceleration", PolarAcceleration()) + private val batteryLevelTopic: DataCache = + createCache("android_polar_battery_level", PolarBatteryLevel()) + private val ecgTopic: DataCache = + createCache("android_polar_ecg", PolarEcg()) + private val heartRateTopic: DataCache = + createCache("android_polar_heart_rate", PolarHeartRate()) + private val ppIntervalTopic: DataCache = + createCache("android_polar_pulse_to_pulse_interval", PolarPpInterval()) + private val ppgTopic: DataCache = + createCache("android_polar_ppg", PolarPpg()) private val mHandler = SafeHandler.getInstance("Polar sensors", THREAD_PRIORITY_BACKGROUND) private var wakeLock: PowerManager.WakeLock? = null private lateinit var api: PolarBleApi - // Polar Device ID example given: D733F724 - private var deviceId: String = "ReplaceMe" + + private var deviceId: String? = null private var isDeviceConnected: Boolean = false private var autoConnectDisposable: Disposable? = null @@ -102,7 +106,7 @@ class PolarManager( api.setApiCallback(object : PolarBleApiCallback() { override fun blePowerStateChanged(powered: Boolean) { Log.d(TAG, "BluetoothStateChanged $powered") - if (powered == false) { + if (!powered) { status = SourceStatusListener.Status.DISCONNECTED // red circle } else { status = SourceStatusListener.Status.READY // blue loading @@ -111,6 +115,7 @@ class PolarManager( override fun deviceConnected(polarDeviceInfo: PolarDeviceInfo) { Log.d(TAG, "Device connected ${polarDeviceInfo.deviceId}") + service.savePolarDevice(polarDeviceInfo.deviceId) deviceId = polarDeviceInfo.deviceId name = polarDeviceInfo.name @@ -132,7 +137,10 @@ class PolarManager( } - override fun bleSdkFeatureReady(identifier: String, feature: PolarBleApi.PolarBleSdkFeature) { + override fun bleSdkFeatureReady( + identifier: String, + feature: PolarBleApi.PolarBleSdkFeature + ) { if (isDeviceConnected) { Log.d(TAG, "Feature ready $feature for $deviceId") @@ -149,6 +157,7 @@ class PolarManager( streamPpi() streamPpg() } + else -> { Log.d(TAG, "No feature was ready") } @@ -163,25 +172,74 @@ class PolarManager( } override fun batteryLevelReceived(identifier: String, level: Int) { - var batteryLevel = level.toFloat() / 100.0f + val batteryLevel = level.toFloat() / 100.0f state.batteryLevel = batteryLevel Log.d(TAG, "Battery level $level%, which is $batteryLevel at " + currentTime) - send(batteryLevelTopic, PolarBatteryLevel(name, currentTime, currentTime, batteryLevel)) + send( + batteryLevelTopic, + PolarBatteryLevel(name, currentTime, currentTime, batteryLevel) + ) } }) try { - api.connectToDevice(deviceId) + deviceId = service.getPolarDevice() + if (deviceId == null) { + Log.d(TAG, "Searching for Polar devices") + connectToPolarDevice() + } else { + Log.d(TAG, "Connecting to Polar device $deviceId") + api.connectToDevice(deviceId!!) + } } catch (a: PolarInvalidArgument) { a.printStackTrace() } } + fun connectToPolarDevice() { + autoConnectToPolarSDK() + } + + fun autoConnectToPolarSDK() { + if (autoConnectDisposable != null && !autoConnectDisposable!!.isDisposed) { + autoConnectDisposable?.dispose() + } + + autoConnectDisposable = Flowable.interval(0, 5, TimeUnit.SECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + if (deviceId == null || !isDeviceConnected) { + searchPolarDevice(true) + } + }, { error: Throwable -> Log.e(TAG, "Searching auto Polar devices failed: $error") }) + } + + fun searchPolarDevice(force: Boolean = false): Disposable? { + try { + return api.searchForDevice() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ device: PolarDeviceInfo -> + Log.d(TAG, "Device found: ${device.deviceId} ${device.name}") + if (deviceId == null || force) { + api.connectToDevice(device.deviceId) + } + }, + { error: Throwable -> + Log.e(TAG, "Search for Polar devices failed: $error") + }) + } catch (a: PolarInvalidArgument) { + a.printStackTrace() + } + return null + } + fun disconnectToPolarSDK(deviceId: String?) { try { - api.disconnectFromDevice(deviceId!!) + if (deviceId != null) { + api.disconnectFromDevice(deviceId) + } api.shutDown() } catch (e: Exception) { Log.e(TAG, "Error occurred during shutdown: ${e.message}") @@ -207,8 +265,8 @@ class PolarManager( } fun getTimeNano(): Double { - var nano = (System.currentTimeMillis() * 1_000_000L).toDouble() - return nano/1000_000_000L + val nano = (System.currentTimeMillis() * 1_000_000L).toDouble() + return nano / 1000_000_000L } fun streamHR() { @@ -222,7 +280,14 @@ class PolarManager( .subscribe( { hrData: PolarHrData -> for (sample in hrData.samples) { - Log.d(TAG, "HeartRate data for ${name}, ${deviceId}: HR ${sample.hr} timeStamp: ${getTimeNano()} currentTime: ${currentTime} R ${sample.rrsMs} rrAvailable: ${sample.rrAvailable} contactStatus: ${sample.contactStatus} contactStatusSupported: ${sample.contactStatusSupported}") + Log.d( + TAG, + "HeartRate data for ${name}, ${deviceId}: HR ${sample.hr} " + + "timeStamp: ${getTimeNano()} currentTime: ${currentTime} " + + "R ${sample.rrsMs} rrAvailable: ${sample.rrAvailable} " + + "contactStatus: ${sample.contactStatus} " + + "contactStatusSupported: ${sample.contactStatusSupported}" + ) send( heartRateTopic, PolarHeartRate( @@ -267,7 +332,12 @@ class PolarManager( .subscribe( { polarEcgData: PolarEcgData -> for (data in polarEcgData.samples) { - Log.d(TAG, "ECG yV: ${data.voltage} timeStamp: ${PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp)} currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}") + Log.d( + TAG, + "ECG yV: ${data.voltage} timeStamp: ${ + PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) + } currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}" + ) send( ecgTopic, PolarEcg( @@ -304,7 +374,12 @@ class PolarManager( .subscribe( { polarAccelerometerData: PolarAccelerometerData -> for (data in polarAccelerometerData.samples) { - Log.d(TAG, "ACC x: ${data.x} y: ${data.y} z: ${data.z} timeStamp: ${PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp)} currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}") + Log.d( + TAG, + "ACC x: ${data.x} y: ${data.y} z: ${data.z} timeStamp: ${ + PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) + } currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}" + ) send( accelerationTopic, PolarAcceleration( @@ -347,7 +422,12 @@ class PolarManager( { polarPpgData: PolarPpgData -> if (polarPpgData.type == PolarPpgData.PpgDataType.PPG3_AMBIENT1) { for (data in polarPpgData.samples) { - Log.d(TAG, "PPG ppg0: ${data.channelSamples[0]} ppg1: ${data.channelSamples[1]} ppg2: ${data.channelSamples[2]} ambient: ${data.channelSamples[3]} timeStamp: ${PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp)} currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}") + Log.d( + TAG, + "PPG ppg0: ${data.channelSamples[0]} ppg1: ${data.channelSamples[1]} ppg2: ${data.channelSamples[2]} ambient: ${data.channelSamples[3]} timeStamp: ${ + PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) + } currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}" + ) send( ppgTopic, PolarPpg( @@ -383,7 +463,12 @@ class PolarManager( .subscribe( { ppiData: PolarPpiData -> for (sample in ppiData.samples) { - Log.d(TAG, "PPI ppi: ${sample.ppi} blocker: ${sample.blockerBit} errorEstimate: ${sample.errorEstimate} currentTime: ${currentTime}") + Log.d( + TAG, + "PPI ppi: ${sample.ppi} blocker: ${sample.blockerBit} " + + "errorEstimate: ${sample.errorEstimate} " + + "currentTime: ${currentTime}" + ) send( ppIntervalTopic, PolarPpInterval( diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarProvider.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarProvider.kt index ea18ac992..16b5e0061 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarProvider.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarProvider.kt @@ -1,11 +1,14 @@ package org.radarbase.passive.polar import android.Manifest +import android.content.Context import android.content.pm.PackageManager import android.os.Build import org.radarbase.android.BuildConfig import org.radarbase.android.RadarService import org.radarbase.android.source.SourceProvider +import org.radarbase.passive.polar.PolarService.Companion.SHARED_PREF_KEY +import org.radarbase.passive.polar.PolarService.Companion.SHARED_PREF_NAME open class PolarProvider(radarService: RadarService) : SourceProvider(radarService) { override val serviceClass: Class = PolarService::class.java @@ -48,6 +51,17 @@ open class PolarProvider(radarService: RadarService) : SourceProvider + get() = + super.actions.toMutableList().apply { add( + Action("Reset Polar device ID", null) { + applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .remove(SHARED_PREF_KEY) + .apply() + } + )}.toList() + override val isFilterable = true companion object { const val PRODUCER = "Polar" diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt index 7a2f04d71..4f62739db 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt @@ -1,15 +1,11 @@ package org.radarbase.passive.polar import android.content.Context -import android.hardware.Sensor import android.os.Process -import android.util.SparseIntArray import org.radarbase.android.config.SingleRadarConfiguration import org.radarbase.android.source.SourceManager import org.radarbase.android.source.SourceService import org.radarbase.android.util.SafeHandler -import org.slf4j.LoggerFactory -import java.util.concurrent.TimeUnit /** * A service that manages the Polar manager and a TableDataHandler to send store the data of @@ -17,7 +13,7 @@ import java.util.concurrent.TimeUnit */ class PolarService : SourceService() { private lateinit var handler: SafeHandler -// private lateinit var context: Context + override val defaultState: PolarState get() = PolarState() @@ -28,8 +24,28 @@ class PolarService : SourceService() { override fun createSourceManager() = PolarManager(this, applicationContext) - override fun configureSourceManager(manager: SourceManager, config: SingleRadarConfiguration) { + override fun configureSourceManager( + manager: SourceManager, + config: SingleRadarConfiguration + ) { manager as PolarManager } + + fun savePolarDevice(deviceId: String) { + applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putString(SHARED_PREF_KEY, deviceId) + .apply() + } + + fun getPolarDevice(): String? { + return applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .getString(SHARED_PREF_KEY, null) + } + + companion object { + val SHARED_PREF_NAME: String = PolarService::class.java.name + const val SHARED_PREF_KEY = "polar_device_id" + } } From cb1928dc7ee32fc26bfe447d7d7e9d8afbacef56 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Thu, 5 Sep 2024 18:48:55 +0530 Subject: [PATCH 2/5] Minor changes --- .../radarbase/passive/polar/PolarManager.kt | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt index 7341f9d23..17651740b 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt @@ -55,7 +55,6 @@ class PolarManager( private var accDisposable: Disposable? = null private var ppiDisposable: Disposable? = null private var ppgDisposable: Disposable? = null - private var timeDisposable: Disposable? = null companion object { private const val TAG = "POLAR" @@ -87,7 +86,7 @@ class PolarManager( } - fun connectToPolarSDK() { + private fun connectToPolarSDK() { Log.d(TAG, "Connecting to Polar API") api = defaultImplementation( @@ -106,10 +105,10 @@ class PolarManager( api.setApiCallback(object : PolarBleApiCallback() { override fun blePowerStateChanged(powered: Boolean) { Log.d(TAG, "BluetoothStateChanged $powered") - if (!powered) { - status = SourceStatusListener.Status.DISCONNECTED // red circle + status = if (!powered) { + SourceStatusListener.Status.DISCONNECTED // red circle } else { - status = SourceStatusListener.Status.READY // blue loading + SourceStatusListener.Status.READY // blue loading } } @@ -174,7 +173,7 @@ class PolarManager( override fun batteryLevelReceived(identifier: String, level: Int) { val batteryLevel = level.toFloat() / 100.0f state.batteryLevel = batteryLevel - Log.d(TAG, "Battery level $level%, which is $batteryLevel at " + currentTime) + Log.d(TAG, "Battery level $level%, which is $batteryLevel at $currentTime") send( batteryLevelTopic, PolarBatteryLevel(name, currentTime, currentTime, batteryLevel) @@ -198,11 +197,11 @@ class PolarManager( } - fun connectToPolarDevice() { + private fun connectToPolarDevice() { autoConnectToPolarSDK() } - fun autoConnectToPolarSDK() { + private fun autoConnectToPolarSDK() { if (autoConnectDisposable != null && !autoConnectDisposable!!.isDisposed) { autoConnectDisposable?.dispose() } @@ -216,7 +215,7 @@ class PolarManager( }, { error: Throwable -> Log.e(TAG, "Searching auto Polar devices failed: $error") }) } - fun searchPolarDevice(force: Boolean = false): Disposable? { + private fun searchPolarDevice(force: Boolean = false): Disposable? { try { return api.searchForDevice() .observeOn(AndroidSchedulers.mainThread()) @@ -235,7 +234,7 @@ class PolarManager( return null } - fun disconnectToPolarSDK(deviceId: String?) { + private fun disconnectToPolarSDK(deviceId: String?) { try { if (deviceId != null) { api.disconnectFromDevice(deviceId) @@ -264,26 +263,26 @@ class PolarManager( } } - fun getTimeNano(): Double { + private fun getTimeNano(): Double { val nano = (System.currentTimeMillis() * 1_000_000L).toDouble() return nano / 1000_000_000L } fun streamHR() { - Log.d(TAG, "start streamHR for ${deviceId}") + Log.d(TAG, "start streamHR for $deviceId") val isDisposed = hrDisposable?.isDisposed ?: true if (isDisposed) { hrDisposable = deviceId?.let { api.startHrStreaming(it) .observeOn(AndroidSchedulers.mainThread()) - .doOnSubscribe { Log.d(TAG, "Subscribed to HrStreaming for ${deviceId}") } + .doOnSubscribe { Log.d(TAG, "Subscribed to HrStreaming for $deviceId") } .subscribe( { hrData: PolarHrData -> for (sample in hrData.samples) { Log.d( TAG, "HeartRate data for ${name}, ${deviceId}: HR ${sample.hr} " + - "timeStamp: ${getTimeNano()} currentTime: ${currentTime} " + + "timeStamp: ${getTimeNano()} currentTime: $currentTime " + "R ${sample.rrsMs} rrAvailable: ${sample.rrAvailable} " + "contactStatus: ${sample.contactStatus} " + "contactStatusSupported: ${sample.contactStatusSupported}" @@ -308,7 +307,7 @@ class PolarManager( Log.e(TAG, "HR stream failed for ${deviceId}. Reason $error") hrDisposable = null }, - { Log.d(TAG, "HR stream for ${deviceId} complete") } + { Log.d(TAG, "HR stream for $deviceId complete") } ) } } else { @@ -319,7 +318,7 @@ class PolarManager( } fun streamEcg() { - Log.d(TAG, "start streamECG for ${deviceId}") + Log.d(TAG, "start streamECG for $deviceId") val isDisposed = ecgDisposable?.isDisposed ?: true if (isDisposed) { val settingMap = mapOf( @@ -336,7 +335,7 @@ class PolarManager( TAG, "ECG yV: ${data.voltage} timeStamp: ${ PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) - } currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}" + } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" ) send( ecgTopic, @@ -378,7 +377,7 @@ class PolarManager( TAG, "ACC x: ${data.x} y: ${data.y} z: ${data.z} timeStamp: ${ PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) - } currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}" + } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" ) send( accelerationTopic, @@ -407,7 +406,7 @@ class PolarManager( } fun streamPpg() { - Log.d(TAG, "start streamPpg for ${deviceId}") + Log.d(TAG, "start streamPpg for $deviceId") val isDisposed = ppgDisposable?.isDisposed ?: true if (isDisposed) { val settingMap = mapOf( @@ -426,7 +425,7 @@ class PolarManager( TAG, "PPG ppg0: ${data.channelSamples[0]} ppg1: ${data.channelSamples[1]} ppg2: ${data.channelSamples[2]} ambient: ${data.channelSamples[3]} timeStamp: ${ PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) - } currentTime: ${currentTime} PolarTimeStamp: ${data.timeStamp}" + } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" ) send( ppgTopic, @@ -467,7 +466,7 @@ class PolarManager( TAG, "PPI ppi: ${sample.ppi} blocker: ${sample.blockerBit} " + "errorEstimate: ${sample.errorEstimate} " + - "currentTime: ${currentTime}" + "currentTime: $currentTime" ) send( ppIntervalTopic, From d19cfa991c1ba25611c0aa0c5a79b18d48de40c4 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Thu, 5 Sep 2024 18:50:28 +0530 Subject: [PATCH 3/5] Utilizing appContext from service instance --- .../main/java/org/radarbase/passive/polar/PolarManager.kt | 5 +---- .../main/java/org/radarbase/passive/polar/PolarService.kt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt index 17651740b..8ad3d8e27 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt @@ -1,7 +1,6 @@ package org.radarbase.passive.polar import android.annotation.SuppressLint -import android.content.Context import android.content.Context.POWER_SERVICE import android.os.PowerManager import android.os.Process.THREAD_PRIORITY_BACKGROUND @@ -25,7 +24,6 @@ import java.util.concurrent.TimeUnit class PolarManager( polarService: PolarService, - private val applicationContext: Context ) : AbstractSourceManager(polarService) { private val accelerationTopic: DataCache = @@ -87,10 +85,9 @@ class PolarManager( } private fun connectToPolarSDK() { - Log.d(TAG, "Connecting to Polar API") api = defaultImplementation( - applicationContext, + service.applicationContext, setOf( PolarBleApi.PolarBleSdkFeature.FEATURE_HR, PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_SDK_MODE, diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt index 4f62739db..e064432ed 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt @@ -22,7 +22,7 @@ class PolarService : SourceService() { handler = SafeHandler.getInstance("Polar", Process.THREAD_PRIORITY_FOREGROUND) } - override fun createSourceManager() = PolarManager(this, applicationContext) + override fun createSourceManager() = PolarManager(this) override fun configureSourceManager( manager: SourceManager, From 47013d471bdb4a5d3af1991e0cf80901ff91e155 Mon Sep 17 00:00:00 2001 From: this-Aditya Date: Thu, 5 Sep 2024 18:57:45 +0530 Subject: [PATCH 4/5] Replacing android.util.Log with slf4j logger --- .../radarbase/passive/polar/PolarManager.kt | 98 +++++++++---------- 1 file changed, 46 insertions(+), 52 deletions(-) diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt index 8ad3d8e27..fe8387f16 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt @@ -19,6 +19,7 @@ import org.radarbase.android.source.SourceStatusListener import org.radarbase.android.util.SafeHandler import org.radarcns.kafka.ObservationKey import org.radarcns.passive.polar.* +import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.TimeUnit @@ -53,12 +54,7 @@ class PolarManager( private var accDisposable: Disposable? = null private var ppiDisposable: Disposable? = null private var ppgDisposable: Disposable? = null - - companion object { - private const val TAG = "POLAR" - - } - + init { status = SourceStatusListener.Status.DISCONNECTED // red icon name = service.getString(R.string.polarDisplayName) @@ -68,7 +64,7 @@ class PolarManager( override fun start(acceptableIds: Set) { status = SourceStatusListener.Status.READY // blue loading - Log.d(TAG, "Polar Device is $deviceId") + logger.debug("Polar Device is $deviceId") disconnectToPolarSDK(deviceId) connectToPolarSDK() @@ -85,7 +81,7 @@ class PolarManager( } private fun connectToPolarSDK() { - Log.d(TAG, "Connecting to Polar API") + logger.debug("Connecting to Polar API") api = defaultImplementation( service.applicationContext, setOf( @@ -101,7 +97,7 @@ class PolarManager( api.setApiLogger { str: String -> Log.d("P-SDK", str) } api.setApiCallback(object : PolarBleApiCallback() { override fun blePowerStateChanged(powered: Boolean) { - Log.d(TAG, "BluetoothStateChanged $powered") + logger.debug("BluetoothStateChanged $powered") status = if (!powered) { SourceStatusListener.Status.DISCONNECTED // red circle } else { @@ -110,7 +106,7 @@ class PolarManager( } override fun deviceConnected(polarDeviceInfo: PolarDeviceInfo) { - Log.d(TAG, "Device connected ${polarDeviceInfo.deviceId}") + logger.debug("Device connected ${polarDeviceInfo.deviceId}") service.savePolarDevice(polarDeviceInfo.deviceId) deviceId = polarDeviceInfo.deviceId name = polarDeviceInfo.name @@ -123,11 +119,11 @@ class PolarManager( override fun deviceConnecting(polarDeviceInfo: PolarDeviceInfo) { status = SourceStatusListener.Status.CONNECTING // green dots - Log.d(TAG, "Device connecting ${polarDeviceInfo.deviceId}") + logger.debug("Device connecting ${polarDeviceInfo.deviceId}") } override fun deviceDisconnected(polarDeviceInfo: PolarDeviceInfo) { - Log.d(TAG, "Device disconnected ${polarDeviceInfo.deviceId}") + logger.debug("Device disconnected ${polarDeviceInfo.deviceId}") isDeviceConnected = false status = SourceStatusListener.Status.DISCONNECTED // red circle @@ -139,7 +135,7 @@ class PolarManager( ) { if (isDeviceConnected) { - Log.d(TAG, "Feature ready $feature for $deviceId") + logger.debug("Feature ready {} for {}", feature, deviceId) if (feature == PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP) { setDeviceTime(deviceId) @@ -155,22 +151,22 @@ class PolarManager( } else -> { - Log.d(TAG, "No feature was ready") + logger.debug("No feature was ready") } } } else { - Log.d(TAG, "No device was connected") + logger.debug("No device was connected") } } override fun disInformationReceived(identifier: String, uuid: UUID, value: String) { - Log.d(TAG, "Firmware: " + identifier + " " + value.trim { it <= ' ' }) + logger.debug("Firmware: " + identifier + " " + value.trim { it <= ' ' }) } override fun batteryLevelReceived(identifier: String, level: Int) { val batteryLevel = level.toFloat() / 100.0f state.batteryLevel = batteryLevel - Log.d(TAG, "Battery level $level%, which is $batteryLevel at $currentTime") + logger.debug("Battery level $level%, which is $batteryLevel at $currentTime") send( batteryLevelTopic, PolarBatteryLevel(name, currentTime, currentTime, batteryLevel) @@ -182,10 +178,10 @@ class PolarManager( try { deviceId = service.getPolarDevice() if (deviceId == null) { - Log.d(TAG, "Searching for Polar devices") + logger.debug("Searching for Polar devices") connectToPolarDevice() } else { - Log.d(TAG, "Connecting to Polar device $deviceId") + logger.debug("Connecting to Polar device $deviceId") api.connectToDevice(deviceId!!) } } catch (a: PolarInvalidArgument) { @@ -209,7 +205,7 @@ class PolarManager( if (deviceId == null || !isDeviceConnected) { searchPolarDevice(true) } - }, { error: Throwable -> Log.e(TAG, "Searching auto Polar devices failed: $error") }) + }, { error: Throwable -> logger.error("Searching auto Polar devices failed: $error") }) } private fun searchPolarDevice(force: Boolean = false): Disposable? { @@ -217,13 +213,13 @@ class PolarManager( return api.searchForDevice() .observeOn(AndroidSchedulers.mainThread()) .subscribe({ device: PolarDeviceInfo -> - Log.d(TAG, "Device found: ${device.deviceId} ${device.name}") + logger.debug("Device found: ${device.deviceId} ${device.name}") if (deviceId == null || force) { api.connectToDevice(device.deviceId) } }, { error: Throwable -> - Log.e(TAG, "Search for Polar devices failed: $error") + logger.error("Search for Polar devices failed: $error") }) } catch (a: PolarInvalidArgument) { a.printStackTrace() @@ -238,7 +234,7 @@ class PolarManager( } api.shutDown() } catch (e: Exception) { - Log.e(TAG, "Error occurred during shutdown: ${e.message}") + logger.error("Error occurred during shutdown: ${e.message}") } } @@ -251,12 +247,12 @@ class PolarManager( .subscribe( { val timeSetString = "Time ${calendar.time} set to device" - Log.d(TAG, timeSetString) + logger.debug(timeSetString) }, - { error: Throwable -> Log.e(TAG, "Set time failed: $error") } + { error: Throwable -> logger.error("Set time failed: $error") } ) } ?: run { - Log.e(TAG, "Device ID is null. Cannot set device time.") + logger.error("Device ID is null. Cannot set device time.") } } @@ -266,18 +262,17 @@ class PolarManager( } fun streamHR() { - Log.d(TAG, "start streamHR for $deviceId") + logger.debug("start streamHR for $deviceId") val isDisposed = hrDisposable?.isDisposed ?: true if (isDisposed) { hrDisposable = deviceId?.let { api.startHrStreaming(it) .observeOn(AndroidSchedulers.mainThread()) - .doOnSubscribe { Log.d(TAG, "Subscribed to HrStreaming for $deviceId") } + .doOnSubscribe { logger.debug("Subscribed to HrStreaming for $deviceId") } .subscribe( { hrData: PolarHrData -> for (sample in hrData.samples) { - Log.d( - TAG, + logger.debug( "HeartRate data for ${name}, ${deviceId}: HR ${sample.hr} " + "timeStamp: ${getTimeNano()} currentTime: $currentTime " + "R ${sample.rrsMs} rrAvailable: ${sample.rrAvailable} " + @@ -301,21 +296,21 @@ class PolarManager( } }, { error: Throwable -> - Log.e(TAG, "HR stream failed for ${deviceId}. Reason $error") + logger.error("HR stream failed for ${deviceId}. Reason $error") hrDisposable = null }, - { Log.d(TAG, "HR stream for $deviceId complete") } + { logger.debug("HR stream for $deviceId complete") } ) } } else { hrDisposable?.dispose() - Log.d(TAG, "HR stream disposed") + logger.debug("HR stream disposed") hrDisposable = null } } fun streamEcg() { - Log.d(TAG, "start streamECG for $deviceId") + logger.debug("start streamECG for $deviceId") val isDisposed = ecgDisposable?.isDisposed ?: true if (isDisposed) { val settingMap = mapOf( @@ -328,8 +323,7 @@ class PolarManager( .subscribe( { polarEcgData: PolarEcgData -> for (data in polarEcgData.samples) { - Log.d( - TAG, + logger.debug( "ECG yV: ${data.voltage} timeStamp: ${ PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" @@ -346,9 +340,9 @@ class PolarManager( } }, { error: Throwable -> - Log.e(TAG, "ECG stream failed. Reason $error") + logger.error("ECG stream failed. Reason $error") }, - { Log.d(TAG, "ECG stream complete") } + { logger.debug("ECG stream complete") } ) } } else { @@ -370,8 +364,7 @@ class PolarManager( .subscribe( { polarAccelerometerData: PolarAccelerometerData -> for (data in polarAccelerometerData.samples) { - Log.d( - TAG, + logger.debug( "ACC x: ${data.x} y: ${data.y} z: ${data.z} timeStamp: ${ PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" @@ -390,10 +383,10 @@ class PolarManager( } }, { error: Throwable -> - Log.e(TAG, "ACC stream failed. Reason $error") + logger.error("ACC stream failed. Reason $error") }, { - Log.d(TAG, "ACC stream complete") + logger.debug("ACC stream complete") } ) } @@ -403,7 +396,7 @@ class PolarManager( } fun streamPpg() { - Log.d(TAG, "start streamPpg for $deviceId") + logger.debug("start streamPpg for $deviceId") val isDisposed = ppgDisposable?.isDisposed ?: true if (isDisposed) { val settingMap = mapOf( @@ -418,8 +411,7 @@ class PolarManager( { polarPpgData: PolarPpgData -> if (polarPpgData.type == PolarPpgData.PpgDataType.PPG3_AMBIENT1) { for (data in polarPpgData.samples) { - Log.d( - TAG, + logger.debug( "PPG ppg0: ${data.channelSamples[0]} ppg1: ${data.channelSamples[1]} ppg2: ${data.channelSamples[2]} ambient: ${data.channelSamples[3]} timeStamp: ${ PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" @@ -440,9 +432,9 @@ class PolarManager( } }, { error: Throwable -> - Log.e(TAG, "ECG stream failed. Reason $error") + logger.error("ECG stream failed. Reason $error") }, - { Log.d(TAG, "ECG stream complete") } + { logger.debug("ECG stream complete") } ) } } else { @@ -459,8 +451,7 @@ class PolarManager( .subscribe( { ppiData: PolarPpiData -> for (sample in ppiData.samples) { - Log.d( - TAG, + logger.debug( "PPI ppi: ${sample.ppi} blocker: ${sample.blockerBit} " + "errorEstimate: ${sample.errorEstimate} " + "currentTime: $currentTime" @@ -482,16 +473,19 @@ class PolarManager( } }, { error: Throwable -> - Log.e(TAG, "PPI stream failed. Reason $error") + logger.error("PPI stream failed. Reason $error") }, - { Log.d(TAG, "PPI stream complete") } + { logger.debug("PPI stream complete") } ) } } else { ppiDisposable?.dispose() } } - + + companion object { + private val logger = LoggerFactory.getLogger(PolarManager::class.java) + } } From 6c9c0e6f48bb5291e430445a4933dc28bacf1564 Mon Sep 17 00:00:00 2001 From: yatharthranjan Date: Mon, 9 Sep 2024 13:17:47 +0100 Subject: [PATCH 5/5] PR feedback and other improvements --- plugins/radar-android-polar/README.md | 17 ++ .../radarbase/passive/polar/PolarManager.kt | 178 +++++++++++------- .../radarbase/passive/polar/PolarService.kt | 19 +- 3 files changed, 131 insertions(+), 83 deletions(-) diff --git a/plugins/radar-android-polar/README.md b/plugins/radar-android-polar/README.md index 320f8f156..f2fbc5495 100644 --- a/plugins/radar-android-polar/README.md +++ b/plugins/radar-android-polar/README.md @@ -50,3 +50,20 @@ Add the provider `.polar.PolarProvider` to the Firebase Remote Config `plugins` This plugin was build using the [POLAR BLE SDK][1]. [1]: https://github.com/polarofficial/polar-ble-sdk + + +## Information + +On the **Polar Vantage V3**, streaming data is only possible when a training session is started on the watch (see https://github.com/polarofficial/polar-ble-sdk/issues/456). + +Here are the instructions how to start data streaming: + +1. Have a phone running the app with Polar SDK paired with the watch. +2. The sensor data sharing must be enabled for the phone in the watch pairing menu (General settings/Pair and sync/Paired devices/SDK/Share). +3. Go to the 'Start training' -menu in watch and choose the desired sport profile. +4. On phone running the app with Polar SDK connect phone with watch. +5. Start the recording in the app. +6. On the watch start the training recording or stay in 'Start training' -menu. + +Note! When streaming data we would recommend removing Polar Flow app from the mobile if it was installed. All other phones or Polar devices not used for streaming should be shut down or at least Bluetooth turned off. +Also after each streaming session you might need to go back to the watch mode before starting a new exercise & streaming session. diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt index fe8387f16..50c39bea6 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarManager.kt @@ -54,7 +54,7 @@ class PolarManager( private var accDisposable: Disposable? = null private var ppiDisposable: Disposable? = null private var ppgDisposable: Disposable? = null - + init { status = SourceStatusListener.Status.DISCONNECTED // red icon name = service.getString(R.string.polarDisplayName) @@ -64,9 +64,7 @@ class PolarManager( override fun start(acceptableIds: Set) { status = SourceStatusListener.Status.READY // blue loading - logger.debug("Polar Device is $deviceId") - disconnectToPolarSDK(deviceId) connectToPolarSDK() register() @@ -125,8 +123,7 @@ class PolarManager( override fun deviceDisconnected(polarDeviceInfo: PolarDeviceInfo) { logger.debug("Device disconnected ${polarDeviceInfo.deviceId}") isDeviceConnected = false - status = SourceStatusListener.Status.DISCONNECTED // red circle - + disconnect() } override fun bleSdkFeatureReady( @@ -137,11 +134,10 @@ class PolarManager( if (isDeviceConnected) { logger.debug("Feature ready {} for {}", feature, deviceId) - if (feature == PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP) { - setDeviceTime(deviceId) - } - when (feature) { + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_DEVICE_TIME_SETUP -> + setDeviceTime(deviceId) + PolarBleApi.PolarBleSdkFeature.FEATURE_POLAR_ONLINE_STREAMING -> { streamHR() streamEcg() @@ -167,14 +163,17 @@ class PolarManager( val batteryLevel = level.toFloat() / 100.0f state.batteryLevel = batteryLevel logger.debug("Battery level $level%, which is $batteryLevel at $currentTime") - send( - batteryLevelTopic, - PolarBatteryLevel(name, currentTime, currentTime, batteryLevel) - ) + mHandler.execute { + send( + batteryLevelTopic, + PolarBatteryLevel(name, currentTime, currentTime, batteryLevel) + ) + } } - }) + api.setApiLogger { s: String -> logger.debug("POLAR_API: {}", s) } + try { deviceId = service.getPolarDevice() if (deviceId == null) { @@ -187,6 +186,33 @@ class PolarManager( } catch (a: PolarInvalidArgument) { a.printStackTrace() } + } + + override fun onClose() { + super.onClose() + if (autoConnectDisposable != null && !autoConnectDisposable!!.isDisposed) { + autoConnectDisposable?.dispose() + } + if (hrDisposable != null && !hrDisposable!!.isDisposed) { + hrDisposable?.dispose() + } + if (ecgDisposable != null && !ecgDisposable!!.isDisposed) { + ecgDisposable?.dispose() + } + if (accDisposable != null && !accDisposable!!.isDisposed) { + accDisposable?.dispose() + } + if (ppiDisposable != null && !ppiDisposable!!.isDisposed) { + ppiDisposable?.dispose() + } + if (ppgDisposable != null && !ppgDisposable!!.isDisposed) { + ppgDisposable?.dispose() + } + + disconnectToPolarSDK(deviceId) + mHandler.stop { + wakeLock?.release() + } } @@ -200,7 +226,6 @@ class PolarManager( } autoConnectDisposable = Flowable.interval(0, 5, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) .subscribe({ if (deviceId == null || !isDeviceConnected) { searchPolarDevice(true) @@ -211,7 +236,6 @@ class PolarManager( private fun searchPolarDevice(force: Boolean = false): Disposable? { try { return api.searchForDevice() - .observeOn(AndroidSchedulers.mainThread()) .subscribe({ device: PolarDeviceInfo -> logger.debug("Device found: ${device.deviceId} ${device.name}") if (deviceId == null || force) { @@ -267,7 +291,6 @@ class PolarManager( if (isDisposed) { hrDisposable = deviceId?.let { api.startHrStreaming(it) - .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe { logger.debug("Subscribed to HrStreaming for $deviceId") } .subscribe( { hrData: PolarHrData -> @@ -279,19 +302,21 @@ class PolarManager( "contactStatus: ${sample.contactStatus} " + "contactStatusSupported: ${sample.contactStatusSupported}" ) - send( - heartRateTopic, - PolarHeartRate( - name, - getTimeNano(), - currentTime, - sample.hr, - sample.rrsMs, - sample.rrAvailable, - sample.contactStatus, - sample.contactStatusSupported + mHandler.execute { + send( + heartRateTopic, + PolarHeartRate( + name, + getTimeNano(), + currentTime, + sample.hr, + sample.rrsMs, + sample.rrAvailable, + sample.contactStatus, + sample.contactStatusSupported + ) ) - ) + } } }, @@ -328,15 +353,17 @@ class PolarManager( PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" ) - send( - ecgTopic, - PolarEcg( - name, - PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp), - currentTime, - data.voltage + mHandler.execute { + send( + ecgTopic, + PolarEcg( + name, + PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp), + currentTime, + data.voltage + ) ) - ) + } } }, { error: Throwable -> @@ -369,17 +396,19 @@ class PolarManager( PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" ) - send( - accelerationTopic, - PolarAcceleration( - name, - PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp), - currentTime, - data.x, - data.y, - data.z + mHandler.execute { + send( + accelerationTopic, + PolarAcceleration( + name, + PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp), + currentTime, + data.x, + data.y, + data.z + ) ) - ) + } } }, { error: Throwable -> @@ -416,18 +445,20 @@ class PolarManager( PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp) } currentTime: $currentTime PolarTimeStamp: ${data.timeStamp}" ) - send( - ppgTopic, - PolarPpg( - name, - PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp), - currentTime, - data.channelSamples[0], - data.channelSamples[1], - data.channelSamples[2], - data.channelSamples[3] + mHandler.execute { + send( + ppgTopic, + PolarPpg( + name, + PolarUtils.convertEpochPolarToUnixEpoch(data.timeStamp), + currentTime, + data.channelSamples[0], + data.channelSamples[1], + data.channelSamples[2], + data.channelSamples[3] + ) ) - ) + } } } }, @@ -447,7 +478,6 @@ class PolarManager( if (isDisposed) { ppiDisposable = deviceId?.let { api.startPpiStreaming(it) - .observeOn(AndroidSchedulers.mainThread()) .subscribe( { ppiData: PolarPpiData -> for (sample in ppiData.samples) { @@ -456,20 +486,22 @@ class PolarManager( "errorEstimate: ${sample.errorEstimate} " + "currentTime: $currentTime" ) - send( - ppIntervalTopic, - PolarPpInterval( - name, - currentTime, - currentTime, - sample.blockerBit, - sample.errorEstimate, - sample.hr, - sample.ppi, - sample.skinContactStatus, - sample.skinContactSupported + mHandler.execute { + send( + ppIntervalTopic, + PolarPpInterval( + name, + currentTime, + currentTime, + sample.blockerBit, + sample.errorEstimate, + sample.hr, + sample.ppi, + sample.skinContactStatus, + sample.skinContactSupported + ) ) - ) + } } }, { error: Throwable -> @@ -482,7 +514,7 @@ class PolarManager( ppiDisposable?.dispose() } } - + companion object { private val logger = LoggerFactory.getLogger(PolarManager::class.java) } diff --git a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt index e064432ed..147c6039e 100644 --- a/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt +++ b/plugins/radar-android-polar/src/main/java/org/radarbase/passive/polar/PolarService.kt @@ -1,25 +1,25 @@ package org.radarbase.passive.polar import android.content.Context -import android.os.Process +import android.content.SharedPreferences import org.radarbase.android.config.SingleRadarConfiguration import org.radarbase.android.source.SourceManager import org.radarbase.android.source.SourceService -import org.radarbase.android.util.SafeHandler /** * A service that manages the Polar manager and a TableDataHandler to send store the data of * the phone sensors and send it to a Kafka REST proxy. */ class PolarService : SourceService() { - private lateinit var handler: SafeHandler override val defaultState: PolarState get() = PolarState() + private lateinit var sharedPreferences: SharedPreferences + override fun onCreate() { super.onCreate() - handler = SafeHandler.getInstance("Polar", Process.THREAD_PRIORITY_FOREGROUND) + sharedPreferences = getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) } override fun createSourceManager() = PolarManager(this) @@ -31,17 +31,16 @@ class PolarService : SourceService() { manager as PolarManager } - fun savePolarDevice(deviceId: String) { - applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + + fun savePolarDevice(deviceId: String) = + sharedPreferences .edit() .putString(SHARED_PREF_KEY, deviceId) .apply() - } - fun getPolarDevice(): String? { - return applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + fun getPolarDevice(): String? = + sharedPreferences .getString(SHARED_PREF_KEY, null) - } companion object { val SHARED_PREF_NAME: String = PolarService::class.java.name