Files
rayhunter/daemon/src/battery/mod.rs
Markus Unterwaditzer 7e2df91702 Fix battery warnings on unsupported devices
Fix #644, break early if battery is unsupported.
2026-01-26 11:22:47 -08:00

118 lines
3.9 KiB
Rust

use std::{path::Path, time::Duration};
use log::{info, warn};
use rayhunter::Device;
use serde::Serialize;
use tokio::select;
use tokio_util::{sync::CancellationToken, task::TaskTracker};
use crate::{
error::RayhunterError,
notifications::{Notification, NotificationType},
};
pub mod orbic;
pub mod tmobile;
pub mod tplink;
pub mod wingtech;
const LOW_BATTERY_LEVEL: u8 = 10;
#[derive(Clone, Copy, PartialEq, Debug, Serialize)]
pub struct BatteryState {
level: u8,
is_plugged_in: bool,
}
async fn is_plugged_in_from_file(path: &Path) -> Result<bool, RayhunterError> {
match tokio::fs::read_to_string(path)
.await
.map_err(RayhunterError::TokioError)?
.chars()
.next()
{
Some('0') => Ok(false),
Some('1') => Ok(true),
_ => Err(RayhunterError::BatteryPluggedInStatusParseError),
}
}
async fn get_level_from_percentage_file(path: &Path) -> Result<u8, RayhunterError> {
tokio::fs::read_to_string(path)
.await
.map_err(RayhunterError::TokioError)?
.trim_end()
.parse()
.or(Err(RayhunterError::BatteryLevelParseError))
}
pub async fn get_battery_status(device: &Device) -> Result<BatteryState, RayhunterError> {
Ok(match device {
Device::Orbic => orbic::get_battery_state().await?,
Device::Wingtech => wingtech::get_battery_state().await?,
Device::Tmobile => tmobile::get_battery_state().await?,
Device::Tplink => tplink::get_battery_state().await?,
_ => return Err(RayhunterError::FunctionNotSupportedForDeviceError),
})
}
pub fn run_battery_notification_worker(
task_tracker: &TaskTracker,
device: Device,
notification_channel: tokio::sync::mpsc::Sender<Notification>,
shutdown_token: CancellationToken,
) {
task_tracker.spawn(async move {
// Don't send a notification initially if the device starts at a low battery level.
let mut triggered = match get_battery_status(&device).await {
Err(RayhunterError::FunctionNotSupportedForDeviceError) => {
info!("Battery status not supported for this device, disabling battery notifications");
return;
}
Err(e) => {
warn!("Failed to get battery status: {e}");
true
}
Ok(status) => status.level <= LOW_BATTERY_LEVEL,
};
loop {
select! {
_ = shutdown_token.cancelled() => break,
_ = tokio::time::sleep(Duration::from_secs(15)) => {}
}
let status = match get_battery_status(&device).await {
Err(RayhunterError::FunctionNotSupportedForDeviceError) => {
info!("Battery status not supported for this device, disabling battery notifications");
break;
}
Err(e) => {
warn!("Failed to get battery status: {e}");
continue;
}
Ok(status) => status,
};
// To avoid flapping, if the notification has already been triggered
// wait until the device has been plugged in and the battery level
// is high enough to re-enable notifications.
if triggered && status.is_plugged_in && status.level > LOW_BATTERY_LEVEL {
triggered = false;
continue;
}
if !triggered && !status.is_plugged_in && status.level <= LOW_BATTERY_LEVEL {
notification_channel
.send(Notification::new(
NotificationType::LowBattery,
"Rayhunter's battery is low".to_string(),
None,
))
.await
.expect("Failed to send to notification channel");
triggered = true;
}
}
});
}