From fcfb544f541367011d69b803b7b90182c1afedf9 Mon Sep 17 00:00:00 2001 From: Markus Ressel Date: Wed, 5 Oct 2022 19:23:22 +0200 Subject: [PATCH 1/3] modified controller logic to support custom pwmMap definition --- internal/configuration/fans.go | 1 + internal/controller/controller.go | 105 ++++++++++++++++++------- internal/controller/controller_test.go | 29 ++++--- 3 files changed, 95 insertions(+), 40 deletions(-) diff --git a/internal/configuration/fans.go b/internal/configuration/fans.go index 9507de2..e47b637 100644 --- a/internal/configuration/fans.go +++ b/internal/configuration/fans.go @@ -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"` diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 70114da..c41ae98 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -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" @@ -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 } @@ -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 @@ -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() @@ -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 { @@ -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()) @@ -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 @@ -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", @@ -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) { @@ -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) diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index 0f70dd5..1c1d95b 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -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, @@ -444,19 +444,21 @@ 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() @@ -464,6 +466,9 @@ func TestFanController_UpdateFanSpeed_FanCurveGaps(t *testing.T) { targetPwm := controller.calculateTargetPwm() // THEN - assert.Contains(t, keys, targetPwm) - assert.Equal(t, 50, targetPwm) + assert.Contains(t, pwmMap, targetPwm) + assert.Equal(t, 54, targetPwm) + + closestTarget := controller.mapToClosestDistinct(targetPwm) + assert.Equal(t, 50, closestTarget) } From 7a9485f51e3227227b8ab7fa86443195933566e9 Mon Sep 17 00:00:00 2001 From: Markus Ressel Date: Wed, 5 Oct 2022 20:25:42 +0200 Subject: [PATCH 2/3] test fix --- internal/controller/controller_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index 1c1d95b..71f131b 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -466,7 +466,6 @@ func TestFanController_UpdateFanSpeed_FanCurveGaps(t *testing.T) { targetPwm := controller.calculateTargetPwm() // THEN - assert.Contains(t, pwmMap, targetPwm) assert.Equal(t, 54, targetPwm) closestTarget := controller.mapToClosestDistinct(targetPwm) From af5103c67cd33031500927a111f97529ae8e7fc6 Mon Sep 17 00:00:00 2001 From: Markus Ressel Date: Wed, 5 Oct 2022 21:04:37 +0200 Subject: [PATCH 3/3] added example configuration --- fan2go.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fan2go.yaml b/fan2go.yaml index 4cf09c8..e16ddd2 100644 --- a/fan2go.yaml +++ b/fan2go.yaml @@ -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: