From bd9bc8bcff242ea2e89e44e061eae066a783de8e Mon Sep 17 00:00:00 2001 From: Luke D Jones Date: Mon, 12 Apr 2021 20:06:47 +1200 Subject: [PATCH] anime: services for system sequences --- daemon/src/config_anime.rs | 126 +++++++++++---- daemon/src/ctrl_anime.rs | 311 +++++++++++++++++++++++++------------ daemon/src/daemon.rs | 2 +- rog-types/src/error.rs | 2 +- 4 files changed, 314 insertions(+), 127 deletions(-) diff --git a/daemon/src/config_anime.rs b/daemon/src/config_anime.rs index 1d7bb936..176d8fc8 100644 --- a/daemon/src/config_anime.rs +++ b/daemon/src/config_anime.rs @@ -1,4 +1,5 @@ -use log::{error, warn}; +use crate::VERSION; +use log::{error, info, warn}; use rog_anime::{error::AnimeError, ActionData, AnimTime, AnimeAction, Vec2}; use serde_derive::{Deserialize, Serialize}; use std::fs::{File, OpenOptions}; @@ -8,28 +9,75 @@ use std::time::Duration; pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf"; pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf"; +#[derive(Deserialize, Serialize)] +pub struct AnimeConfigV341 { + pub system: Option, + pub boot: Option, + pub suspend: Option, + pub shutdown: Option, +} + +impl AnimeConfigV341 { + pub(crate) fn into_current(self) -> AnimeConfig { + AnimeConfig { + system: if let Some(ani) = self.system { + vec![ani] + } else { + vec![] + }, + boot: if let Some(ani) = self.boot { + vec![ani] + } else { + vec![] + }, + wake: if let Some(ani) = self.suspend { + vec![ani] + } else { + vec![] + }, + shutdown: if let Some(ani) = self.shutdown { + vec![ani] + } else { + vec![] + }, + brightness: 1.0, + } + } +} + #[derive(Deserialize, Serialize, Default)] pub struct AnimeConfigCached { - pub system: Option, - pub boot: Option, - pub suspend: Option, - pub shutdown: Option, + pub system: Vec, + pub boot: Vec, + pub wake: Vec, + pub shutdown: Vec, } impl AnimeConfigCached { pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> { - if let Some(ref sys) = config.system { - self.system = Some(ActionData::from_anime_action(sys)?) + let mut sys = Vec::with_capacity(config.system.len()); + for ani in config.system.iter() { + sys.push(ActionData::from_anime_action(ani)?); } - if let Some(ref boot) = config.boot { - self.boot = Some(ActionData::from_anime_action(boot)?) + self.system = sys; + + let mut boot = Vec::with_capacity(config.boot.len()); + for ani in config.boot.iter() { + boot.push(ActionData::from_anime_action(ani)?); } - if let Some(ref suspend) = config.boot { - self.suspend = Some(ActionData::from_anime_action(suspend)?) + self.boot = boot; + + let mut wake = Vec::with_capacity(config.wake.len()); + for ani in config.wake.iter() { + wake.push(ActionData::from_anime_action(ani)?); } - if let Some(ref shutdown) = config.boot { - self.shutdown = Some(ActionData::from_anime_action(shutdown)?) + self.wake = wake; + + let mut shutdown = Vec::with_capacity(config.shutdown.len()); + for ani in config.shutdown.iter() { + shutdown.push(ActionData::from_anime_action(ani)?); } + self.shutdown = shutdown; Ok(()) } } @@ -37,19 +85,21 @@ impl AnimeConfigCached { /// Config for base system actions for the anime display #[derive(Deserialize, Serialize)] pub struct AnimeConfig { - pub system: Option, - pub boot: Option, - pub suspend: Option, - pub shutdown: Option, + pub system: Vec, + pub boot: Vec, + pub wake: Vec, + pub shutdown: Vec, + pub brightness: f32, } impl Default for AnimeConfig { fn default() -> Self { AnimeConfig { - system: None, - boot: None, - suspend: None, - shutdown: None, + system: Vec::new(), + boot: Vec::new(), + wake: Vec::new(), + shutdown: Vec::new(), + brightness: 1.0, } } } @@ -75,6 +125,11 @@ impl AnimeConfig { } else { if let Ok(data) = serde_json::from_str(&buf) { return data; + } else if let Ok(data) = serde_json::from_str::(&buf) { + let config = data.into_current(); + config.write(); + info!("Updated config version to: {}", VERSION); + return config; } warn!("Could not deserialise {}", ANIME_CONFIG_PATH); panic!("Please remove {} then restart asusd", ANIME_CONFIG_PATH); @@ -86,20 +141,35 @@ impl AnimeConfig { fn create_default(file: &mut File) -> Self { // create a default config here let config = AnimeConfig { - system: None, - boot: Some(AnimeAction::ImageAnimation { + system: vec![], + boot: vec![AnimeAction::ImageAnimation { file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(), scale: 0.9, angle: 0.65, translation: Vec2::default(), - brightness: 0.5, + brightness: 1.0, time: AnimTime::Time(Duration::from_secs(5)), - }), - suspend: None, - shutdown: None, + }], + wake: vec![AnimeAction::ImageAnimation { + file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(), + scale: 0.9, + angle: 0.65, + translation: Vec2::default(), + brightness: 1.0, + time: AnimTime::Time(Duration::from_secs(5)), + }], + shutdown: vec![AnimeAction::ImageAnimation { + file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(), + scale: 0.9, + angle: 0.0, + translation: Vec2::new(3.0, 2.0), + brightness: 1.0, + time: AnimTime::Infinite, + }], + brightness: 1.0, }; // Should be okay to unwrap this as is since it is a Default - let json = serde_json::to_string(&config).unwrap(); + let json = serde_json::to_string_pretty(&config).unwrap(); file.write_all(json.as_bytes()) .unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH)); config diff --git a/daemon/src/ctrl_anime.rs b/daemon/src/ctrl_anime.rs index eb965a99..58904394 100644 --- a/daemon/src/ctrl_anime.rs +++ b/daemon/src/ctrl_anime.rs @@ -1,4 +1,5 @@ use log::{error, info, warn}; +use logind_zbus::ManagerProxy; use rog_anime::{ usb::{ pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID, @@ -14,8 +15,11 @@ use std::{ thread::sleep, time::Instant, }; -use std::{sync::atomic::AtomicBool, time::Duration}; -use zbus::dbus_interface; +use std::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; +use zbus::{dbus_interface, Connection}; use zvariant::ObjectPath; use crate::{ @@ -35,11 +39,11 @@ impl GetSupported for CtrlAnime { pub struct CtrlAnime { handle: DeviceHandle, cache: AnimeConfigCached, - _config: AnimeConfig, + config: AnimeConfig, // set to force thread to exit - thread_exit: AtomicBool, + thread_exit: Arc, // Set to false when the thread exits - thread_running: AtomicBool, + thread_running: Arc, } impl CtrlAnime { @@ -68,9 +72,9 @@ impl CtrlAnime { let ctrl = CtrlAnime { handle: device, cache, - _config: config, - thread_exit: AtomicBool::new(false), - thread_running: AtomicBool::new(false), + config, + thread_exit: Arc::new(AtomicBool::new(false)), + thread_running: Arc::new(AtomicBool::new(false)), }; ctrl.do_initialization(); @@ -87,103 +91,112 @@ impl CtrlAnime { Err(rusb::Error::NoDevice) } - // DOUBLE THREAD NEST! - fn run_thread(inner: Arc>, action: Option, mut once: bool) { + /// Start an action thread. This is classed as a singleton and there should be only + /// one running - so the thread uses atomics to signal run/exit. + /// + /// Because this also writes to the usb device, other write tries (display only) *must* + /// get the mutex lock and set the thread_exit atomic. + fn run_thread(inner: Arc>, actions: Vec, mut once: bool) { + if actions.is_empty() { + warn!("AniMe system actions was empty"); + return; + } + // Loop rules: + // - Lock the mutex **only when required**. That is, the lock must be held for the shortest duration possible. + // - An AtomicBool used for thread exit should be checked in every loop, including nested + + // The only reason for this outer thread is to prevent blocking while waiting for the + // next spawned thread to exit std::thread::Builder::new() .name("AniMe system thread start".into()) .spawn(move || { - // Make the loop exit first + info!("AniMe system thread started"); + // Getting copies of these Atomics is done *in* the thread to ensure + // we don't block other threads/main + let thread_exit; + let thread_running; + // First two loops are to ensure we *do* aquire a lock on the mutex + // The reason the loop is required is because the USB writes can block + // for up to 10ms. We can't fail to get the atomics. loop { if let Ok(lock) = inner.try_lock() { - lock.thread_exit - .store(true, std::sync::atomic::Ordering::SeqCst); + thread_exit = lock.thread_exit.clone(); + thread_running = lock.thread_running.clone(); + // Make any running loop exit first + thread_exit.store(true, Ordering::SeqCst); break; } } + loop { - if let Ok(lock) = inner.try_lock() { - if !lock - .thread_running - .load(std::sync::atomic::Ordering::SeqCst) - { - lock.thread_exit - .store(false, std::sync::atomic::Ordering::SeqCst); - info!("AniMe system thread exited"); - break; - } + // wait for other threads to set not running so we know they exited + if !thread_running.load(Ordering::SeqCst) { + thread_exit.store(false, Ordering::SeqCst); + info!("AniMe forced a thread to exit"); + break; } } - std::thread::Builder::new() - .name("AniMe system actions".into()) - .spawn(move || { - info!("AniMe system thread started"); - 'main: loop { - if let Ok(lock) = inner.try_lock() { - if !once - && lock.thread_exit.load(std::sync::atomic::Ordering::SeqCst) - { - break 'main; - } - if let Some(ref action) = action { - match action { - ActionData::Animation(frames) => { - let mut count = 0; - let start = Instant::now(); - 'animation: loop { - for frame in frames.frames() { - lock.write_data_buffer(frame.frame().clone()); - if let AnimTime::Time(time) = frames.duration() - { - if Instant::now().duration_since(start) - > time - { - break 'animation; - } - } - sleep(frame.delay()); - } - if let AnimTime::Cycles(times) = frames.duration() { - count += 1; - if count >= times { - break 'animation; - } - } + 'main: loop { + if thread_exit.load(Ordering::SeqCst) { + break 'main; + } + for action in actions.iter() { + match action { + ActionData::Animation(frames) => { + let mut count = 0; + let start = Instant::now(); + 'animation: loop { + for frame in frames.frames() { + if let Ok(lock) = inner.try_lock() { + lock.write_data_buffer(frame.frame().clone()); + } + if let AnimTime::Time(time) = frames.duration() { + if Instant::now().duration_since(start) > time { + break 'animation; } } - ActionData::Image(image) => { - once = false; - lock.write_data_buffer(image.as_ref().clone()) + sleep(frame.delay()); + // Need to check for early exit condition here or it might run + // until end of gif or time + if thread_exit.load(Ordering::SeqCst) { + break 'main; + } + } + if let AnimTime::Cycles(times) = frames.duration() { + count += 1; + if count >= times { + break 'animation; } - ActionData::Pause(_) => {} - ActionData::AudioEq => {} - ActionData::SystemInfo => {} - ActionData::TimeDate => {} - ActionData::Matrix => {} } - } else { - break 'main; - } - if once { - let data = - AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec()); - lock.write_data_buffer(data); - break 'main; } } - } - 'exit: loop { - if let Ok(lock) = inner.try_lock() { - lock.thread_exit - .store(false, std::sync::atomic::Ordering::SeqCst); - lock.thread_running - .store(false, std::sync::atomic::Ordering::SeqCst); - break 'exit; + ActionData::Image(image) => { + once = false; + if let Ok(lock) = inner.try_lock() { + lock.write_data_buffer(image.as_ref().clone()) + } } + ActionData::Pause(duration) => sleep(*duration), + ActionData::AudioEq => {} + ActionData::SystemInfo => {} + ActionData::TimeDate => {} + ActionData::Matrix => {} } - }) - .map(|err| info!("AniMe system thread: {:?}", err)) - .ok(); + } + if once || actions.is_empty() { + break 'main; + } + } + // Clear the display on exit + if let Ok(lock) = inner.try_lock() { + let data = AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec()); + lock.write_data_buffer(data); + } + // Loop ended, set the atmonics + thread_exit.store(false, Ordering::SeqCst); + thread_running.store(false, Ordering::SeqCst); + info!("AniMe system thread exited"); }) .map(|err| info!("AniMe system thread: {:?}", err)) .ok(); @@ -206,7 +219,16 @@ impl CtrlAnime { } } - fn write_data_buffer(&self, buffer: AnimeDataBuffer) { + /// Write only a data packet. This will modify the leds brightness using the + /// global brightness set in config. + fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) { + for led in buffer.get_mut()[7..].iter_mut() { + let mut bright = *led as f32 * self.config.brightness; + if bright > 254.0 { + bright = 254.0; + } + *led = bright as u8; + } let data = AnimePacketType::from(buffer); for row in data.iter() { self.write_bytes(row); @@ -221,10 +243,87 @@ impl CtrlAnime { } } -pub struct CtrlAnimeTask(pub Arc>); +pub struct CtrlAnimeTask<'a> { + inner: Arc>, + _c: Connection, + manager: ManagerProxy<'a>, +} -impl crate::CtrlTask for CtrlAnimeTask { +impl<'a> CtrlAnimeTask<'a> { + pub fn new(inner: Arc>) -> Self { + let connection = Connection::new_system().unwrap(); + + let manager = ManagerProxy::new(&connection).unwrap(); + + let c1 = inner.clone(); + // Run this action when the system starts shutting down + manager + .connect_prepare_for_shutdown(move |shutdown| { + if shutdown { + 'outer: loop { + if let Ok(lock) = c1.try_lock() { + lock.thread_exit.store(true, Ordering::SeqCst); + CtrlAnime::run_thread(c1.clone(), lock.cache.shutdown.clone(), false); + break 'outer; + } + } + } + Ok(()) + }) + .map_err(|err| { + warn!("CtrlAnimeTask: new() {}", err); + err + }) + .ok(); + + let c1 = inner.clone(); + // Run this action when the system wakes up from sleep + manager + .connect_prepare_for_sleep(move |sleep| { + if !sleep { + // wait a fraction for things to wake up properly + std::thread::sleep(Duration::from_millis(100)); + 'outer: loop { + if let Ok(lock) = c1.try_lock() { + lock.thread_exit.store(true, Ordering::SeqCst); + CtrlAnime::run_thread(c1.clone(), lock.cache.wake.clone(), true); + break 'outer; + } + } + } + Ok(()) + }) + .map_err(|err| { + warn!("CtrlAnimeTask: new() {}", err); + err + }) + .ok(); + + Self { + inner, + _c: connection, + manager, + } + } +} + +impl<'a> crate::CtrlTask for CtrlAnimeTask<'a> { fn do_task(&self) -> Result<(), RogError> { + if let Ok(mut lock) = self.inner.try_lock() { + // Refresh the config and cache incase the user has edited it + let config = AnimeConfig::load(); + lock.cache + .init_from_config(&config) + .map_err(|err| { + warn!("CtrlAnimeTask: do_task {}", err); + err + }) + .ok(); + } + + // Check for signals on each task iteration, this will run the callbacks + // if any signal is recieved + self.manager.next_signal()?; Ok(()) } } @@ -268,14 +367,29 @@ impl CtrlAnimeZbus { fn write(&self, input: AnimeDataBuffer) { 'outer: loop { if let Ok(lock) = self.0.try_lock() { - lock.thread_exit - .store(true, std::sync::atomic::Ordering::SeqCst); + lock.thread_exit.store(true, Ordering::SeqCst); lock.write_data_buffer(input); break 'outer; } } } + fn set_brightness(&self, bright: f32) { + 'outer: loop { + if let Ok(mut lock) = self.0.try_lock() { + let mut bright = bright; + if bright < 0.0 { + bright = 0.0 + } else if bright > 254.0 { + bright = 254.0; + } + lock.config.brightness = bright; + lock.config.write(); + break 'outer; + } + } + } + fn set_on_off(&self, status: bool) { 'outer: loop { if let Ok(lock) = self.0.try_lock() { @@ -295,13 +409,16 @@ impl CtrlAnimeZbus { } } - fn run_main_loop(&self, on: bool) { - 'outer: loop { - if let Ok(lock) = self.0.try_lock() { - lock.thread_exit - .store(on, std::sync::atomic::Ordering::SeqCst); - CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false); - break 'outer; + /// The main loop is the base system set action if the user isn't running + /// the user daemon + fn run_main_loop(&self, start: bool) { + if start { + 'outer: loop { + if let Ok(lock) = self.0.try_lock() { + lock.thread_exit.store(true, Ordering::SeqCst); + CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false); + break 'outer; + } } } } diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs index 21684bfe..5d7f5eff 100644 --- a/daemon/src/daemon.rs +++ b/daemon/src/daemon.rs @@ -111,7 +111,7 @@ fn start_daemon() -> Result<(), Box> { let zbus = CtrlAnimeZbus(inner.clone()); zbus.add_to_server(&mut object_server); - tasks.push(Box::new(CtrlAnimeTask(inner))); + tasks.push(Box::new(CtrlAnimeTask::new(inner))); } Err(err) => { error!("AniMe control: {}", err); diff --git a/rog-types/src/error.rs b/rog-types/src/error.rs index da861707..432772f0 100644 --- a/rog-types/src/error.rs +++ b/rog-types/src/error.rs @@ -17,4 +17,4 @@ impl fmt::Display for GraphicsError { } } -impl Error for GraphicsError {} \ No newline at end of file +impl Error for GraphicsError {}