Skip to content

Commit 5bbec14

Browse files
authored
Merge pull request #512 from epicfail/fix/init-regression-nct6687
fix: honor FanResponseDelay in PWM readback verification; restore fan state after failed or interrupted CLI init
2 parents fc61395 + 7095afc commit 5bbec14

5 files changed

Lines changed: 396 additions & 57 deletions

File tree

cmd/fan/init.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package fan
22

33
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
49
"github.com/markusressel/fan2go/internal"
510
"github.com/markusressel/fan2go/internal/configuration"
611
"github.com/markusressel/fan2go/internal/control_loop"
@@ -56,7 +61,10 @@ var initCmd = &cobra.Command{
5661
return err
5762
}
5863

59-
_, err = fanController.RunInitialization()
64+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
65+
defer stop()
66+
67+
_, err = fanController.RunInitialization(ctx)
6068
if err == nil {
6169
ui.Success("Done!")
6270
// print measured fan curve

internal/controller/controller.go

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ type FanController interface {
4646

4747
// UpdateCurve dynamically updates the curve reference
4848
UpdateCurve(curve curves.SpeedCurve)
49+
50+
// RunInitialization runs the fan initialization sequence.
51+
RunInitialization(ctx context.Context) (map[int]float64, error)
52+
}
53+
54+
type FanStateSnapshot struct {
55+
// the ControlMode the fan was in before the controller started
56+
ControlMode fans.ControlMode
57+
// the raw pwm value read from the fan before the controller started
58+
// Note: this is the raw value, no pwmMap is applied to it
59+
PwmValue int
4960
}
5061

5162
type DefaultFanController struct {
@@ -61,11 +72,8 @@ type DefaultFanController struct {
6172
curve curves.SpeedCurve
6273
// rate to update the target fan speed
6374
updateRate time.Duration
64-
// the original ControlMode state of the fan before starting the controller
65-
originalControlMode fans.ControlMode
66-
// the original pwm value of the fan before starting the controller
67-
// Note: this is the raw value read from the fan, no pwmMap is applied to it
68-
originalPwmValue int
75+
// the fan state as it was before the controller started (nil until captured)
76+
originalFanState *FanStateSnapshot
6977
// the last pwm value that was set to the fan, **before** applying the pwmMap to it
7078
lastTarget *int
7179
// a list of all pre-pwmMap pwm values where setPwm(x) != setPwm(y) for the controlled fan
@@ -180,26 +188,45 @@ func (f *DefaultFanController) prepareController() (err error) {
180188
return err
181189
}
182190

191+
// storeCurrentFanState captures the current fan control mode and PWM value,
192+
// overwriting any previously saved snapshot in originalFanState.
183193
func (f *DefaultFanController) storeCurrentFanState() error {
184194
fan := f.fan
185195
// store original pwm value
186196
pwm, err := f.getPwm()
187197
if err != nil {
188198
ui.Warning("Cannot read pwm value of %s", fan.GetId())
189199
}
190-
f.originalPwmValue = pwm
200+
snapshot := FanStateSnapshot{
201+
PwmValue: pwm,
202+
}
191203

192204
// store original pwm_enable value
193205
if f.fan.Supports(fans.FeatureControlModeRead) {
194206
controlMode, err := fan.GetControlMode()
195207
if err != nil {
196208
ui.Warning("Cannot read pwm_enable value of %s", fan.GetId())
197209
}
198-
f.originalControlMode = controlMode
210+
snapshot.ControlMode = controlMode
199211
}
212+
f.originalFanState = &snapshot
200213
return nil
201214
}
202215

216+
// storeInitialFanState stores the initial fan state snapshot if it hasn't been captured yet.
217+
// Subsequent calls are no-ops to preserve the state before any controller actions.
218+
func (f *DefaultFanController) storeInitialFanState() error {
219+
if f.originalFanState != nil {
220+
return nil
221+
}
222+
return f.storeCurrentFanState()
223+
}
224+
225+
// clearInitialFanState resets the captured initial fan state snapshot.
226+
func (f *DefaultFanController) clearInitialFanState() {
227+
f.originalFanState = nil
228+
}
229+
203230
func (f *DefaultFanController) Run(ctx context.Context) error {
204231
// prepare the controller by initializing persistence and checking the fan
205232
err := f.prepareController()
@@ -208,7 +235,7 @@ func (f *DefaultFanController) Run(ctx context.Context) error {
208235
}
209236

210237
// store the current fan state to restore it when stopping the controller
211-
err = f.storeCurrentFanState()
238+
err = f.storeInitialFanState()
212239
if err != nil {
213240
return err
214241
}
@@ -219,7 +246,7 @@ func (f *DefaultFanController) Run(ctx context.Context) error {
219246
// wait a bit to gather monitoring data
220247
time.Sleep(2*time.Second + configuration.CurrentConfig.TempSensorPollingRate)
221248

222-
fanPwmData, err := f.runInitializationIfNeeded()
249+
fanPwmData, err := f.runInitializationIfNeeded(ctx)
223250
if err != nil {
224251
return err
225252
}
@@ -332,7 +359,7 @@ func (f *DefaultFanController) Run(ctx context.Context) error {
332359
return err
333360
}
334361

335-
func (f *DefaultFanController) runInitializationIfNeeded() (map[int]float64, error) {
362+
func (f *DefaultFanController) runInitializationIfNeeded(ctx context.Context) (map[int]float64, error) {
336363
fan := f.fan
337364
// check if we have data for this fan in persistence,
338365
// if not we need to run the initialization sequence
@@ -342,7 +369,7 @@ func (f *DefaultFanController) runInitializationIfNeeded() (map[int]float64, err
342369
config := fan.GetConfig()
343370
if config.HwMon != nil || config.Nvidia != nil {
344371
ui.Warning("Fan '%s' has not yet been analyzed, starting initialization sequence...", fan.GetId())
345-
fanCurveData, err := f.RunInitialization()
372+
fanCurveData, err := f.RunInitialization(ctx)
346373
if err != nil {
347374
return nil, err
348375
}
@@ -356,7 +383,21 @@ func (f *DefaultFanController) runInitializationIfNeeded() (map[int]float64, err
356383
return fanRpmData, err
357384
}
358385

359-
func (f *DefaultFanController) RunInitialization() (map[int]float64, error) {
386+
func (f *DefaultFanController) RunInitialization(ctx context.Context) (map[int]float64, error) {
387+
err := f.storeInitialFanState()
388+
if err != nil {
389+
return nil, err
390+
}
391+
392+
curveData, err := f.runInitialization(ctx)
393+
if err != nil {
394+
f.restoreControlMode()
395+
return nil, err
396+
}
397+
return curveData, nil
398+
}
399+
400+
func (f *DefaultFanController) runInitialization(ctx context.Context) (map[int]float64, error) {
360401
fan := f.fan
361402

362403
err := f.computeFanSpecificMappings()
@@ -366,9 +407,8 @@ func (f *DefaultFanController) RunInitialization() (map[int]float64, error) {
366407
}
367408

368409
fanAnalyzer := NewFanCurveAnalyzer(f)
369-
curveData, err := fanAnalyzer.RunInitializationSequence()
410+
curveData, err := fanAnalyzer.RunInitializationSequence(ctx)
370411
if err != nil {
371-
f.restoreControlMode()
372412
return nil, err
373413
}
374414

@@ -389,7 +429,6 @@ func (f *DefaultFanController) RunInitialization() (map[int]float64, error) {
389429

390430
fanRpmData, err := f.persistence.LoadFanRpmData(fan)
391431
if err != nil {
392-
f.restoreControlMode()
393432
return nil, err
394433
}
395434

@@ -582,6 +621,13 @@ func trySetManualPwm(fan fans.Fan) error {
582621
}
583622

584623
func (f *DefaultFanController) restoreControlMode() {
624+
if f.originalFanState == nil {
625+
ui.Warning("Skipping fan settings restore for %s, original state was never captured", f.fan.GetId())
626+
return
627+
}
628+
originalControlMode := f.originalFanState.ControlMode
629+
originalPwmValue := f.originalFanState.PwmValue
630+
585631
ui.Info("Trying to restore fan settings for %s...", f.fan.GetId())
586632

587633
var onExit *configuration.OnExitConfig
@@ -602,7 +648,7 @@ func (f *DefaultFanController) restoreControlMode() {
602648
if onExit != nil {
603649
// determine control mode to set on exit, if any
604650
if onExit.Restore != nil {
605-
controlModeToSet = &f.originalControlMode
651+
controlModeToSet = &originalControlMode
606652
} else if onExit.ControlMode != nil {
607653
parsedControlMode, err := parseControlModeValue(*onExit.ControlMode)
608654
if err != nil {
@@ -612,8 +658,8 @@ func (f *DefaultFanController) restoreControlMode() {
612658
}
613659
} else {
614660
// if no explicit control mode to set is provided, but the fan supports writing the control mode and the original mode was not automatic, restore the original mode
615-
if f.originalControlMode != fans.ControlModeAutomatic {
616-
controlModeToSet = &f.originalControlMode
661+
if originalControlMode != fans.ControlModeAutomatic {
662+
controlModeToSet = &originalControlMode
617663
}
618664
}
619665

@@ -623,14 +669,13 @@ func (f *DefaultFanController) restoreControlMode() {
623669
}
624670
} else {
625671
// default restore behavior
626-
controlModeToSet = &f.originalControlMode
672+
controlModeToSet = &originalControlMode
627673
}
628674

629675
if pwmToSet == nil {
630676
// if the original control mode was manual, restore it to manual and set the original PWM value
631677
if controlModeToSet != nil && *controlModeToSet != fans.ControlModeAutomatic {
632678
// if control mode is set to manual but no speed is provided, set the original value
633-
originalPwmValue := f.originalPwmValue
634679
pwmToSet = &originalPwmValue
635680
}
636681
}

0 commit comments

Comments
 (0)