From f371d58e121d98e94a2227fde83ba54df150f538 Mon Sep 17 00:00:00 2001 From: Andrii Zymohliad Date: Sat, 2 Mar 2024 21:23:31 +0200 Subject: [PATCH] Implement periodic discovery --- Cargo.lock | 1 + watchmate/Cargo.toml | 1 + watchmate/src/ui.rs | 29 +++++++++-- watchmate/src/ui/devices_page.rs | 89 ++++++++++++++++++++++++++++++-- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a38a568..7363e00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3159,6 +3159,7 @@ dependencies = [ "relm4", "relm4-components", "relm4-icons", + "tokio", "version-compare", ] diff --git a/watchmate/Cargo.toml b/watchmate/Cargo.toml index 1cd8056..ca51a8d 100644 --- a/watchmate/Cargo.toml +++ b/watchmate/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] infinitime = { path = "../infinitime", features = ["freedesktop", "github"] } +tokio = { version = "1.36", features = ["time"] } futures = "0.3" anyhow = "1.0" version-compare = "0.1" diff --git a/watchmate/src/ui.rs b/watchmate/src/ui.rs index dc2f3ab..3b14655 100644 --- a/watchmate/src/ui.rs +++ b/watchmate/src/ui.rs @@ -1,5 +1,5 @@ use infinitime::{bluer, bt}; -use std::{sync::Arc, path::PathBuf, env}; +use std::{env, path::PathBuf, sync::Arc, time::Duration}; use futures::{pin_mut, StreamExt}; use gtk::{gio, glib, prelude::{ApplicationExt, BoxExt, GtkWindowExt, SettingsExt, WidgetExt}}; use relm4::{ @@ -50,7 +50,8 @@ enum Input { label: &'static str, url: &'static str, }, - WindowShown, // Temporary hack + WindowShown, + WindowHidden, About, Close, Quit, @@ -86,8 +87,8 @@ impl Component for Model { set_default_height: 720, set_hide_on_close: settings.boolean(SETTING_BACKGROUND), - // Temporary hack connect_show => Input::WindowShown, + connect_hide => Input::WindowHidden, #[local] toast_overlay -> adw::ToastOverlay { @@ -309,9 +310,29 @@ impl Component for Model { // main window visible upon gtk::Application::activate signal if self.hide_on_startup { root.set_visible(false); + self.hide_on_startup = false; } - self.hide_on_startup = false; + + if root.is_visible() { + self.devices_page.emit( + devices_page::Input::DiscoveryMode( + devices_page::DiscoveryMode::Continuous + ) + ) + } + } + + Input::WindowHidden => { + self.devices_page.emit( + devices_page::Input::DiscoveryMode( + devices_page::DiscoveryMode::Periodic { + active: Duration::from_secs(5), + pause: Duration::from_secs(60), + } + ) + ); } + Input::About => { adw::AboutWindow::builder() .transient_for(root) diff --git a/watchmate/src/ui/devices_page.rs b/watchmate/src/ui/devices_page.rs index 40b93b3..fea48f7 100644 --- a/watchmate/src/ui/devices_page.rs +++ b/watchmate/src/ui/devices_page.rs @@ -9,14 +9,33 @@ use relm4::{ factory::{FactoryComponent, FactorySender, FactoryVecDeque, DynamicIndex}, ComponentParts, ComponentSender, Component, JoinHandle, RelmWidgetExt, }; +use tokio::time::{sleep, timeout}; +#[derive(Debug, PartialEq)] +pub enum DiscoveryMode { + Periodic { active: Duration, pause: Duration }, + Continuous, +} + +impl DiscoveryMode { + pub fn is_continous(&self) -> bool { + self == &Self::Continuous + } + + pub fn is_periodic(&self) -> bool { + !self.is_continous() + } +} + #[derive(Debug)] pub enum Input { InitAdapter, StartDiscovery, StopDiscovery, DiscoveryFailed, + DiscoveryPaused(bool), + DiscoveryMode(DiscoveryMode), DeviceInfoReady(DeviceInfo), DeviceAdded(bluer::Address), DeviceRemoved(bluer::Address), @@ -47,7 +66,8 @@ pub struct Model { adapter: Option>, gatt_server: Option, discovery_task: Option>, - + discovery_mode: DiscoveryMode, + discovery_paused: bool, saved_address: Option, autoconnect_address: Option, disconnecting_address: Option, @@ -80,6 +100,26 @@ impl Model { Err(_) => sender.input(Input::DiscoveryFailed), } } + + async fn run_periodic_discovery( + adapter: Arc, + sender: ComponentSender, + active_duration: Duration, + pause_duration: Duration + ) { + loop { + // Active interval + sender.input(Input::DiscoveryPaused(false)); + let discovery = Self::run_discovery(adapter.clone(), sender.clone()); + if let Ok(()) = timeout(active_duration, discovery).await { + // run_discovery returns only in case of error + break; + } + // Pause interval + sender.input(Input::DiscoveryPaused(true)); + sleep(pause_duration).await; + } + } } @@ -122,7 +162,8 @@ impl Component for Model { gtk::Spinner { #[watch] set_visible: model.discovery_task.is_some(), - set_spinning: true, + #[watch] + set_spinning: !model.discovery_paused, } }, @@ -209,6 +250,8 @@ impl Component for Model { adapter: None, gatt_server: None, discovery_task: None, + discovery_mode: DiscoveryMode::Continuous, + discovery_paused: false, autoconnect_address: saved_address.clone(), saved_address, disconnecting_address: None, @@ -234,8 +277,21 @@ impl Component for Model { if self.discovery_task.is_none() { if let Some(adapter) = self.adapter.clone() { self.devices.guard().clear(); - self.discovery_task = Some(relm4::spawn(Self::run_discovery(adapter, sender))); - log::info!("Device discovery started"); + self.discovery_paused = false; + match self.discovery_mode { + DiscoveryMode::Periodic { active, pause } => { + self.discovery_task = Some(relm4::spawn( + Self::run_periodic_discovery(adapter, sender, active, pause) + )); + log::info!("Device discovery started (periodic)"); + } + DiscoveryMode::Continuous => { + self.discovery_task = Some(relm4::spawn( + Self::run_discovery(adapter, sender) + )); + log::info!("Device discovery started (continuous)"); + } + } } } } @@ -257,6 +313,30 @@ impl Component for Model { }); } + Input::DiscoveryPaused(paused) => { + if !paused { + self.devices.guard().clear(); + } + self.discovery_paused = paused; + } + + Input::DiscoveryMode(mode) => { + match &mode { + DiscoveryMode::Periodic { active, pause } => { + log::info!("Setting discovery mode to periodic: {}s scan | {}s pause", active.as_secs(), pause.as_secs()); + } + DiscoveryMode::Continuous => { + log::info!("Setting discovery mode to continuous"); + } + } + self.discovery_mode = mode; + // Restart ongoing discovery + if self.discovery_task.is_some() { + sender.input(Input::StopDiscovery); + sender.input(Input::StartDiscovery); + } + } + Input::DeviceInfoReady(info) => { let address = info.address; let mut devices = self.devices.guard(); @@ -423,6 +503,7 @@ impl Component for Model { if let Some((i, d)) = devices_guard.iter().enumerate().find( |(_, d)| Some(d.address) == self.autoconnect_address ) { + // If saved device is available, try to connect to it log::info!("Trying to connect to InfiniTime ({})", d.address.to_string()); devices_guard.send(i, DeviceInput::Connect); } else {