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) 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. // httpIndex is the / view.
func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) { func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" { if r.URL.Path != "/" {
@ -57,33 +141,9 @@ func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) {
return return
} }
volts, mbar := d.pirani() data := d.apiData(false)
rp := d.rpGet() //data.Pirani.Mbar = html.St
dp := d.dpGet() templateIndex.Execute(w, data)
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,
})
} }
// httpStream is the websocket clientwards data hose, returning a 10Hz update // 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, "") c.Close(websocket.StatusNormalClosure, "")
return return
case <-t.C: case <-t.C:
// TODO(q3k): don't poll, get notified when new ADC readout is available. v := d.apiData(true)
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,
}
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

View file

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