169 lines
4.2 KiB
Go
169 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/simonvetter/modbus"
|
|
"k8s.io/klog"
|
|
)
|
|
|
|
// daemon is the main service of the succdaemon.
|
|
type daemon struct {
|
|
modbusClient *modbus.ModbusClient
|
|
// adcPirani is the adc implementation returning the voltage of the Pfeiffer
|
|
// Pirani gauge.
|
|
adcPirani adc
|
|
|
|
load atomic.Int64
|
|
|
|
// mu guards the state below.
|
|
mu sync.RWMutex
|
|
daemonState
|
|
}
|
|
|
|
// daemonState contains all the state of the daemon. A copy of it can be
|
|
// requested for consumers, eg. the web view.
|
|
type daemonState struct {
|
|
safety struct {
|
|
// failsafe mode is enabled when the pirani gauge appears to be
|
|
// disconnected, and is disabled only when an atmosphere is read.
|
|
failsafe bool
|
|
// highPressure mode is enabled when the pressure reading is above 1e-1
|
|
// mbar, locking out the diffusion pump from being enabled.
|
|
highPressure bool
|
|
}
|
|
|
|
piraniVolts100 ringbufferInput
|
|
piraniMbar100 pfeifferVoltsToMbar
|
|
piraniVolts3 ringbufferInput
|
|
piraniMbar3 pfeifferVoltsToMbar
|
|
|
|
rpOn bool
|
|
dpOn bool
|
|
|
|
vent momentaryOutput
|
|
pumpdown momentaryOutput
|
|
aboveRough thresholdOutput
|
|
aboveHigh thresholdOutput
|
|
|
|
tempDPBottom float32
|
|
tempDPTop float32
|
|
tempDPInlet float32
|
|
|
|
tempSEM float32
|
|
humiditySEM float32
|
|
}
|
|
|
|
func (d *daemonState) vacuumStatus() (rough, high bool) {
|
|
rough = !d.aboveRough.output
|
|
high = !d.aboveHigh.output
|
|
return
|
|
}
|
|
|
|
// process runs the pain acquisition and control loop of succd.
|
|
func (d *daemon) process(ctx context.Context) {
|
|
var lastRun time.Time
|
|
hz := 100
|
|
period := time.Second / time.Duration(hz)
|
|
// Extra grace period for GC pauses and other non-realtime system jitter.
|
|
periodGrace := period + time.Millisecond*5
|
|
|
|
ticker := time.NewTicker(period)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
now := time.Now()
|
|
if elapsed := now.Sub(lastRun); !lastRun.IsZero() && elapsed > periodGrace {
|
|
klog.Warningf("Processing loop lag: took %s, want %s", elapsed, period)
|
|
}
|
|
lastRun = now
|
|
if err := d.processOnce(ctx); err != nil {
|
|
if errors.Is(err, ctx.Err()) {
|
|
return
|
|
} else {
|
|
klog.Errorf("Processing error: %v", err)
|
|
time.Sleep(time.Second * 10)
|
|
}
|
|
}
|
|
runtime := time.Since(lastRun)
|
|
load := int64(100 * runtime / period)
|
|
d.load.Store(load)
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// processOnce runs the main loop step of succd.
|
|
func (d *daemon) processOnce(_ context.Context) error {
|
|
v, err := d.adcPirani.Read()
|
|
if err != nil {
|
|
return fmt.Errorf("when reading ADC: %w", err)
|
|
}
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
// Process pirani ringbuffers.
|
|
d.piraniVolts3.process(v)
|
|
d.piraniMbar3.process(d.piraniVolts3.avg)
|
|
d.piraniVolts100.process(v)
|
|
d.piraniMbar100.process(d.piraniVolts100.avg)
|
|
|
|
d.pumpdown.process()
|
|
d.vent.process()
|
|
|
|
// Run safety checks based on small ringbuffer.
|
|
if d.piraniVolts3.saturated() {
|
|
mbar := d.piraniMbar3.mbar
|
|
if !d.safety.failsafe && mbar < 4e-6 {
|
|
// Unrealistic result, Pirani probe probably disconnected. Failsafe mode.
|
|
d.safety.failsafe = true
|
|
klog.Errorf("SAFETY: Pirani probe seems disconnected; enabling failsafe mode")
|
|
}
|
|
if d.safety.failsafe && mbar > 1e2 {
|
|
d.safety.failsafe = false
|
|
klog.Infof("SAFETY: Pirani probe value (%s) is plausible again; quitting failsafe mode", formatMbar(mbar))
|
|
}
|
|
|
|
if !d.safety.highPressure && mbar >= 1e-1 {
|
|
d.safety.highPressure = true
|
|
klog.Warningf("SAFETY: Pressure is too high (%s mbar); enabling diffusion pump lockout", formatMbar(mbar))
|
|
}
|
|
if d.safety.highPressure && mbar < (1e-1)-(1e-2) {
|
|
d.safety.highPressure = false
|
|
klog.Infof("SAFETY: Pressure is low enough (%s mbar) for diffusion pump operation; quitting diffusion pump lockout", formatMbar(mbar))
|
|
}
|
|
} else {
|
|
d.safety.highPressure = true
|
|
}
|
|
|
|
// Control threhold/feedback values based on main pirani ringbuffer, failing
|
|
// safe if not enough data is present.
|
|
if d.piraniVolts100.saturated() {
|
|
mbar := d.piraniMbar100.mbar
|
|
d.aboveRough.process(float64(mbar))
|
|
d.aboveHigh.process(float64(mbar))
|
|
} else {
|
|
d.aboveRough.output = true
|
|
d.aboveHigh.output = true
|
|
}
|
|
|
|
// Apply safety overrides.
|
|
if d.safety.failsafe {
|
|
d.aboveRough.output = true
|
|
d.aboveHigh.output = true
|
|
d.dpOn = false
|
|
}
|
|
if d.safety.highPressure {
|
|
d.dpOn = false
|
|
}
|
|
|
|
return nil
|
|
}
|