succbone: init
This commit is contained in:
parent
4e31493f0a
commit
05d102ab9b
1
succd/.gitignore
vendored
Normal file
1
succd/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
succd
|
35
succd/README.md
Normal file
35
succd/README.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
succd
|
||||||
|
====
|
||||||
|
|
||||||
|
A little daemon for monitoring the SEM. This is a temporary solution that runs on a BeagleBone Enhanced (`succbone.lab`) and *SHOULD* be replaced with a proper PLC/SCADA system and general process control in the future.
|
||||||
|
|
||||||
|
Currently it monitors the state of the Pirani gauge via the BBE's builtin ADC.
|
||||||
|
|
||||||
|
Accessing at the lab
|
||||||
|
---
|
||||||
|
|
||||||
|
Go to [succbone.lab.fa-fo.de](http://succbone.lab.fa-fo.de).
|
||||||
|
|
||||||
|
Known issues
|
||||||
|
---
|
||||||
|
|
||||||
|
Sometimes the websocket doesn't connect. Refreshing the page a few times should fix it.
|
||||||
|
|
||||||
|
Running locally
|
||||||
|
---
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go run . -fake
|
||||||
|
```
|
||||||
|
|
||||||
|
Then point your browser to localhost:8080
|
||||||
|
|
||||||
|
Deploying on the succbone
|
||||||
|
---
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ssh root@succbone systemctl stop succd
|
||||||
|
$ GOARCH=arm go build .
|
||||||
|
$ scp succd root@succbone:/usr/bin/succd
|
||||||
|
$ ssh root@succbone systemctl start succd
|
||||||
|
```
|
61
succd/adc.go
Normal file
61
succd/adc.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// adc is an abstract ADC-based analog input.
|
||||||
|
type adc interface {
|
||||||
|
// Read returns the ADC value in volts.
|
||||||
|
Read() (float32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bbADC implements adc using a BeagleBone's built-in ADC.
|
||||||
|
type bbADC struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBBADC returns a BeagleBone ADC for a given channel number.
|
||||||
|
func newBBADC(num int) (*bbADC, error) {
|
||||||
|
path := fmt.Sprintf("/sys/bus/iio/devices/iio:device0/in_voltage%d_raw", num)
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not access: %w", err)
|
||||||
|
}
|
||||||
|
return &bbADC{
|
||||||
|
path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bbADC) Read() (float32, error) {
|
||||||
|
by, err := os.ReadFile(b.path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d := strings.TrimSpace(string(by))
|
||||||
|
v, err := strconv.ParseUint(d, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// The ADC Vref/Vdd is at 1.8V and is 10-bit (0-4095).
|
||||||
|
vadc := float32(v) * 1.8 / 4096.0
|
||||||
|
// The ADC is connected through a resistor divider.
|
||||||
|
r1 := float32(1000.0)
|
||||||
|
r2 := float32(4698.0)
|
||||||
|
vin := vadc / (r1 / (r1 + r2))
|
||||||
|
return vin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeADC implements an adc that outputs a sine wave. This is used for testing.
|
||||||
|
type fakeADC struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *fakeADC) Read() (float32, error) {
|
||||||
|
t := float64(time.Now().UnixMilli()) / 1000
|
||||||
|
v := (math.Sin(t/10)+1)*(6.5/2) + 2
|
||||||
|
return float32(v), nil
|
||||||
|
}
|
8
succd/go.mod
Normal file
8
succd/go.mod
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module git.fa-fo.de/fafo/jeol-t330a/succd
|
||||||
|
|
||||||
|
go 1.22.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/coder/websocket v1.8.12
|
||||||
|
k8s.io/klog v1.0.0
|
||||||
|
)
|
5
succd/go.sum
Normal file
5
succd/go.sum
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
|
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||||
|
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
121
succd/http.go
Normal file
121
succd/http.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatVolts(v float32) string {
|
||||||
|
return fmt.Sprintf("%.3f 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()
|
||||||
|
|
||||||
|
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),
|
||||||
|
"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()
|
||||||
|
v := struct {
|
||||||
|
Volts string
|
||||||
|
Mbar string
|
||||||
|
MbarFloat float32
|
||||||
|
}{
|
||||||
|
Volts: formatVolts(volts),
|
||||||
|
Mbar: string(formatMbar(mbar)),
|
||||||
|
MbarFloat: mbar,
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
247
succd/index.html
Normal file
247
succd/index.html
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>succd</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
padding: 0.4em;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
font-weight: 100;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h1>succd</h1>
|
||||||
|
<h2>nothing more permanent than a temporary solution</h2>
|
||||||
|
|
||||||
|
<p style="margin-top: 5em;">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Voltage</th>
|
||||||
|
<td id="volts">{{.volts}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Pressure</th>
|
||||||
|
<td id="mbar">{{.mbar}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td id="status">OK</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
<p style="margin-top: 2em;">
|
||||||
|
<canvas id="graph" width="1024" height="512"></canvas>
|
||||||
|
</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>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
let historical = [];
|
||||||
|
let canvas = null;
|
||||||
|
|
||||||
|
// Push a datapoint (in mbar) to the historical buffer, maintaining enough data
|
||||||
|
// for ~10 minutes.
|
||||||
|
let historicalPush = (v) => {
|
||||||
|
historical.push({v: v, time: Date.now() / 1000});
|
||||||
|
let len = historical.length;
|
||||||
|
// TODO(q3k): trim based on recorded timestamp, not constant buffer size.
|
||||||
|
let trim = len - 8192;
|
||||||
|
if (trim > 0) {
|
||||||
|
historical = historical.slice(trim);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw the historical graph and schedule next draw in 100msec.
|
||||||
|
let historicalDraw = (w, h) => {
|
||||||
|
const now = Date.now() / 1000;
|
||||||
|
|
||||||
|
// TODO(q3k): better use canvas API to not have so much silly math around
|
||||||
|
// coordinate calculation.
|
||||||
|
|
||||||
|
canvas.clearRect(0, 0, w, h);
|
||||||
|
canvas.fillStyle = "#f0f0f0";
|
||||||
|
canvas.fillRect(0, 0, w, h);
|
||||||
|
|
||||||
|
// Margins of the main graph window.
|
||||||
|
const marginLeft = 64;
|
||||||
|
const marginRight = 32;
|
||||||
|
const marginTop = 32;
|
||||||
|
const marginBottom = 32;
|
||||||
|
|
||||||
|
// Draw main graph window.
|
||||||
|
canvas.fillStyle = "#f8f8f8";
|
||||||
|
canvas.strokeStyle = "#444";
|
||||||
|
canvas.lineWidth = 1;
|
||||||
|
canvas.fillRect(marginLeft, marginTop, w-(marginLeft+marginRight), h-(marginTop+marginBottom));
|
||||||
|
canvas.strokeRect(marginLeft, marginTop, w-(marginLeft+marginRight), h-(marginTop+marginBottom));
|
||||||
|
|
||||||
|
// Range of decades for Y value.
|
||||||
|
const ymin = -4;
|
||||||
|
const ymax = 4;
|
||||||
|
// Pixels per decade.
|
||||||
|
const yscale = (h - (marginTop+marginBottom)) / (ymax - ymin);
|
||||||
|
|
||||||
|
// For every decade...
|
||||||
|
for (let i = ymin; i < ymax; i++) {
|
||||||
|
const yoff = (i - ymin) * yscale + yscale / 2;
|
||||||
|
const y = Math.floor(h - marginBottom - yoff) + 0.5;
|
||||||
|
// Draw Y scale ticks.
|
||||||
|
canvas.beginPath();
|
||||||
|
canvas.moveTo(marginLeft-5, y);
|
||||||
|
canvas.lineTo(marginLeft, y);
|
||||||
|
canvas.strokeStyle = "#000";
|
||||||
|
canvas.stroke();
|
||||||
|
|
||||||
|
// Draw Y grid.
|
||||||
|
canvas.beginPath();
|
||||||
|
canvas.moveTo(marginLeft, y);
|
||||||
|
canvas.lineTo(w-marginRight-1, y);
|
||||||
|
canvas.strokeStyle = "#ccc";
|
||||||
|
canvas.stroke();
|
||||||
|
|
||||||
|
// Draw Y fine grid.
|
||||||
|
if (i > ymin) {
|
||||||
|
for (let j = 2; j < 10; j++) {
|
||||||
|
let yy = y - Math.log10(j/10) * yscale;
|
||||||
|
canvas.beginPath();
|
||||||
|
canvas.moveTo(marginLeft, yy);
|
||||||
|
canvas.lineTo(w-marginRight-1, yy);
|
||||||
|
canvas.strokeStyle = "#eee";
|
||||||
|
canvas.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw Y labels.
|
||||||
|
canvas.font = "10px sans-serif";
|
||||||
|
canvas.fillStyle = "#000";
|
||||||
|
canvas.textAlign = "right";
|
||||||
|
const text = `10^${i}`;
|
||||||
|
canvas.fillText(text, marginLeft-10, y+5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// How much space to leave in front of the graph.
|
||||||
|
const xhead = 10;
|
||||||
|
|
||||||
|
// Draw X labels..
|
||||||
|
canvas.textAlign = "center";
|
||||||
|
canvas.fillText("Now", w - marginRight - xhead, h - marginBottom + 15)
|
||||||
|
canvas.fillText("-10min", marginLeft, h - marginBottom + 15)
|
||||||
|
|
||||||
|
const xmax = 60 * 10;
|
||||||
|
const xscale = (w - (marginLeft+marginRight+xhead)) / (xmax);
|
||||||
|
|
||||||
|
// Clip to main window.
|
||||||
|
canvas.save();
|
||||||
|
canvas.beginPath();
|
||||||
|
canvas.rect(marginLeft, marginTop, w-(marginLeft+marginRight), h-(marginTop+marginBottom+1));
|
||||||
|
canvas.clip();
|
||||||
|
|
||||||
|
// Draw actual data line.
|
||||||
|
let first = true;
|
||||||
|
canvas.beginPath();
|
||||||
|
historical.forEach((v) => {
|
||||||
|
const time = v.time;
|
||||||
|
const mbar = v.v;
|
||||||
|
const elapsed = now-time;
|
||||||
|
if (elapsed > xmax) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = (w - marginRight - xhead) - (elapsed * xscale);
|
||||||
|
const yoff = (Math.log10(mbar) - ymin) * yscale + yscale / 2;
|
||||||
|
const y = h - marginBottom - yoff;
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
canvas.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
canvas.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
canvas.strokeStyle = "#de1010";
|
||||||
|
canvas.lineWidth = 1;
|
||||||
|
canvas.stroke();
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
setTimeout(() => { historicalDraw(w, h); }, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("load", (_) => {
|
||||||
|
console.log("s u c c");
|
||||||
|
|
||||||
|
let status = document.querySelector("#status");
|
||||||
|
let volts = document.querySelector("#volts");
|
||||||
|
let mbar = document.querySelector("#mbar");
|
||||||
|
let ping = document.querySelector("#ping");
|
||||||
|
canvas = document.querySelector("#graph").getContext("2d");
|
||||||
|
|
||||||
|
// TODO(q3k): unhardcode this and generally support scaling canvas.
|
||||||
|
historicalDraw(1024, 512);
|
||||||
|
|
||||||
|
// Basic retry loop for connecting to WS.
|
||||||
|
let loc = window.location;
|
||||||
|
let wsloc = "";
|
||||||
|
if (loc.protocol == "https:") {
|
||||||
|
wsloc = "wss:";
|
||||||
|
} else {
|
||||||
|
wsloc = "ws:";
|
||||||
|
}
|
||||||
|
wsloc += "//" + loc.host + "/stream";
|
||||||
|
console.log("Connecting to " + wsloc + "...");
|
||||||
|
|
||||||
|
let connected = false;
|
||||||
|
let connect = () => {
|
||||||
|
const socket = new WebSocket(wsloc);
|
||||||
|
socket.addEventListener("open", (event) => {
|
||||||
|
connected = true;
|
||||||
|
console.log("Socket connected!");
|
||||||
|
status.innerHTML = "Online";
|
||||||
|
status.style = "background-color: #60f060;";
|
||||||
|
});
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
volts.innerHTML = data.Volts;
|
||||||
|
mbar.innerHTML = data.Mbar;
|
||||||
|
historicalPush(data.MbarFloat);
|
||||||
|
ping.innerHTML = Date.now();
|
||||||
|
});
|
||||||
|
socket.addEventListener("close", (event) => {
|
||||||
|
status.innerHTML = "Offline";
|
||||||
|
status.style = "background-color: #f06060;";
|
||||||
|
if (connected) {
|
||||||
|
console.log("Socket dead, reconnecting...");
|
||||||
|
}
|
||||||
|
connected = false;
|
||||||
|
setTimeout(connect, 1000);
|
||||||
|
});
|
||||||
|
socket.addEventListener("error", (event) => {
|
||||||
|
socket.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
connect();
|
||||||
|
});
|
||||||
|
</script>
|
113
succd/main.go
Normal file
113
succd/main.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// daemon is the main state of the succdaemon.
|
||||||
|
type daemon struct {
|
||||||
|
// adcPirani is the adc implementation returning the voltage of the Pfeiffer
|
||||||
|
// Pirani gauge.
|
||||||
|
adcPirani adc
|
||||||
|
|
||||||
|
// mu guards state variables below.
|
||||||
|
mu sync.RWMutex
|
||||||
|
// adcPiraniVolts is the last readout of adcPirani.
|
||||||
|
adcPiraniVolts float32
|
||||||
|
}
|
||||||
|
|
||||||
|
// process runs the pain acquisition and control loop of succd.
|
||||||
|
func (d *daemon) process(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(time.Millisecond * 100)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := d.processOnce(ctx); err != nil {
|
||||||
|
if errors.Is(err, ctx.Err()) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
klog.Errorf("Processing error: %v", err)
|
||||||
|
time.Sleep(time.Second * 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processOnce runs the main loop step of succd.
|
||||||
|
func (d *daemon) processOnce(_ context.Context) error {
|
||||||
|
v, err := d.adcPirani.Read()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("when reading ADC: %w", err)
|
||||||
|
}
|
||||||
|
d.mu.Lock()
|
||||||
|
d.adcPiraniVolts = v
|
||||||
|
d.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pirani returns the Pirani gauge voltage and pressure.
|
||||||
|
func (d *daemon) pirani() (volts float32, mbar float32) {
|
||||||
|
d.mu.RLock()
|
||||||
|
volts = d.adcPiraniVolts
|
||||||
|
d.mu.RUnlock()
|
||||||
|
|
||||||
|
// Per Pirani probe docs.
|
||||||
|
bar := math.Pow(10.0, float64(volts)-8.5)
|
||||||
|
mbar = float32(bar * 1000.0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagFake bool
|
||||||
|
flagListenHTTP string
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.BoolVar(&flagFake, "fake", false, "Enable fake mode which allows to run succd for tests outside the succbone")
|
||||||
|
flag.StringVar(&flagListenHTTP, "listen_http", ":8080", "Address at which to listen for HTTP requests")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
|
||||||
|
d := daemon{}
|
||||||
|
if flagFake {
|
||||||
|
klog.Infof("Starting with fake Pirani probe")
|
||||||
|
d.adcPirani = &fakeADC{}
|
||||||
|
} else {
|
||||||
|
adc, err := newBBADC(0)
|
||||||
|
if err != nil {
|
||||||
|
klog.Exitf("Failed to setup Pirani ADC: %v", err)
|
||||||
|
}
|
||||||
|
d.adcPirani = adc
|
||||||
|
}
|
||||||
|
|
||||||
|
http.HandleFunc("/", d.httpIndex)
|
||||||
|
http.HandleFunc("/stream", d.httpStream)
|
||||||
|
http.HandleFunc("/metrics", d.httpMetrics)
|
||||||
|
|
||||||
|
klog.Infof("Listening for HTTP at %s", flagListenHTTP)
|
||||||
|
go func() {
|
||||||
|
if err := http.ListenAndServe(flagListenHTTP, nil); err != nil {
|
||||||
|
klog.Errorf("HTTP listen failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go d.process(ctx)
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
Loading…
Reference in a new issue