diff --git a/succbone/succd/main.go b/succbone/succd/main.go index cfd93a9..52a10db 100644 --- a/succbone/succd/main.go +++ b/succbone/succd/main.go @@ -48,12 +48,6 @@ 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 { @@ -65,23 +59,6 @@ func main() { 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) - } - } } web := webServer{ diff --git a/succbone/succd/modbus.go b/succbone/succd/modbus.go index 15c3697..61f38de 100644 --- a/succbone/succd/modbus.go +++ b/succbone/succd/modbus.go @@ -63,7 +63,7 @@ func (d *daemon) modbusUpdate() error { d.daemonState.tempSEM = modbusValuesToFloat(registers[0]) d.daemonState.humiditySEM = modbusValuesToFloat(registers[1]) - // Switch to slave 2 + // Switch to slave 2 (KEC2) d.modbusClient.SetUnitId(2) // PT100 mapping @@ -76,18 +76,74 @@ func (d *daemon) modbusUpdate() error { } if len(registers) != 3 { - msg := fmt.Sprintf("Expected three registers from modbus slave 2, but got %d", len(registers)) - return errors.New(msg) + return fmt.Errorf("expected three registers from modbus slave 2, but got %d", len(registers)) } d.daemonState.tempDPBottom = modbusValuesToFloat(registers[0]) d.daemonState.tempDPInlet = modbusValuesToFloat(registers[1]) 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 } -// Call modbusUpdate every second +// Call modbusUpdate every 100 milliseconds func (d *daemon) modbusProcess(ctx context.Context) { for { select { @@ -95,7 +151,7 @@ func (d *daemon) modbusProcess(ctx context.Context) { return default: d.modbusUpdate() - time.Sleep(time.Second * 1) + time.Sleep(time.Millisecond * 100) } } } diff --git a/succbone/succd/process.go b/succbone/succd/process.go index 7ade055..7504b19 100644 --- a/succbone/succd/process.go +++ b/succbone/succd/process.go @@ -19,13 +19,6 @@ type daemon struct { // Pirani gauge. adcPirani adc - gpioDiffusionPump gpio - gpioRoughingPump gpio - gpioBtnPumpDown gpio - gpioBtnVent gpio - gpioBelowRough gpio - gpioBelowHigh gpio - load atomic.Int64 // mu guards the state below. @@ -171,33 +164,5 @@ 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 }