jeol-t330a/succbone/succd/modbus.go
hmelder 6f93b96c39 succd: do not early return on error in modbusUpdate
When one device fails, this should not influence updates of the other
devices.  Thus, early return was the wrong strategy here.

Instead, when communication with a device fails, skip the process data
update and continue with the next device.
2024-11-10 06:35:50 +01:00

154 lines
4.1 KiB
Go

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: 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() {
var err error
// 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 {
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 {
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 {
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
}
// 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 {
klog.Warningf("error while updating registers %v", err)
}
}
// Call modbusUpdate every 100 milliseconds
func (d *daemon) modbusProcess(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
d.modbusUpdate()
time.Sleep(time.Millisecond * 100)
}
}
}