diff --git a/succbone/succd/go.mod b/succbone/succd/go.mod
index d35e107..574985d 100644
--- a/succbone/succd/go.mod
+++ b/succbone/succd/go.mod
@@ -4,8 +4,5 @@ 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 ab9ca54..c8b5195 100644
--- a/succbone/succd/go.sum
+++ b/succbone/succd/go.sum
@@ -1,9 +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=
-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 01eab8e..f1503e7 100644
--- a/succbone/succd/http.go
+++ b/succbone/succd/http.go
@@ -62,17 +62,6 @@ 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
@@ -126,11 +115,6 @@ 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()
@@ -233,26 +217,6 @@ func (s *webServer) viewMetrics(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "# HELP sem_vacuum_high_reached Whether a high vacuum has been reached (boolean)\n")
fmt.Fprintf(w, "# TYPE sem_vacuum_high_reached gauge\n")
fmt.Fprintf(w, "sem_vacuum_high_reached %f\n", boolToFloat(high))
-
- fmt.Fprintf(w, "# HELP sem_environment_temperature_celsius Environmental temperature of the SEM, in degrees celsius\n")
- fmt.Fprintf(w, "# TYPE sem_environment_temperature_celsius gauge\n")
- fmt.Fprintf(w, "sem_environment_temperature_celsius %f\n", state.tempSEM)
-
- fmt.Fprintf(w, "# HELP sem_environment_humidity_percent Environmental relative humidity of the SEM, in percent\n")
- fmt.Fprintf(w, "# TYPE sem_environment_humidity_percent gauge\n")
- fmt.Fprintf(w, "sem_environment_humidity_percent %f\n", state.humiditySEM)
-
- fmt.Fprintf(w, "# HELP sem_dp_bottom_temperature_celsius Temperature of the DP bottom, in degrees celsius\n")
- fmt.Fprintf(w, "# TYPE sem_dp_bottom_temperature_celsius gauge\n")
- fmt.Fprintf(w, "sem_dp_bottom_temperature_celsius %f\n", state.tempDPBottom)
-
- fmt.Fprintf(w, "# HELP sem_dp_top_temperature_celsius Temperature of the DP top, in degrees celsius\n")
- fmt.Fprintf(w, "# TYPE sem_dp_top_temperature_celsius gauge\n")
- fmt.Fprintf(w, "sem_dp_top_temperature_celsius %f\n", state.tempDPTop)
-
- fmt.Fprintf(w, "# HELP sem_dp_inlet_temperature_celsius Temperature of the DP inlet flange, in degrees celsius\n")
- fmt.Fprintf(w, "# TYPE sem_dp_inlet_temperature_celsius gauge\n")
- fmt.Fprintf(w, "sem_dp_inlet_temperature_celsius %f\n", state.tempDPInlet)
}
func (s *webServer) viewRoughingPumpEnable(w http.ResponseWriter, r *http.Request) {
diff --git a/succbone/succd/index.html b/succbone/succd/index.html
index 37f0849..abd5695 100644
--- a/succbone/succd/index.html
+++ b/succbone/succd/index.html
@@ -9,19 +9,19 @@ body {
padding: 2em;
}
table {
- font-size: 30px;
+ font-size: 40px;
}
table.status td {
width: 2em;
}
th, td {
background-color: #e8e8e8;
- padding: 0.3em;
+ padding: 0.4em;
}
th {
font-weight: 100;
text-align: right;
- font-size: 25px;
+ font-size: 30px;
}
td {
text-align: left;
@@ -34,7 +34,7 @@ h2 {
font-weight: 100;
}
button {
- height: 3.3em;
+ height: 4.5em;
padding-left: 1.5em;
padding-right: 1.5em;
}
@@ -76,7 +76,6 @@ td > span {
.has-hidden:hover .hidden-text {
display: block;
-}
@media only screen and (max-width: 700px) {
body {
@@ -121,6 +120,19 @@ td > span {
+
+
+ | Pirani Pressure |
+
+ {{ .Pirani.Mbar }}
+
+ Voltage: {{ .Pirani.Volts }}
+
+ |
+
+
+
+
| Control |
@@ -151,41 +163,6 @@ td > span {
-
-
- | Pirani Pressure |
-
- {{ .Pirani.Mbar }}
-
- Voltage: {{ .Pirani.Volts }}
-
- |
-
-
- | Temperatures |
- DP Bottom |
- {{ .Temperatures.DPBottom }} °C |
-
-
- | DP Top |
- {{ .Temperatures.DPTop }} °C |
-
-
- | DP Inlet |
- {{ .Temperatures.DPInlet }} °C |
-
-
- | SEM Environment |
- {{ .Temperatures.SEM }} °C |
-
-
- | Humidity |
- SEM Environment |
- {{ .Humidity.SEM }}% |
-
-
-
-
@@ -339,11 +316,6 @@ 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");
@@ -416,30 +388,6 @@ 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";
@@ -465,7 +413,7 @@ window.addEventListener("load", (_) => {
// Indicate all process values as unknown
- [failsafe, highpressure, rp, dp, trough, thigh, volts, mbar, tempDPBottom, tempDPTop, tempDPInlet].forEach((el) => {
+ [failsafe, highpressure, rp, dp, trough, thigh, volts, mbar].forEach((el) => {
if (!el.innerHTML.includes("??")) {
el.innerHTML += "??";
}
diff --git a/succbone/succd/main.go b/succbone/succd/main.go
index 52a10db..64663a8 100644
--- a/succbone/succd/main.go
+++ b/succbone/succd/main.go
@@ -35,12 +35,6 @@ 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)
@@ -48,6 +42,12 @@ func main() {
if flagFake {
klog.Infof("Starting with fake peripherals")
d.adcPirani = &fakeADC{}
+ d.gpioRoughingPump = &fakeGPIO{desc: "rp"}
+ d.gpioDiffusionPump = &fakeGPIO{desc: "~dp"}
+ d.gpioBtnPumpDown = &fakeGPIO{desc: "~pd"}
+ d.gpioBtnVent = &fakeGPIO{desc: "~vent"}
+ d.gpioBelowRough = &fakeGPIO{desc: "~rough"}
+ d.gpioBelowHigh = &fakeGPIO{desc: "~high"}
} else {
adc, err := newBBADC(0)
if err != nil {
@@ -55,9 +55,21 @@ 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
+ }{
+ {&d.gpioRoughingPump, 115},
+ {&d.gpioDiffusionPump, 49},
+ {&d.gpioBtnPumpDown, 48},
+ {&d.gpioBtnVent, 60},
+ {&d.gpioBelowRough, 30},
+ {&d.gpioBelowHigh, 7},
+ } {
+ *c.out, err = newBBGPIO(c.num, true)
+ if err != nil {
+ klog.Exitf("Failed to setup GPIO: %v", err)
+ }
}
}
@@ -74,9 +86,5 @@ 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
deleted file mode 100644
index 4aebd03..0000000
--- a/succbone/succd/modbus.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package main
-
-import (
- "context"
- "time"
-
- "github.com/simonvetter/modbus"
- "k8s.io/klog"
-)
-
-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: 1 * time.Second,
- })
- if err != nil {
- return err
- }
- // Connect to modbus client
- err = d.modbusClient.Open()
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (d *daemon) modbusRestart() error {
- d.modbusClient.Close()
- return d.modbusClient.Open()
-}
-
-// 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
-//
-// Returns whether modbus should restart (only in case of an underlying network error)
-func (d *daemon) modbusUpdate() bool {
- var err error
- var numDevicesNotResponding int
-
- // Switch to slave 1 (BTA1)
- d.modbusClient.SetUnitId(1)
-
- // Read temperature and humidity
- var registersBTA1 []uint16 // temperature, humidity
- registersBTA1, err = d.modbusClient.ReadRegisters(1, 2, modbus.INPUT_REGISTER)
- if err != nil {
- numDevicesNotResponding += 1
- klog.Warningf("error while reading registers from BTA1 %v", err)
- } else if len(registersBTA1) != 2 {
- klog.Warningf("expected two registers from modbus slave 1, but got %d", len(registersBTA1))
- } else {
- d.mu.Lock()
- d.daemonState.tempSEM = modbusValuesToFloat(registersBTA1[0])
- d.daemonState.humiditySEM = modbusValuesToFloat(registersBTA1[1])
- d.mu.Unlock()
- }
-
- // Switch to slave 2 (KEC2)
- 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"
- var registersKEC2 []uint16 // temperatures dp
- registersKEC2, err = d.modbusClient.ReadRegisters(0, 3, modbus.HOLDING_REGISTER)
- if err != nil {
- numDevicesNotResponding += 1
- klog.Warningf("error while reading registers from KEC2 %v", err)
- } else if len(registersKEC2) != 3 {
- klog.Warningf("expected three registers from modbus slave 2, but got %d", len(registersKEC2))
- } else {
- d.mu.Lock()
- d.daemonState.tempDPBottom = modbusValuesToFloat(registersKEC2[0])
- d.daemonState.tempDPInlet = modbusValuesToFloat(registersKEC2[1])
- d.daemonState.tempDPTop = modbusValuesToFloat(registersKEC2[2])
- d.mu.Unlock()
- }
-
- // Switch to slave 3 (KEC1)
- d.modbusClient.SetUnitId(3)
-
- // Do a read first to avoid side-effects from the subsequent write to the relay states
- var digitalInputs [8]bool
- var digitalInputRegisters []uint16
-
- digitalInputRegisters, err = d.modbusClient.ReadRegisters(0x81, 8, modbus.HOLDING_REGISTER)
- if err != nil {
- numDevicesNotResponding += 1
- klog.Warningf("error while reading digital inputs from KEC1 %v", err)
- } else {
- // Convert MODBUS words into bools
- for idx, value := range digitalInputRegisters {
- if value != 0 {
- digitalInputs[idx] = true
- } else {
- digitalInputs[idx] = false
- }
- }
- // TODO: Input mapping goes here
- }
-
- // We must wait between reading and writing to the -KEC1 relay board
- // because otherwise it chokes and times out the write registers
- // command.
- time.Sleep(time.Millisecond * 10)
-
- // KFA1-KFA8
- var relayState [8]bool
- d.mu.Lock()
- // -KFA1 Roughing Pump (normally closed contact)
- relayState[0] = !d.daemonState.rpOn
- // -KFA2 Diffusion Pump
- relayState[1] = d.daemonState.dpOn
- // -KFA4 Button Vent
- relayState[3] = d.daemonState.vent.output
- // -KFA5 Button Pump-Down
- relayState[4] = d.daemonState.pumpdown.output
- // -KFA6 Fake-Pirani Rough (normally closed contact)
- relayState[5] = !d.aboveRough.output
- // -KFA7 Fake-Pirani High (normally closed contact)
- relayState[6] = !d.aboveHigh.output
- d.mu.Unlock()
-
- // The KEC1 module uses a non-standard MODBUS interface
- // instead of coils
- // 0x0100 is the open command
- // 0x0200 is the close command
- // We write 8 words (16-bit) to address 0x01 to update the relays
- var registerValuesKEC1 [8]uint16
- // Convert the boolean values to the commands
- for idx, state := range relayState {
- if state {
- registerValuesKEC1[idx] = 0x0100
- } else {
- registerValuesKEC1[idx] = 0x0200
- }
- }
-
- err = d.modbusClient.WriteRegisters(0x01, registerValuesKEC1[:])
- if err != nil {
- numDevicesNotResponding += 1
- klog.Warningf("error while updating registers %v", err)
- }
-
- if numDevicesNotResponding >= 4 {
- klog.Warningf("no device did respond to our request. Probably a network timeout.")
- return true
- }
-
- return false
-}
-
-// Call modbusUpdate every 100 milliseconds
-func (d *daemon) modbusProcess(ctx context.Context) {
- for {
- select {
- case <-ctx.Done():
- return
- default:
- shouldRestart := d.modbusUpdate()
- // the modbus library does not reopen the tcp socket in case of
- // a connection loss.
- if shouldRestart {
- klog.Infof("restarting modbus connection...")
- err := d.modbusRestart()
- if err != nil {
- klog.Warningf("failed to restart modbus %v", err)
- }
- }
- time.Sleep(time.Millisecond * 100)
- }
- }
-}
diff --git a/succbone/succd/process.go b/succbone/succd/process.go
index 7504b19..de99e84 100644
--- a/succbone/succd/process.go
+++ b/succbone/succd/process.go
@@ -8,17 +8,22 @@ 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
+ gpioDiffusionPump gpio
+ gpioRoughingPump gpio
+ gpioBtnPumpDown gpio
+ gpioBtnVent gpio
+ gpioBelowRough gpio
+ gpioBelowHigh gpio
+
load atomic.Int64
// mu guards the state below.
@@ -50,13 +55,6 @@ 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) {
@@ -164,5 +162,33 @@ func (d *daemon) processOnce(_ context.Context) error {
d.dpOn = false
}
+ // Update relay outputs.
+ for _, rel := range []struct {
+ name string
+ gpio gpio
+ // activeHigh means the relay is active high, ie. a true source will
+ // mean that NO/COM get connected, and a false source means that NC/COM
+ // get connected.
+ activeHigh bool
+ source bool
+ }{
+ {"rp", d.gpioRoughingPump, false, d.rpOn},
+ {"dp", d.gpioDiffusionPump, true, d.dpOn},
+ {"pumpdown", d.gpioBtnPumpDown, true, d.pumpdown.output},
+ {"vent", d.gpioBtnVent, true, d.vent.output},
+ {"rough", d.gpioBelowRough, false, d.aboveRough.output},
+ {"high", d.gpioBelowHigh, false, d.aboveHigh.output},
+ } {
+ val := rel.source
+ if rel.activeHigh {
+ // Invert because the relays go through logical inversion (ie. a
+ // GPIO false is a relay trigger).
+ val = !val
+ }
+ if err := rel.gpio.set(val); err != nil {
+ return fmt.Errorf("when outputting %s: %w", rel.name, err)
+ }
+ }
+
return nil
}