Rust Ver
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,9 +1,2 @@
|
|||||||
venv/
|
/target/
|
||||||
__pycache__/
|
Cargo.lock
|
||||||
*.py[cod]
|
|
||||||
*.egg-info/
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
.pytest_cache/
|
|
||||||
.mypy_cache/
|
|
||||||
.ruff_cache/
|
|
||||||
|
|||||||
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "towerd"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Tower daemon for KW1FOX-1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
ctrlc = "3"
|
||||||
|
rppal = "0.22"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
18
README.md
18
README.md
@@ -3,16 +3,24 @@
|
|||||||
I wrote this to control the "hardware" on KW1FOX-1 tower.
|
I wrote this to control the "hardware" on KW1FOX-1 tower.
|
||||||
|
|
||||||
its.. not really intended for use anywhere else but, if anything inspires you go ahead and grab it!
|
its.. not really intended for use anywhere else but, if anything inspires you go ahead and grab it!
|
||||||
Just some simple python.
|
|
||||||
|
|
||||||
# Install
|
## Install
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo git clone https://git.kitsunehosting.net/Kenwood/towerd.git /opt/towerd
|
sudo git clone https://git.kitsunehosting.net/Kenwood/towerd.git /opt/towerd
|
||||||
cd /opt/towerd
|
cd /opt/towerd
|
||||||
sudo python3 -m venv venv
|
cargo build --release
|
||||||
sudo venv/bin/pip install -r requirements.txt
|
|
||||||
sudo cp systemd/towerd.service /etc/systemd/system/
|
sudo cp systemd/towerd.service /etc/systemd/system/
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now towerd
|
sudo systemctl enable --now towerd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./target/release/towerd --verbose
|
||||||
|
|
||||||
|
# systemd
|
||||||
|
sudo systemctl status towerd
|
||||||
|
sudo journalctl -u towerd -f
|
||||||
|
```
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
gpiozero>=2.0
|
|
||||||
22
src/config.rs
Normal file
22
src/config.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
pub status_pin: u8,
|
||||||
|
pub fan_pin: u8,
|
||||||
|
pub fan_on_temp_c: f64,
|
||||||
|
pub fan_off_temp_c: f64,
|
||||||
|
pub poll_interval_s: f64,
|
||||||
|
pub error_temp_c: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
status_pin: 4,
|
||||||
|
fan_pin: 17,
|
||||||
|
fan_on_temp_c: 40.0,
|
||||||
|
fan_off_temp_c: 35.0,
|
||||||
|
poll_interval_s: 2.0,
|
||||||
|
error_temp_c: 80.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/daemon.rs
Normal file
99
src/daemon.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::gpio::{Status, TowerGpio};
|
||||||
|
use crate::thermal;
|
||||||
|
|
||||||
|
pub struct TowerDaemon {
|
||||||
|
config: Config,
|
||||||
|
gpio: TowerGpio,
|
||||||
|
running: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TowerDaemon {
|
||||||
|
/// Creates a new TowerDaemon instance
|
||||||
|
// fn new is like the constructor and builds this object
|
||||||
|
pub fn new(config: Config) -> anyhow::Result<Self> {
|
||||||
|
let gpio = TowerGpio::new(config.status_pin, config.fan_pin)?;
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
config,
|
||||||
|
gpio,
|
||||||
|
running,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs the daemon
|
||||||
|
pub fn run(mut self) -> anyhow::Result<()> {
|
||||||
|
let running = Arc::clone(&self.running);
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
info!("Received shutdown signal");
|
||||||
|
running.store(false, Ordering::Relaxed);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
status_pin = self.config.status_pin,
|
||||||
|
fan_pin = self.config.fan_pin,
|
||||||
|
"Tower daemon started"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Do tick (main loop)
|
||||||
|
while self.running.load(Ordering::Relaxed) {
|
||||||
|
self.tick()?;
|
||||||
|
thread::sleep(Duration::from_secs_f64(self.config.poll_interval_s));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gpio.stop();
|
||||||
|
info!("Tower daemon stopped");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single daemon tick
|
||||||
|
fn tick(&mut self) -> anyhow::Result<()> {
|
||||||
|
let temp_c = thermal::read_cpu_temp_c()?;
|
||||||
|
self.update_fan(temp_c);
|
||||||
|
self.update_status(temp_c);
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
temp_c,
|
||||||
|
fan = if self.gpio.fan_on() { "on" } else { "off" },
|
||||||
|
status = ?self.gpio.status(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the fan based on temperature
|
||||||
|
fn update_fan(&mut self, temp_c: f64) {
|
||||||
|
if !self.gpio.fan_on() && temp_c >= self.config.fan_on_temp_c {
|
||||||
|
self.gpio.set_fan(true);
|
||||||
|
info!(
|
||||||
|
temp_c,
|
||||||
|
threshold = self.config.fan_on_temp_c,
|
||||||
|
"Fan on"
|
||||||
|
);
|
||||||
|
} else if self.gpio.fan_on() && temp_c <= self.config.fan_off_temp_c {
|
||||||
|
self.gpio.set_fan(false);
|
||||||
|
info!(
|
||||||
|
temp_c,
|
||||||
|
threshold = self.config.fan_off_temp_c,
|
||||||
|
"Fan off"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the status
|
||||||
|
fn update_status(&self, temp_c: f64) {
|
||||||
|
if temp_c >= self.config.error_temp_c {
|
||||||
|
self.gpio.set_status(Status::Error);
|
||||||
|
} else {
|
||||||
|
self.gpio.set_status(Status::Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/gpio.rs
Normal file
144
src/gpio.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use rppal::gpio::{Gpio, OutputPin};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Status {
|
||||||
|
Ok,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SLOW_ON: Duration = Duration::from_secs(1);
|
||||||
|
const SLOW_OFF: Duration = Duration::from_secs(1);
|
||||||
|
const FAST_ON: Duration = Duration::from_millis(150);
|
||||||
|
const FAST_OFF: Duration = Duration::from_millis(150);
|
||||||
|
|
||||||
|
pub struct TowerGpio {
|
||||||
|
_gpio: Gpio,
|
||||||
|
fan: OutputPin,
|
||||||
|
status: Arc<Mutex<Status>>,
|
||||||
|
blink_shutdown: Arc<AtomicBool>,
|
||||||
|
blink_handle: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TowerGpio {
|
||||||
|
/// Sets up the GPIO pins and spawns the blink thread
|
||||||
|
pub fn new(status_pin: u8, fan_pin: u8) -> anyhow::Result<Self> {
|
||||||
|
let gpio = Gpio::new().context("failed to initialize GPIO")?;
|
||||||
|
|
||||||
|
// Setup the fan
|
||||||
|
let mut fan = gpio
|
||||||
|
.get(fan_pin)
|
||||||
|
.context("failed to open fan pin")?
|
||||||
|
.into_output();
|
||||||
|
fan.set_low();
|
||||||
|
|
||||||
|
// Setup the status LED
|
||||||
|
let status_led = gpio
|
||||||
|
.get(status_pin)
|
||||||
|
.context("failed to open status pin")?
|
||||||
|
.into_output();
|
||||||
|
|
||||||
|
let status = Arc::new(Mutex::new(Status::Ok));
|
||||||
|
let blink_shutdown = Arc::new(AtomicBool::new(false)); // A signal to use to stop the blink thread
|
||||||
|
|
||||||
|
let blink_status = Arc::clone(&status); // Shared reference for the current status
|
||||||
|
let blink_stop = Arc::clone(&blink_shutdown); // Shared reference to the shutdown signal
|
||||||
|
let blink_handle = thread::spawn(move || {
|
||||||
|
blink_loop(status_led, blink_status, blink_stop); // Spawn the blink thread
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self { // Return the struct with the GPIO pins and the blink thread handle
|
||||||
|
_gpio: gpio,
|
||||||
|
fan,
|
||||||
|
status,
|
||||||
|
blink_shutdown,
|
||||||
|
blink_handle: Some(blink_handle),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current status
|
||||||
|
pub fn status(&self) -> Status {
|
||||||
|
*self.status.lock().expect("status lock poisoned")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the fan is on
|
||||||
|
pub fn fan_on(&self) -> bool {
|
||||||
|
self.fan.is_set_high()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the current status
|
||||||
|
pub fn set_status(&self, status: Status) {
|
||||||
|
let mut current = self.status.lock().expect("status lock poisoned");
|
||||||
|
if *current == status {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*current = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the fan on or off
|
||||||
|
pub fn set_fan(&mut self, on: bool) {
|
||||||
|
if on {
|
||||||
|
self.fan.set_high();
|
||||||
|
} else {
|
||||||
|
self.fan.set_low();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the GPIO pins and join the blink thread
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.blink_shutdown.store(true, Ordering::Relaxed);
|
||||||
|
if let Some(handle) = self.blink_handle.take() {
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
self.fan.set_low();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For when the tower GPIO is dropped
|
||||||
|
impl Drop for TowerGpio {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The blink loop for the status LED
|
||||||
|
fn blink_loop(mut led: OutputPin, status: Arc<Mutex<Status>>, shutdown: Arc<AtomicBool>) {
|
||||||
|
while !shutdown.load(Ordering::Relaxed) {
|
||||||
|
let current = *status.lock().expect("status lock poisoned");
|
||||||
|
let (on_time, off_time) = match current {
|
||||||
|
Status::Ok => (SLOW_ON, SLOW_OFF),
|
||||||
|
Status::Error => (FAST_ON, FAST_OFF),
|
||||||
|
};
|
||||||
|
|
||||||
|
led.set_high();
|
||||||
|
if sleep_or_shutdown(on_time, &shutdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
led.set_low();
|
||||||
|
if sleep_or_shutdown(off_time, &shutdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
led.set_low();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep_or_shutdown(duration: Duration, shutdown: &AtomicBool) -> bool {
|
||||||
|
let step = Duration::from_millis(50);
|
||||||
|
let mut remaining = duration;
|
||||||
|
while remaining > Duration::ZERO {
|
||||||
|
if shutdown.load(Ordering::Relaxed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let slice = remaining.min(step);
|
||||||
|
thread::sleep(slice);
|
||||||
|
remaining = remaining.saturating_sub(slice);
|
||||||
|
}
|
||||||
|
shutdown.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
38
src/main.rs
Normal file
38
src/main.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
mod config;
|
||||||
|
mod daemon;
|
||||||
|
mod gpio;
|
||||||
|
mod thermal;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::daemon::TowerDaemon;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "towerd", about = "Tower Daemon")]
|
||||||
|
struct Args {
|
||||||
|
#[arg(short, long, help = "Enable debug logging")]
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let default_level = if args.verbose {
|
||||||
|
Level::DEBUG
|
||||||
|
} else {
|
||||||
|
Level::INFO
|
||||||
|
};
|
||||||
|
let filter = EnvFilter::builder()
|
||||||
|
.with_default_directive(default_level.into())
|
||||||
|
.from_env_lossy();
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(filter)
|
||||||
|
.with_target(false)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
TowerDaemon::new(Config::default())?.run()
|
||||||
|
}
|
||||||
21
src/thermal.rs
Normal file
21
src/thermal.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
const THERMAL_ZONE: &str = "/sys/class/thermal/thermal_zone0/temp";
|
||||||
|
|
||||||
|
pub fn read_cpu_temp_c() -> anyhow::Result<f64> {
|
||||||
|
let path = Path::new(THERMAL_ZONE);
|
||||||
|
if !path.exists() {
|
||||||
|
// Bail throws an error and exits for us
|
||||||
|
anyhow::bail!(
|
||||||
|
"Thermal sensor not found at {THERMAL_ZONE}. Maybe running on bad hardware?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let millidegrees: i32 = fs::read_to_string(path)?
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| anyhow::anyhow!("failed to parse thermal reading: {e}"))?;
|
||||||
|
|
||||||
|
Ok(millidegrees as f64 / 1000.0)
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ After=multi-user.target
|
|||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/opt/towerd
|
WorkingDirectory=/opt/towerd
|
||||||
ExecStart=/opt/towerd/venv/bin/python -m towerd
|
ExecStart=/opt/towerd/target/release/towerd
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
"""Tower Daemon"""
|
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from towerd.main import main
|
|
||||||
|
|
||||||
main()
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
from gpiozero import LED, OutputDevice
|
|
||||||
|
|
||||||
|
|
||||||
class Status(Enum):
|
|
||||||
OK = "ok"
|
|
||||||
ERROR = "error"
|
|
||||||
|
|
||||||
|
|
||||||
# Blink timings (seconds)
|
|
||||||
SLOW_ON = 1.0
|
|
||||||
SLOW_OFF = 1.0
|
|
||||||
FAST_ON = 0.15
|
|
||||||
FAST_OFF = 0.15
|
|
||||||
|
|
||||||
|
|
||||||
class TowerGPIO:
|
|
||||||
"""GPIO controls"""
|
|
||||||
|
|
||||||
def __init__(self, status_pin: int = 4, fan_pin: int = 17) -> None:
|
|
||||||
self._status_led = LED(status_pin)
|
|
||||||
self._fan = OutputDevice(fan_pin, active_high=True, initial_value=False)
|
|
||||||
self._status = Status.OK
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status(self) -> Status:
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fan_on(self) -> bool:
|
|
||||||
return self._fan.value
|
|
||||||
|
|
||||||
def set_status(self, status: Status) -> None:
|
|
||||||
if status == self._status:
|
|
||||||
return
|
|
||||||
self._status = status
|
|
||||||
self._apply_blink()
|
|
||||||
|
|
||||||
def set_fan(self, on: bool) -> None:
|
|
||||||
if on:
|
|
||||||
self._fan.on()
|
|
||||||
else:
|
|
||||||
self._fan.off()
|
|
||||||
|
|
||||||
def start(self) -> None:
|
|
||||||
self._apply_blink()
|
|
||||||
|
|
||||||
def stop(self) -> None:
|
|
||||||
self._status_led.off()
|
|
||||||
self._fan.off()
|
|
||||||
self._status_led.close()
|
|
||||||
self._fan.close()
|
|
||||||
|
|
||||||
def _apply_blink(self) -> None:
|
|
||||||
if self._status is Status.OK:
|
|
||||||
self._status_led.blink(on_time=SLOW_ON, off_time=SLOW_OFF, background=True)
|
|
||||||
else:
|
|
||||||
self._status_led.blink(on_time=FAST_ON, off_time=FAST_OFF, background=True)
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import signal
|
|
||||||
import time
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from towerd.gpio_ctrl import Status, TowerGPIO
|
|
||||||
from towerd.thermal import read_cpu_temp_c
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Config:
|
|
||||||
status_pin: int = 4
|
|
||||||
fan_pin: int = 17
|
|
||||||
fan_on_temp_c: float = 50.0
|
|
||||||
fan_off_temp_c: float = 45.0
|
|
||||||
poll_interval_s: float = 2.0
|
|
||||||
error_temp_c: float = 80.0
|
|
||||||
|
|
||||||
|
|
||||||
class TowerDaemon:
|
|
||||||
def __init__(self, config: Config) -> None:
|
|
||||||
self.config = config
|
|
||||||
self._running = True
|
|
||||||
self._gpio = TowerGPIO(
|
|
||||||
status_pin=config.status_pin,
|
|
||||||
fan_pin=config.fan_pin,
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
signal.signal(signal.SIGTERM, self._handle_signal)
|
|
||||||
signal.signal(signal.SIGINT, self._handle_signal)
|
|
||||||
|
|
||||||
self._gpio.start()
|
|
||||||
log.info(
|
|
||||||
"Tower daemon started (status GPIO %d, fan GPIO %d)",
|
|
||||||
self.config.status_pin,
|
|
||||||
self.config.fan_pin,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
while self._running:
|
|
||||||
self._tick()
|
|
||||||
time.sleep(self.config.poll_interval_s)
|
|
||||||
finally:
|
|
||||||
self._gpio.stop()
|
|
||||||
log.info("Tower daemon stopped")
|
|
||||||
|
|
||||||
def _tick(self) -> None:
|
|
||||||
temp_c = read_cpu_temp_c()
|
|
||||||
self._update_fan(temp_c)
|
|
||||||
self._update_status(temp_c)
|
|
||||||
|
|
||||||
log.debug(
|
|
||||||
"temp=%.1fC fan=%s status=%s",
|
|
||||||
temp_c,
|
|
||||||
"on" if self._gpio.fan_on else "off",
|
|
||||||
self._gpio.status.value,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _update_fan(self, temp_c: float) -> None:
|
|
||||||
if not self._gpio.fan_on and temp_c >= self.config.fan_on_temp_c:
|
|
||||||
self._gpio.set_fan(True)
|
|
||||||
log.info("Fan on (%.1fC >= %.1fC)", temp_c, self.config.fan_on_temp_c)
|
|
||||||
elif self._gpio.fan_on and temp_c <= self.config.fan_off_temp_c:
|
|
||||||
self._gpio.set_fan(False)
|
|
||||||
log.info("Fan off (%.1fC <= %.1fC)", temp_c, self.config.fan_off_temp_c)
|
|
||||||
|
|
||||||
def _update_status(self, temp_c: float) -> None:
|
|
||||||
if temp_c >= self.config.error_temp_c:
|
|
||||||
self._gpio.set_status(Status.ERROR)
|
|
||||||
else:
|
|
||||||
self._gpio.set_status(Status.OK)
|
|
||||||
|
|
||||||
def _handle_signal(self, signum: int, _frame) -> None:
|
|
||||||
log.info("Received signal %s, shutting down", signum)
|
|
||||||
self._running = False
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser(description="Tower Daemon")
|
|
||||||
parser.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
||||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
||||||
)
|
|
||||||
|
|
||||||
TowerDaemon(Config()).run()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
THERMAL_ZONE = Path("/sys/class/thermal/thermal_zone0/temp")
|
|
||||||
|
|
||||||
|
|
||||||
def read_cpu_temp_c() -> float:
|
|
||||||
"""Get the CPU temp"""
|
|
||||||
if not THERMAL_ZONE.exists():
|
|
||||||
raise FileNotFoundError(
|
|
||||||
f"Thermal sensor not found at {THERMAL_ZONE}. "
|
|
||||||
"Maybe running on bad hardware?"
|
|
||||||
)
|
|
||||||
millidegrees = int(THERMAL_ZONE.read_text().strip())
|
|
||||||
return millidegrees / 1000.0
|
|
||||||
Reference in New Issue
Block a user