succd: Add hysteresis feature to thresholdOutput blocks

Add a hysteresis value that can be optionally configured for
thresholdOutput blocks.  This will hopefully help to prevent jumping
outputs from feedback that is caused by the thresholdOutput itself.
This commit is contained in:
Rahix 2024-10-04 22:33:22 +02:00
parent d50ffc0b58
commit 0259ca7ac3
2 changed files with 29 additions and 5 deletions

View file

@ -30,13 +30,20 @@ type thresholdOutput struct {
debounce time.Time debounce time.Time
// threshold is the setpoint of the block. // threshold is the setpoint of the block.
threshold float64 threshold float64
// hysteresis around the process setpoint (min/max is threshold +- hysteresis)
hysteresis float64
} }
func (t *thresholdOutput) process(value float64) { func (t *thresholdOutput) process(value float64) {
if time.Now().Before(t.debounce) { if time.Now().Before(t.debounce) {
return return
} }
new := value > t.threshold new := t.output
if t.output {
new = value > (t.threshold - t.hysteresis)
} else {
new = value > (t.threshold + t.hysteresis)
}
if new != t.output { if new != t.output {
t.output = new t.output = new
t.debounce = time.Now().Add(time.Second * 5) t.debounce = time.Now().Add(time.Second * 5)

View file

@ -26,20 +26,37 @@ func TestMomentaryOutput(t *testing.T) {
func TestThresholdOutput(t *testing.T) { func TestThresholdOutput(t *testing.T) {
to := thresholdOutput{ to := thresholdOutput{
threshold: 1, threshold: 1,
hysteresis: 0.2,
} }
to.process(0) to.process(0.7)
if to.output { if to.output {
t.Fatalf("output shouldn't have triggered") t.Fatalf("output shouldn't have triggered")
} }
to.process(2) to.process(1)
if to.output {
t.Fatalf("output shouldn't have triggered")
}
to.process(1.3)
if !to.output { if !to.output {
t.Fatalf("output should have triggered") t.Fatalf("output should have triggered")
} }
to.process(0) to.process(1)
if !to.output {
t.Fatalf("output should have triggered")
}
to.process(0.7)
if !to.output { if !to.output {
t.Fatalf("output should have triggered (in debounce)") t.Fatalf("output should have triggered (in debounce)")
} }
// let debounce timeout pass
time.Sleep(time.Second * 6)
to.process(0.7)
if to.output {
t.Fatalf("output shouldn't have triggered")
}
} }
func TestRingbufferInput(t *testing.T) { func TestRingbufferInput(t *testing.T) {