From 4df00f0a63978a78ef9ecd8383b07cfdb810fa7b Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 28 Sep 2024 09:35:41 +0200 Subject: [PATCH 1/6] succd: factor out ringbuffer, do not recalculate average on every request --- succbone/succd/main.go | 2 ++ succbone/succd/process.go | 36 ++++++++++++++++++++++++----- succbone/succd/process_state.go | 41 ++++++++++++--------------------- 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/succbone/succd/main.go b/succbone/succd/main.go index 99635ad..23427b6 100644 --- a/succbone/succd/main.go +++ b/succbone/succd/main.go @@ -28,6 +28,8 @@ func main() { d := daemon{} d.daemonState.rpOn = true + d.daemonState.piraniVolts3.limit = 3 + d.daemonState.piraniVolts100.limit = 100 d.aboveRough.threshold = float64(flagPressureThresholdRough) d.aboveHigh.threshold = float64(flagPressureThresholdHigh) diff --git a/succbone/succd/process.go b/succbone/succd/process.go index bd3f85e..b285fab 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -66,6 +66,33 @@ func (t *thresholdOutput) process(value float64) { } } +// ringbufferInput accumulates analog data up to limit samples, and calculates +// an average. +type ringbufferInput struct { + data []float32 + limit uint + 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 +} + // process runs the pain acquisition and control loop of succd. func (d *daemon) process(ctx context.Context) { ticker := time.NewTicker(time.Millisecond * 100) @@ -96,12 +123,9 @@ func (d *daemon) processOnce(_ context.Context) error { d.mu.Lock() defer d.mu.Unlock() - // Process pirani ringbuffer. - d.adcPiraniVolts = append(d.adcPiraniVolts, v) - trim := len(d.adcPiraniVolts) - 100 - if trim > 0 { - d.adcPiraniVolts = d.adcPiraniVolts[trim:] - } + // Process pirani ringbuffers. + d.piraniVolts3.process(v) + d.piraniVolts100.process(v) d.pumpdown.process() d.vent.process() diff --git a/succbone/succd/process_state.go b/succbone/succd/process_state.go index 0bbb09e..8ed1c41 100644 --- a/succbone/succd/process_state.go +++ b/succbone/succd/process_state.go @@ -7,11 +7,11 @@ import "math" type daemonState struct { safety safetyStatus - // adcPiraniVolts is a moving window of read ADC values, used to calculate a - // moving average. - adcPiraniVolts []float32 - rpOn bool - dpOn bool + piraniVolts100 ringbufferInput + piraniVolts3 ringbufferInput + + rpOn bool + dpOn bool vent momentaryOutput pumpdown momentaryOutput @@ -42,20 +42,18 @@ const ( piraniDetectionDisconnected = iota ) +func piraniVoltsToMbar(v float32) float32 { + // Per Pirani probe docs. + bar := math.Pow(10.0, float64(v)-8.5) + return float32(bar * 1000.0) +} + // piraniDetection guesses whether the pirani gauge is connected. func (d *daemonState) piraniDetection() piraniDetection { - if len(d.adcPiraniVolts) < 3 { + if len(d.piraniVolts3.data) < int(d.piraniVolts3.limit) { return piraniDetectionUnknown } - volts := float32(0.0) - for _, v := range d.adcPiraniVolts[len(d.adcPiraniVolts)-3:] { - volts += v - } - volts /= 3.0 - - bar := math.Pow(10.0, float64(volts)-8.5) - mbar := float32(bar * 1000.0) - + mbar := piraniVoltsToMbar(d.piraniVolts3.avg) if mbar < 4e-6 { return piraniDetectionDisconnected } @@ -63,17 +61,8 @@ func (d *daemonState) piraniDetection() piraniDetection { } func (d *daemonState) pirani() (volts float32, mbar float32) { - volts = 0.0 - for _, v := range d.adcPiraniVolts { - volts += v - } - if len(d.adcPiraniVolts) != 0 { - volts /= float32(len(d.adcPiraniVolts)) - } - - // Per Pirani probe docs. - bar := math.Pow(10.0, float64(volts)-8.5) - mbar = float32(bar * 1000.0) + volts = d.piraniVolts100.avg + mbar = piraniVoltsToMbar(volts) return } From 451b44e31b6fa17e3617bcc65d45e92e3417b891 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 28 Sep 2024 09:39:44 +0200 Subject: [PATCH 2/6] succd: move out processing blocks to separate file --- succbone/succd/process.go | 65 --------------------------- succbone/succd/process_blocks.go | 75 ++++++++++++++++++++++++++++++++ succbone/succd/process_state.go | 2 +- 3 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 succbone/succd/process_blocks.go diff --git a/succbone/succd/process.go b/succbone/succd/process.go index b285fab..a62c261 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -28,71 +28,6 @@ type daemon struct { daemonState } -// 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 -} - -func (t *thresholdOutput) process(value float64) { - if time.Now().Before(t.debounce) { - return - } - new := value > t.threshold - 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 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 -} - // process runs the pain acquisition and control loop of succd. func (d *daemon) process(ctx context.Context) { ticker := time.NewTicker(time.Millisecond * 100) diff --git a/succbone/succd/process_blocks.go b/succbone/succd/process_blocks.go new file mode 100644 index 0000000..39d7886 --- /dev/null +++ b/succbone/succd/process_blocks.go @@ -0,0 +1,75 @@ +package main + +import "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 +} + +func (t *thresholdOutput) process(value float64) { + if time.Now().Before(t.debounce) { + return + } + new := value > t.threshold + 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) +} diff --git a/succbone/succd/process_state.go b/succbone/succd/process_state.go index 8ed1c41..c7ab5eb 100644 --- a/succbone/succd/process_state.go +++ b/succbone/succd/process_state.go @@ -50,7 +50,7 @@ func piraniVoltsToMbar(v float32) float32 { // piraniDetection guesses whether the pirani gauge is connected. func (d *daemonState) piraniDetection() piraniDetection { - if len(d.piraniVolts3.data) < int(d.piraniVolts3.limit) { + if !d.piraniVolts3.saturated() { return piraniDetectionUnknown } mbar := piraniVoltsToMbar(d.piraniVolts3.avg) From 590e93e43eb25c3ff2de6ead9d80244c68d57c9d Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 28 Sep 2024 09:46:18 +0200 Subject: [PATCH 3/6] succd: log pressures in error messages --- succbone/succd/http.go | 20 +------------------- succbone/succd/process.go | 8 ++++---- succbone/succd/scientific.go | 28 ++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/succbone/succd/http.go b/succbone/succd/http.go index 506236b..0d1ce02 100644 --- a/succbone/succd/http.go +++ b/succbone/succd/http.go @@ -36,24 +36,6 @@ func formatVolts(v float32) string { return fmt.Sprintf("%.4f V", v) } -// formatMbar formats a millibar value using scientific notation and returns a -// HTML fragment (for superscript support). -func formatMbar(v float32) template.HTML { - exp := 0 - for v < 1 { - v *= 10 - exp -= 1 - } - for v >= 10 { - v /= 10 - exp += 1 - } - res := fmt.Sprintf("%.3f", v) - res += fmt.Sprintf(" x 10%d", exp) - res += " mbar" - return template.HTML(res) -} - // apiData is the data model served to the user via HTTP/WebSockets type apiData struct { // Safety interlocks. @@ -125,7 +107,7 @@ func (s *webServer) apiData(skipSystem bool) *apiData { ad.Safety.Failsafe = state.safety.failsafe ad.Safety.HighPressure = state.safety.highPressure ad.Pirani.Volts = formatVolts(volts) - ad.Pirani.Mbar = formatMbar(mbar) + ad.Pirani.Mbar = formatMbarHTML(mbar) ad.Pirani.MbarFloat = mbar ad.Pumps.RPOn = state.rpOn ad.Pumps.DPOn = state.dpOn diff --git a/succbone/succd/process.go b/succbone/succd/process.go index a62c261..f19dc5e 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -78,11 +78,11 @@ func (d *daemon) processOnce(_ context.Context) error { d.safety.failsafe = true klog.Errorf("Pirani probe seems disconnected; enabling failsafe mode") } - } else { + } else if d.piraniDetection() == piraniDetectionConnected { if d.safety.failsafe { if mbar >= 1e2 { d.safety.failsafe = false - klog.Infof("Values are plausible again; quitting failsafe mode") + klog.Infof("Pirani probe value (%s) is plausible again; quitting failsafe mode", formatMbar(mbar)) } } } @@ -90,12 +90,12 @@ func (d *daemon) processOnce(_ context.Context) error { if mbar >= 1e-1 { if !d.safety.highPressure { d.safety.highPressure = true - klog.Errorf("Pressure is too high; enabling diffusion pump lockout") + klog.Errorf("Pressure is too high (%s mbar); enabling diffusion pump lockout", formatMbar(mbar)) } } else if mbar < (1e-1)-(1e-2) { if d.safety.highPressure { d.safety.highPressure = false - klog.Infof("Pressure is low enough for diffusion pump operation; quitting diffusion pump lockout") + klog.Infof("Pressure is low enough (%s mbar) for diffusion pump operation; quitting diffusion pump lockout", formatMbar(mbar)) } } diff --git a/succbone/succd/scientific.go b/succbone/succd/scientific.go index d2df8d8..a15f273 100644 --- a/succbone/succd/scientific.go +++ b/succbone/succd/scientific.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "html/template" "strconv" ) @@ -16,8 +17,25 @@ func (s *ScientificNotationValue) UnmarshalText(text []byte) error { return nil } -func (s *ScientificNotationValue) MarshalText() ([]byte, error) { - v := float64(*s) +// formatMbarHTML formats a millibar value using scientific notation and returns +// a HTML fragment (for superscript support). +func formatMbarHTML(v float32) template.HTML { + exp := 0 + for v < 1 { + v *= 10 + exp -= 1 + } + for v >= 10 { + v /= 10 + exp += 1 + } + res := fmt.Sprintf("%.3f", v) + res += fmt.Sprintf(" x 10%d", exp) + res += " mbar" + return template.HTML(res) +} + +func formatMbar(v float32) string { exp := 0 for v < 1 { v *= 10 @@ -29,5 +47,11 @@ func (s *ScientificNotationValue) MarshalText() ([]byte, error) { } res := fmt.Sprintf("%.3f", v) res += fmt.Sprintf("e%d", exp) + return res +} + +func (s *ScientificNotationValue) MarshalText() ([]byte, error) { + v := float32(*s) + res := formatMbar(v) return []byte(res), nil } From 185525ca30458c4f092fdc97cc54ebb9f35191cc Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 28 Sep 2024 09:46:49 +0200 Subject: [PATCH 4/6] succd: downgrade high pressure lockout message to warning --- succbone/succd/process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/succbone/succd/process.go b/succbone/succd/process.go index f19dc5e..a9421a2 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -90,7 +90,7 @@ func (d *daemon) processOnce(_ context.Context) error { if mbar >= 1e-1 { if !d.safety.highPressure { d.safety.highPressure = true - klog.Errorf("Pressure is too high (%s mbar); enabling diffusion pump lockout", formatMbar(mbar)) + klog.Warningf("Pressure is too high (%s mbar); enabling diffusion pump lockout", formatMbar(mbar)) } } else if mbar < (1e-1)-(1e-2) { if d.safety.highPressure { From 3d81a1f56c2083389d56dfe4028a8ed211334d70 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 28 Sep 2024 09:48:50 +0200 Subject: [PATCH 5/6] succd: roll process_state.go back into process.go --- succbone/succd/process.go | 69 +++++++++++++++++++++++++++++++ succbone/succd/process_state.go | 73 --------------------------------- 2 files changed, 69 insertions(+), 73 deletions(-) delete mode 100644 succbone/succd/process_state.go diff --git a/succbone/succd/process.go b/succbone/succd/process.go index a9421a2..3f23af0 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "sync" "time" @@ -28,6 +29,74 @@ type daemon struct { 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 + piraniVolts3 ringbufferInput + + rpOn bool + dpOn bool + + vent momentaryOutput + pumpdown momentaryOutput + aboveRough thresholdOutput + aboveHigh thresholdOutput +} + +type piraniDetection uint + +const ( + // piraniDetectionUnknown means the system isn't yet sure whether the pirani + // gauge is connected. + piraniDetectionUnknown piraniDetection = iota + // piraniDetectionConnected means the system assumes the pirani gauge is + // connected. + piraniDetectionConnected = iota + // piraniDetectionDisconnected means the system assumes the pirani gauge is + // disconnected. + piraniDetectionDisconnected = iota +) + +func piraniVoltsToMbar(v float32) float32 { + // Per Pirani probe docs. + bar := math.Pow(10.0, float64(v)-8.5) + return float32(bar * 1000.0) +} + +// piraniDetection guesses whether the pirani gauge is connected. +func (d *daemonState) piraniDetection() piraniDetection { + if !d.piraniVolts3.saturated() { + return piraniDetectionUnknown + } + mbar := piraniVoltsToMbar(d.piraniVolts3.avg) + if mbar < 4e-6 { + return piraniDetectionDisconnected + } + return piraniDetectionConnected +} + +func (d *daemonState) pirani() (volts float32, mbar float32) { + volts = d.piraniVolts100.avg + mbar = piraniVoltsToMbar(volts) + return +} + +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) { ticker := time.NewTicker(time.Millisecond * 100) diff --git a/succbone/succd/process_state.go b/succbone/succd/process_state.go deleted file mode 100644 index c7ab5eb..0000000 --- a/succbone/succd/process_state.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import "math" - -// 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 safetyStatus - - piraniVolts100 ringbufferInput - piraniVolts3 ringbufferInput - - rpOn bool - dpOn bool - - vent momentaryOutput - pumpdown momentaryOutput - aboveRough thresholdOutput - aboveHigh thresholdOutput -} - -type safetyStatus 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 -} - -type piraniDetection uint - -const ( - // piraniDetectionUnknown means the system isn't yet sure whether the pirani - // gauge is connected. - piraniDetectionUnknown piraniDetection = iota - // piraniDetectionConnected means the system assumes the pirani gauge is - // connected. - piraniDetectionConnected = iota - // piraniDetectionDisconnected means the system assumes the pirani gauge is - // disconnected. - piraniDetectionDisconnected = iota -) - -func piraniVoltsToMbar(v float32) float32 { - // Per Pirani probe docs. - bar := math.Pow(10.0, float64(v)-8.5) - return float32(bar * 1000.0) -} - -// piraniDetection guesses whether the pirani gauge is connected. -func (d *daemonState) piraniDetection() piraniDetection { - if !d.piraniVolts3.saturated() { - return piraniDetectionUnknown - } - mbar := piraniVoltsToMbar(d.piraniVolts3.avg) - if mbar < 4e-6 { - return piraniDetectionDisconnected - } - return piraniDetectionConnected -} - -func (d *daemonState) pirani() (volts float32, mbar float32) { - volts = d.piraniVolts100.avg - mbar = piraniVoltsToMbar(volts) - return -} - -func (d *daemonState) vacuumStatus() (rough, high bool) { - rough = !d.aboveRough.output - high = !d.aboveHigh.output - return -} From 960be9cd236d42f553b02b10f551a56c9e559ed7 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 28 Sep 2024 10:17:05 +0200 Subject: [PATCH 6/6] succd: rewrite processing loop --- succbone/succd/http.go | 5 +- succbone/succd/process.go | 96 +++++++++++--------------------- succbone/succd/process_blocks.go | 15 ++++- 3 files changed, 50 insertions(+), 66 deletions(-) diff --git a/succbone/succd/http.go b/succbone/succd/http.go index 0d1ce02..61df8a8 100644 --- a/succbone/succd/http.go +++ b/succbone/succd/http.go @@ -84,7 +84,8 @@ type apiData struct { // not being served via websockets). func (s *webServer) apiData(skipSystem bool) *apiData { state := s.d.snapshot() - volts, mbar := state.pirani() + volts := state.piraniVolts100.avg + mbar := state.piraniMbar100.mbar rough, high := state.vacuumStatus() var hostname, load string @@ -164,7 +165,7 @@ func (s *webServer) viewMetrics(w http.ResponseWriter, r *http.Request) { // library. // TODO(q3k): serve the rest of the data model state := s.d.snapshot() - _, mbar := state.pirani() + mbar := state.piraniMbar100.mbar fmt.Fprintf(w, "# HELP sem_pressure_mbar Pressure in the SEM chamber, in millibar\n") fmt.Fprintf(w, "# TYPE sem_pressure_mbar gauge\n") fmt.Fprintf(w, "sem_pressure_mbar %f\n", mbar) diff --git a/succbone/succd/process.go b/succbone/succd/process.go index 3f23af0..550792b 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "math" "sync" "time" @@ -42,7 +41,9 @@ type daemonState struct { } piraniVolts100 ringbufferInput + piraniMbar100 pfeifferVoltsToMbar piraniVolts3 ringbufferInput + piraniMbar3 pfeifferVoltsToMbar rpOn bool dpOn bool @@ -53,44 +54,6 @@ type daemonState struct { aboveHigh thresholdOutput } -type piraniDetection uint - -const ( - // piraniDetectionUnknown means the system isn't yet sure whether the pirani - // gauge is connected. - piraniDetectionUnknown piraniDetection = iota - // piraniDetectionConnected means the system assumes the pirani gauge is - // connected. - piraniDetectionConnected = iota - // piraniDetectionDisconnected means the system assumes the pirani gauge is - // disconnected. - piraniDetectionDisconnected = iota -) - -func piraniVoltsToMbar(v float32) float32 { - // Per Pirani probe docs. - bar := math.Pow(10.0, float64(v)-8.5) - return float32(bar * 1000.0) -} - -// piraniDetection guesses whether the pirani gauge is connected. -func (d *daemonState) piraniDetection() piraniDetection { - if !d.piraniVolts3.saturated() { - return piraniDetectionUnknown - } - mbar := piraniVoltsToMbar(d.piraniVolts3.avg) - if mbar < 4e-6 { - return piraniDetectionDisconnected - } - return piraniDetectionConnected -} - -func (d *daemonState) pirani() (volts float32, mbar float32) { - volts = d.piraniVolts100.avg - mbar = piraniVoltsToMbar(volts) - return -} - func (d *daemonState) vacuumStatus() (rough, high bool) { rough = !d.aboveRough.output high = !d.aboveHigh.output @@ -129,51 +92,58 @@ func (d *daemon) processOnce(_ context.Context) error { // 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() - _, mbar := d.daemonState.pirani() - d.aboveRough.process(float64(mbar)) - d.aboveHigh.process(float64(mbar)) - - // Check if the pirani gauge is disconnected. Note: this will assume the - // pirani gauge is connected for the first couple of processing runs as - // samples are still being captured. - if d.piraniDetection() == piraniDetectionDisconnected { - // Unrealistic result, Pirani probe probably disconnected. Failsafe mode. - if !d.safety.failsafe { - d.safety.failsafe = true - klog.Errorf("Pirani probe seems disconnected; enabling failsafe mode") - } - } else if d.piraniDetection() == piraniDetectionConnected { - if d.safety.failsafe { - if mbar >= 1e2 { - d.safety.failsafe = false - klog.Infof("Pirani probe value (%s) is plausible again; quitting failsafe mode", formatMbar(mbar)) + // 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. + if !d.safety.failsafe { + d.safety.failsafe = true + klog.Errorf("Pirani probe seems disconnected; enabling failsafe mode") } } - } + if d.safety.failsafe && mbar > 1e2 { + d.safety.failsafe = false + klog.Infof("Pirani probe value (%s) is plausible again; quitting failsafe mode", formatMbar(mbar)) + } - if mbar >= 1e-1 { - if !d.safety.highPressure { + if !d.safety.highPressure && mbar >= 1e-1 { d.safety.highPressure = true klog.Warningf("Pressure is too high (%s mbar); enabling diffusion pump lockout", formatMbar(mbar)) } - } else if mbar < (1e-1)-(1e-2) { - if d.safety.highPressure { + if d.safety.highPressure && mbar < (1e-1)-(1e-2) { d.safety.highPressure = false klog.Infof("Pressure is low enough (%s mbar) for diffusion pump operation; quitting diffusion pump lockout", formatMbar(mbar)) } + } else { + d.safety.failsafe = true + 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 } diff --git a/succbone/succd/process_blocks.go b/succbone/succd/process_blocks.go index 39d7886..0b37193 100644 --- a/succbone/succd/process_blocks.go +++ b/succbone/succd/process_blocks.go @@ -1,6 +1,9 @@ package main -import "time" +import ( + "math" + "time" +) // momentaryOutput is an output that can be triggered for 500ms. type momentaryOutput struct { @@ -73,3 +76,13 @@ func (r *ringbufferInput) process(input float32) { 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) +}