Files
asusctl/asusd/src/aura_anime/mod.rs
2025-01-20 13:43:38 +13:00

252 lines
9.4 KiB
Rust

pub mod config;
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
pub mod trait_impls;
use std::convert::TryFrom;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::sleep;
use config_traits::StdConfig;
use log::{debug, error, info, warn};
use rog_anime::usb::{
pkt_flush, pkt_set_brightness, pkt_set_enable_display, pkt_set_enable_powersave_anim,
pkts_for_init, Brightness
};
use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType};
use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw;
use tokio::sync::{Mutex, MutexGuard};
use self::config::{AniMeConfig, AniMeConfigCached};
use crate::error::RogError;
#[derive(Debug, Clone)]
pub struct AniMe {
hid: Option<Arc<Mutex<HidRaw>>>,
usb: Option<Arc<Mutex<USBRaw>>>,
config: Arc<Mutex<AniMeConfig>>,
cache: AniMeConfigCached,
// set to force thread to exit
thread_exit: Arc<AtomicBool>,
// Set to false when the thread exits
thread_running: Arc<AtomicBool>
}
impl AniMe {
pub fn new(
hid: Option<Arc<Mutex<HidRaw>>>,
usb: Option<Arc<Mutex<USBRaw>>>,
config: Arc<Mutex<AniMeConfig>>
) -> Self {
Self {
hid,
usb,
config,
cache: AniMeConfigCached::default(),
thread_exit: Arc::new(AtomicBool::new(false)),
thread_running: Arc::new(AtomicBool::new(false))
}
}
/// Will fail if something is already holding the config lock
async fn do_init_cache(&mut self) {
if let Ok(mut config) = self.config.try_lock() {
if let Err(e) = self.cache.init_from_config(&config, config.anime_type) {
error!(
"Trying to cache the Anime Config failed, will reset to default config: {e:?}"
);
config.rename_file_old();
*config = AniMeConfig::new();
config.write();
} else {
debug!("Initialised AniMe cache");
}
} else {
error!("AniMe Matrix could not init cache")
}
}
/// Initialise the device if required.
pub async fn do_initialization(&mut self) -> Result<(), RogError> {
self.do_init_cache().await;
let pkts = pkts_for_init();
self.write_bytes(&pkts[0]).await?;
self.write_bytes(&pkts[1]).await?;
debug!("Succesfully initialised AniMe matrix display");
Ok(())
}
pub async fn lock_config(&self) -> MutexGuard<AniMeConfig> {
self.config.lock().await
}
pub async fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
if let Some(hid) = &self.hid {
hid.lock().await.write_bytes(message)?;
} else if let Some(usb) = &self.usb {
usb.lock().await.write_bytes(message)?;
}
Ok(())
}
/// Write only a data packet. This will modify the leds brightness using the
/// global brightness set in config.
async fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
for led in buffer.data_mut().iter_mut() {
let mut bright = *led as f32;
if bright > 254.0 {
bright = 254.0;
}
*led = bright as u8;
}
let data = AnimePacketType::try_from(buffer)?;
for row in &data {
self.write_bytes(row).await?;
}
self.write_bytes(&pkt_flush()).await
}
pub async fn set_builtins_enabled(
&self,
enabled: bool,
bright: Brightness
) -> Result<(), RogError> {
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))
.await?;
self.write_bytes(&pkt_set_enable_display(enabled)).await?;
self.write_bytes(&pkt_set_brightness(bright)).await?;
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))
.await
}
/// 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.
async fn run_thread(&self, actions: Vec<ActionData>, mut once: bool) {
if actions.is_empty() {
warn!("AniMe system actions was empty");
return;
}
self.write_bytes(&pkt_set_enable_powersave_anim(false))
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
let thread_exit = self.thread_exit.clone();
let thread_running = self.thread_running.clone();
let anime_type = self.config.lock().await.anime_type;
let inner = self.clone();
// 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
// TODO: turn this in to async task (maybe? COuld still risk blocking main
// thread)
tokio::spawn(async move {
info!("AniMe new system thread started");
// 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.
while thread_running.load(Ordering::SeqCst) {
// Make any running loop exit first
thread_exit.store(true, Ordering::SeqCst);
}
info!("AniMe no previous system thread running (now)");
thread_exit.store(false, Ordering::SeqCst);
thread_running.store(true, Ordering::SeqCst);
'main: loop {
for action in &actions {
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
match action {
ActionData::Animation(frames) => {
// TODO: sort all this out
rog_anime::run_animation(frames, &|frame| {
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: animation sub-loop was asked to exit");
return Ok(true); // Do safe exit
}
let inner = inner.clone();
tokio::task::spawn_local(async move {
inner
.write_data_buffer(frame)
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
});
Ok(false) // Don't exit yet
});
if thread_exit.load(Ordering::Acquire) {
info!("rog-anime: sub-loop exited and main loop exiting now");
break 'main;
}
}
ActionData::Image(image) => {
once = false;
inner
.write_data_buffer(image.as_ref().clone())
.await
.map_err(|e| error!("{}", e))
.ok();
}
ActionData::Pause(duration) => sleep(*duration),
ActionData::AudioEq
| ActionData::SystemInfo
| ActionData::TimeDate
| ActionData::Matrix => {}
}
}
if thread_exit.load(Ordering::SeqCst) {
break 'main;
}
if once || actions.is_empty() {
break 'main;
}
}
// Clear the display on exit
if let Ok(data) =
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
.map_err(|e| error!("{}", e))
{
inner
.write_data_buffer(data)
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
}
inner
.write_bytes(&pkt_set_enable_powersave_anim(
inner.config.lock().await.builtin_anims_enabled
))
.await
.map_err(|err| {
warn!("rog_anime::run_animation:callback {}", err);
})
.ok();
// Loop ended, set the atmonics
thread_running.store(false, Ordering::SeqCst);
info!("AniMe system thread exited");
})
.await
.map(|err| info!("AniMe system thread: {:?}", err))
.ok();
}
}