kopia lustrzana https://github.com/cyoung/stratux
Rework fan PWM control
rodzic
7d7c2abe35
commit
01cc01f8fa
15
Makefile
15
Makefile
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue