Skip to content

Commit

Permalink
Merge pull request #167 from markusressel/feature/#161_override_pwm_m…
Browse files Browse the repository at this point in the history
…ap_from_config

Feature/#161 override pwm map from config
  • Loading branch information
markusressel authored Oct 5, 2022
2 parents 4084731 + af5103c commit fcfb308
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 40 deletions.
9 changes: 9 additions & 0 deletions fan2go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ fans:
# an increased rotational speed compared to lower values.
# Note: you can also use this to limit the max speed of a fan.
maxPwm: 255
# (Optional) Override for the PWM map used internally by fan2go for
# mapping the "normal" 0-255 value range to values supported by this fan.
# This can be used to compensate for a very limited set of supported values
# (f.ex. off, low, high). If not set manually, the map will be computed
# automatically by fan2go during fan initialization.
pwmMap:
0: 0
64: 128
192: 255

- id: in_front
hwmon:
Expand Down
1 change: 1 addition & 0 deletions internal/configuration/fans.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type FanConfig struct {
// StartPwm defines the lowest PWM value where the fans are able to start spinning from a standstill
StartPwm *int `json:"startPwm,omitempty"`
// MaxPwm defines the highest PWM value that yields an RPM increase
PwmMap *map[int]int `json:"pwmMap,omitempty"`
MaxPwm *int `json:"maxPwm,omitempty"`
Curve string `json:"curve"`
HwMon *HwMonFanConfig `json:"hwMon,omitempty"`
Expand Down
105 changes: 77 additions & 28 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controller

import (
"context"
"fmt"
"github.com/markusressel/fan2go/internal/configuration"
"github.com/markusressel/fan2go/internal/curves"
"github.com/markusressel/fan2go/internal/fans"
Expand All @@ -24,7 +25,7 @@ type FanController interface {
// RunInitializationSequence for the given fan to determine its characteristics
RunInitializationSequence() (err error)
// computePwmMap computes a mapping between "requested pwm value" -> "actual set pwm value"
computePwmMap()
ComputePwmMap() (err error)

UpdateFanSpeed() error
}
Expand All @@ -41,7 +42,7 @@ type fanController struct {
originalPwmEnabled fans.ControlMode
// the original pwm value of the fan before starting the controller
originalPwmValue int
// the last pwm value that was set to the fan
// the last pwm value that was set to the fan, **before** applying the pwmMap to it
lastSetPwm *int
// a list of all pwm values where setPwm(x) != setPwm(y) for the controlled fan
pwmValuesWithDistinctTarget []int
Expand Down Expand Up @@ -125,11 +126,7 @@ func (f *fanController) Run(ctx context.Context) error {
return err
}

f.pwmMap, err = f.persistence.LoadFanPwmMap(fan.GetId())
if err != nil {
f.computePwmMap()
f.persistence.SaveFanPwmMap(fan.GetId(), f.pwmMap)
}
f.ComputePwmMap()

f.updateDistinctPwmValues()

Expand Down Expand Up @@ -198,18 +195,29 @@ func (f *fanController) Run(ctx context.Context) error {
func (f *fanController) UpdateFanSpeed() error {
fan := f.fan

currentPwm, err := f.fan.GetPwm()
if err != nil {
return err
lastSetPwm := 0
if f.lastSetPwm != nil {
lastSetPwm = *(f.lastSetPwm)
} else {
pwm, err := f.fan.GetPwm()
if err != nil {
return err
}
lastSetPwm = pwm
}

// calculate the direct optimal target speed
target := f.calculateTargetPwm()

// ask the PID controller how to proceed
pidControllerTarget := math.Ceil(f.pidLoop.Loop(float64(target), float64(currentPwm)))
pidChange := math.Ceil(f.pidLoop.Loop(float64(target), float64(lastSetPwm)))

// the last value set on the pid controller target
pidControllerTarget := math.Ceil(f.pidLoop.Loop(float64(target), float64(lastSetPwm)))
pidControllerTarget = pidControllerTarget + pidChange

// ensure we are within sane bounds
coerced := util.Coerce(float64(currentPwm)+pidControllerTarget, 0, 255)
coerced := util.Coerce(float64(lastSetPwm)+pidControllerTarget, 0, 255)
roundedTarget := int(math.Round(coerced))

if target >= 0 {
Expand All @@ -226,15 +234,8 @@ func (f *fanController) UpdateFanSpeed() error {
func (f *fanController) RunInitializationSequence() (err error) {
fan := f.fan

curveData := map[int]float64{}
f.ComputePwmMap()

if configuration.CurrentConfig.RunFanInitializationInParallel == false {
InitializationSequenceMutex.Lock()
defer InitializationSequenceMutex.Unlock()
}

ui.Info("Computing pwm map...")
f.computePwmMap()
err = f.persistence.SaveFanPwmMap(fan.GetId(), f.pwmMap)
if err != nil {
ui.Error("Unable to persist pwmMap for fan %s", fan.GetId())
Expand All @@ -252,6 +253,8 @@ func (f *fanController) RunInitializationSequence() (err error) {
ui.Warning("Could not enable manual fan mode on %s, trying to continue anyway...", fan.GetId())
}

curveData := map[int]float64{}

initialMeasurement := true
for pwm := range f.pwmValuesWithDistinctTarget {
// set a pwm
Expand Down Expand Up @@ -391,12 +394,9 @@ func (f *fanController) calculateTargetPwm() int {
// TODO: this assumes a linear curve, but it might be something else
target = minPwm + int((float64(target)/fans.MaxPwmValue)*(float64(maxPwm)-float64(minPwm)))

// map the target value to the closest value supported by the fan
target = f.mapToClosestDistinct(target)

if f.lastSetPwm != nil && f.pwmMap != nil {
lastSetPwm := *(f.lastSetPwm)
expected := f.pwmMap[lastSetPwm]
expected := f.mapToClosestDistinct(lastSetPwm)
if currentPwm, err := fan.GetPwm(); err == nil {
if currentPwm != expected {
ui.Warning("PWM of %s was changed by third party! Last set PWM value was: %d but is now: %d",
Expand Down Expand Up @@ -435,15 +435,16 @@ func (f *fanController) calculateTargetPwm() int {
func (f *fanController) setPwm(target int) (err error) {
current, err := f.fan.GetPwm()

closestAvailable := f.mapToClosestDistinct(target)

f.lastSetPwm = &target
if err == nil {
if f.pwmMap[target] == current {
if closestAvailable == current {
// nothing to do
return nil
}
}
err = f.fan.SetPwm(target)
return err
return f.fan.SetPwm(closestAvailable)
}

func (f *fanController) waitForFanToSettle(fan fans.Fan) {
Expand Down Expand Up @@ -475,7 +476,55 @@ func (f *fanController) mapToClosestDistinct(target int) int {
return f.pwmMap[closest]
}

func (f *fanController) computePwmMap() {
func (f *fanController) ComputePwmMap() (err error) {
if configuration.CurrentConfig.RunFanInitializationInParallel == false {
InitializationSequenceMutex.Lock()
defer InitializationSequenceMutex.Unlock()
}

var configOverride *map[int]int

switch f := f.fan.(type) {
case *fans.HwMonFan:
c := f.Config.PwmMap
if c != nil {
configOverride = c
}
case *fans.CmdFan:
c := f.Config.PwmMap
if c != nil {
configOverride = c
}
case *fans.FileFan:
c := f.Config.PwmMap
if c != nil {
configOverride = c
}
default:
// if type is other than above
fmt.Println("Type is unknown!")
}

if configOverride != nil {
ui.Info("Using pwm map override from config...")
f.pwmMap = *configOverride
return nil
}

f.pwmMap, err = f.persistence.LoadFanPwmMap(f.fan.GetId())
if err == nil && f.pwmMap != nil {
ui.Info("FanController: Using saved value for pwm map of Fan '%s'", f.fan.GetId())
return nil
}

ui.Info("Computing pwm map...")
f.computePwmMapAutomatically()

ui.Debug("Saving pwm map to fan...")
return f.persistence.SaveFanPwmMap(f.fan.GetId(), f.pwmMap)
}

func (f *fanController) computePwmMapAutomatically() {
fan := f.fan
trySetManualPwm(fan)

Expand Down
28 changes: 16 additions & 12 deletions internal/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ func TestFanController_UpdateFanSpeed_FanCurveGaps(t *testing.T) {
}
sensors.SensorMap[s.GetId()] = &s

curveValue := 10
curveValue := 5
curve := &MockCurve{
ID: "curve",
Value: curveValue,
Expand All @@ -444,26 +444,30 @@ func TestFanController_UpdateFanSpeed_FanCurveGaps(t *testing.T) {
}
sort.Ints(keys)

pwmMap := map[int]int{
0: 0,
1: 1,
40: 40,
58: 50,
100: 120,
222: 200,
255: 255,
}

controller := fanController{
persistence: mockPersistence{}, fan: fan,
curve: curve,
updateRate: time.Duration(100),
pwmMap: map[int]int{
0: 0,
1: 1,
40: 40,
58: 50,
100: 120,
222: 200,
255: 255,
},
pwmMap: pwmMap,
}
controller.updateDistinctPwmValues()

// WHEN
targetPwm := controller.calculateTargetPwm()

// THEN
assert.Contains(t, keys, targetPwm)
assert.Equal(t, 50, targetPwm)
assert.Equal(t, 54, targetPwm)

closestTarget := controller.mapToClosestDistinct(targetPwm)
assert.Equal(t, 50, closestTarget)
}

0 comments on commit fcfb308

Please sign in to comment.