Compare commits

...

4 commits

Author SHA1 Message Date
Rahix 260f77a2a8 succd: Add highlight colors for temperatures
All checks were successful
/ test (push) Successful in 10s
/ test (pull_request) Successful in 10s
Highlight out-of-range temperatures for the user.  The limits are
currently by intuition and should be reconsidered.
2024-11-10 01:48:23 +01:00
Rahix 5f5c653a5d succd: Improve styling of temperature values
HMI design goes brrrr...
2024-11-10 01:48:23 +01:00
hmelder 0da94f7161 Add modbus integration
All checks were successful
/ test (push) Successful in 11s
2024-11-10 01:47:01 +01:00
hmelder 5bf20711d6 Add temperature and humidity stuff
All checks were successful
/ test (push) Successful in 10s
2024-11-10 01:22:18 +01:00
7 changed files with 218 additions and 18 deletions

View file

@ -4,5 +4,8 @@ go 1.22.3
require ( require (
github.com/coder/websocket v1.8.12 github.com/coder/websocket v1.8.12
github.com/simonvetter/modbus v1.6.3
k8s.io/klog v1.0.0 k8s.io/klog v1.0.0
) )
require github.com/goburrow/serial v0.1.0 // indirect

View file

@ -1,5 +1,9 @@
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= 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/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 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=

View file

@ -62,6 +62,17 @@ type apiData struct {
// DPOn means the diffusion pump is turned on. // DPOn means the diffusion pump is turned on.
DPOn bool 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. // Pressure feedback into evacuation board.
Feedback struct { Feedback struct {
// RoughReached is true when the system has reached a rough vacuum // 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.Pirani.MbarFloat = mbar
ad.Pumps.RPOn = state.rpOn ad.Pumps.RPOn = state.rpOn
ad.Pumps.DPOn = state.dpOn 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.RoughReached = rough
ad.Feedback.HighReached = high ad.Feedback.HighReached = high
ad.LoopLoad = s.d.loopLoad() ad.LoopLoad = s.d.loopLoad()

View file

@ -9,19 +9,19 @@ body {
padding: 2em; padding: 2em;
} }
table { table {
font-size: 40px; font-size: 30px;
} }
table.status td { table.status td {
width: 2em; width: 2em;
} }
th, td { th, td {
background-color: #e8e8e8; background-color: #e8e8e8;
padding: 0.4em; padding: 0.3em;
} }
th { th {
font-weight: 100; font-weight: 100;
text-align: right; text-align: right;
font-size: 30px; font-size: 25px;
} }
td { td {
text-align: left; text-align: left;
@ -34,7 +34,7 @@ h2 {
font-weight: 100; font-weight: 100;
} }
button { button {
height: 4.5em; height: 3.3em;
padding-left: 1.5em; padding-left: 1.5em;
padding-right: 1.5em; padding-right: 1.5em;
} }
@ -76,6 +76,7 @@ td > span {
.has-hidden:hover .hidden-text { .has-hidden:hover .hidden-text {
display: block; display: block;
}
@media only screen and (max-width: 700px) { @media only screen and (max-width: 700px) {
body { body {
@ -120,19 +121,6 @@ td > span {
</tr> </tr>
</table> </table>
<table>
<tr>
<th>Pirani Pressure</th>
<td class="has-hidden">
<div id="mbar">{{ .Pirani.Mbar }}</div>
<div class="hidden-text" style="color: #606060;">
<span>Voltage: </span><span id="volts">{{ .Pirani.Volts }}</span>
</div>
</td>
</tr>
</table>
<table> <table>
<tr> <tr>
<th rowspan="3">Control</th> <th rowspan="3">Control</th>
@ -163,6 +151,41 @@ td > span {
</tr> </tr>
</table> </table>
<table>
<tr>
<th>Pirani Pressure</th>
<td colspan="2" class="has-hidden">
<div id="mbar">{{ .Pirani.Mbar }}</div>
<div class="hidden-text" style="color: #606060;">
<span>Voltage: </span><span id="volts">{{ .Pirani.Volts }}</span>
</div>
</td>
</tr>
<tr>
<th rowspan="4">Temperatures</th>
<th>DP Bottom</th>
<td id="temp-dp-bottom">{{ .Temperatures.DPBottom }}</td>
</tr>
<tr>
<th>DP Top</th>
<td id="temp-dp-top">{{ .Temperatures.DPTop }}</td>
</tr>
<tr>
<th>DP Inlet</th>
<td id="temp-dp-inlet">{{ .Temperatures.DPInlet }}</td>
</tr>
<tr>
<th>SEM Environment</th>
<td id="temp-sem">{{ .Temperatures.SEM }}</td>
</tr>
<tr>
<th>Humidity</th>
<th>SEM Environment</th>
<td id="humidity-sem">{{ .Humidity.SEM }}</td>
</tr>
</table>
<div class="graph-container"> <div class="graph-container">
<canvas id="graph" width="1024" height="512" style="max-width: 100%;"></canvas> <canvas id="graph" width="1024" height="512" style="max-width: 100%;"></canvas>
</div> </div>
@ -316,6 +339,11 @@ window.addEventListener("load", (_) => {
let trough = document.querySelector("#trough"); let trough = document.querySelector("#trough");
let thigh = document.querySelector("#thigh"); let thigh = document.querySelector("#thigh");
let load = document.querySelector("#load"); 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 // Buttons
let pd = document.querySelector("#pd"); let pd = document.querySelector("#pd");
@ -388,6 +416,30 @@ window.addEventListener("load", (_) => {
dp.style = colors.default; dp.style = colors.default;
} }
tempSEM.innerHTML = data.Temperatures.SEM + "&nbsp;°C";
tempSEM.style = (data.Temperatures.SEM > 30) ?
colors.highlightCaution : colors.default;
humiditySEM.innerHTML = data.Humidity.SEM + "&nbsp;%";
humiditySEM.style = (data.Humidity.SEM > 59) ?
colors.highlightCaution : colors.default;
tempDPTop.innerHTML = data.Temperatures.DPTop + "&nbsp;°C";
tempDPTop.style = (data.Temperatures.DPTop > 30) ?
colors.highlightCaution : colors.default;
tempDPInlet.innerHTML = data.Temperatures.DPInlet + "&nbsp;°C";
tempDPInlet.style = (data.Temperatures.DPInlet > 30) ?
colors.highlightCaution : colors.default;
tempDPBottom.innerHTML = data.Temperatures.DPBottom + "&nbsp;°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 = []; let t = [];
if (data.Feedback.RoughReached) { if (data.Feedback.RoughReached) {
trough.innerHTML = "OK"; trough.innerHTML = "OK";
@ -413,7 +465,7 @@ window.addEventListener("load", (_) => {
// Indicate all process values as unknown // 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("??")) { if (!el.innerHTML.includes("??")) {
el.innerHTML += "??"; el.innerHTML += "??";
} }

View file

@ -35,6 +35,12 @@ func main() {
d.daemonState.piraniVolts3.limit = 3 d.daemonState.piraniVolts3.limit = 3
d.daemonState.piraniVolts100.limit = 100 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.threshold = float64(flagPressureThresholdRough)
d.aboveRough.hysteresis = float64(flagPressureThresholdRoughHysteresis) d.aboveRough.hysteresis = float64(flagPressureThresholdRoughHysteresis)
d.aboveHigh.threshold = float64(flagPressureThresholdHigh) d.aboveHigh.threshold = float64(flagPressureThresholdHigh)
@ -55,6 +61,11 @@ func main() {
} }
d.adcPirani = adc d.adcPirani = adc
err = d.modbusConnect()
if err != nil {
klog.Exitf("Failed to connect to modbus %v", err)
}
for _, c := range []struct { for _, c := range []struct {
out *gpio out *gpio
num int num int
@ -86,5 +97,9 @@ func main() {
}() }()
go d.process(ctx) go d.process(ctx)
if !flagFake {
go d.modbusProcess(ctx)
}
<-ctx.Done() <-ctx.Done()
} }

101
succbone/succd/modbus.go Normal file
View file

@ -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)
}
}
}

View file

@ -8,11 +8,13 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/simonvetter/modbus"
"k8s.io/klog" "k8s.io/klog"
) )
// daemon is the main service of the succdaemon. // daemon is the main service of the succdaemon.
type daemon struct { type daemon struct {
modbusClient *modbus.ModbusClient
// adcPirani is the adc implementation returning the voltage of the Pfeiffer // adcPirani is the adc implementation returning the voltage of the Pfeiffer
// Pirani gauge. // Pirani gauge.
adcPirani adc adcPirani adc
@ -55,6 +57,13 @@ type daemonState struct {
pumpdown momentaryOutput pumpdown momentaryOutput
aboveRough thresholdOutput aboveRough thresholdOutput
aboveHigh thresholdOutput aboveHigh thresholdOutput
tempDPBottom float32
tempDPTop float32
tempDPInlet float32
tempSEM float32
humiditySEM float32
} }
func (d *daemonState) vacuumStatus() (rough, high bool) { func (d *daemonState) vacuumStatus() (rough, high bool) {