Compare commits
No commits in common. "f66afc0c8fd1989f4a0928740f96a29aa81a48bf" and "85d2afbdd0ae5be00af852ebf120ca01a8179313" have entirely different histories.
f66afc0c8f
...
85d2afbdd0
|
@ -15,10 +15,6 @@ import (
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpServer struct {
|
|
||||||
d daemonController
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
//go:embed index.html
|
//go:embed index.html
|
||||||
templateIndexText string
|
templateIndexText string
|
||||||
|
@ -27,7 +23,7 @@ var (
|
||||||
favicon []byte
|
favicon []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *httpServer) viewFavicon(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpFavicon(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "image/png")
|
w.Header().Set("Content-Type", "image/png")
|
||||||
w.Write(favicon)
|
w.Write(favicon)
|
||||||
}
|
}
|
||||||
|
@ -54,103 +50,45 @@ func formatMbar(v float32) template.HTML {
|
||||||
return template.HTML(res)
|
return template.HTML(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiData is the data model served to the user via HTTP/WebSockets
|
|
||||||
type apiData struct {
|
|
||||||
// Safety interlocks.
|
|
||||||
Safety struct {
|
|
||||||
// Failsafe mode enabled - pirani gauge seems disconnected.
|
|
||||||
Failsafe bool
|
|
||||||
// HighPressure interlock enabled - pressure too high to run diffusion
|
|
||||||
// pump.
|
|
||||||
HighPressure bool
|
|
||||||
}
|
|
||||||
// Pirani gauge data.
|
|
||||||
Pirani struct {
|
|
||||||
// Volts read from the gauge (0-10).
|
|
||||||
Volts string
|
|
||||||
// Mbar read from the gauge, formatted as HTML.
|
|
||||||
Mbar template.HTML
|
|
||||||
// MbarFloat read from the gauge.
|
|
||||||
MbarFloat float32
|
|
||||||
}
|
|
||||||
// Pump state.
|
|
||||||
Pumps struct {
|
|
||||||
// RPOn means the roughing pump is turned on.
|
|
||||||
RPOn bool
|
|
||||||
// DPOn means the diffusion pump is turned on.
|
|
||||||
DPOn bool
|
|
||||||
}
|
|
||||||
// Pressure feedback into evacuation board.
|
|
||||||
Feedback struct {
|
|
||||||
// RoughReached is true when the system has reached a rough vacuum
|
|
||||||
// stage.
|
|
||||||
RoughReached bool
|
|
||||||
// HighReached is true when the system has reached a high vacuum stage.
|
|
||||||
HighReached bool
|
|
||||||
}
|
|
||||||
// System junk.
|
|
||||||
System struct {
|
|
||||||
// Load of the system.
|
|
||||||
Load string
|
|
||||||
// Hostname of the system.
|
|
||||||
Hostname string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apiData returns the user data model for the current state of the system. If
|
|
||||||
// skipSystem is set, the System subset is ignored (saves system load, and is
|
|
||||||
// not being served via websockets).
|
|
||||||
func (s *httpServer) apiData(skipSystem bool) *apiData {
|
|
||||||
state := s.d.snapshot()
|
|
||||||
volts, mbar := state.pirani()
|
|
||||||
rough, high := state.vacuumStatus()
|
|
||||||
|
|
||||||
var hostname, load string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if !skipSystem {
|
|
||||||
hostname, err = os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
hostname = "unknown"
|
|
||||||
}
|
|
||||||
loadB, err := os.ReadFile("/proc/loadavg")
|
|
||||||
load = "unknown"
|
|
||||||
if err == nil {
|
|
||||||
parts := strings.Fields(string(loadB))
|
|
||||||
load = strings.Join(parts[:3], " ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ad := 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.MbarFloat = mbar
|
|
||||||
ad.Pumps.RPOn = state.rpOn
|
|
||||||
ad.Pumps.DPOn = state.dpOn
|
|
||||||
ad.Feedback.RoughReached = rough
|
|
||||||
ad.Feedback.HighReached = high
|
|
||||||
ad.System.Load = load
|
|
||||||
ad.System.Hostname = hostname
|
|
||||||
return &ad
|
|
||||||
}
|
|
||||||
|
|
||||||
// httpIndex is the / view.
|
// httpIndex is the / view.
|
||||||
func (s *httpServer) viewIndex(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := s.apiData(false)
|
volts, mbar := d.pirani()
|
||||||
//data.Pirani.Mbar = html.St
|
rp := d.rpGet()
|
||||||
templateIndex.Execute(w, data)
|
dp := d.dpGet()
|
||||||
|
failsafe, highpressure := d.safetyStatusGet()
|
||||||
|
|
||||||
|
loadB, err := os.ReadFile("/proc/loadavg")
|
||||||
|
load := "unknown"
|
||||||
|
if err == nil {
|
||||||
|
parts := strings.Fields(string(loadB))
|
||||||
|
load = strings.Join(parts[:3], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
hostname = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
templateIndex.Execute(w, map[string]any{
|
||||||
|
"failsafe": failsafe,
|
||||||
|
"highpressure": highpressure,
|
||||||
|
"volts": formatVolts(volts),
|
||||||
|
"mbar": formatMbar(mbar),
|
||||||
|
"rp": rp,
|
||||||
|
"dp": dp,
|
||||||
|
"hostname": hostname,
|
||||||
|
"load": load,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpStream is the websocket clientwards data hose, returning a 10Hz update
|
// httpStream is the websocket clientwards data hose, returning a 10Hz update
|
||||||
// stream of pressure/voltage.
|
// stream of pressure/voltage.
|
||||||
func (s *httpServer) viewStream(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpStream(w http.ResponseWriter, r *http.Request) {
|
||||||
c, err := websocket.Accept(w, r, nil)
|
c, err := websocket.Accept(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -167,7 +105,33 @@ func (s *httpServer) viewStream(w http.ResponseWriter, r *http.Request) {
|
||||||
c.Close(websocket.StatusNormalClosure, "")
|
c.Close(websocket.StatusNormalClosure, "")
|
||||||
return
|
return
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
v := s.apiData(true)
|
// TODO(q3k): don't poll, get notified when new ADC readout is available.
|
||||||
|
volts, mbar := d.pirani()
|
||||||
|
rp := d.rpGet()
|
||||||
|
dp := d.dpGet()
|
||||||
|
rough, high := d.vacuumStatusGet()
|
||||||
|
failsafe, highpressure := d.safetyStatusGet()
|
||||||
|
v := struct {
|
||||||
|
Failsafe bool
|
||||||
|
HighPressure bool
|
||||||
|
Volts string
|
||||||
|
Mbar string
|
||||||
|
MbarFloat float32
|
||||||
|
RPOn bool
|
||||||
|
DPOn bool
|
||||||
|
RoughReached bool
|
||||||
|
HighReached bool
|
||||||
|
}{
|
||||||
|
Failsafe: failsafe,
|
||||||
|
HighPressure: highpressure,
|
||||||
|
Volts: formatVolts(volts),
|
||||||
|
Mbar: string(formatMbar(mbar)),
|
||||||
|
MbarFloat: mbar,
|
||||||
|
RPOn: rp,
|
||||||
|
DPOn: dp,
|
||||||
|
RoughReached: rough,
|
||||||
|
HighReached: high,
|
||||||
|
}
|
||||||
if err := wsjson.Write(ctx, c, v); err != nil {
|
if err := wsjson.Write(ctx, c, v); err != nil {
|
||||||
klog.Errorf("Websocket write failed: %v", err)
|
klog.Errorf("Websocket write failed: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -177,54 +141,39 @@ func (s *httpServer) viewStream(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// httpMetrics serves minimalistic Prometheus-compatible metrics.
|
// httpMetrics serves minimalistic Prometheus-compatible metrics.
|
||||||
func (s *httpServer) viewMetrics(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpMetrics(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO(q3k): also serve Go stuff using the actual Prometheus metrics client
|
// TODO(q3k): also serve Go stuff using the actual Prometheus metrics client
|
||||||
// library.
|
// library.
|
||||||
// TODO(q3k): serve the rest of the data model
|
_, mbar := d.pirani()
|
||||||
state := s.d.snapshot()
|
|
||||||
_, mbar := state.pirani()
|
|
||||||
fmt.Fprintf(w, "# HELP sem_pressure_mbar Pressure in the SEM chamber, in millibar\n")
|
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, "# TYPE sem_pressure_mbar gauge\n")
|
||||||
fmt.Fprintf(w, "sem_pressure_mbar %f\n", mbar)
|
fmt.Fprintf(w, "sem_pressure_mbar %f\n", mbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpServer) viewRoughingPumpEnable(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpRoughingPumpEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
s.d.rpSet(true)
|
d.rpSet(true)
|
||||||
fmt.Fprintf(w, "succ on\n")
|
fmt.Fprintf(w, "succ on\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpServer) viewRoughingPumpDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpRoughingPumpDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
s.d.rpSet(false)
|
d.rpSet(false)
|
||||||
fmt.Fprintf(w, "succ off\n")
|
fmt.Fprintf(w, "succ off\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpServer) viewDiffusionPumpEnable(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpDiffusionPumpEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
s.d.dpSet(true)
|
d.dpSet(true)
|
||||||
fmt.Fprintf(w, "deep succ on\n")
|
fmt.Fprintf(w, "deep succ on\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpServer) viewDiffusionPumpDisable(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpDiffusionPumpDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
s.d.dpSet(false)
|
d.dpSet(false)
|
||||||
fmt.Fprintf(w, "deep succ off\n")
|
fmt.Fprintf(w, "deep succ off\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpServer) viewButtonPumpDown(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpButtonPumpDown(w http.ResponseWriter, r *http.Request) {
|
||||||
s.d.pumpDownPress()
|
d.pumpDownPress()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpServer) viewButtonVent(w http.ResponseWriter, r *http.Request) {
|
func (d *daemon) httpButtonVent(w http.ResponseWriter, r *http.Request) {
|
||||||
s.d.ventPress()
|
d.ventPress()
|
||||||
}
|
|
||||||
|
|
||||||
func (s *httpServer) setupViews() {
|
|
||||||
http.HandleFunc("/", s.viewIndex)
|
|
||||||
http.HandleFunc("/favicon.png", s.viewFavicon)
|
|
||||||
http.HandleFunc("/stream", s.viewStream)
|
|
||||||
http.HandleFunc("/metrics", s.viewMetrics)
|
|
||||||
http.HandleFunc("/button/vent", s.viewButtonVent)
|
|
||||||
http.HandleFunc("/button/pumpdown", s.viewButtonPumpDown)
|
|
||||||
http.HandleFunc("/rp/on", s.viewRoughingPumpEnable)
|
|
||||||
http.HandleFunc("/rp/off", s.viewRoughingPumpDisable)
|
|
||||||
http.HandleFunc("/dp/on", s.viewDiffusionPumpEnable)
|
|
||||||
http.HandleFunc("/dp/off", s.viewDiffusionPumpDisable)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,6 @@ body {
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
table.status td {
|
|
||||||
width: 2em;
|
|
||||||
}
|
}
|
||||||
th, td {
|
th, td {
|
||||||
background-color: #e8e8e8;
|
background-color: #e8e8e8;
|
||||||
|
@ -62,66 +58,50 @@ td > span {
|
||||||
<p style="margin-top: 2em; clear: both;">
|
<p style="margin-top: 2em; clear: both;">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th rowspan="2">Pirani Gauge</th>
|
|
||||||
<th>Voltage</th>
|
<th>Voltage</th>
|
||||||
<td id="volts">{{ .Pirani.Volts }}</td>
|
<td id="volts" colspan="4">{{.volts}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Pressure</th>
|
<th>Pressure</th>
|
||||||
<td id="mbar">{{ .Pirani.Mbar }}</td>
|
<td id="mbar" colspan="4">{{.mbar}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
|
||||||
|
|
||||||
<table class="status">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Thresholds</th>
|
<th>Thresholds</th>
|
||||||
<th>Rough</th>
|
<td>Rough:</td>
|
||||||
<td id="trough">{{ if .Feedback.RoughReached }}OK{{ else }}NOK{{ end }}</td>
|
<td id="trough">...</td>
|
||||||
<th>High</th>
|
<td>High:</td>
|
||||||
<td id="thigh">{{ if .Feedback.HighReached }}OK{{ else }}NOK{{ end }}</td>
|
<td id="thigh">...</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Pumps</th>
|
<th>Pumps</th>
|
||||||
<th>RP</th>
|
<td>RP:</td>
|
||||||
<td id="rp">{{ if .Pumps.RPOn }}ON{{ else }}OFF{{ end }}</td>
|
<td id="rp">{{ if .rp }}ON{{ else }}OFF{{ end }}</td>
|
||||||
<th>DP</th>
|
<td>DP:</td>
|
||||||
<td id="dp">{{ if .Pumps.DPOn }}ON{{ else }}OFF{{ end }}</td>
|
<td id="dp">{{ if .dp }}ON{{ else }}OFF{{ end }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Safety</th>
|
<th>Evac Control</th>
|
||||||
<th style="font-size: 0.7em;">Pirani<br />Failsafe</th>
|
<td colspan="4">
|
||||||
<td id="failsafe">{{ if .Safety.Failsafe }}ON{{ else }}OFF{{ end }}</td>
|
|
||||||
<th style="font-size: 0.7em;">DP<br />Lockout</th>
|
|
||||||
<td id="highpressure">{{ if .Safety.HighPressure }}ON{{ else }}OFF{{ end }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th rowspan="3">Control</th>
|
|
||||||
<th>RP</th>
|
|
||||||
<td>
|
|
||||||
<button id="rpon">On</button>
|
|
||||||
<button id="rpoff">Off</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>DP</th>
|
|
||||||
<td>
|
|
||||||
<button id="dpon">On</button>
|
|
||||||
<button id="dpoff">Off</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<button id="pd">Pump Down</button>
|
<button id="pd">Pump Down</button>
|
||||||
<button id="vent">Vent</button>
|
<button id="vent">Vent</button>
|
||||||
|
<button id="rpon">RP On</button>
|
||||||
|
<button id="rpoff">RP Off</button>
|
||||||
|
<button id="dpon">DP On</button>
|
||||||
|
<button id="dpoff">DP Off</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<td id="status" colspan="4">OK</td>
|
<td id="status" colspan="4">OK</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Failsafe</th>
|
||||||
|
<td id="failsafe" colspan="4">OK</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Diffusion Pump Lockout</th>
|
||||||
|
<td id="highpressure" colspan="4">OK</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin-top: 2em;">
|
<p style="margin-top: 2em;">
|
||||||
|
@ -129,7 +109,7 @@ td > span {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<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>
|
{{.hostname}} | load: {{.load}} | <a href="/debug/pprof">pprof</a> | <a href="/metrics">metrics</a> | ws ping: <span id="ping">…</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -308,53 +288,50 @@ window.addEventListener("load", (_) => {
|
||||||
});
|
});
|
||||||
socket.addEventListener("message", (event) => {
|
socket.addEventListener("message", (event) => {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
volts.innerHTML = data.Pirani.Volts;
|
volts.innerHTML = data.Volts;
|
||||||
mbar.innerHTML = data.Pirani.Mbar;
|
mbar.innerHTML = data.Mbar;
|
||||||
if (data.Safety.Failsafe) {
|
failsafe.innerHTML = data.Failsafe ? "ON" : "OFF";
|
||||||
failsafe.innerHTML = "ON";
|
if (data.Failsafe) {
|
||||||
failsafe.style = "background-color: #f06060";
|
failsafe.style = "background-color: #f06060";
|
||||||
} else {
|
} else {
|
||||||
failsafe.innerHTML = "OFF";
|
failsafe.style = "background-color: #60f060";
|
||||||
failsafe.style = "background-color: #60f060";
|
|
||||||
}
|
}
|
||||||
if (data.Safety.HighPressure) {
|
if (data.HighPressure) {
|
||||||
highpressure.innerHTML = "ON";
|
highpressure.innerHTML = "ON";
|
||||||
highpressure.style = "background-color: #f06060";
|
highpressure.style = "background-color: #f06060";
|
||||||
} else {
|
} else {
|
||||||
highpressure.innerHTML = "OFF";
|
highpressure.innerHTML = "OFF";
|
||||||
highpressure.style = "background-color: #60f060";
|
highpressure.style = "background-color: #60f060";
|
||||||
}
|
}
|
||||||
if (data.Pumps.RPOn) {
|
rp.innerHTML = data.RPOn ? "ON" : "OFF";
|
||||||
rp.innerHTML = "ON";
|
if (data.RPOn) {
|
||||||
rp.style = "background-color: #60f060";
|
rp.style = "background-color: #60f060";
|
||||||
} else {
|
} else {
|
||||||
rp.innerHTML = "OFF";
|
|
||||||
rp.style = "background-color: #f06060";
|
rp.style = "background-color: #f06060";
|
||||||
}
|
}
|
||||||
if (data.Pumps.DPOn) {
|
dp.innerHTML = data.DPOn ? "ON" : "OFF";
|
||||||
dp.innerHTML = "ON";
|
if (data.DPOn) {
|
||||||
dp.style = "background-color: #60f060";
|
dp.style = "background-color: #60f060";
|
||||||
} else {
|
} else {
|
||||||
dp.innerHTML = "OFF";
|
|
||||||
dp.style = "background-color: #f06060";
|
dp.style = "background-color: #f06060";
|
||||||
}
|
}
|
||||||
|
|
||||||
let t = [];
|
let t = [];
|
||||||
if (data.Feedback.RoughReached) {
|
if (data.RoughReached) {
|
||||||
trough.innerHTML = "OK";
|
trough.innerHTML = "OK";
|
||||||
trough.style = "background-color: #60f060";
|
trough.style = "background-color: #60f060";
|
||||||
} else {
|
} else {
|
||||||
trough.innerHTML = "NOK";
|
trough.innerHTML = "NOK";
|
||||||
trough.style = "background-color: #f06060";
|
trough.style = "background-color: #f06060";
|
||||||
}
|
}
|
||||||
if (data.Feedback.HighReached) {
|
if (data.HighReached) {
|
||||||
thigh.innerHTML = "OK";
|
thigh.innerHTML = "OK";
|
||||||
thigh.style = "background-color: #60f060";
|
thigh.style = "background-color: #60f060";
|
||||||
} else {
|
} else {
|
||||||
thigh.innerHTML = "NOK";
|
thigh.innerHTML = "NOK";
|
||||||
thigh.style = "background-color: #f06060";
|
thigh.style = "background-color: #f06060";
|
||||||
}
|
}
|
||||||
historicalPush(data.Pirani.MbarFloat);
|
historicalPush(data.MbarFloat);
|
||||||
ping.innerHTML = Date.now();
|
ping.innerHTML = Date.now();
|
||||||
});
|
});
|
||||||
socket.addEventListener("close", (event) => {
|
socket.addEventListener("close", (event) => {
|
||||||
|
|
|
@ -26,9 +26,9 @@ func main() {
|
||||||
|
|
||||||
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
|
||||||
d := daemon{}
|
d := daemon{
|
||||||
d.daemonState.rpOn = true
|
rpOn: true,
|
||||||
|
}
|
||||||
d.aboveRough.threshold = float64(flagPressureThresholdRough)
|
d.aboveRough.threshold = float64(flagPressureThresholdRough)
|
||||||
d.aboveHigh.threshold = float64(flagPressureThresholdHigh)
|
d.aboveHigh.threshold = float64(flagPressureThresholdHigh)
|
||||||
if flagFake {
|
if flagFake {
|
||||||
|
@ -65,10 +65,16 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpServer := httpServer{
|
http.HandleFunc("/", d.httpIndex)
|
||||||
d: &d,
|
http.HandleFunc("/favicon.png", d.httpFavicon)
|
||||||
}
|
http.HandleFunc("/stream", d.httpStream)
|
||||||
httpServer.setupViews()
|
http.HandleFunc("/metrics", d.httpMetrics)
|
||||||
|
http.HandleFunc("/button/vent", d.httpButtonVent)
|
||||||
|
http.HandleFunc("/button/pumpdown", d.httpButtonPumpDown)
|
||||||
|
http.HandleFunc("/rp/on", d.httpRoughingPumpEnable)
|
||||||
|
http.HandleFunc("/rp/off", d.httpRoughingPumpDisable)
|
||||||
|
http.HandleFunc("/dp/on", d.httpDiffusionPumpEnable)
|
||||||
|
http.HandleFunc("/dp/off", d.httpDiffusionPumpDisable)
|
||||||
|
|
||||||
klog.Infof("Listening for HTTP at %s", flagListenHTTP)
|
klog.Infof("Listening for HTTP at %s", flagListenHTTP)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -4,18 +4,22 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// daemon is the main service of the succdaemon.
|
// daemon is the main state of the succdaemon.
|
||||||
type daemon struct {
|
type daemon struct {
|
||||||
// adcPirani is the adc implementation returning the voltage of the Pfeiffer
|
// adcPirani is the adc implementation returning the voltage of the Pfeiffer
|
||||||
// Pirani gauge.
|
// Pirani gauge.
|
||||||
adcPirani adc
|
adcPirani adc
|
||||||
|
|
||||||
|
failsafe bool
|
||||||
|
highPressure bool
|
||||||
|
|
||||||
gpioDiffusionPump gpio
|
gpioDiffusionPump gpio
|
||||||
gpioRoughingPump gpio
|
gpioRoughingPump gpio
|
||||||
gpioBtnPumpDown gpio
|
gpioBtnPumpDown gpio
|
||||||
|
@ -23,9 +27,18 @@ type daemon struct {
|
||||||
gpioBelowRough gpio
|
gpioBelowRough gpio
|
||||||
gpioBelowHigh gpio
|
gpioBelowHigh gpio
|
||||||
|
|
||||||
// mu guards the state below.
|
// mu guards state variables below.
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
daemonState
|
// adcPiraniVolts is a moving window of read ADC values, used to calculate a
|
||||||
|
// moving average.
|
||||||
|
adcPiraniVolts []float32
|
||||||
|
rpOn bool
|
||||||
|
dpOn bool
|
||||||
|
|
||||||
|
vent momentaryOutput
|
||||||
|
pumpdown momentaryOutput
|
||||||
|
aboveRough thresholdOutput
|
||||||
|
aboveHigh thresholdOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
// momentaryOutput is an output that can be triggered for 500ms.
|
// momentaryOutput is an output that can be triggered for 500ms.
|
||||||
|
@ -106,47 +119,44 @@ func (d *daemon) processOnce(_ context.Context) error {
|
||||||
d.pumpdown.process()
|
d.pumpdown.process()
|
||||||
d.vent.process()
|
d.vent.process()
|
||||||
|
|
||||||
_, mbar := d.daemonState.pirani()
|
_, mbar := d.piraniUnlocked()
|
||||||
d.aboveRough.process(float64(mbar))
|
d.aboveRough.process(float64(mbar))
|
||||||
d.aboveHigh.process(float64(mbar))
|
d.aboveHigh.process(float64(mbar))
|
||||||
|
|
||||||
// Check if the pirani gauge is disconnected. Note: this will assume the
|
if d.piraniWireBreakDetection() {
|
||||||
// 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.
|
// Unrealistic result, Pirani probe probably disconnected. Failsafe mode.
|
||||||
if !d.safety.failsafe {
|
if !d.failsafe {
|
||||||
d.safety.failsafe = true
|
d.failsafe = true
|
||||||
klog.Errorf("Pirani probe seems disconnected; enabling failsafe mode")
|
klog.Errorf("Pirani probe seems disconnected; enabling failsafe mode")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if d.safety.failsafe {
|
if d.failsafe {
|
||||||
if mbar >= 1e2 {
|
if mbar >= 1e2 {
|
||||||
d.safety.failsafe = false
|
d.failsafe = false
|
||||||
klog.Infof("Values are plausible again; quitting failsafe mode")
|
klog.Infof("Values are plausible again; quitting failsafe mode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mbar >= 1e-1 {
|
if mbar >= 1e-1 {
|
||||||
if !d.safety.highPressure {
|
if !d.highPressure {
|
||||||
d.safety.highPressure = true
|
d.highPressure = true
|
||||||
klog.Errorf("Pressure is too high; enabling diffusion pump lockout")
|
klog.Errorf("Pressure is too high; enabling diffusion pump lockout")
|
||||||
}
|
}
|
||||||
} else if mbar < (1e-1)-(1e-2) {
|
} else {
|
||||||
if d.safety.highPressure {
|
if d.highPressure {
|
||||||
d.safety.highPressure = false
|
d.highPressure = false
|
||||||
klog.Infof("Pressure is low enough for diffusion pump operation; quitting diffusion pump lockout")
|
klog.Infof("Pressure is low enough for diffusion pump operation; quitting diffusion pump lockout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.safety.failsafe {
|
if d.failsafe {
|
||||||
d.aboveRough.output = true
|
d.aboveRough.output = true
|
||||||
d.aboveHigh.output = true
|
d.aboveHigh.output = true
|
||||||
d.dpOn = false
|
d.dpOn = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.safety.highPressure {
|
if d.highPressure {
|
||||||
d.dpOn = false
|
d.dpOn = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,3 +190,100 @@ func (d *daemon) processOnce(_ context.Context) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *daemon) piraniWireBreakDetection() bool {
|
||||||
|
if len(d.adcPiraniVolts) < 3 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
return mbar < 4e-6
|
||||||
|
}
|
||||||
|
|
||||||
|
// pirani returns the Pirani gauge voltage and pressure.
|
||||||
|
func (d *daemon) pirani() (volts float32, mbar float32) {
|
||||||
|
d.mu.RLock()
|
||||||
|
volts, mbar = d.piraniUnlocked()
|
||||||
|
d.mu.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daemon) piraniUnlocked() (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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// rpSet enables/disables the roughing pump.
|
||||||
|
func (d *daemon) rpSet(state bool) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
d.rpOn = state
|
||||||
|
}
|
||||||
|
|
||||||
|
// rpGet returns whether the roughing pump is enabled/disabled.
|
||||||
|
func (d *daemon) rpGet() bool {
|
||||||
|
d.mu.RLock()
|
||||||
|
defer d.mu.RUnlock()
|
||||||
|
return d.rpOn
|
||||||
|
}
|
||||||
|
|
||||||
|
// dpSet enables/disables the diffusion pump.
|
||||||
|
func (d *daemon) dpSet(state bool) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
d.dpOn = state
|
||||||
|
}
|
||||||
|
|
||||||
|
// dpGet returns whether the diffusion pump is enabled/disabled.
|
||||||
|
func (d *daemon) dpGet() bool {
|
||||||
|
d.mu.RLock()
|
||||||
|
defer d.mu.RUnlock()
|
||||||
|
return d.dpOn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daemon) vacuumStatusGet() (rough, high bool) {
|
||||||
|
d.mu.RLock()
|
||||||
|
defer d.mu.RUnlock()
|
||||||
|
rough = !d.aboveRough.output
|
||||||
|
high = !d.aboveHigh.output
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *daemon) safetyStatusGet() (failsafe, highPressure bool) {
|
||||||
|
d.mu.RLock()
|
||||||
|
defer d.mu.RUnlock()
|
||||||
|
failsafe = d.failsafe
|
||||||
|
highPressure = d.highPressure
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// pumpDownPressed toggles the pump down relay for 500ms.
|
||||||
|
func (d *daemon) pumpDownPress() {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
d.pumpdown.trigger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ventPress toggles the vent relay for 500ms.
|
||||||
|
func (d *daemon) ventPress() {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
d.vent.trigger()
|
||||||
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// daemonController is the control/data interface passed on to external system
|
|
||||||
// controllers, eg. the web interface.
|
|
||||||
//
|
|
||||||
// This is a subset of daemon functions limited to a safe, explicit subset.
|
|
||||||
type daemonController interface {
|
|
||||||
// snapshot returns an internally consistent copy of the daemon state.
|
|
||||||
snapshot() *daemonState
|
|
||||||
// rpSet enables/disables the roughing pump.
|
|
||||||
rpSet(bool)
|
|
||||||
// dpSet enables/disables the diffusion pump.
|
|
||||||
dpSet(bool)
|
|
||||||
// pumpDownPress simulates a pumpdown button press.
|
|
||||||
pumpDownPress()
|
|
||||||
// ventPRess simulates a vent button press.
|
|
||||||
ventPress()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *daemon) snapshot() *daemonState {
|
|
||||||
d.mu.RLock()
|
|
||||||
ds := d.daemonState
|
|
||||||
d.mu.RUnlock()
|
|
||||||
return &ds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *daemon) rpSet(state bool) {
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
d.rpOn = state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *daemon) dpSet(state bool) {
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
d.dpOn = state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *daemon) pumpDownPress() {
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
d.pumpdown.trigger()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *daemon) ventPress() {
|
|
||||||
d.mu.Lock()
|
|
||||||
defer d.mu.Unlock()
|
|
||||||
d.vent.trigger()
|
|
||||||
}
|
|
|
@ -1,84 +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
|
|
||||||
|
|
||||||
// adcPiraniVolts is a moving window of read ADC values, used to calculate a
|
|
||||||
// moving average.
|
|
||||||
adcPiraniVolts []float32
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
// piraniDetection guesses whether the pirani gauge is connected.
|
|
||||||
func (d *daemonState) piraniDetection() piraniDetection {
|
|
||||||
if len(d.adcPiraniVolts) < 3 {
|
|
||||||
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)
|
|
||||||
|
|
||||||
if mbar < 4e-6 {
|
|
||||||
return piraniDetectionDisconnected
|
|
||||||
}
|
|
||||||
return piraniDetectionConnected
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *daemonState) vacuumStatus() (rough, high bool) {
|
|
||||||
rough = !d.aboveRough.output
|
|
||||||
high = !d.aboveHigh.output
|
|
||||||
return
|
|
||||||
}
|
|
Loading…
Reference in a new issue