Skip to content

Commit

Permalink
fix config parsing, make maxPwmChangePerCycle optional, update docume…
Browse files Browse the repository at this point in the history
…ntation
  • Loading branch information
markusressel committed Sep 23, 2024
1 parent cdfd290 commit f9a6c9f
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 13 deletions.
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,45 @@ sensor value.

## Fan Controllers

Fan speed is controlled by a PID controller per each configured fan. The default
The speed of a Fan is controlled using a combination of its curve, a control algorithm and the properties of
the fan controller itself.

The curve is used as the target value for the control algorithm to reach. The control algorithm then calculates the
next PWM value to apply to the fan to reach this target value. The fan controller then applies this PWM value to the
fan, while respecting constraints like the minimum and maximum PWM values, as well as the `neverStop` flag.

### Control Algorithms

A control algorithm
is a function that returns the next PWM value to apply based on the target value calculated by the curve. The simplest
control algorithm is the direct control algorithm, which simply forwards the target value to the fan.

#### Direct Control Algorithm

The simplest control algorithm is the direct control algorithm. It simply forwards the curve value to the fan
controller.

```yaml
fans:
- id: some_fan
...
controlAlgorithm: direct
```

This control algorithm can also be used to approach the curve value more slowly:

```yaml
fans:
- id: some_fan
...
controlAlgorithm:
direct:
maxPwmChangePerCycle: 10
```

### PID Control Algorithm

The PID control algorithm uses a PID loop to approach the target value. The default
configuration is pretty non-aggressive using the following values:

| P | I | D |
Expand All @@ -667,10 +705,11 @@ If you don't like the default behaviour you can configure your own in the config
fans:
- id: some_fan
...
controlLoop:
p: 0.03
i: 0.002
d: 0.0005
controlAlgorithm:
pid:
p: 0.03
i: 0.002
d: 0.0005
```

The loop is advanced at a constant rate, specified by the `controllerAdjustmentTickRate` config option, which
Expand Down
3 changes: 2 additions & 1 deletion cmd/fan/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ var initCmd = &cobra.Command{
p,
fan,
control_loop.NewDirectControlLoop(nil),
configuration.CurrentConfig.ControllerAdjustmentTickRate)
configuration.CurrentConfig.ControllerAdjustmentTickRate,
)

ui.Info("Deleting existing data for fan '%s'...", fan.GetId())

Expand Down
3 changes: 2 additions & 1 deletion fan2go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ fans:
# speed of this fan
curve: cpu_curve
# (Optional) The algorithm how the target speed, determined by the curve is approached.
# default: pid: uses the PID control algorithm with default tuning variables
# direct: the target value will be directly applied to the fan
# pid: uses a PID loop with default tuning variables
controlAlgorithm:
direct:
# together with maxPwmChangePerCycle, fan speeds will approach target value
Expand Down Expand Up @@ -84,6 +84,7 @@ fans:
hwmon:
platform: it8620
rpmChannel: 4
controlAlgorithm: direct
neverStop: true
curve: case_avg_curve

Expand Down
2 changes: 1 addition & 1 deletion internal/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func initializeObjects(pers persistence.Persistence) map[fans.Fan]controller.Fan
)
} else if config.ControlAlgorithm.Direct != nil {
controlLoop = control_loop.NewDirectControlLoop(
&config.ControlAlgorithm.Direct.MaxPwmChangePerCycle,
config.ControlAlgorithm.Direct.MaxPwmChangePerCycle,
)
} else {
controlLoop = control_loop.NewDirectControlLoop(nil)
Expand Down
22 changes: 19 additions & 3 deletions internal/configuration/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,17 @@ func GetFilePath() string {
func LoadConfig() {
// load default configuration values
CurrentConfig = Configuration{}
err := viper.Unmarshal(&CurrentConfig, viper.DecodeHook(mapstructure.TextUnmarshallerHookFunc()))

err := viper.Unmarshal(
&CurrentConfig,
viper.DecodeHook(
mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapstructure.TextUnmarshallerHookFunc(),
),
),
)
if err != nil {
ui.Fatal("unable to decode into struct, %v", err)
}
Expand All @@ -136,15 +146,21 @@ func (s *ControlAlgorithmConfig) UnmarshalText(text []byte) error {
// check if the value matches one of the enum values
switch controlAlgorithm {
case string(Pid):
*s = ControlAlgorithmConfig{Pid: &PidControlAlgorithmConfig{}}
// default configuration for PID control algorithm
*s = ControlAlgorithmConfig{Pid: &PidControlAlgorithmConfig{
0.03,
0.002,
0.0005,
}}
case string(Direct):
// default configuration for Direct control algorithm
*s = ControlAlgorithmConfig{Direct: &DirectControlAlgorithmConfig{}}
default:
// if the value is not one of the enum values, try to unmarshal into a ControlAlgorithmConfig struct
config := ControlAlgorithmConfig{}
err := json.Unmarshal(text, &config)
if err != nil {
return fmt.Errorf("invalid control algorithm: %s", controlAlgorithm)
return fmt.Errorf("invalid control algorithm config: %s", controlAlgorithm)
} else {
*s = config
}
Expand Down
2 changes: 1 addition & 1 deletion internal/configuration/fans.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type ControlAlgorithmConfig struct {
}

type DirectControlAlgorithmConfig struct {
MaxPwmChangePerCycle int `json:"maxPwmChangePerCycle,omitempty"`
MaxPwmChangePerCycle *int `json:"maxPwmChangePerCycle,omitempty"`
}

type PidControlAlgorithmConfig struct {
Expand Down
3 changes: 2 additions & 1 deletion internal/configuration/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ func validateFans(config *Configuration) error {

if fanConfig.ControlAlgorithm != nil {
if fanConfig.ControlAlgorithm.Direct != nil {
if fanConfig.ControlAlgorithm.Direct.MaxPwmChangePerCycle <= 0 {
maxPwmChangePerCycle := fanConfig.ControlAlgorithm.Direct.MaxPwmChangePerCycle
if maxPwmChangePerCycle != nil && *maxPwmChangePerCycle <= 0 {
return fmt.Errorf("fan %s: invalid maxPwmChangePerCycle, must be > 0", fanConfig.ID)
}
}
Expand Down

0 comments on commit f9a6c9f

Please sign in to comment.