package sensor import ( "fmt" "sync" "time" ) // Step is one scripted outcome for a Fake. type Step struct { Reading Reading Err error // Block holds the Read for this long before returning, so callers can // exercise their read deadline. Block time.Duration } // Fake is a scripted Sensor for tests and for `just run-fake`. It replays // Steps in order and repeats the last one once the script runs out. type Fake struct { mu sync.Mutex steps []Step index int closed bool // Now supplies the timestamp when a Step leaves Reading.At zero. Now func() time.Time } // NewFake returns a Fake that replays steps. func NewFake(steps ...Step) *Fake { return &Fake{steps: steps, Now: time.Now} } // Read returns the next scripted outcome. func (f *Fake) Read() (Reading, error) { f.mu.Lock() if f.closed { f.mu.Unlock() return Reading{}, fmt.Errorf("read after close") } if len(f.steps) == 0 { f.mu.Unlock() return Reading{}, fmt.Errorf("fake sensor has no steps") } step := f.steps[f.index] if f.index < len(f.steps)-1 { f.index++ } now := f.Now f.mu.Unlock() if step.Block > 0 { time.Sleep(step.Block) } if step.Err != nil { return Reading{}, step.Err } r := step.Reading if r.At.IsZero() { r.At = now() } return r, nil } // Close marks the Fake closed; later reads fail. func (f *Fake) Close() error { f.mu.Lock() defer f.mu.Unlock() f.closed = true return nil } // Discharging returns a script that starts on mains and then drains, which is // the sequence the shutdown state machine must accept. func Discharging(mainsTicks, batteryTicks int) *Fake { steps := make([]Step, 0, mainsTicks+batteryTicks) for i := 0; i < mainsTicks; i++ { steps = append(steps, Step{Reading: Reading{ACPresent: true, Volts: 4.10, SOC: 100}}) } for i := 0; i < batteryTicks; i++ { steps = append(steps, Step{Reading: Reading{ ACPresent: false, Volts: 3.60 - float64(i)*0.02, SOC: float64(30 - i), }}) } return NewFake(steps...) }