jeol-t330a/succbone/succd/http.go

172 lines
3.9 KiB
Go

package main
import (
_ "embed"
"fmt"
"html/template"
"net/http"
_ "net/http/pprof"
"os"
"strings"
"time"
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
"k8s.io/klog"
)
var (
//go:embed index.html
templateIndexText string
templateIndex = template.Must(template.New("index").Parse(templateIndexText))
//go:embed succd.png
favicon []byte
)
func (d *daemon) httpFavicon(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
w.Write(favicon)
}
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<sup>%d</sup>", exp)
res += " mbar"
return template.HTML(res)
}
// httpIndex is the / view.
func (d *daemon) httpIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
volts, mbar := d.pirani()
rp := d.rpGet()
dp := d.dpGet()
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{
"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
// stream of pressure/voltage.
func (d *daemon) httpStream(w http.ResponseWriter, r *http.Request) {
c, err := websocket.Accept(w, r, nil)
if err != nil {
return
}
defer c.CloseNow()
t := time.NewTicker(time.Second / 10)
defer t.Stop()
ctx := c.CloseRead(r.Context())
for {
select {
case <-ctx.Done():
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()
v := struct {
Volts string
Mbar string
MbarFloat float32
RPOn bool
DPOn bool
RoughReached bool
HighReached bool
}{
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 {
klog.Errorf("Websocket write failed: %v", err)
return
}
}
}
}
// httpMetrics serves minimalistic Prometheus-compatible metrics.
func (d *daemon) httpMetrics(w http.ResponseWriter, r *http.Request) {
// TODO(q3k): also serve Go stuff using the actual Prometheus metrics client
// library.
_, mbar := d.pirani()
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)
}
func (d *daemon) httpRoughingPumpEnable(w http.ResponseWriter, r *http.Request) {
d.rpSet(true)
fmt.Fprintf(w, "succ on\n")
}
func (d *daemon) httpRoughingPumpDisable(w http.ResponseWriter, r *http.Request) {
d.rpSet(false)
fmt.Fprintf(w, "succ off\n")
}
func (d *daemon) httpDiffusionPumpEnable(w http.ResponseWriter, r *http.Request) {
d.dpSet(true)
fmt.Fprintf(w, "deep succ on\n")
}
func (d *daemon) httpDiffusionPumpDisable(w http.ResponseWriter, r *http.Request) {
d.dpSet(false)
fmt.Fprintf(w, "deep succ off\n")
}
func (d *daemon) httpButtonPumpDown(w http.ResponseWriter, r *http.Request) {
d.pumpDownPress()
}
func (d *daemon) httpButtonVent(w http.ResponseWriter, r *http.Request) {
d.ventPress()
}