Initial commit for rewrite

This commit is contained in:
2025-04-05 00:50:12 +03:00
commit 5b686e38c4
9 changed files with 1053 additions and 0 deletions

58
src/config.rs Normal file
View 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
View 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
View 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
View 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)
}
}