diff --git a/succbone/succd/go.mod b/succbone/succd/go.mod
index 574985d..d35e107 100644
--- a/succbone/succd/go.mod
+++ b/succbone/succd/go.mod
@@ -4,5 +4,8 @@ go 1.22.3
require (
github.com/coder/websocket v1.8.12
+ github.com/simonvetter/modbus v1.6.3
k8s.io/klog v1.0.0
)
+
+require github.com/goburrow/serial v0.1.0 // indirect
diff --git a/succbone/succd/go.sum b/succbone/succd/go.sum
index c8b5195..ab9ca54 100644
--- a/succbone/succd/go.sum
+++ b/succbone/succd/go.sum
@@ -1,5 +1,9 @@
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=
+github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
+github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
+github.com/simonvetter/modbus v1.6.3 h1:kDzwVfIPczsM4Iz09il/Dij/bqlT4XiJVa0GYaOVA9w=
+github.com/simonvetter/modbus v1.6.3/go.mod h1:hh90ZaTaPLcK2REj6/fpTbiV0J6S7GWmd8q+GVRObPw=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
diff --git a/succbone/succd/http.go b/succbone/succd/http.go
index 4365428..a0ac21a 100644
--- a/succbone/succd/http.go
+++ b/succbone/succd/http.go
@@ -62,6 +62,17 @@ type apiData struct {
// DPOn means the diffusion pump is turned on.
DPOn bool
}
+ // Temperature state.
+ Temperatures struct {
+ DPBottom float32
+ DPTop float32
+ DPInlet float32
+ SEM float32
+ }
+ // Humidity state.
+ Humidity struct {
+ SEM float32
+ }
// Pressure feedback into evacuation board.
Feedback struct {
// RoughReached is true when the system has reached a rough vacuum
@@ -115,6 +126,11 @@ func (s *webServer) apiData(skipSystem bool) *apiData {
ad.Pirani.MbarFloat = mbar
ad.Pumps.RPOn = state.rpOn
ad.Pumps.DPOn = state.dpOn
+ ad.Temperatures.DPBottom = state.tempDPBottom
+ ad.Temperatures.DPTop = state.tempDPTop
+ ad.Temperatures.DPInlet = state.tempDPInlet
+ ad.Temperatures.SEM = state.tempSEM
+ ad.Humidity.SEM = state.humiditySEM
ad.Feedback.RoughReached = rough
ad.Feedback.HighReached = high
ad.LoopLoad = s.d.loopLoad()
diff --git a/succbone/succd/index.html b/succbone/succd/index.html
index abd5695..def9ac9 100644
--- a/succbone/succd/index.html
+++ b/succbone/succd/index.html
@@ -9,19 +9,19 @@ body {
padding: 2em;
}
table {
- font-size: 40px;
+ font-size: 30px;
}
table.status td {
width: 2em;
}
th, td {
background-color: #e8e8e8;
- padding: 0.4em;
+ padding: 0.3em;
}
th {
font-weight: 100;
text-align: right;
- font-size: 30px;
+ font-size: 25px;
}
td {
text-align: left;
@@ -34,7 +34,7 @@ h2 {
font-weight: 100;
}
button {
- height: 4.5em;
+ height: 3.3em;
padding-left: 1.5em;
padding-right: 1.5em;
}
@@ -76,6 +76,7 @@ td > span {
.has-hidden:hover .hidden-text {
display: block;
+}
@media only screen and (max-width: 700px) {
body {
@@ -120,19 +121,6 @@ td > span {
-
-
- Pirani Pressure |
-
- {{ .Pirani.Mbar }}
-
- Voltage: {{ .Pirani.Volts }}
-
- |
-
-
-
-
Control |
@@ -163,6 +151,41 @@ td > span {
+
+
+ Pirani Pressure |
+
+ {{ .Pirani.Mbar }}
+
+ Voltage: {{ .Pirani.Volts }}
+
+ |
+
+
+ Temperatures |
+ DP Bottom |
+ {{ .Temperatures.DPBottom }} |
+
+
+ DP Top |
+ {{ .Temperatures.DPTop }} |
+
+
+ DP Inlet |
+ {{ .Temperatures.DPInlet }} |
+
+
+ SEM Environment |
+ {{ .Temperatures.SEM }} |
+
+
+ Humidity |
+ SEM Environment |
+ {{ .Humidity.SEM }} |
+
+
+
+
@@ -316,6 +339,11 @@ window.addEventListener("load", (_) => {
let trough = document.querySelector("#trough");
let thigh = document.querySelector("#thigh");
let load = document.querySelector("#load");
+ let tempSEM = document.querySelector("#temp-sem");
+ let tempDPBottom = document.querySelector("#temp-dp-bottom");
+ let tempDPTop = document.querySelector("#temp-dp-top");
+ let tempDPInlet = document.querySelector("#temp-dp-inlet");
+ let humiditySEM = document.querySelector('#humidity-sem');
// Buttons
let pd = document.querySelector("#pd");
@@ -388,6 +416,30 @@ window.addEventListener("load", (_) => {
dp.style = colors.default;
}
+ tempSEM.innerHTML = data.Temperatures.SEM + " °C";
+ tempSEM.style = (data.Temperatures.SEM > 30) ?
+ colors.highlightCaution : colors.default;
+ humiditySEM.innerHTML = data.Humidity.SEM + " %";
+ humiditySEM.style = (data.Humidity.SEM > 59) ?
+ colors.highlightCaution : colors.default;
+
+ tempDPTop.innerHTML = data.Temperatures.DPTop + " °C";
+ tempDPTop.style = (data.Temperatures.DPTop > 30) ?
+ colors.highlightCaution : colors.default;
+
+ tempDPInlet.innerHTML = data.Temperatures.DPInlet + " °C";
+ tempDPInlet.style = (data.Temperatures.DPInlet > 30) ?
+ colors.highlightCaution : colors.default;
+
+ tempDPBottom.innerHTML = data.Temperatures.DPBottom + " °C";
+ if (data.Temperatures.DPBottom > 200) {
+ tempDPBottom.style = colors.highlightFault;
+ } else if (data.Temperatures.DPBottom > 50) {
+ tempDPBottom.style = colors.highlightNeutral;
+ } else {
+ tempDPBottom.style = colors.default;
+ }
+
let t = [];
if (data.Feedback.RoughReached) {
trough.innerHTML = "OK";
@@ -413,7 +465,7 @@ window.addEventListener("load", (_) => {
// Indicate all process values as unknown
- [failsafe, highpressure, rp, dp, trough, thigh, volts, mbar].forEach((el) => {
+ [failsafe, highpressure, rp, dp, trough, thigh, volts, mbar, tempDPBottom, tempDPTop, tempDPInlet].forEach((el) => {
if (!el.innerHTML.includes("??")) {
el.innerHTML += "??";
}
diff --git a/succbone/succd/main.go b/succbone/succd/main.go
index 64663a8..cfd93a9 100644
--- a/succbone/succd/main.go
+++ b/succbone/succd/main.go
@@ -35,6 +35,12 @@ func main() {
d.daemonState.piraniVolts3.limit = 3
d.daemonState.piraniVolts100.limit = 100
+ d.tempDPBottom = 420.6
+ d.tempDPTop = 69.0
+ d.tempDPInlet = 42.0
+ d.tempSEM = 42.5
+ d.humiditySEM = 66.6
+
d.aboveRough.threshold = float64(flagPressureThresholdRough)
d.aboveRough.hysteresis = float64(flagPressureThresholdRoughHysteresis)
d.aboveHigh.threshold = float64(flagPressureThresholdHigh)
@@ -55,6 +61,11 @@ func main() {
}
d.adcPirani = adc
+ err = d.modbusConnect()
+ if err != nil {
+ klog.Exitf("Failed to connect to modbus %v", err)
+ }
+
for _, c := range []struct {
out *gpio
num int
@@ -86,5 +97,9 @@ func main() {
}()
go d.process(ctx)
+ if !flagFake {
+ go d.modbusProcess(ctx)
+ }
+
<-ctx.Done()
}
diff --git a/succbone/succd/modbus.go b/succbone/succd/modbus.go
new file mode 100644
index 0000000..15c3697
--- /dev/null
+++ b/succbone/succd/modbus.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/simonvetter/modbus"
+)
+
+func modbusValuesToFloat(v uint16) float32 {
+ return float32(v) / 10.0
+}
+
+func (d *daemon) modbusConnect() error {
+ var err error
+
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ // Setup modbus client
+ d.modbusClient, err = modbus.NewClient(&modbus.ClientConfiguration{
+ URL: "tcp://10.250.241.20:8887",
+ Timeout: 5 * time.Second,
+ })
+ if err != nil {
+ return err
+ }
+ // Connect to modbus client
+ err = d.modbusClient.Open()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// There are currently two devices connected to the modbus.
+// The first one (slave 1) is a temperature/humidity sensor.
+// The second one (slave 2) is a PTA8D08 transmitter
+func (d *daemon) modbusUpdate() error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ var err error
+ var registers []uint16 // temperature, humidity
+
+ // Switch to slave 1
+ d.modbusClient.SetUnitId(1)
+
+ // Read temperature and humidity
+ registers, err = d.modbusClient.ReadRegisters(1, 2, modbus.INPUT_REGISTER)
+ if err != nil {
+ return err
+ }
+
+ if len(registers) != 2 {
+ msg := fmt.Sprintf("Expected two registers from modbus slave 1, but got %d", len(registers))
+ return errors.New(msg)
+ }
+
+ d.daemonState.tempSEM = modbusValuesToFloat(registers[0])
+ d.daemonState.humiditySEM = modbusValuesToFloat(registers[1])
+
+ // Switch to slave 2
+ d.modbusClient.SetUnitId(2)
+
+ // PT100 mapping
+ // Channel 0: Cable -WGA6, Sensor "dp bottom"
+ // Channel 1: Cable -WGA8, Sensor "dp inlet"
+ // Channel 2: Cable WGA7, Sensor "dp top"
+ registers, err = d.modbusClient.ReadRegisters(0, 3, modbus.HOLDING_REGISTER)
+ if err != nil {
+ return err
+ }
+
+ if len(registers) != 3 {
+ msg := fmt.Sprintf("Expected three registers from modbus slave 2, but got %d", len(registers))
+ return errors.New(msg)
+ }
+
+ d.daemonState.tempDPBottom = modbusValuesToFloat(registers[0])
+ d.daemonState.tempDPInlet = modbusValuesToFloat(registers[1])
+ d.daemonState.tempDPTop = modbusValuesToFloat(registers[2])
+
+ return nil
+}
+
+// Call modbusUpdate every second
+func (d *daemon) modbusProcess(ctx context.Context) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ d.modbusUpdate()
+ time.Sleep(time.Second * 1)
+ }
+ }
+}
diff --git a/succbone/succd/process.go b/succbone/succd/process.go
index de99e84..7ade055 100644
--- a/succbone/succd/process.go
+++ b/succbone/succd/process.go
@@ -8,11 +8,13 @@ import (
"sync/atomic"
"time"
+ "github.com/simonvetter/modbus"
"k8s.io/klog"
)
// daemon is the main service of the succdaemon.
type daemon struct {
+ modbusClient *modbus.ModbusClient
// adcPirani is the adc implementation returning the voltage of the Pfeiffer
// Pirani gauge.
adcPirani adc
@@ -55,6 +57,13 @@ type daemonState struct {
pumpdown momentaryOutput
aboveRough thresholdOutput
aboveHigh thresholdOutput
+
+ tempDPBottom float32
+ tempDPTop float32
+ tempDPInlet float32
+
+ tempSEM float32
+ humiditySEM float32
}
func (d *daemonState) vacuumStatus() (rough, high bool) {