-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix polar plugin #461
Fix polar plugin #461
Conversation
yatharthranjan
commented
Sep 5, 2024
•
edited
Loading
edited
- This fixes the auto-connect of polar devices
- Searches for polar devices every 5 seconds until one is found
- On connection to a new device, it stores the Polar device ID and always reconnects to it in the future. Only one ID is stored at once.
- Pair a new device using the "Reset Polar Device ID" action button.
- Tested with Polar Vantage V3
- Also fixes reconnection of the device and reload functionality in the plugin
- Closes Polar plugin uses hardcoded deviceId’s to establish the connection to the device #450, The ‘reload’ button functionality is not yet implemented for the Polar devices #453, There is no automatic reconnection after user has lost connection to a Polar device #454
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @yatharthranjan, this PR looks good, but I have identified some areas for improvement in the code merged from PR #448 that could optimize the plugin for better performance and usability.
private val ppIntervalTopic: DataCache<ObservationKey, PolarPpInterval> = | ||
createCache("android_polar_pulse_to_pulse_interval", PolarPpInterval()) | ||
private val ppgTopic: DataCache<ObservationKey, PolarPpg> = | ||
createCache("android_polar_ppg", PolarPpg()) | ||
|
||
private val mHandler = SafeHandler.getInstance("Polar sensors", THREAD_PRIORITY_BACKGROUND) | ||
private var wakeLock: PowerManager.WakeLock? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a wakelock here? We are using it with the PARTIAL_WAKE_LOCK
flag, which ensures that the CPU remains active while allowing the screen to turn off. We already have this functionality with RadarService and SourceService, which always run in the background. If we still need it, the wakelock should also be released after it has been acquired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have left it in for now as the app did lose connection with the device without this after a while of screen being off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, sorry I skipped it. I’ve recommented.
|
||
private val mHandler = SafeHandler.getInstance("Polar sensors", THREAD_PRIORITY_BACKGROUND) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include handler.close()
in the onClose method.
override val defaultState: PolarState | ||
get() = PolarState() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see any use for PolarState
, it could be removed and replaced with BaseSourceState
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have left it for now as it does set hasAcceleration to true, we can remove it in future if not required (I don't think it matters that much TBH)
manager as PolarManager | ||
} | ||
|
||
fun savePolarDevice(deviceId: String) { | ||
applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can declare sharedPreferences
as a variable to avoid repeatedly calling applicationContext.getSharedPreferences
.
applicationContext.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) | |
class PolarService : SourceService<BaseSourceState>() { | |
private lateinit var sharedPreferences: SharedPreferences | |
override fun onCreate() { | |
super.onCreate() | |
// Instantiating sharedPreferences inside onCreate to make sure that service context is available | |
sharedPreferences = getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) | |
} | |
} |
handler = SafeHandler.getInstance("Polar", Process.THREAD_PRIORITY_FOREGROUND) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handler instance is not being utilized here, we could remove it.
status = SourceStatusListener.Status.DISCONNECTED // red circle | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Directly setting the source status to DISCONNECTED
could disrupt the logic in AbstractSourceManager
and SourceService
. Therefore, none of the plugins set the DISCONNECTED
status directly. Instead, we might consider using AbstractSourceManager::disconnect
. However, this approach could introduce issues, potentially causing a continuous loop of connecting and disconnecting the plugin.
The connection and disconnection logic can be inspired by the implementations in Empatica and Faros. For example, they override superclass methods to fit their needs, like here in E4Service.
The exact implementation logic needs to be tested on the device to ensure its effectiveness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
autoConnectDisposable = Flowable.interval(0, 5, TimeUnit.SECONDS) | ||
.observeOn(AndroidSchedulers.mainThread()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not very familiar with RxJava, but we are using observeOn(AndroidSchedulers.mainThread())
in every method. It seems that this forces the code inside these methods to always execute on the main thread, which could potentially freeze the UI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is a good spot; I am also not very familiar with RxJava but this was provided in Polar documentation, hence being used here. I will try to remove it and test it. However, at least here it does not freeze the UI, as it only runs initially until the device is paired. Once there is a paired device, it will always connect to that and will not need scanning of devices. But I agree that sending the data could be an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, we can use observerOn
with the main thread and simply wrap the code inside the methods using a handler if there are no UI updates.
send( | ||
batteryLevelTopic, | ||
PolarBatteryLevel(name, currentTime, currentTime, batteryLevel) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Safehandler instance should be used to send data in background.
Hi @this-Aditya, thanks for the comments. I also recognised various issues with the existing polar impl. but only made changes in this PR to fix the auto-connect, as there are other issues in this repo for those things that can be handled in specific PRs (and also wanted to test this in isolation). However, I will incorporate your suggestions (and delegate to separate PR/issue where suitable). Thanks |
Oh, okay. I thought this was the finalized PR for the Polar plugin, which is why I mentioned these here 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @yatharthranjan for updating this PR. It looks great so far. Could you please review these additional comments and include them if necessary?
Thanks
@@ -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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not entirely sure, but does it not loop between connecting and disconnecting the manager when this callback is received? If so, we could add logic specific to the needs of this plugin ( such as handling the merging of ::disconnect
with the UNAVAILABLE
status or similar to this) to prevent the loop and starting from READY again during reconnections.
Please disregard this review if the behavior is functioning correctly with ::disconnect
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I completely understand; when the device is disconnected, this callback is called, which sets the plugin state to disconnect. The recording is started again, and it becomes ready, looking for the device to connect again.
Here are logs showing the process
2024-09-09 16:01:27.858 19539-19539 pRMT:PolarManager org.radarcns.detail D Device disconnected CF27042A
2024-09-09 16:01:27.859 19539-19539 pRMT:SourceServiceConn* org.radarcns.detail I AppSource status changed of source Polar Vantage V3 CF27042A
2024-09-09 16:01:27.859 19539-19539 pRMT:SourceServiceConn* org.radarcns.detail I Updated source status to DISCONNECTING
2024-09-09 16:01:27.859 19539-19539 pRMT:RadarService org.radarcns.detail I Source of ServiceConnection<org.radarbase.passive.polar.PolarService> was updated to DISCONNECTING
2024-09-09 16:01:27.874 19539-19539 pRMT:SourceServiceConn* org.radarcns.detail I AppSource status changed of source null
2024-09-09 16:01:27.874 19539-19539 pRMT:SourceServiceConn* org.radarcns.detail I Updated source status to DISCONNECTED
2024-09-09 16:01:27.874 19539-19539 pRMT:RadarService org.radarcns.detail I Source of ServiceConnection<org.radarbase.passive.polar.PolarService> was updated to DISCONNECTED
2024-09-09 16:01:27.875 19539-19665 pRMT:RadarService org.radarcns.detail I Starting recording on connection ServiceConnection<org.radarbase.passive.polar.PolarService>
2024-09-09 16:01:27.880 19539-11102 pRMT:SourceService org.radarcns.detail I Starting recording now for PolarService
2024-09-09 16:01:27.883 19539-11102 pRMT:PolarManager org.radarcns.detail D Connecting to Polar API
2024-09-09 16:01:27.883 19539-19539 pRMT:SourceServiceConn* org.radarcns.detail I AppSource status changed of source Polar
2024-09-09 16:01:27.883 19539-19539 pRMT:SourceServiceConn* org.radarcns.detail I Updated source status to READY
2024-09-09 16:01:27.884 19539-19539 pRMT:RadarService org.radarcns.detail I Source of ServiceConnection<org.radarbase.passive.polar.PolarService> was updated to READY
I did not notice any looping in my testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that’s great. I have observed this looping behavior in other plugins, but I have not worked with any wearable devices for pRMT, so I was not sure about the behavior for wearables on ::disconnect
Thanks for the logs.
There is new work after this pr is accepted
Nice work 👍🏽 @this-Aditya @yatharthranjan briefed me on some of the caveats, but this will still definitely be very useful, particularly as we push for good gold standards for benchmarking studies. |
Thanks @afolarin, glad to hear it. |