Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#10 incremental avg #11

Merged
merged 4 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,9 @@ dbPath: "/etc/fan2go/fan2go.db"
# The rate to poll temperature sensors at.
tempSensorPollingRate: 200ms
# The number of sensor items to keep in a rolling window array.
rollingWindowSize: 100
rollingWindowSize: 50
# The rate to poll fan RPM input sensors at.
rpmPollingRate: 1s
# Time to wait before increasing the (initially measured) startPWM of a fan
increaseStartPwmAfter: 10s
# The rate to update fan speed targets at.
controllerAdjustmentTickRate: 200ms

Expand Down Expand Up @@ -203,15 +201,14 @@ All of this is saved to a local database, so it is only needed once per fan conf
### Monitoring

To monitor changes in temperature sensor values, a goroutine is started which continuously reads the `tempX_input` files
of all sensors specified in the config. Sensor values are stored in a moving window of size `rollingWindowSize` (see
of all sensors specified in the config. Sensor values are stored as a moving average of size `rollingWindowSize` (see
configuration).

### Fan Controllers

To update the fan speed, one goroutine is started **per fan**, which continuously adjusts the PWM value of a given fan
based on the sensor data measured by the monitor. This means:

* calculating the average temperature per sensor using the rolling window data
* calculating the ratio between the average temp and the max/min values defined in the config
* calculating the target PWM of a fan using the previous ratio, taking its startPWM and maxPWM into account
* applying the calculated target PWM to the fan
Expand Down
9 changes: 3 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,17 @@ func readConfigFile() {
}

func validateConfig() {
config := &internal.CurrentConfig
if config.IncreaseStartPwmAfter <= config.RpmPollingRate {
log.Fatalf("increaseStartPwmAfter must be greater or equal to rpmPollingRate")
}
//config := &internal.CurrentConfig
// nothing yet
}

func setDefaultValues() {
viper.SetDefault("dbpath", "/etc/fan2go/fan2go.db")
viper.SetDefault("TempSensorPollingRate", 200*time.Millisecond)
viper.SetDefault("TempRollingWindowSize", 100)
viper.SetDefault("TempRollingWindowSize", 50)
viper.SetDefault("RpmPollingRate", 1*time.Second)
viper.SetDefault("RpmRollingWindowSize", 10)

viper.SetDefault("IncreaseStartPwmAfter", 10*time.Second)
viper.SetDefault("ControllerAdjustmentTickRate", 200*time.Millisecond)

viper.SetDefault("sensors", []internal.SensorConfig{})
Expand Down
4 changes: 1 addition & 3 deletions fan2go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ dbPath: "/etc/fan2go/fan2go.db"
# The rate to poll temperature sensors at.
tempSensorPollingRate: 200ms
# The number of sensor items to keep in a rolling window array.
rollingWindowSize: 100
rollingWindowSize: 50
# The rate to poll fan RPM input sensors at.
rpmPollingRate: 1s
# Time to wait before increasing the (initially measured) startPWM of a fan
increaseStartPwmAfter: 10s
# The rate to update fan speed targets at.
controllerAdjustmentTickRate: 200ms

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/markusressel/fan2go

go 1.13
go 1.16

require (
github.com/asecurityteam/rolling v2.0.4+incompatible
Expand Down
72 changes: 33 additions & 39 deletions internal/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/oklog/run"
bolt "go.etcd.io/bbolt"
"log"
"math"
"os"
"os/exec"
"os/signal"
Expand Down Expand Up @@ -158,7 +157,7 @@ func sensorMonitor(ctx context.Context, sensor *Sensor, tick <-chan time.Time) e
case <-ctx.Done():
return nil
case <-tick:
err := updateSensor(*sensor)
err := updateSensor(sensor)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -206,9 +205,7 @@ func mapConfigToControllers(controllers []*Controller) {
if err != nil {
log.Fatalf("Error reading sensor %s: %s", sensorConfig.Id, err.Error())
}
for i := 0; i < CurrentConfig.TempRollingWindowSize; i++ {
sensor.Values.Append(float64(currentValue))
}
sensor.MovingAvg = float64(currentValue)
}
}
}
Expand All @@ -223,7 +220,7 @@ func measureRpm(fan *Fan) {
log.Printf("Measured RPM of %d at PWM %d for fan %s", rpm, pwm, fan.Config.Id)
}

fan.RpmInputWindow.Append(float64(rpm))
fan.RpmMovingAvg = updateSimpleMovingAvg(fan.RpmMovingAvg, CurrentConfig.RpmRollingWindowSize, float64(rpm))

pwmRpmMap := fan.FanCurveData
pointWindow, ok := (*pwmRpmMap)[pwm]
Expand Down Expand Up @@ -276,19 +273,19 @@ func updatePwmBoundaries(fan *Fan) {
}

// read the current value of a sensor and append it to the moving window
func updateSensor(sensor Sensor) (err error) {
func updateSensor(sensor *Sensor) (err error) {
value, err := util.ReadIntFromFile(sensor.Input)
if err != nil {
return err
}

values := sensor.Values
values.Append(float64(value))
if value > sensor.Config.Max {
var n = CurrentConfig.TempRollingWindowSize
sensor.MovingAvg = updateSimpleMovingAvg(sensor.MovingAvg, n, float64(value))
if value > int(sensor.Config.Max) {
// if the value is higher than the specified max temperature,
// insert the value twice into the moving window,
// to give it a bigger impact
values.Append(float64(value))
sensor.MovingAvg = updateSimpleMovingAvg(sensor.MovingAvg, n, float64(value))
}

return nil
Expand Down Expand Up @@ -414,10 +411,9 @@ func calculateTargetSpeed(fan *Fan) int {
minTemp := sensor.Config.Min * 1000 // degree to milli-degree
maxTemp := sensor.Config.Max * 1000

var avgTemp int
avgTemp = int(sensor.Values.Reduce(rolling.Avg))
var avgTemp = sensor.MovingAvg

//log.Printf("Avg temp of %s: %d", sensor.name, avgTemp)
log.Printf("Avg temp of %s: %f", sensor.Config.Id, avgTemp)

if avgTemp >= maxTemp {
// full throttle if max temp is reached
Expand All @@ -427,7 +423,7 @@ func calculateTargetSpeed(fan *Fan) int {
return 0
}

ratio := (float64(avgTemp) - float64(minTemp)) / (float64(maxTemp) - float64(minTemp))
ratio := (avgTemp - minTemp) / (maxTemp - minTemp)
return int(ratio * 255)
}

Expand Down Expand Up @@ -485,20 +481,16 @@ func createFans(devicePath string) []*Fan {
log.Fatal(err)
}

rpmWindowSize := int(math.Ceil(float64(CurrentConfig.IncreaseStartPwmAfter.Milliseconds()) / float64(CurrentConfig.RpmPollingRate.Milliseconds())))
log.Printf("rpmWindowSize: %d", rpmWindowSize)
rpmWindow := rolling.NewWindow(rpmWindowSize)

fan := &Fan{
Name: file,
Index: index,
PwmOutput: output,
RpmInput: inputs[idx],
RpmInputWindow: rolling.NewPointPolicy(rpmWindow),
StartPwm: MinPwmValue,
MaxPwm: MaxPwmValue,
FanCurveData: &map[int]*rolling.PointPolicy{},
LastSetPwm: InitialLastSetPwm,
Name: file,
Index: index,
PwmOutput: output,
RpmInput: inputs[idx],
RpmMovingAvg: 0,
StartPwm: MinPwmValue,
MaxPwm: MaxPwmValue,
FanCurveData: &map[int]*rolling.PointPolicy{},
LastSetPwm: InitialLastSetPwm,
}

// store original pwm_enabled value
Expand Down Expand Up @@ -529,10 +521,9 @@ func createSensors(devicePath string) []*Sensor {
}

sensors = append(sensors, &Sensor{
Name: file,
Index: index,
Input: input,
Values: rolling.NewPointPolicy(rolling.NewWindow(CurrentConfig.TempRollingWindowSize)),
Name: file,
Index: index,
Input: input,
})
}

Expand Down Expand Up @@ -630,7 +621,7 @@ func setPwm(fan *Fan, pwm int) (err error) {
// make sure fans never stop by validating the current RPM
// and adjusting the target PWM value upwards if necessary
if fan.Config.NeverStop && fan.LastSetPwm == target {
avgRpm := fan.RpmInputWindow.Reduce(rolling.Avg)
avgRpm := fan.RpmMovingAvg
if avgRpm <= 0 {
if target >= maxPwm {
log.Printf("CRITICAL: Fan avg. RPM is %f, even at PWM value %d", avgRpm, target)
Expand All @@ -639,12 +630,10 @@ func setPwm(fan *Fan, pwm int) (err error) {
log.Printf("WARNING: Increasing startPWM of %s from %d to %d, which is supposed to never stop, but RPM is %f", fan.Config.Id, fan.StartPwm, fan.StartPwm+1, avgRpm)
fan.StartPwm++
target++
// initialize the complete window with values > 0 to make sure
// the whole window is refilled before increasing the minPWM value again
measurementCount := int(fan.RpmInputWindow.Reduce(rolling.Count))
for i := 0; i < measurementCount; i++ {
fan.RpmInputWindow.Append(1)
}

// set the moving avg to a value > 0 to prevent
// this increase from happening too fast
fan.RpmMovingAvg = 1
}
}

Expand All @@ -667,3 +656,8 @@ func GetRpm(fan *Fan) int {
}
return value
}

// calculates the new moving average, based on an existing average and buffer size
func updateSimpleMovingAvg(oldAvg float64, n int, newValue float64) float64 {
return oldAvg + (1/float64(n))*(newValue-oldAvg)
}
5 changes: 2 additions & 3 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ type Configuration struct {
ControllerAdjustmentTickRate time.Duration
TempRollingWindowSize int
RpmRollingWindowSize int
IncreaseStartPwmAfter time.Duration
Sensors []SensorConfig
Fans []FanConfig
}
Expand All @@ -20,8 +19,8 @@ type SensorConfig struct {
Id string
Platform string
Index int
Min int
Max int
Min float64
Max float64
}

type FanConfig struct {
Expand Down
12 changes: 6 additions & 6 deletions internal/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Fan struct {
Name string `json:"name"`
Index int `json:"index"`
RpmInput string `json:"rpminput"`
RpmInputWindow *rolling.PointPolicy `json:"rpminputwindow"`
RpmMovingAvg float64 `json:"rpmmovingavg"`
PwmOutput string `json:"pwmoutput"`
Config *FanConfig `json:"config"`
StartPwm int `json:"startpwm"` // lowest PWM value where the fans are still spinning
Expand All @@ -29,9 +29,9 @@ type Fan struct {
}

type Sensor struct {
Name string `json:"name"`
Index int `json:"index"`
Input string `json:"string"`
Config *SensorConfig `json:"config"`
Values *rolling.PointPolicy `json:"values"`
Name string `json:"name"`
Index int `json:"index"`
Input string `json:"string"`
Config *SensorConfig `json:"config"`
MovingAvg float64 `json:"movingaverage"`
}