Rework fan PWM control

pull/593/head
Luke Palmer 2017-05-11 00:26:17 -04:00
rodzic 7d7c2abe35
commit 01cc01f8fa
7 zmienionych plików z 269 dodań i 89 usunięć

Wyświetl plik

@ -10,10 +10,15 @@ all:
make xdump978
make xdump1090
make xgen_gdl90
make fancontrol
xgen_gdl90:
go get -t -d -v ./main ./test ./godump978 ./uatparse
go build $(BUILDINFO) -p 4 main/gen_gdl90.go main/traffic.go main/gps.go main/network.go main/managementinterface.go main/sdr.go main/ping.go main/uibroadcast.go main/monotonic.go main/datalog.go main/equations.go
go build $(BUILDINFO) -p 4 main/gen_gdl90.go main/traffic.go main/gps.go main/network.go main/managementinterface.go main/sdr.go main/ping.go main/uibroadcast.go main/monotonic.go main/datalog.go main/equations.go main/cputemp.go
fancontrol:
go get -t -d -v ./main
go build $(BUILDINFO) -p 4 main/fancontrol.go main/equations.go main/cputemp.go
xdump1090:
git submodule update --init
@ -21,7 +26,6 @@ xdump1090:
xdump978:
cd dump978 && make lib
sudo cp -f ./libdump978.so /usr/lib/libdump978.so
.PHONY: test
test:
@ -33,6 +37,10 @@ www:
install:
cp -f gen_gdl90 /usr/bin/gen_gdl90
chmod 755 /usr/bin/gen_gdl90
cp -f fancontrol /usr/bin/fancontrol
chmod 755 /usr/bin/fancontrol
/usr/bin/fancontrol remove
/usr/bin/fancontrol install
cp image/10-stratux.rules /etc/udev/rules.d/10-stratux.rules
cp image/99-uavionix.rules /etc/udev/rules.d/99-uavionix.rules
rm -f /etc/init.d/stratux
@ -42,11 +50,12 @@ install:
chmod 744 /root/stratux-pre-start.sh
ln -fs /lib/systemd/system/stratux.service /etc/systemd/system/multi-user.target.wants/stratux.service
make www
cp -f libdump978.so /usr/lib/libdump978.so
cp -f dump1090/dump1090 /usr/bin/
cp -f image/hostapd_manager.sh /usr/sbin/
cp -f image/stratux-wifi.sh /usr/sbin/
clean:
rm -f gen_gdl90 libdump978.so
rm -f gen_gdl90 libdump978.so fancontrol
cd dump1090 && make clean
cd dump978 && make clean

Wyświetl plik

@ -1,45 +0,0 @@
#!/usr/bin/python
# @Author Ryan Dewsbury (helno)
#
# This script throttles a fan based on CPU temperature.
#
# It expects a fan that's externally powered, and uses GPIO pin 12 for control.
import RPi.GPIO as GPIO
import time
import os
from daemon import runner
class FanControl():
# Return CPU temperature as float
def getCPUtemp(self):
cTemp = os.popen('vcgencmd measure_temp').readline()
return float(cTemp.replace("temp=","").replace("'C\n",""))
def __init__(self):
self.stdin_path = '/dev/null'
self.stdout_path = '/var/log/fancontrol.log'
self.stderr_path = '/var/log/fancontrol.log'
self.pidfile_path = '/var/run/fancontrol.pid'
self.pidfile_timeout = 5
def run(self):
GPIO.setmode(GPIO.BOARD)
GPIO.setup(12, GPIO.OUT)
GPIO.setwarnings(False)
p=GPIO.PWM(12, 1000)
PWM = 50
while True:
CPU_temp = self.getCPUtemp()
if CPU_temp > 40.5:
PWM = min(max(PWM + 1, 0), 100)
p.start(PWM)
elif CPU_temp < 39.5:
PWM = min(max(PWM - 1, 0), 100)
p.start(PWM)
time.sleep(5)
GPIO.cleanup()
fancontrol = FanControl()
daemon_runner = runner.DaemonRunner(fancontrol)
daemon_runner.do_action()

Wyświetl plik

@ -17,9 +17,6 @@ if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
/usr/bin/fancontrol.py start
/usr/bin/stratux-screen.py start
exit 0

48
main/cputemp.go 100644
Wyświetl plik

@ -0,0 +1,48 @@
package main
import (
"io/ioutil"
"strconv"
"strings"
"time"
)
const invalidCpuTemp = float32(-99.0)
type CpuTempUpdateFunc func(cpuTemp float32)
/* cpuTempMonitor() reads the RPi board temperature every second and
calls a callback. This is broken out into its own function (run as
its own goroutine) because the RPi temperature monitor code is buggy,
and often times reading this file hangs quite some time. */
func cpuTempMonitor(updater CpuTempUpdateFunc) {
timer := time.NewTicker(1 * time.Second)
for {
<-timer.C
// Update CPUTemp.
temp, err := ioutil.ReadFile("/sys/class/thermal/thermal_zone0/temp")
tempStr := strings.Trim(string(temp), "\n")
t := invalidCpuTemp
if err == nil {
tInt, err := strconv.Atoi(tempStr)
if err == nil {
if tInt > 1000 {
t = float32(tInt) / float32(1000.0)
} else {
t = float32(tInt) // case where Temp is returned as simple integer
}
}
}
if t >= invalidCpuTemp { // Only update if valid value was obtained.
updater(t)
}
}
}
// Check if CPU temperature is valid. Assume <= 0 is invalid.
func isCPUTempValid(cpuTemp float32) bool {
return cpuTemp > 0
}

Wyświetl plik

@ -322,3 +322,18 @@ func distance(lat1, lon1, lat2, lon2 float64) (dist, bearing float64) {
return
}
// golang only defines min/max for float64. Really.
func iMin(x, y int) int {
if x < y {
return x
}
return y
}
func iMax(x, y int) int {
if x > y {
return x
}
return y
}

190
main/fancontrol.go 100644
Wyświetl plik

@ -0,0 +1,190 @@
package main
import (
"flag"
"fmt"
"github.com/takama/daemon"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
)
// #include <wiringPi.h>
// #cgo LDFLAGS: -lwiringPi
import "C"
const (
// CPU temperature target, degrees C
defaultTempTarget = 65.
hysteresis = float32(1.)
/* This puts our PWM frequency at 19.2 MHz / 2048 =
/* 9.4kHz. Higher frequencies will reduce audible switching
/* noise but will be less efficient */
pwmClockDivisor = 2048
/* Minimum duty cycle is the point below which the fan does
/* not spin. This depends on both your fan and the switching
/* transistor used. */
defaultPwmDutyMin = 80
pwmDutyMax = 256
// how often to update
delaySeconds = 2
// GPIO-12 on a Raspberry PI 3
defaultPin = 26
// name of the service
name = "fancontrol"
description = "cooling fan speed control based on CPU temperature"
// port which daemon should be listen
port = ":9977"
)
var stdlog, errlog *log.Logger
func fanControl(pwmDutyMin int, pin int, tempTarget float32) {
cPin := C.int(pin)
C.wiringPiSetup()
C.pwmSetMode(C.PWM_MODE_BAL)
C.pinMode(cPin, C.PWM_OUTPUT)
C.pwmSetRange(pwmDutyMax)
C.pwmSetClock(pwmClockDivisor)
C.pwmWrite(cPin, C.int(pwmDutyMin))
temp := float32(0.)
go cpuTempMonitor(func(cpuTemp float32) {
if isCPUTempValid(cpuTemp) {
temp = cpuTemp
}
})
pwmDuty := 0
for {
if temp > (tempTarget + hysteresis) {
pwmDuty = iMax(iMin(pwmDutyMax, pwmDuty+1), pwmDutyMin)
} else if temp < (tempTarget - hysteresis) {
pwmDuty = iMax(pwmDuty-1, 0)
if pwmDuty < pwmDutyMin {
pwmDuty = 0
}
}
//log.Println(temp, " ", pwmDuty)
C.pwmWrite(cPin, C.int(pwmDuty))
time.Sleep(delaySeconds * time.Second)
}
}
// Service has embedded daemon
type Service struct {
daemon.Daemon
}
// Manage by daemon commands or run the daemon
func (service *Service) Manage() (string, error) {
tempTarget := flag.Float64("temp", defaultTempTarget, "Target CPU Temperature, degrees C")
pwmDutyMin := flag.Int("minduty", defaultPwmDutyMin, "Minimum PWM duty cycle")
pin := flag.Int("pin", defaultPin, "PWM pin (wiringPi numbering)")
flag.Parse()
usage := "Usage: " + name + " install | remove | start | stop | status"
// if received any kind of command, do it
if flag.NArg() > 0 {
command := os.Args[flag.NFlag()+1]
switch command {
case "install":
return service.Install()
case "remove":
return service.Remove()
case "start":
return service.Start()
case "stop":
return service.Stop()
case "status":
return service.Status()
default:
return usage, nil
}
}
go fanControl(*pwmDutyMin, *pin, float32(*tempTarget))
// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)
// Set up listener for defined host and port
listener, err := net.Listen("tcp", port)
if err != nil {
return "Possibly was a problem with the port binding", err
}
// set up channel on which to send accepted connections
listen := make(chan net.Conn, 100)
go acceptConnection(listener, listen)
// loop work cycle with accept connections or interrupt
// by system signal
for {
select {
case conn := <-listen:
go handleClient(conn)
case killSignal := <-interrupt:
stdlog.Println("Got signal:", killSignal)
stdlog.Println("Stoping listening on ", listener.Addr())
listener.Close()
if killSignal == os.Interrupt {
return "Daemon was interrupted by system signal", nil
}
return "Daemon was killed", nil
}
}
}
// Accept a client connection and collect it in a channel
func acceptConnection(listener net.Listener, listen chan<- net.Conn) {
for {
conn, err := listener.Accept()
if err != nil {
continue
}
listen <- conn
}
}
func handleClient(client net.Conn) {
for {
buf := make([]byte, 4096)
numbytes, err := client.Read(buf)
if numbytes == 0 || err != nil {
return
}
client.Write(buf[:numbytes])
}
}
func init() {
stdlog = log.New(os.Stdout, "", 0)
errlog = log.New(os.Stderr, "", 0)
}
func main() {
srv, err := daemon.New(name, description, []string{}...)
if err != nil {
errlog.Println("Error: ", err)
os.Exit(1)
}
service := &Service{srv}
status, err := service.Manage()
if err != nil {
errlog.Println(status, "\nError: ", err)
os.Exit(1)
}
fmt.Println(status)
}

Wyświetl plik

@ -455,7 +455,7 @@ func makeStratuxStatus() []byte {
}
// Valid/Enabled: CPU temperature portion.
if isCPUTempValid() {
if isCPUTempValid(globalStatus.CPUTemp) {
msg[13] = msg[13] | (1 << 4)
}
@ -746,42 +746,6 @@ func updateMessageStats() {
}
// Check if CPU temperature is valid. Assume <= 0 is invalid.
func isCPUTempValid() bool {
return globalStatus.CPUTemp > 0
}
/*
cpuTempMonitor() reads the RPi board temperature every second and updates it in globalStatus.
This is broken out into its own function (run as its own goroutine) because the RPi temperature
monitor code is buggy, and often times reading this file hangs quite some time.
*/
func cpuTempMonitor() {
timer := time.NewTicker(1 * time.Second)
for {
<-timer.C
// Update CPUTemp.
temp, err := ioutil.ReadFile("/sys/class/thermal/thermal_zone0/temp")
tempStr := strings.Trim(string(temp), "\n")
t := float32(-99.0)
if err == nil {
tInt, err := strconv.Atoi(tempStr)
if err == nil {
if tInt > 1000 {
t = float32(tInt) / float32(1000.0)
} else {
t = float32(tInt) // case where Temp is returned as simple integer
}
}
}
if t >= -99.0 { // Only update if valid value was obtained.
globalStatus.CPUTemp = t
}
}
}
func updateStatus() {
if mySituation.Quality == 2 {
globalStatus.GPS_solution = "GPS + SBAS (WAAS)"
@ -1489,7 +1453,9 @@ func main() {
go printStats()
// Monitor RPi CPU temp.
go cpuTempMonitor()
go cpuTempMonitor(func(cpuTemp float32)(){
globalStatus.CPUTemp = cpuTemp
})
reader := bufio.NewReader(os.Stdin)