POC import

This commit is contained in:
Martin Sekera 2020-04-12 18:45:15 +02:00
commit b1f112b2a4
2 changed files with 158 additions and 0 deletions

24
config.json.example Normal file
View file

@ -0,0 +1,24 @@
{
"control": {
"interval": 5, // [s] delay between rounds
"verbose": true
},
"sensor": {
"source": "/sys/class/thermal/thermal_zone0/temp",
"weight": 0.1 // [0.0-1.0] sample contribution weight
// lower value -> slower response
},
"pwm": {
"chip": 1,
"channel": 0,
"period": 100000 // [ns]
},
"map": [
// each tuple is [m°C, percentage of period]
[25000, 0],
[35000, 50],
[45000, 75],
[50000, 90],
[55000, 100]
]
}

134
fand.py Executable file
View file

@ -0,0 +1,134 @@
#! /usr/bin/env python3
# encoding: utf-8
"""
Fan control service.
Measures temperatures and sets fan PWM according to a configured curve.
See /etc/fand/config.json.example for more.
"""
import dec8
import os
import sys
import time
class Sensor:
def __init__(self, source, weight=0.2):
self.f = open(source, "rb")
self.weight = weight
self.value = None
def measure(self):
self.f.seek(0)
raw = int(self.f.read())
if self.value is None:
self.value = raw
else:
self.value = int(self.weight * raw + (1-self.weight) * self.value)
return self.value
class PWM:
def __init__(self, chip, ch, period):
self.chip_base = "/sys/class/pwm/pwmchip%d/" %(chip)
self.ch_base = "/sys/class/pwm/pwmchip%d/pwm%d/" %(chip, ch)
self.period = period
# export the channel to sysfs if necessary
if not os.path.isdir(self.ch_base):
with open(self.chip_base + "export", "w") as f:
f.write(str(ch))
# disable channel
self.set_enabled(False)
# set period & polarity
with open(self.ch_base + "period", "w") as f:
f.write(str(period))
with open(self.ch_base + "polarity", "w") as f:
f.write("normal")
# acquire a duty cycle file descriptor
self.f_dc = open(self.ch_base + "duty_cycle", "w")
# set to idle
self.set_percent(0)
# re-enable channel
self.set_enabled(True)
def set_enabled(self, enabled):
with open(self.ch_base + "enable", "r+") as f:
current = f.read().strip()
requested = "1" if enabled else "0"
if current != requested:
f.seek(0)
f.write(requested)
def set_percent(self, pc):
self.duty_cycle = int(self.period * pc/100)
self.f_dc.seek(0)
self.f_dc.write(str(self.duty_cycle))
self.f_dc.flush()
def linlut(a, seq):
"""
Given a value `a` and a sorted list of tuples `seq`,
finds a pair in `seq` whose first elements are closest
to ... well you know what I mean. Read the code. It's
shorter than this paragraph.
"""
smaller = None
larger = None
for d in seq:
if d[0] <= a:
smaller = d
elif d[0] > a:
larger = d
break
if not smaller:
return seq[0][1]
if not larger:
return seq[-1][1]
ratio = (a - smaller[0]) / (larger[0] - smaller[0])
dy = larger[1] - smaller[1]
return smaller[1] + ratio * dy
def printf(fmt, *args, stream=sys.stderr):
print(fmt % args, file=stream)
if __name__ == "__main__":
try:
cfg = dec8.cfg.Config.from_file("/etc/fand/config.json")
except FileNotFoundError:
printf("!!! missing /etc/fand/config.json")
s = Sensor(cfg.sensor.source, cfg.sensor.weight)
p = PWM(cfg.pwm.chip, cfg.pwm.channel, cfg.pwm.period)
while True:
temperature = s.measure()
percentage = linlut(temperature, cfg.map)
p.set_percent(percentage)
if cfg.control.verbose:
printf("%.01f °C -> %d %% (%d/%d µs)",
temperature,
percentage,
p.duty_cycle // 1000,
p.period // 1000
)
time.sleep(cfg.control.interval)