succd: Migrate to KEC1 MODBUS relay board
All checks were successful
/ test (push) Successful in 10s
/ test (pull_request) Successful in 10s

This commit is contained in:
hmelder 2024-11-10 05:11:48 +01:00
parent 0e45650972
commit 606d470577
3 changed files with 61 additions and 63 deletions

View file

@ -48,12 +48,6 @@ func main() {
if flagFake { if flagFake {
klog.Infof("Starting with fake peripherals") klog.Infof("Starting with fake peripherals")
d.adcPirani = &fakeADC{} 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 { } else {
adc, err := newBBADC(0) adc, err := newBBADC(0)
if err != nil { if err != nil {
@ -65,23 +59,6 @@ func main() {
if err != nil { if err != nil {
klog.Exitf("Failed to connect to modbus %v", err) 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)
}
}
} }
web := webServer{ web := webServer{

View file

@ -63,7 +63,7 @@ func (d *daemon) modbusUpdate() error {
d.daemonState.tempSEM = modbusValuesToFloat(registers[0]) d.daemonState.tempSEM = modbusValuesToFloat(registers[0])
d.daemonState.humiditySEM = modbusValuesToFloat(registers[1]) d.daemonState.humiditySEM = modbusValuesToFloat(registers[1])
// Switch to slave 2 // Switch to slave 2 (KEC2)
d.modbusClient.SetUnitId(2) d.modbusClient.SetUnitId(2)
// PT100 mapping // PT100 mapping
@ -76,18 +76,74 @@ func (d *daemon) modbusUpdate() error {
} }
if len(registers) != 3 { if len(registers) != 3 {
msg := fmt.Sprintf("Expected three registers from modbus slave 2, but got %d", len(registers)) return fmt.Errorf("expected three registers from modbus slave 2, but got %d", len(registers))
return errors.New(msg)
} }
d.daemonState.tempDPBottom = modbusValuesToFloat(registers[0]) d.daemonState.tempDPBottom = modbusValuesToFloat(registers[0])
d.daemonState.tempDPInlet = modbusValuesToFloat(registers[1]) d.daemonState.tempDPInlet = modbusValuesToFloat(registers[1])
d.daemonState.tempDPTop = modbusValuesToFloat(registers[2]) d.daemonState.tempDPTop = modbusValuesToFloat(registers[2])
// 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 {
return err
}
// 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
// -KFA1 Roughing Pump
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
relayState[5] = d.aboveRough.output
// -KFA7 Fake-Pirani High
relayState[6] = d.aboveHigh.output
// 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 registerValues [8]uint16
// Convert the boolean values to the commands
for idx, state := range relayState {
if state {
registerValues[idx] = 0x0100
} else {
registerValues[idx] = 0x0200
}
}
err = d.modbusClient.WriteRegisters(0x01, registerValues[:])
if err != nil {
return err
}
return nil return nil
} }
// Call modbusUpdate every second // Call modbusUpdate every 100 milliseconds
func (d *daemon) modbusProcess(ctx context.Context) { func (d *daemon) modbusProcess(ctx context.Context) {
for { for {
select { select {
@ -95,7 +151,7 @@ func (d *daemon) modbusProcess(ctx context.Context) {
return return
default: default:
d.modbusUpdate() d.modbusUpdate()
time.Sleep(time.Second * 1) time.Sleep(time.Millisecond * 100)
} }
} }
} }

View file

@ -19,13 +19,6 @@ type daemon struct {
// Pirani gauge. // Pirani gauge.
adcPirani adc adcPirani adc
gpioDiffusionPump gpio
gpioRoughingPump gpio
gpioBtnPumpDown gpio
gpioBtnVent gpio
gpioBelowRough gpio
gpioBelowHigh gpio
load atomic.Int64 load atomic.Int64
// mu guards the state below. // mu guards the state below.
@ -171,33 +164,5 @@ func (d *daemon) processOnce(_ context.Context) error {
d.dpOn = false 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 return nil
} }