2024-11-10 00:47:01 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/simonvetter/modbus"
|
2024-11-10 05:19:01 +00:00
|
|
|
"k8s.io/klog"
|
2024-11-10 00:47:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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",
|
2024-11-10 06:06:56 +00:00
|
|
|
Timeout: 1 * time.Second,
|
2024-11-10 00:47:01 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Connect to modbus client
|
|
|
|
err = d.modbusClient.Open()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-10 05:51:21 +00:00
|
|
|
func (d *daemon) modbusRestart() error {
|
|
|
|
d.modbusClient.Close()
|
|
|
|
return d.modbusClient.Open()
|
|
|
|
}
|
|
|
|
|
2024-11-10 00:47:01 +00:00
|
|
|
// 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
|
2024-11-10 05:51:21 +00:00
|
|
|
//
|
|
|
|
// Returns whether modbus should restart (only in case of an underlying network error)
|
|
|
|
func (d *daemon) modbusUpdate() bool {
|
2024-11-10 00:47:01 +00:00
|
|
|
var err error
|
2024-11-10 06:06:56 +00:00
|
|
|
var numDevicesNotResponding int
|
2024-11-10 00:47:01 +00:00
|
|
|
|
2024-11-10 04:45:32 +00:00
|
|
|
// Switch to slave 1 (BTA1)
|
2024-11-10 00:47:01 +00:00
|
|
|
d.modbusClient.SetUnitId(1)
|
|
|
|
|
|
|
|
// Read temperature and humidity
|
2024-11-10 04:45:32 +00:00
|
|
|
var registersBTA1 []uint16 // temperature, humidity
|
|
|
|
registersBTA1, err = d.modbusClient.ReadRegisters(1, 2, modbus.INPUT_REGISTER)
|
2024-11-10 00:47:01 +00:00
|
|
|
if err != nil {
|
2024-11-10 06:06:56 +00:00
|
|
|
numDevicesNotResponding += 1
|
2024-11-10 05:19:01 +00:00
|
|
|
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()
|
2024-11-10 00:47:01 +00:00
|
|
|
}
|
|
|
|
|
2024-11-10 04:11:48 +00:00
|
|
|
// Switch to slave 2 (KEC2)
|
2024-11-10 00:47:01 +00:00
|
|
|
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"
|
2024-11-10 04:45:32 +00:00
|
|
|
var registersKEC2 []uint16 // temperatures dp
|
|
|
|
registersKEC2, err = d.modbusClient.ReadRegisters(0, 3, modbus.HOLDING_REGISTER)
|
2024-11-10 00:47:01 +00:00
|
|
|
if err != nil {
|
2024-11-10 06:06:56 +00:00
|
|
|
numDevicesNotResponding += 1
|
2024-11-10 05:19:01 +00:00
|
|
|
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()
|
2024-11-10 00:47:01 +00:00
|
|
|
}
|
|
|
|
|
2024-11-10 04:11:48 +00:00
|
|
|
// 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 {
|
2024-11-10 06:06:56 +00:00
|
|
|
numDevicesNotResponding += 1
|
2024-11-10 05:19:01 +00:00
|
|
|
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
|
|
|
|
}
|
2024-11-10 04:11:48 +00:00
|
|
|
}
|
2024-11-10 05:19:01 +00:00
|
|
|
// TODO: Input mapping goes here
|
2024-11-10 04:11:48 +00:00
|
|
|
}
|
|
|
|
|
2024-11-10 05:33:20 +00:00
|
|
|
// 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)
|
|
|
|
|
2024-11-10 04:11:48 +00:00
|
|
|
// KFA1-KFA8
|
|
|
|
var relayState [8]bool
|
2024-11-10 04:45:32 +00:00
|
|
|
d.mu.Lock()
|
2024-11-10 04:20:24 +00:00
|
|
|
// -KFA1 Roughing Pump (normally closed contact)
|
|
|
|
relayState[0] = !d.daemonState.rpOn
|
2024-11-10 04:11:48 +00:00
|
|
|
// -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
|
2024-11-10 04:20:24 +00:00
|
|
|
// -KFA6 Fake-Pirani Rough (normally closed contact)
|
|
|
|
relayState[5] = !d.aboveRough.output
|
|
|
|
// -KFA7 Fake-Pirani High (normally closed contact)
|
|
|
|
relayState[6] = !d.aboveHigh.output
|
2024-11-10 04:45:32 +00:00
|
|
|
d.mu.Unlock()
|
2024-11-10 04:11:48 +00:00
|
|
|
|
|
|
|
// 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
|
2024-11-10 04:45:32 +00:00
|
|
|
var registerValuesKEC1 [8]uint16
|
2024-11-10 04:11:48 +00:00
|
|
|
// Convert the boolean values to the commands
|
|
|
|
for idx, state := range relayState {
|
|
|
|
if state {
|
2024-11-10 04:45:32 +00:00
|
|
|
registerValuesKEC1[idx] = 0x0100
|
2024-11-10 04:11:48 +00:00
|
|
|
} else {
|
2024-11-10 04:45:32 +00:00
|
|
|
registerValuesKEC1[idx] = 0x0200
|
2024-11-10 04:11:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-10 04:45:32 +00:00
|
|
|
err = d.modbusClient.WriteRegisters(0x01, registerValuesKEC1[:])
|
2024-11-10 04:11:48 +00:00
|
|
|
if err != nil {
|
2024-11-10 06:06:56 +00:00
|
|
|
numDevicesNotResponding += 1
|
2024-11-10 05:19:01 +00:00
|
|
|
klog.Warningf("error while updating registers %v", err)
|
2024-11-10 04:11:48 +00:00
|
|
|
}
|
2024-11-10 05:51:21 +00:00
|
|
|
|
2024-11-10 06:06:56 +00:00
|
|
|
if numDevicesNotResponding >= 4 {
|
|
|
|
klog.Warningf("no device did respond to our request. Probably a network timeout.")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-11-10 05:51:21 +00:00
|
|
|
return false
|
2024-11-10 00:47:01 +00:00
|
|
|
}
|
|
|
|
|
2024-11-10 04:11:48 +00:00
|
|
|
// Call modbusUpdate every 100 milliseconds
|
2024-11-10 00:47:01 +00:00
|
|
|
func (d *daemon) modbusProcess(ctx context.Context) {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
default:
|
2024-11-10 05:51:21 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2024-11-10 04:11:48 +00:00
|
|
|
time.Sleep(time.Millisecond * 100)
|
2024-11-10 00:47:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|