Initial commit for rewrite
This commit is contained in:
58
src/config.rs
Normal file
58
src/config.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_temp_path")]
|
||||
pub temp_path: String,
|
||||
|
||||
#[serde(default = "default_gpio_line")]
|
||||
pub gpio_line: u32,
|
||||
|
||||
#[serde(default = "default_gpio_chip")]
|
||||
pub gpio_chip: String,
|
||||
|
||||
#[serde(default = "default_log_path")]
|
||||
pub log_path: String,
|
||||
|
||||
#[serde(default = "default_threshold")]
|
||||
pub threshold: f32,
|
||||
|
||||
#[serde(default = "default_variance")]
|
||||
pub variance: f32,
|
||||
|
||||
#[serde(default = "default_interval_ms")]
|
||||
pub interval_ms: u64,
|
||||
}
|
||||
|
||||
fn default_temp_path() -> String {
|
||||
String::from("/sys/class/thermal/thermal_zone0/temp")
|
||||
}
|
||||
|
||||
fn default_gpio_line() -> u32 { 17 }
|
||||
fn default_gpio_chip() -> String { String::from("/dev/gpiochip0") }
|
||||
fn default_log_path() -> String { String::from("/var/log/fan_control.log") }
|
||||
fn default_threshold() -> f32 { 55.0 }
|
||||
fn default_variance() -> f32 { 5.0 }
|
||||
fn default_interval_ms() -> u64 { 1500 }
|
||||
|
||||
impl Config {
|
||||
pub fn load(config_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
if Path::new(config_path).exists() {
|
||||
let contents = fs::read_to_string(config_path)?;
|
||||
let config: Config = toml::from_str(&contents)?;
|
||||
Ok(config)
|
||||
} else {
|
||||
Ok(Config {
|
||||
temp_path: default_temp_path(),
|
||||
gpio_line: default_gpio_line(),
|
||||
gpio_chip: default_gpio_chip(),
|
||||
log_path: default_log_path(),
|
||||
threshold: default_threshold(),
|
||||
variance: default_variance(),
|
||||
interval_ms: default_interval_ms(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/fan.rs
Normal file
82
src/fan.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use log::info;
|
||||
use std::io;
|
||||
use libgpiod::line;
|
||||
use libgpiod::line::{Direction, Value};
|
||||
use libgpiod::request::Request;
|
||||
|
||||
pub struct FanController {
|
||||
gpio_line: Request,
|
||||
line_num: u32,
|
||||
is_running: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
impl FanController {
|
||||
// Helper method to convert libgpiod errors to io::Error
|
||||
fn to_io_error<E>(err: E) -> io::Error
|
||||
where
|
||||
E: std::error::Error + Send + Sync + 'static
|
||||
{
|
||||
io::Error::new(io::ErrorKind::Other, err)
|
||||
}
|
||||
|
||||
pub fn new(chip_path: String, line_num: u32) -> Result<Self, io::Error> {
|
||||
let mut settings = line::Settings::new()
|
||||
.map_err(Self::to_io_error)?;
|
||||
settings
|
||||
.set_direction(Direction::Output)
|
||||
.map_err(Self::to_io_error)?
|
||||
.set_output_value(Value::InActive)
|
||||
.map_err(Self::to_io_error)?; // Ensure fan is off initially
|
||||
|
||||
let mut lconfig = line::Config::new()
|
||||
.map_err(Self::to_io_error)?;
|
||||
lconfig.add_line_settings(&[line_num], settings)
|
||||
.map_err(Self::to_io_error)?;
|
||||
|
||||
let mut rconfig = libgpiod::request::Config::new()
|
||||
.map_err(Self::to_io_error)?;
|
||||
rconfig.set_consumer("toggle-line-value")
|
||||
.map_err(Self::to_io_error)?;
|
||||
|
||||
let chip = libgpiod::chip::Chip::open(&chip_path)
|
||||
.map_err(Self::to_io_error)?;
|
||||
let request = chip.request_lines(Some(&rconfig), &lconfig)
|
||||
.map_err(Self::to_io_error)?;
|
||||
|
||||
Ok(FanController {
|
||||
gpio_line: request,
|
||||
line_num,
|
||||
is_running: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, temperature: f32, threshold: f32, variance: f32) -> Result<(), io::Error> {
|
||||
if !self.is_running && temperature >= threshold + variance {
|
||||
self.gpio_line.set_value(self.line_num, Value::Active)
|
||||
.map_err(Self::to_io_error)?;
|
||||
self.is_running = true;
|
||||
info!("Setting pin GPIO17 to HIGH");
|
||||
} else if self.is_running {
|
||||
if temperature <= threshold - variance {
|
||||
self.gpio_line.set_value(self.line_num, Value::InActive)
|
||||
.map_err(Self::to_io_error)?;
|
||||
self.is_running = false;
|
||||
info!("Setting pin GPIO17 to LOW");
|
||||
} else {
|
||||
info!("Pin GPIO17 is already HIGH");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FanController {
|
||||
fn drop(&mut self) {
|
||||
// Ensure fan is turned off when program exits
|
||||
if let Err(e) = self.gpio_line.set_value(self.line_num, Value::InActive) {
|
||||
eprintln!("Failed to turn off fan during shutdown: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/main.rs
Normal file
84
src/main.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
mod config;
|
||||
mod temperature;
|
||||
mod fan;
|
||||
|
||||
use config::Config;
|
||||
use temperature::TemperatureMonitor;
|
||||
use fan::FanController;
|
||||
use log::{info, error, LevelFilter};
|
||||
use simplelog::{WriteLogger, Config as LogConfig};
|
||||
use std::{fs::File, thread, time::Duration};
|
||||
use std::process;
|
||||
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
|
||||
|
||||
fn main() {
|
||||
// Set up signal handling for clean shutdown
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let r = running.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
info!("Received shutdown signal, exiting...");
|
||||
r.store(false, Ordering::SeqCst);
|
||||
}).expect("Error setting Ctrl-C handler");
|
||||
|
||||
// Load configuration
|
||||
let config_path = "fan_control_config.toml";
|
||||
let config = match Config::load(config_path) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Error loading configuration: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize logging
|
||||
if let Err(e) = WriteLogger::init(
|
||||
LevelFilter::Info,
|
||||
LogConfig::default(),
|
||||
File::create(&config.log_path).unwrap_or_else(|_| {
|
||||
eprintln!("Failed to open log file, logging to stdout");
|
||||
process::exit(1);
|
||||
}),
|
||||
) {
|
||||
eprintln!("Failed to initialize logger: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
info!("Fan control daemon starting");
|
||||
info!("Configuration: temperature path={}, GPIO chip={}, GPIO line={}, threshold={}°C, variance={}°C",
|
||||
config.temp_path, config.gpio_chip, config.gpio_line, config.threshold, config.variance);
|
||||
|
||||
// Initialize temperature monitor
|
||||
let temp_monitor = TemperatureMonitor::new(config.temp_path);
|
||||
|
||||
// Initialize fan controller
|
||||
let mut fan_controller = match FanController::new(config.gpio_chip, config.gpio_line) {
|
||||
Ok(controller) => controller,
|
||||
Err(e) => {
|
||||
error!("Failed to initialize fan controller: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Main loop
|
||||
info!("Entering main control loop");
|
||||
while running.load(Ordering::SeqCst) {
|
||||
// Read temperature
|
||||
match temp_monitor.read_celsius() {
|
||||
Ok(temperature) => {
|
||||
// Update fan state based on temperature
|
||||
if let Err(e) = fan_controller.update(temperature, config.threshold, config.variance) {
|
||||
error!("Failed to update fan state: {}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to read temperature: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep for the configured interval
|
||||
thread::sleep(Duration::from_millis(config.interval_ms));
|
||||
}
|
||||
|
||||
info!("Fan control daemon shutting down");
|
||||
}
|
||||
21
src/temperature.rs
Normal file
21
src/temperature.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
|
||||
pub struct TemperatureMonitor {
|
||||
temp_path: String,
|
||||
}
|
||||
|
||||
impl TemperatureMonitor {
|
||||
pub fn new(temp_path: String) -> Self {
|
||||
TemperatureMonitor { temp_path }
|
||||
}
|
||||
|
||||
pub fn read_celsius(&self) -> Result<f32, io::Error> {
|
||||
let temp_str = fs::read_to_string(&self.temp_path)?;
|
||||
let temp_millicelsius = temp_str.trim().parse::<u32>()
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
// Convert millidegrees to degrees
|
||||
Ok(temp_millicelsius as f32 / 1000.0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user