Rahix
0757ec58a8
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.
96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// momentaryOutput is an output that can be triggered for 500ms.
|
|
type momentaryOutput struct {
|
|
// output of the block.
|
|
output bool
|
|
// scheduledOff is when the block should be outputting false again.
|
|
scheduledOff time.Time
|
|
}
|
|
|
|
func (m *momentaryOutput) process() {
|
|
m.output = m.scheduledOff.After(time.Now())
|
|
}
|
|
|
|
func (m *momentaryOutput) trigger() {
|
|
m.scheduledOff = time.Now().Add(time.Millisecond * 500)
|
|
}
|
|
|
|
// thresholdOutput outputs true if a given value is above a setpoint/threshold.
|
|
// It contains debounce logic for processing noisy analog signals.
|
|
type thresholdOutput struct {
|
|
// output of the block.
|
|
output bool
|
|
// debounce is when the debouncer should be inactive again.
|
|
debounce time.Time
|
|
// threshold is the setpoint of the block.
|
|
threshold float64
|
|
// hysteresis around the process setpoint (min/max is threshold +- hysteresis)
|
|
hysteresis float64
|
|
}
|
|
|
|
func (t *thresholdOutput) process(value float64) {
|
|
if time.Now().Before(t.debounce) {
|
|
return
|
|
}
|
|
new := t.output
|
|
if t.output {
|
|
new = value > (t.threshold - t.hysteresis)
|
|
} else {
|
|
new = value > (t.threshold + t.hysteresis)
|
|
}
|
|
if new != t.output {
|
|
t.output = new
|
|
t.debounce = time.Now().Add(time.Second * 5)
|
|
}
|
|
}
|
|
|
|
// ringbufferInput accumulates analog data up to limit samples, and calculates
|
|
// an average.
|
|
type ringbufferInput struct {
|
|
data []float32
|
|
limit uint
|
|
// avg is the mean average of the samples in data, or 0.0 if no data is
|
|
// present yet. This is the main output of the block.
|
|
avg float32
|
|
}
|
|
|
|
func (r *ringbufferInput) process(input float32) {
|
|
// TODO(q3k): use actual ringbuffer
|
|
// TODO(q3k): optimize average calculation
|
|
// TODO(q3k): precalculate value in mbar
|
|
r.data = append(r.data, input)
|
|
trim := len(r.data) - int(r.limit)
|
|
if trim > 0 {
|
|
r.data = r.data[trim:]
|
|
}
|
|
avg := float32(0.0)
|
|
for _, v := range r.data {
|
|
avg += v
|
|
}
|
|
if len(r.data) != 0 {
|
|
avg /= float32(len(r.data))
|
|
}
|
|
r.avg = avg
|
|
}
|
|
|
|
// saturated returns true if the number of samples is at the configured limit.
|
|
func (r *ringbufferInput) saturated() bool {
|
|
return len(r.data) >= int(r.limit)
|
|
}
|
|
|
|
type pfeifferVoltsToMbar struct {
|
|
mbar float32
|
|
}
|
|
|
|
func (p *pfeifferVoltsToMbar) process(volts float32) {
|
|
// Per Pirani probe docs.
|
|
bar := math.Pow(10.0, float64(volts)-8.5)
|
|
p.mbar = float32(bar * 1000.0)
|
|
}
|