kopia lustrzana https://github.com/cyoung/stratux
197 wiersze
4.8 KiB
Go
197 wiersze
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"github.com/takama/daemon"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// #include <wiringPi.h>
|
|
// #cgo LDFLAGS: -lwiringPi
|
|
import "C"
|
|
|
|
const (
|
|
// CPU temperature target, degrees C
|
|
defaultTempTarget = 50.
|
|
hysteresis = float32(1.)
|
|
|
|
pwmClockDivisor = 100
|
|
|
|
/* 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 = 1
|
|
pwmDutyMax = 10
|
|
|
|
// Temperature at which we give up attempting active fan speed control and set it to full speed.
|
|
failsafeTemp = 65
|
|
|
|
// how often to update
|
|
delaySeconds = 30
|
|
|
|
// GPIO-1/BCM "18"/Pin 12 on a Raspberry Pi 3
|
|
defaultPin = 1
|
|
|
|
// name of the service
|
|
name = "fancontrol"
|
|
description = "cooling fan speed control based on CPU temperature"
|
|
|
|
// Address on which daemon should be listen.
|
|
addr = ":9977"
|
|
)
|
|
|
|
type FanControl struct {
|
|
TempTarget float32
|
|
TempCurrent float32
|
|
PWMDutyMin int
|
|
PWMDutyMax int
|
|
PWMDutyCurrent int
|
|
PWMPin int
|
|
}
|
|
|
|
var myFanControl FanControl
|
|
|
|
var stdlog, errlog *log.Logger
|
|
|
|
func fanControl() {
|
|
cPin := C.int(myFanControl.PWMPin)
|
|
|
|
C.wiringPiSetup()
|
|
|
|
// Power on "test". Allows the user to verify that their fan is working.
|
|
C.pinMode(cPin, C.OUTPUT)
|
|
C.digitalWrite(cPin, C.HIGH)
|
|
time.Sleep(5 * time.Second)
|
|
C.digitalWrite(cPin, C.LOW)
|
|
|
|
C.pwmSetMode(C.PWM_MODE_MS)
|
|
C.pinMode(cPin, C.PWM_OUTPUT)
|
|
C.pwmSetRange(C.uint(myFanControl.PWMDutyMax))
|
|
C.pwmSetClock(pwmClockDivisor)
|
|
C.pwmWrite(cPin, C.int(myFanControl.PWMDutyMin))
|
|
myFanControl.TempCurrent = 0
|
|
go cpuTempMonitor(func(cpuTemp float32) {
|
|
if isCPUTempValid(cpuTemp) {
|
|
myFanControl.TempCurrent = cpuTemp
|
|
}
|
|
})
|
|
|
|
myFanControl.PWMDutyCurrent = 0
|
|
|
|
delay := time.NewTicker(delaySeconds * time.Second)
|
|
|
|
for {
|
|
if myFanControl.TempCurrent > (myFanControl.TempTarget + hysteresis) {
|
|
myFanControl.PWMDutyCurrent = iMax(iMin(myFanControl.PWMDutyMax, myFanControl.PWMDutyCurrent+1), myFanControl.PWMDutyMin)
|
|
} else if myFanControl.TempCurrent < (myFanControl.TempTarget - hysteresis) {
|
|
myFanControl.PWMDutyCurrent = iMax(myFanControl.PWMDutyCurrent-1, 0)
|
|
if myFanControl.PWMDutyCurrent < myFanControl.PWMDutyMin {
|
|
myFanControl.PWMDutyCurrent = 0
|
|
}
|
|
}
|
|
//log.Println(myFanControl.TempCurrent, " ", myFanControl.PWMDutyCurrent)
|
|
C.pwmWrite(cPin, C.int(myFanControl.PWMDutyCurrent))
|
|
<-delay.C
|
|
if myFanControl.PWMDutyCurrent == myFanControl.PWMDutyMax && myFanControl.TempCurrent >= failsafeTemp {
|
|
// Reached the maximum temperature. We stop using PWM control and set the fan to "on" permanently.
|
|
break
|
|
}
|
|
}
|
|
|
|
// Default to "ON".
|
|
C.pinMode(cPin, C.OUTPUT)
|
|
C.digitalWrite(cPin, C.HIGH)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
myFanControl.TempTarget = float32(*tempTarget)
|
|
myFanControl.PWMDutyMin = *pwmDutyMin
|
|
myFanControl.PWMDutyMax = pwmDutyMax
|
|
myFanControl.PWMPin = *pin
|
|
|
|
go fanControl()
|
|
|
|
// 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)
|
|
|
|
http.HandleFunc("/", handleStatusRequest)
|
|
http.ListenAndServe(addr, nil)
|
|
|
|
// interrupt by system signal
|
|
for {
|
|
killSignal := <-interrupt
|
|
stdlog.Println("Got signal:", killSignal)
|
|
if killSignal == os.Interrupt {
|
|
return "Daemon was interrupted by system signal", nil
|
|
}
|
|
return "Daemon was killed", nil
|
|
}
|
|
}
|
|
|
|
func handleStatusRequest(w http.ResponseWriter, r *http.Request) {
|
|
statusJSON, _ := json.Marshal(&myFanControl)
|
|
w.Write(statusJSON)
|
|
}
|
|
|
|
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)
|
|
}
|