succd: unify html/js data source

This commit is contained in:
Serge Bazanski 2024-09-28 07:49:03 +02:00
parent 776f7a9911
commit 3ec6fd1d1b
2 changed files with 118 additions and 81 deletions

View file

@ -50,6 +50,90 @@ func formatMbar(v float32) template.HTML {
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 (d *daemon) apiData(skipSystem bool) *apiData {
volts, mbar := d.pirani()
rp := d.rpGet()
dp := d.dpGet()
rough, high := d.vacuumStatusGet()
safety := d.safetyStatusGet()
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 = safety.failsafe
ad.Safety.HighPressure = safety.highPressure
ad.Pirani.Volts = formatVolts(volts)
ad.Pirani.Mbar = formatMbar(mbar)
ad.Pirani.MbarFloat = mbar
ad.Pumps.RPOn = rp
ad.Pumps.DPOn = dp
ad.Feedback.RoughReached = rough
ad.Feedback.HighReached = high
ad.System.Load = load
ad.System.Hostname = hostname
return &ad
}
// httpIndex is the / view.
func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
@ -57,33 +141,9 @@ func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) {
return
}
volts, mbar := d.pirani()
rp := d.rpGet()
dp := d.dpGet()
safety := 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": safety.failsafe,
"highpressure": safety.highPressure,
"volts": formatVolts(volts),
"mbar": formatMbar(mbar),
"rp": rp,
"dp": dp,
"hostname": hostname,
"load": load,
})
data := d.apiData(false)
//data.Pirani.Mbar = html.St
templateIndex.Execute(w, data)
}
// httpStream is the websocket clientwards data hose, returning a 10Hz update
@ -105,33 +165,7 @@ func (d *daemon) httpStream(w http.ResponseWriter, r *http.Request) {
c.Close(websocket.StatusNormalClosure, "")
return
case <-t.C:
// 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()
safety := d.safetyStatusGet()
v := struct {
Failsafe bool
HighPressure bool
Volts string
Mbar string
MbarFloat float32
RPOn bool
DPOn bool
RoughReached bool
HighReached bool
}{
Failsafe: safety.failsafe,
HighPressure: safety.highPressure,
Volts: formatVolts(volts),
Mbar: string(formatMbar(mbar)),
MbarFloat: mbar,
RPOn: rp,
DPOn: dp,
RoughReached: rough,
HighReached: high,
}
v := d.apiData(true)
if err := wsjson.Write(ctx, c, v); err != nil {
klog.Errorf("Websocket write failed: %v", err)
return

View file

@ -59,25 +59,25 @@ td > span {
<table>
<tr>
<th>Voltage</th>
<td id="volts" colspan="4">{{.volts}}</td>
<td id="volts" colspan="4">{{ .Pirani.Volts }}</td>
</tr>
<tr>
<th>Pressure</th>
<td id="mbar" colspan="4">{{.mbar}}</td>
<td id="mbar" colspan="4">{{ .Pirani.Mbar }}</td>
</tr>
<tr>
<th>Thresholds</th>
<td>Rough:</td>
<td id="trough">...</td>
<td id="trough">{{ if .Feedback.RoughReached }}OK{{ else }}NOK{{ end }}</td>
<td>High:</td>
<td id="thigh">...</td>
<td id="thigh">{{ if .Feedback.HighReached }}OK{{ else }}NOK{{ end }}</td>
</tr>
<tr>
<th>Pumps</th>
<td>RP:</td>
<td id="rp">{{ if .rp }}ON{{ else }}OFF{{ end }}</td>
<td id="rp">{{ if .Pumps.RPOn }}ON{{ else }}OFF{{ end }}</td>
<td>DP:</td>
<td id="dp">{{ if .dp }}ON{{ else }}OFF{{ end }}</td>
<td id="dp">{{ if .Pumps.DPOn }}ON{{ else }}OFF{{ end }}</td>
</tr>
<tr>
<th>Evac Control</th>
@ -96,11 +96,11 @@ td > span {
</tr>
<tr>
<th>Failsafe</th>
<td id="failsafe" colspan="4">OK</td>
<td id="failsafe" colspan="4">{{ if .Safety.Failsafe }}ON{{ else }}OFF{{ end }}</td>
</tr>
<tr>
<th>Diffusion Pump Lockout</th>
<td id="highpressure" colspan="4">OK</td>
<td id="highpressure" colspan="4">{{ if .Safety.HighPressure }}ON{{ else }}OFF{{ end }}</td>
</tr>
</table>
</p>
@ -109,7 +109,7 @@ td > span {
</p>
<p style="font-style: italic; font-size: 12px; margin-top: 5em;">
{{.hostname}} | load: {{.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>
</p>
<script>
@ -288,50 +288,53 @@ window.addEventListener("load", (_) => {
});
socket.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
volts.innerHTML = data.Volts;
mbar.innerHTML = data.Mbar;
failsafe.innerHTML = data.Failsafe ? "ON" : "OFF";
if (data.Failsafe) {
failsafe.style = "background-color: #f06060";
volts.innerHTML = data.Pirani.Volts;
mbar.innerHTML = data.Pirani.Mbar;
if (data.Safety.Failsafe) {
failsafe.innerHTML = "ON";
failsafe.style = "background-color: #f06060";
} else {
failsafe.style = "background-color: #60f060";
failsafe.innerHTML = "OFF";
failsafe.style = "background-color: #60f060";
}
if (data.HighPressure) {
highpressure.innerHTML = "ON";
highpressure.style = "background-color: #f06060";
if (data.Safety.HighPressure) {
highpressure.innerHTML = "ON";
highpressure.style = "background-color: #f06060";
} else {
highpressure.innerHTML = "OFF";
highpressure.style = "background-color: #60f060";
highpressure.innerHTML = "OFF";
highpressure.style = "background-color: #60f060";
}
rp.innerHTML = data.RPOn ? "ON" : "OFF";
if (data.RPOn) {
if (data.Pumps.RPOn) {
rp.innerHTML = "ON";
rp.style = "background-color: #60f060";
} else {
rp.innerHTML = "OFF";
rp.style = "background-color: #f06060";
}
dp.innerHTML = data.DPOn ? "ON" : "OFF";
if (data.DPOn) {
if (data.Pumps.DPOn) {
dp.innerHTML = "ON";
dp.style = "background-color: #60f060";
} else {
dp.innerHTML = "OFF";
dp.style = "background-color: #f06060";
}
let t = [];
if (data.RoughReached) {
if (data.Feedback.RoughReached) {
trough.innerHTML = "OK";
trough.style = "background-color: #60f060";
} else {
trough.innerHTML = "NOK";
trough.style = "background-color: #f06060";
}
if (data.HighReached) {
if (data.Feedback.HighReached) {
thigh.innerHTML = "OK";
thigh.style = "background-color: #60f060";
} else {
thigh.innerHTML = "NOK";
thigh.style = "background-color: #f06060";
}
historicalPush(data.MbarFloat);
historicalPush(data.Pirani.MbarFloat);
ping.innerHTML = Date.now();
});
socket.addEventListener("close", (event) => {