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%d", 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() v := struct { Volts string Mbar string MbarFloat float32 RPOn bool DPOn bool }{ Volts: formatVolts(volts), Mbar: string(formatMbar(mbar)), MbarFloat: mbar, RPOn: rp, DPOn: dp, } 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() }