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) } // 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 != "/" { http.NotFound(w, r) return } data := d.apiData(false) //data.Pirani.Mbar = html.St templateIndex.Execute(w, data) } // 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: v := d.apiData(true) 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() }