Compare commits

..

5 commits

Author SHA1 Message Date
Rahix e2fc15ed9b succd: Use tigher hysteresis values
All checks were successful
/ test (pull_request) Successful in 10s
/ test (push) Successful in 10s
It seems we can get away with less hysteresis still.  Make the values a
bit smaller.
2024-10-04 23:30:28 +02:00
Rahix ef959f4be3 succd: Configure hysteresis for rough and high thresholds
Add CLI flags for rough and high hysteresis and configure default values
based on experiments.
2024-10-04 23:30:28 +02:00
Rahix 8645718748 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.
2024-10-04 23:30:28 +02:00
Rahix ed8adad611 succd: Fix table jiggle
Make sure the table size doesn't dynamically jiggle with movement of
width changes of the load-average value.
2024-10-04 23:30:28 +02:00
Rahix 313569e1dc succd: Use grid layout to show all info on a single screen
Use CSS grids to all information on a single screen if possible.  There
is a breakpoint for smaller screens.
2024-10-04 23:30:28 +02:00
4 changed files with 70 additions and 29 deletions

View file

@ -5,12 +5,11 @@
<link rel="shortcut icon" type="image/png" href="/favicon.png"> <link rel="shortcut icon" type="image/png" href="/favicon.png">
<style> <style>
body { body {
font-size: 14px; font-size: 12px;
padding: 2em; padding: 2em;
} }
table { table {
font-size: 40px; font-size: 40px;
margin-top: 1em;
} }
table.status td { table.status td {
width: 2em; width: 2em;
@ -52,6 +51,16 @@ td > span {
.logo > img { .logo > img {
height: 10em; height: 10em;
} }
.main-grid {
margin: 2em;
clear: both;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(50em, 1fr));
column-gap: 2em;
row-gap: 2em;
}
</style> </style>
<div class="logo"><img src="/favicon.png" /></div> <div class="logo"><img src="/favicon.png" /></div>
@ -59,19 +68,7 @@ td > span {
<h1>succd</h1> <h1>succd</h1>
<h2>nothing more permanent than a temporary solution</h2> <h2>nothing more permanent than a temporary solution</h2>
<p style="margin-top: 2em; clear: both;"> <div class="main-grid">
<table>
<tr>
<th rowspan="2">Pirani Gauge</th>
<th>Voltage</th>
<td id="volts">{{ .Pirani.Volts }}</td>
</tr>
<tr>
<th>Pressure</th>
<td id="mbar">{{ .Pirani.Mbar }}</td>
</tr>
</table>
<table class="status"> <table class="status">
<tr> <tr>
<th>Thresholds</th> <th>Thresholds</th>
@ -96,6 +93,19 @@ td > span {
</tr> </tr>
</table> </table>
<table>
<tr>
<th rowspan="2">Pirani Gauge</th>
<th>Voltage</th>
<td id="volts">{{ .Pirani.Volts }}</td>
</tr>
<tr>
<th>Pressure</th>
<td id="mbar">{{ .Pirani.Mbar }}</td>
</tr>
</table>
<table> <table>
<tr> <tr>
<th rowspan="3">Control</th> <th rowspan="3">Control</th>
@ -122,13 +132,14 @@ td > span {
<th>Status</th> <th>Status</th>
<td id="status" colspan="1">OK</td> <td id="status" colspan="1">OK</td>
<th>Load</th> <th>Load</th>
<td id="load" colspan="1">...</td> <td id="load" colspan="1" style="width: 4em;">...</td>
</tr> </tr>
</table> </table>
</p>
<p style="margin-top: 2em;"> <p>
<canvas id="graph" width="1024" height="512" style="max-width: 100%;"></canvas> <canvas id="graph" width="1024" height="512" style="max-width: 100%;"></canvas>
</p> </p>
</div>
<p style="font-style: italic; font-size: 12px; margin-top: 5em;"> <p style="font-style: italic; font-size: 12px; margin-top: 5em;">
{{ .System.Hostname }} | load: {{ .System.Load }} | <a href="/debug/pprof">pprof</a> | <a href="/metrics">metrics</a> | ws ping: <span id="ping"></span> {{ .System.Hostname }} | load: {{ .System.Load }} | <a href="/debug/pprof">pprof</a> | <a href="/metrics">metrics</a> | ws ping: <span id="ping"></span>

View file

@ -11,17 +11,21 @@ import (
) )
var ( var (
flagFake bool flagFake bool
flagListenHTTP string flagListenHTTP string
flagPressureThresholdRough = ScientificNotationValue(1e-1) flagPressureThresholdRough = ScientificNotationValue(1e-1)
flagPressureThresholdHigh = ScientificNotationValue(1e-4) flagPressureThresholdRoughHysteresis = ScientificNotationValue(2e-2)
flagPressureThresholdHigh = ScientificNotationValue(1e-4)
flagPressureThresholdHighHysteresis = ScientificNotationValue(2e-5)
) )
func main() { func main() {
flag.BoolVar(&flagFake, "fake", false, "Enable fake mode which allows to run succd for tests outside the succbone") flag.BoolVar(&flagFake, "fake", false, "Enable fake mode which allows to run succd for tests outside the succbone")
flag.StringVar(&flagListenHTTP, "listen_http", ":8080", "Address at which to listen for HTTP requests") flag.StringVar(&flagListenHTTP, "listen_http", ":8080", "Address at which to listen for HTTP requests")
flag.TextVar(&flagPressureThresholdRough, "pressure_threshold_rough", &flagPressureThresholdRough, "Threshold for opening up diffusion pump (mbar)") flag.TextVar(&flagPressureThresholdRough, "pressure_threshold_rough", &flagPressureThresholdRough, "Threshold for opening up diffusion pump (mbar)")
flag.TextVar(&flagPressureThresholdRoughHysteresis, "pressure_threshold_rough_hysteresis", &flagPressureThresholdRoughHysteresis, "+-Hysteresis for rough threshold (mbar)")
flag.TextVar(&flagPressureThresholdHigh, "pressure_threshold_high", &flagPressureThresholdHigh, "Threshold for enabling high voltage circuits (mbar)") flag.TextVar(&flagPressureThresholdHigh, "pressure_threshold_high", &flagPressureThresholdHigh, "Threshold for enabling high voltage circuits (mbar)")
flag.TextVar(&flagPressureThresholdHighHysteresis, "pressure_threshold_high_hysteresis", &flagPressureThresholdHighHysteresis, "+-Hysteresis for high threshold (mbar)")
flag.Parse() flag.Parse()
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
@ -32,7 +36,9 @@ func main() {
d.daemonState.piraniVolts100.limit = 100 d.daemonState.piraniVolts100.limit = 100
d.aboveRough.threshold = float64(flagPressureThresholdRough) d.aboveRough.threshold = float64(flagPressureThresholdRough)
d.aboveRough.hysteresis = float64(flagPressureThresholdRoughHysteresis)
d.aboveHigh.threshold = float64(flagPressureThresholdHigh) d.aboveHigh.threshold = float64(flagPressureThresholdHigh)
d.aboveHigh.hysteresis = float64(flagPressureThresholdHighHysteresis)
if flagFake { if flagFake {
klog.Infof("Starting with fake peripherals") klog.Infof("Starting with fake peripherals")
d.adcPirani = &fakeADC{} d.adcPirani = &fakeADC{}

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) {