From 5ec976500920a70e0c71f3486347325c4b30fac7 Mon Sep 17 00:00:00 2001 From: Blake Freer Date: Sun, 23 Nov 2025 18:08:26 -0500 Subject: [PATCH 1/2] IOControl Cleanup - Remove the IOControl class. It is now the IOController interface. The class only forwarded behaviour to the underlying classes so it did not need to exist - Implements the interface on speedgoat, raspi, sil. Currently, the interface passes an abstract `iocontrol.[Analog,Digital]Pin` to each `Read` or `Write` function which requires each class to verify the pin's type. The fail on type mismatch behaviour is identical to what the `IOControl` class used to check, but with the logic further down. I intend to use th eh eh he eh eh - Updates hilapp to use the new interface - Rename SetDigital to WriteDigital to be consistent with WriteAnalog --- cmd/hilapp/main.go | 9 +- cmd/iocheckout/main.go | 9 +- cmd/raspitest/main.go | 8 +- cmd/speedgoattest/main.go | 6 +- iocontrol/iocontrol.go | 354 +----------------------------- iocontrol/raspi/controller.go | 69 +++--- iocontrol/sil/controller.go | 55 ++++- iocontrol/speedgoat/controller.go | 95 ++++---- iocontrol/types.go | 18 -- macformula/iocheckout.go | 9 +- macformula/pinout/controller.go | 6 +- 11 files changed, 163 insertions(+), 475 deletions(-) delete mode 100644 iocontrol/types.go diff --git a/cmd/hilapp/main.go b/cmd/hilapp/main.go index 205bcd0e..845cf844 100644 --- a/cmd/hilapp/main.go +++ b/cmd/hilapp/main.go @@ -153,21 +153,18 @@ func main() { canlink.WithFileName(_ptCan), ) } - // Get controllers - var ioOpts = make([]iocontrol.IOControlOption, 0) + + var ioController iocontrol.IOController switch rev { case pinout.Sil: silController = sil.NewController(cfg.SilPort, logger, utils.GetValues(pinout.SilDigitalInputPins), utils.GetValues(pinout.SilDigitalOutputPins), utils.GetValues(pinout.SilAnalogInputPins), utils.GetValues(pinout.SilAnalogOutputPins)) - ioOpts = append(ioOpts, iocontrol.WithSil(silController)) + ioController = silController default: panic("unconfigured revision") } - // Create io controller. - ioController := iocontrol.NewIOControl(logger, ioOpts...) - err = ioController.Open(ctx) if err != nil { logger.Error("failed to open io controller", diff --git a/cmd/iocheckout/main.go b/cmd/iocheckout/main.go index 1589ee2f..11ad3c81 100644 --- a/cmd/iocheckout/main.go +++ b/cmd/iocheckout/main.go @@ -9,6 +9,7 @@ import ( "github.com/macformula/hil/iocontrol" "github.com/macformula/hil/iocontrol/raspi" + // "github.com/macformula/hil/iocontrol/sil" "github.com/macformula/hil/iocontrol/speedgoat" "github.com/macformula/hil/macformula" @@ -54,16 +55,16 @@ func main() { logger.Info("starting iocheckout", zap.String("revision", revision.String())) - var ioControlOpts []iocontrol.IOControlOption + var ioControl iocontrol.IOController if *useSpeedgoat { sg := speedgoat.NewController(logger, _speedgoatAddr, speedgoat.WithModelAutoload(_speedgoatScriptPath, _speedgoatPw, _speedgoatAddrFromPi, _speedgoatModelName)) - ioControlOpts = append(ioControlOpts, iocontrol.WithSpeedgoat(sg)) + ioControl = sg } if *useRaspi { rp := raspi.NewController(logger) - ioControlOpts = append(ioControlOpts, iocontrol.WithRaspi(rp)) + ioControl = rp } // if *useSil { @@ -71,8 +72,6 @@ func main() { // ioControlOpts = append(ioControlOpts, iocontrol.WithSil(s)) // } - ioControl := iocontrol.NewIOControl(logger, ioControlOpts...) - iocheckout := macformula.NewIoCheckout(revision, ioControl, logger) logger.Info("opening iocheckout") diff --git a/cmd/raspitest/main.go b/cmd/raspitest/main.go index 3154ff25..6af2cf4f 100644 --- a/cmd/raspitest/main.go +++ b/cmd/raspitest/main.go @@ -51,12 +51,12 @@ func main() { case *oneSet != "": switch strings.ToLower(*oneSet) { case "high", "1", "on": - if err := ctrl.SetDigital(pin, true); err != nil { + if err := ctrl.WriteDigital(pin, true); err != nil { log.Fatalf("set high: %v", err) } fmt.Printf("P1-%d -> HIGH\n", *boardPin) case "low", "0", "off": - if err := ctrl.SetDigital(pin, false); err != nil { + if err := ctrl.WriteDigital(pin, false); err != nil { log.Fatalf("set low: %v", err) } fmt.Printf("P1-%d -> LOW\n", *boardPin) @@ -99,13 +99,13 @@ func main() { switch cmd { case "high", "1", "on": - if err := ctrl.SetDigital(pin, true); err != nil { + if err := ctrl.WriteDigital(pin, true); err != nil { fmt.Fprintf(tty, "error setting HIGH: %v\n", err) continue } fmt.Fprintln(tty, "-> HIGH") case "low", "0", "off": - if err := ctrl.SetDigital(pin, false); err != nil { + if err := ctrl.WriteDigital(pin, false); err != nil { fmt.Fprintf(tty, "error setting LOW: %v\n", err) continue } diff --git a/cmd/speedgoattest/main.go b/cmd/speedgoattest/main.go index 1149cd28..1c580ac7 100644 --- a/cmd/speedgoattest/main.go +++ b/cmd/speedgoattest/main.go @@ -27,10 +27,10 @@ func main() { } pin := speedgoat.NewDigitalPin(8) // First digital output pin (idx 8-15) - controller.SetDigital(pin, true) + controller.WriteDigital(pin, true) pin2 := speedgoat.NewDigitalPin(15) // Last digital output pin - controller.SetDigital(pin2, true) + controller.WriteDigital(pin2, true) pin3 := speedgoat.NewAnalogPin(8) // First analog output pin (idx 8-11) controller.WriteVoltage(pin3, 2.5) @@ -39,7 +39,7 @@ func main() { // Verify that these change on the Speedgoat via Simulink or LED // If they don't change (or take a while), verify that the Simulink TCP server has a sufficiently small sample time - err = controller.SetDigital(pin2, false) + err = controller.WriteDigital(pin2, false) if err != nil { panic(err) } diff --git a/iocontrol/iocontrol.go b/iocontrol/iocontrol.go index 418ccb82..18f70f7d 100644 --- a/iocontrol/iocontrol.go +++ b/iocontrol/iocontrol.go @@ -2,353 +2,13 @@ package iocontrol import ( "context" - - "github.com/pkg/errors" - "go.uber.org/zap" - - "github.com/macformula/hil/iocontrol/raspi" - "github.com/macformula/hil/iocontrol/sil" - "github.com/macformula/hil/iocontrol/speedgoat" ) -const ( - _loggerName = "iocontrol" -) - -// IOControlOption is a type for functions operating on IOControl -type IOControlOption func(*IOControl) - -// IOControl contains I/O controllers -type IOControl struct { - sg *speedgoat.Controller - rp *raspi.Controller - sil *sil.Controller - - l *zap.Logger -} - -// NewIOControl returns a new IOControl -func NewIOControl( - l *zap.Logger, - opts ...IOControlOption) *IOControl { - io := &IOControl{ - l: l.Named(_loggerName), - sg: nil, - rp: nil, - } - - for _, o := range opts { - o(io) - } - - return io -} - -// WithSpeedgoat initializes the iocontroller with a speedgoat device -func WithSpeedgoat(sg *speedgoat.Controller) IOControlOption { - return func(i *IOControl) { - i.sg = sg - } -} - -// WithRaspi initializes the iocontroller with a raspi device -func WithRaspi(rp *raspi.Controller) IOControlOption { - return func(i *IOControl) { - i.rp = rp - } -} - -// WithSil initializes the iocontroller with a sil device -func WithSil(sil *sil.Controller) IOControlOption { - return func(i *IOControl) { - i.sil = sil - } -} - -func (io *IOControl) Open(ctx context.Context) error { - if io.rp != nil { - err := io.rp.Open(ctx) - if err != nil { - return errors.Wrap(err, "raspi controller open") - } - } - - if io.sg != nil { - err := io.sg.Open(ctx) - if err != nil { - return errors.Wrap(err, "speedgoat controller open") - } - } - - if io.sil != nil { - go io.sil.Open(ctx) - } - - return nil -} - -// SetDigital sets an output digital pin for a specified pin -func (io *IOControl) SetDigital(output DigitalPin, b bool) error { - var err error - - switch pin := output.(type) { - case *speedgoat.DigitalPin: - if io.sg == nil { - return errors.New("speedgoat target is nil") - } - - err = io.sg.SetDigital(pin, b) - if err != nil { - return errors.Wrap(err, "set digital") - } - case *raspi.DigitalPin: - if io.rp == nil { - return errors.New("raspi target is nil") - } - - err = io.rp.SetDigital(pin, b) - if err != nil { - return errors.Wrap(err, "set digital") - } - case *sil.DigitalPin: - if io.sil == nil { - return errors.New("sil target is nil") - } - - err = io.sil.Pins.SetDigitalOutput(pin, b) - if err != nil { - return errors.Wrap(err, "set digital") - } - default: - return errors.Errorf("unknown digital pin type (%s)", pin.String()) - } - - return nil -} - -// ReadDigital reads an input digital pin for a specified pin -func (io *IOControl) ReadDigital(input DigitalPin) (bool, error) { - var ( - lvl bool - err error - ) - - switch pin := input.(type) { - case *speedgoat.DigitalPin: - if io.sg == nil { - return lvl, errors.New("speedgoat target is nil") - } - - lvl, err = io.sg.ReadDigital(pin) - if err != nil { - return false, errors.Wrap(err, "read digital") - } - case *raspi.DigitalPin: - if io.rp == nil { - return lvl, errors.New("raspi target is nil") - } - - lvl, err = io.rp.ReadDigital(pin) - if err != nil { - return lvl, errors.Wrap(err, "read digital") - } - case *sil.DigitalPin: - if io.sil == nil { - return lvl, errors.New("sil target is nil") - } - - lvl, err = io.sil.Pins.ReadDigitalInput(pin) - if err != nil { - return false, errors.Wrap(err, "read digital") - } - default: - return false, errors.Errorf("unknown digital pin type (%s)", pin.String()) - } - - return lvl, nil -} - -// WriteVoltage sets a voltage for a specified output analog pin -func (io *IOControl) WriteVoltage(output AnalogPin, voltage float64) error { - var err error - - switch pin := output.(type) { - case *speedgoat.AnalogPin: - if io.sg == nil { - return errors.New("speedgoat target is nil") - } - - err = io.sg.WriteVoltage(pin, voltage) - if err != nil { - return errors.Wrap(err, "write voltage") - } - case *raspi.AnalogPin: - if io.rp == nil { - return errors.New("raspi target is nil") - } - - err = io.rp.WriteVoltage(pin, voltage) - if err != nil { - return errors.Wrap(err, "write voltage") - } - case *sil.AnalogPin: - if io.sil == nil { - return errors.New("sil target is nil") - } - - err = io.sil.Pins.SetAnalogOutput(pin, voltage) - if err != nil { - return errors.Wrap(err, "write voltage") - } - default: - return errors.Errorf("unknown analog pin type (%s)", pin.String()) - } - return nil -} - -// ReadVoltage returns the voltage of a specified input analog pin -func (io *IOControl) ReadVoltage(input AnalogPin) (float64, error) { - var ( - voltage float64 - err error - ) - - switch pin := input.(type) { - case *speedgoat.AnalogPin: - if io.sg == nil { - return voltage, errors.New("speedgoat target is nil") - } - - voltage, err = io.sg.ReadVoltage(pin) - if err != nil { - return 0.0, errors.Wrap(err, "read voltage") - } - case *raspi.AnalogPin: - if io.rp != nil { - return voltage, errors.New("raspi target is nil") - } - - voltage, err = io.rp.ReadVoltage(pin) - if err != nil { - return voltage, errors.Wrap(err, "read voltage") - } - case *sil.AnalogPin: - if io.sil == nil { - return voltage, errors.New("sil target is nil") - } - - voltage, err = io.sil.Pins.ReadAnalogInput(pin) - if err != nil { - return 0.0, errors.Wrap(err, "read voltage") - } - default: - return 0.0, errors.Errorf("unknown analog pin type (%s)", pin.String()) - } - return voltage, nil -} - -// WriteCurrent sets the current of a specified output analog pin -func (io *IOControl) WriteCurrent(output AnalogPin, current float64) error { - var err error - - switch pin := output.(type) { - case *speedgoat.AnalogPin: - if io.sg == nil { - return errors.New("speedgoat target is nil") - } - - err = io.sg.WriteCurrent(pin, current) - if err != nil { - return errors.Wrap(err, "write current") - } - case *raspi.AnalogPin: - if io.rp == nil { - return errors.New("raspi target is nil") - } - - err = io.rp.WriteCurrent(pin, current) - if err != nil { - return errors.Wrap(err, "write current") - } - case *sil.AnalogPin: - if io.sil == nil { - return errors.New("sil target is nil") - } - - err = io.sil.WriteCurrent(pin, current) - if err != nil { - return errors.Wrap(err, "write current") - } - default: - return errors.Errorf("unknown analog pin type (%s)", pin.String()) - } - - return nil -} - -// ReadCurrent returns the current of a specified input analog pin -func (io *IOControl) ReadCurrent(input AnalogPin) (float64, error) { - var ( - current float64 - err error - ) - - switch pin := input.(type) { - case *speedgoat.AnalogPin: - if io.sg == nil { - return current, errors.New("speedgoat target is nil") - } - - current, err = io.sg.ReadCurrent(pin) - if err != nil { - return current, errors.Wrap(err, "read current") - } - case *raspi.AnalogPin: - if io.rp == nil { - return current, errors.New("raspi target is nil") - } - - current, err = io.rp.ReadCurrent(pin) - if err != nil { - return current, errors.Wrap(err, "read current") - } - case *sil.AnalogPin: - if io.sil == nil { - return current, errors.New("sil target is nil") - } - - current, err = io.sil.ReadCurrent(pin) - if err != nil { - return current, errors.Wrap(err, "read current") - } - default: - return 0.0, errors.Errorf("unknown analog pin type (%s)", pin.String()) - } - - return current, nil -} - -func (io *IOControl) Close() error { - if io.rp != nil { - err := io.rp.Close() - if err != nil { - return errors.Wrap(err, "raspi controller close") - } - } - - if io.sg != nil { - err := io.sg.Close() - if err != nil { - return errors.Wrap(err, "speedgoat controller close") - } - } - - if io.sil != nil { - err := io.sil.Close() - if err != nil { - return errors.Wrap(err, "sil controller close") - } - } - - return nil +type IOController interface { + Open(context.Context) error + Close() error + WriteDigital(DigitalPin, bool) error + ReadDigital(DigitalPin) (bool, error) + WriteVoltage(AnalogPin, float64) error + ReadVoltage(AnalogPin) (float64, error) } diff --git a/iocontrol/raspi/controller.go b/iocontrol/raspi/controller.go index d25893b5..48396db7 100644 --- a/iocontrol/raspi/controller.go +++ b/iocontrol/raspi/controller.go @@ -10,6 +10,7 @@ import ( "fmt" "sync" + "github.com/macformula/hil/iocontrol" "github.com/pkg/errors" "go.uber.org/zap" "periph.io/x/conn/v3/gpio" @@ -70,52 +71,54 @@ func (c *Controller) Close() error { } // SetDigital sets an output digital pin for a Raspberry Pi digital pin -func (c *Controller) SetDigital(output *DigitalPin, level bool) error { - pin, err := resolvePin(output) - if err != nil { - return err +func (c *Controller) WriteDigital(output iocontrol.DigitalPin, level bool) error { + switch p := output.(type) { + case *DigitalPin: + pin, err := resolvePin(p) + if err != nil { + return err + } + + // configure as output, then write + if err := pin.Out(gpio.Low); err != nil { + return errors.Wrap(err, "set output mode") + } + if level { + return errors.Wrap(pin.Out(gpio.High), "write high") + } + return errors.Wrap(pin.Out(gpio.Low), "write low") + + default: + return errors.Errorf("Invalid pin type") } - - // configure as output, then write - if err := pin.Out(gpio.Low); err != nil { - return errors.Wrap(err, "set output mode") - } - if level { - return errors.Wrap(pin.Out(gpio.High), "write high") - } - return errors.Wrap(pin.Out(gpio.Low), "write low") } // ReadDigital returns the level of a Raspberry Pi digital pin -func (c *Controller) ReadDigital(input *DigitalPin) (bool, error) { - pin, err := resolvePin(input) - if err != nil { - return false, err +func (c *Controller) ReadDigital(input iocontrol.DigitalPin) (bool, error) { + switch p := input.(type) { + case *DigitalPin: + pin, err := resolvePin(p) + if err != nil { + return false, err + } + + if err := pin.In(gpio.Float, gpio.NoEdge); err != nil { + return false, errors.Wrap(err, "set input mode") + } + return pin.Read() == gpio.High, nil + default: + return false, errors.Errorf("Invalid pin type") } - if err := pin.In(gpio.Float, gpio.NoEdge); err != nil { - return false, errors.Wrap(err, "set input mode") - } - return pin.Read() == gpio.High, nil } // WriteVoltage sets the voltage of a Raspberry Pi analog pin -func (c *Controller) WriteVoltage(output *AnalogPin, voltage float64) error { +func (c *Controller) WriteVoltage(output iocontrol.AnalogPin, voltage float64) error { return errors.New("currently unsupported on raspi") } // ReadVoltage returns the voltage of a Raspberry Pi analog pin -func (c *Controller) ReadVoltage(output *AnalogPin) (float64, error) { - return 0.00, errors.New("currently unsupported on raspi") -} - -// WriteCurrent sets the current of a Raspberry Pi analog pin -func (c *Controller) WriteCurrent(output *AnalogPin, current float64) error { - return errors.New("currently unsupported on raspi") -} - -// ReadCurrent returns the current of a Raspberry Pi analog pin -func (c *Controller) ReadCurrent(output *AnalogPin) (float64, error) { +func (c *Controller) ReadVoltage(output iocontrol.AnalogPin) (float64, error) { return 0.00, errors.New("currently unsupported on raspi") } diff --git a/iocontrol/sil/controller.go b/iocontrol/sil/controller.go index 759d4937..16e58414 100644 --- a/iocontrol/sil/controller.go +++ b/iocontrol/sil/controller.go @@ -10,6 +10,7 @@ import ( "go.uber.org/zap" flatbuffers "github.com/google/flatbuffers/go" + "github.com/macformula/hil/iocontrol" signals "github.com/macformula/hil/iocontrol/sil/signals" ) @@ -49,6 +50,14 @@ func (c *Controller) Open(ctx context.Context) error { c.l.Info(fmt.Sprintf("sil listening on %s", addr)) + go c.RunServer() + + c.l.Info("Started server") + + return nil +} + +func (c *Controller) RunServer() { for { conn, err := c.listener.Accept() if err != nil { @@ -64,6 +73,42 @@ func (c *Controller) Close() error { return nil } +func (c *Controller) ReadDigital(pin iocontrol.DigitalPin) (bool, error) { + switch p := pin.(type) { + case *DigitalPin: + return c.Pins.ReadDigitalInput(p) + default: + return false, errors.Errorf("Invalid pin type") + } +} + +func (c *Controller) WriteDigital(pin iocontrol.DigitalPin, b bool) error { + switch p := pin.(type) { + case *DigitalPin: + return c.Pins.SetDigitalInput(p, b) + default: + return errors.Errorf("Invalid pin type") + } +} + +func (c *Controller) ReadVoltage(pin iocontrol.AnalogPin) (float64, error) { + switch p := pin.(type) { + case *AnalogPin: + return c.Pins.ReadAnalogInput(p) + default: + return 0.0, errors.Errorf("Invalid pin type") + } +} + +func (c *Controller) WriteVoltage(pin iocontrol.AnalogPin, v float64) error { + switch p := pin.(type) { + case *AnalogPin: + return c.Pins.SetAnalogInput(p, v) + default: + return errors.Errorf("Invalid pin type") + } +} + func (c *Controller) handleConnection(conn net.Conn) { defer conn.Close() @@ -218,16 +263,6 @@ func (c *Controller) handleConnection(conn net.Conn) { } } -// WriteCurrent sets the current of a SIL analog pin (unimplemented for SIL). -func (c *Controller) WriteCurrent(_ *AnalogPin, _ float64) error { - return errors.New("unimplemented function on sil FbController") -} - -// ReadCurrent returns the current of a SIL analog pin (unimplemented for SIL). -func (c *Controller) ReadCurrent(_ *AnalogPin) (float64, error) { - return 0.00, errors.New("unimplemented function on sil FbController") -} - func deserializeReadRequest(unionTable *flatbuffers.Table) (string, string, signals.SIGNAL_TYPE, signals.SIGNAL_DIRECTION) { unionRequest := new(signals.ReadRequest) unionRequest.Init(unionTable.Bytes, unionTable.Pos) diff --git a/iocontrol/speedgoat/controller.go b/iocontrol/speedgoat/controller.go index ddceea89..2c879122 100644 --- a/iocontrol/speedgoat/controller.go +++ b/iocontrol/speedgoat/controller.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/macformula/hil/iocontrol" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -130,65 +131,75 @@ func (c *Controller) Close() error { } // SetDigital sets an output digital pin for a Speedgoat digital pin. -func (c *Controller) SetDigital(output *DigitalPin, b bool) error { - c.muDigital.Lock() - defer c.muDigital.Unlock() - - if output.index >= _digitalArraySize || output.index < _digitalOutputStartIndex { - return errors.Errorf("invalid output index (%d)", output.index) - } +func (c *Controller) WriteDigital(output iocontrol.DigitalPin, b bool) error { + switch p := output.(type) { + case *DigitalPin: + c.muDigital.Lock() + defer c.muDigital.Unlock() + + if p.index >= _digitalArraySize || p.index < _digitalOutputStartIndex { + return errors.Errorf("invalid output index (%d)", p.index) + } - c.digital[output.index] = b + c.digital[p.index] = b - return nil + return nil + default: + return errors.Errorf("Invalid pin type") + } } // ReadDigital returns the level of a Speedgoat digital pin. -func (c *Controller) ReadDigital(input *DigitalPin) (bool, error) { - c.muDigital.Lock() - defer c.muDigital.Unlock() +func (c *Controller) ReadDigital(input iocontrol.DigitalPin) (bool, error) { + switch p := input.(type) { + case *DigitalPin: + c.muDigital.Lock() + defer c.muDigital.Unlock() + + if p.index >= _digitalArraySize || p.index < _digitalInputStartIndex { + return false, errors.Errorf("invalid input index (%d)", p.index) + } - if input.index >= _digitalArraySize || input.index < _digitalInputStartIndex { - return false, errors.Errorf("invalid input index (%d)", input.index) + return c.digital[p.index], nil + default: + return false, errors.Errorf("Invalid pin type") } - - return c.digital[input.index], nil } // WriteVoltage sets the voltage of a Speedgoat analog pin. -func (c *Controller) WriteVoltage(output *AnalogPin, voltage float64) error { - c.muAnalog.Lock() - defer c.muAnalog.Unlock() - - if output.index >= _analogArraySize || output.index < _analogOutputStartIndex { - return errors.Errorf("invalid output index (%d)", output.index) - } +func (c *Controller) WriteVoltage(output iocontrol.AnalogPin, voltage float64) error { + switch p := output.(type) { + case *AnalogPin: + c.muAnalog.Lock() + defer c.muAnalog.Unlock() + + if p.index >= _analogArraySize || p.index < _analogOutputStartIndex { + return errors.Errorf("invalid output index (%d)", p.index) + } - c.analog[output.index] = voltage + c.analog[p.index] = voltage - return nil + return nil + default: + return errors.Errorf("Invalid pin type") + } } // ReadVoltage returns the voltage of a Speedgoat analog pin. -func (c *Controller) ReadVoltage(input *AnalogPin) (float64, error) { - c.muAnalog.Lock() - defer c.muAnalog.Unlock() +func (c *Controller) ReadVoltage(input iocontrol.AnalogPin) (float64, error) { + switch p := input.(type) { + case *AnalogPin: + c.muAnalog.Lock() + defer c.muAnalog.Unlock() + + if p.index >= _analogArraySize || p.index < _analogInputStartIndex { + return 0.0, errors.Errorf("invalid input index (%d)", p.index) + } - if input.index >= _analogArraySize || input.index < _analogInputStartIndex { - return 0.0, errors.Errorf("invalid input index (%d)", input.index) + return c.analog[p.index], nil + default: + return 0.0, errors.Errorf("Invalid pin type") } - - return c.analog[input.index], nil -} - -// WriteCurrent sets the current of a Speedgoat analog pin (unimplemented for Speedgoat). -func (c *Controller) WriteCurrent(_ *AnalogPin, _ float64) error { - return errors.New("unimplemented function on speedgoat controller") -} - -// ReadCurrent returns the current of a Speedgoat analog pin (unimplemented for Speedgoat). -func (c *Controller) ReadCurrent(_ *AnalogPin) (float64, error) { - return 0.0, errors.New("unimplemented function on speedgoat controller") } // tickOutputs transmits the packed data for the digital and analog outputs to the Speedgoat at a set time interval. diff --git a/iocontrol/types.go b/iocontrol/types.go deleted file mode 100644 index c83890f7..00000000 --- a/iocontrol/types.go +++ /dev/null @@ -1,18 +0,0 @@ -package iocontrol - -//go:generate enumer -type=Level types.go -type Level int - -const ( - Unknown Level = iota - 1 - Low - High -) - -//go:generate enumer -type=Direction types.go -type Direction int - -const ( - Input Direction = iota - Output -) diff --git a/macformula/iocheckout.go b/macformula/iocheckout.go index 067bc78b..4a4319b8 100644 --- a/macformula/iocheckout.go +++ b/macformula/iocheckout.go @@ -2,9 +2,10 @@ package macformula import ( "context" - "github.com/macformula/hil/macformula/pinout" "strconv" + "github.com/macformula/hil/macformula/pinout" + "github.com/fatih/color" "github.com/macformula/hil/iocontrol" "github.com/manifoldco/promptui" @@ -46,7 +47,7 @@ var ( type IoCheckout struct { l *zap.Logger rev pinout.Revision - ioControl *iocontrol.IOControl + ioControl iocontrol.IOController diPins pinout.DigitalPinout doPins pinout.DigitalPinout @@ -62,7 +63,7 @@ type IoCheckout struct { } // NewIoCheckout returns a pointer to an IoCheckout object. -func NewIoCheckout(rev pinout.Revision, ioControl *iocontrol.IOControl, l *zap.Logger) *IoCheckout { +func NewIoCheckout(rev pinout.Revision, ioControl iocontrol.IOController, l *zap.Logger) *IoCheckout { return &IoCheckout{ l: l.Named(_loggerName), rev: rev, @@ -353,7 +354,7 @@ func (io *IoCheckout) handleDigitalOutputSelect() error { return errors.Wrap(err, "high to low bool") } - err = io.ioControl.SetDigital(digitalOut, lvl) + err = io.ioControl.WriteDigital(digitalOut, lvl) if err != nil { return errors.Wrap(err, "set digital") } diff --git a/macformula/pinout/controller.go b/macformula/pinout/controller.go index af0b2a6a..5fd6a1f9 100644 --- a/macformula/pinout/controller.go +++ b/macformula/pinout/controller.go @@ -14,7 +14,7 @@ const _controllerName = "pinout_controller" // Controller allows for easy control of the I/O's given the current pinout Revision. type Controller struct { l *zap.Logger - ioController *iocontrol.IOControl + ioController iocontrol.IOController rev Revision digitalOutputs DigitalPinout @@ -24,7 +24,7 @@ type Controller struct { } // NewController creates a new pinout controller. -func NewController(rev Revision, ioController *iocontrol.IOControl, l *zap.Logger) *Controller { +func NewController(rev Revision, ioController iocontrol.IOController, l *zap.Logger) *Controller { return &Controller{ l: l.Named(_controllerName), ioController: ioController, @@ -67,7 +67,7 @@ func (c *Controller) SetDigitalLevel(out PhysicalIo, level bool) error { out.String(), c.rev.String()) } - err := c.ioController.SetDigital(digitalOutput, level) + err := c.ioController.WriteDigital(digitalOutput, level) if err != nil { return errors.Wrap(err, "set digital") } From c401b7dbd1aabb9c9418207766cda2590bcd212c Mon Sep 17 00:00:00 2001 From: Blake Freer Date: Sun, 23 Nov 2025 22:34:08 -0500 Subject: [PATCH 2/2] Restructure Pinout Previous heirarchy was `PinType -> Revision -> Pin`. Now it is `Revision -> PinType -> Pin`. This means every revision has the same structure (4 maps of pins for DI DO AI AO) and we don't need to check if the revision has each. Fix an import loop --- cmd/hilapp/main.go | 39 ++++++- macformula/iocheckout.go | 25 ++--- macformula/pinout/controller.go | 35 ++---- macformula/pinout/pinout.go | 189 +++++++++++--------------------- 4 files changed, 114 insertions(+), 174 deletions(-) diff --git a/cmd/hilapp/main.go b/cmd/hilapp/main.go index 845cf844..be23837b 100644 --- a/cmd/hilapp/main.go +++ b/cmd/hilapp/main.go @@ -25,7 +25,6 @@ import ( "github.com/macformula/hil/macformula/state" "github.com/macformula/hil/orchestrator" "github.com/macformula/hil/results" - "github.com/macformula/hil/utils" "github.com/pkg/errors" ) @@ -53,6 +52,20 @@ var ( logLevelStr = flag.String("log", _defaultLogLevel.String(), "Changes the log level (debug, info, warn, error)") ) +func CastPins[T_IO any, T_SIL any](m map[pinout.PhysicalIo]T_IO) ([]*T_SIL, error) { + out := make([]*T_SIL, 0, len(m)) + + for _, v := range m { + impl, ok := any(v).(*T_SIL) + if !ok { + return nil, fmt.Errorf("Expected a sil.DigitalPin (got %T)", v) + } + out = append(out, impl) + } + + return out, nil +} + func main() { // Parse command-line flags before accessing them. flag.Parse() @@ -159,7 +172,29 @@ func main() { switch rev { case pinout.Sil: - silController = sil.NewController(cfg.SilPort, logger, utils.GetValues(pinout.SilDigitalInputPins), utils.GetValues(pinout.SilDigitalOutputPins), utils.GetValues(pinout.SilAnalogInputPins), utils.GetValues(pinout.SilAnalogOutputPins)) + pinout := pinout.Pinouts[pinout.Sil] + + // Yes this casting is ugly. It will change soon (bfreer) + diPins, err := CastPins[iocontrol.DigitalPin, sil.DigitalPin](pinout.DigitalInputs) + if err != nil { + panic("Invalid SIL pins") + } + doPins, err := CastPins[iocontrol.DigitalPin, sil.DigitalPin](pinout.DigitalOutputs) + if err != nil { + panic("Invalid SIL pins") + } + + aiPins, err := CastPins[iocontrol.AnalogPin, sil.AnalogPin](pinout.AnalogInputs) + if err != nil { + panic("Invalid SIL pins") + } + + aoPins, err := CastPins[iocontrol.AnalogPin, sil.AnalogPin](pinout.AnalogOutputs) + if err != nil { + panic("Invalid SIL pins") + } + + silController = sil.NewController(cfg.SilPort, logger, diPins, doPins, aiPins, aoPins) ioController = silController default: panic("unconfigured revision") diff --git a/macformula/iocheckout.go b/macformula/iocheckout.go index 4a4319b8..5171f411 100644 --- a/macformula/iocheckout.go +++ b/macformula/iocheckout.go @@ -78,25 +78,14 @@ func (io *IoCheckout) Open(ctx context.Context) error { return errors.Wrap(err, "iocontrol open") } - io.diPins, err = pinout.GetDigitalInputs(io.rev) - if err != nil { - return errors.Wrap(err, "get digital inputs") - } - - io.doPins, err = pinout.GetDigitalOutputs(io.rev) - if err != nil { - return errors.Wrap(err, "get digital outputs") - } - - io.aiPins, err = pinout.GetAnalogInputs(io.rev) - if err != nil { - return errors.Wrap(err, "get analog inputs") - } - - io.aoPins, err = pinout.GetAnalogOutputs(io.rev) - if err != nil { - return errors.Wrap(err, "get analog outputs") + po, ok := pinout.Pinouts[io.rev] + if !ok { + return errors.Errorf("Invalid revision %s", io.rev.String()) } + io.diPins = po.DigitalInputs + io.doPins = po.DigitalOutputs + io.aiPins = po.AnalogInputs + io.aoPins = po.AnalogOutputs return nil } diff --git a/macformula/pinout/controller.go b/macformula/pinout/controller.go index 5fd6a1f9..a7772bd6 100644 --- a/macformula/pinout/controller.go +++ b/macformula/pinout/controller.go @@ -17,10 +17,7 @@ type Controller struct { ioController iocontrol.IOController rev Revision - digitalOutputs DigitalPinout - digitalInputs DigitalPinout - analogOutputs AnalogPinout - analogInputs AnalogPinout + pins Pinout } // NewController creates a new pinout controller. @@ -34,34 +31,20 @@ func NewController(rev Revision, ioController iocontrol.IOController, l *zap.Log // Open opens the controller and initializes the digital and analog I/O's. func (c *Controller) Open(_ context.Context) error { - var err error + po, ok := Pinouts[c.rev] - c.digitalOutputs, err = GetDigitalOutputs(c.rev) - if err != nil { - return errors.Wrap(err, "get digital outputs") - } - - c.digitalInputs, err = GetDigitalInputs(c.rev) - if err != nil { - return errors.Wrap(err, "get digital inputs") - } - - c.analogOutputs, err = GetAnalogOutputs(c.rev) - if err != nil { - return errors.Wrap(err, "get analog outputs") + if !ok { + return errors.Errorf("Invalid revision %s", c.rev.String()) } - c.analogInputs, err = GetAnalogInputs(c.rev) - if err != nil { - return errors.Wrap(err, "get analog inputs") - } + c.pins = po return nil } // SetDigitalLevel sets the digital level of the given output. func (c *Controller) SetDigitalLevel(out PhysicalIo, level bool) error { - digitalOutput, ok := c.digitalOutputs[out] + digitalOutput, ok := c.pins.DigitalOutputs[out] if !ok { return errors.Errorf("no digital output for physical io (%s) in revision (%s)", out.String(), c.rev.String()) @@ -77,7 +60,7 @@ func (c *Controller) SetDigitalLevel(out PhysicalIo, level bool) error { // ReadDigitalLevel reads the digital level of the given input. func (c *Controller) ReadDigitalLevel(in PhysicalIo) (bool, error) { - digitalInput, ok := c.digitalInputs[in] + digitalInput, ok := c.pins.DigitalInputs[in] if !ok { return false, errors.Errorf("no digital input for physical io (%s) in revision (%s)", in.String(), c.rev.String()) @@ -93,7 +76,7 @@ func (c *Controller) ReadDigitalLevel(in PhysicalIo) (bool, error) { // SetVoltage sets the voltage of the given output. func (c *Controller) SetVoltage(out PhysicalIo, voltage float64) error { - analogOutput, ok := c.analogOutputs[out] + analogOutput, ok := c.pins.AnalogOutputs[out] if !ok { return errors.Errorf("no analog output for physical io (%s) in revision (%s)", out.String(), c.rev.String()) @@ -109,7 +92,7 @@ func (c *Controller) SetVoltage(out PhysicalIo, voltage float64) error { // ReadVoltage reads the voltage of the given input. func (c *Controller) ReadVoltage(in PhysicalIo) (float64, error) { - analogInput, ok := c.analogInputs[in] + analogInput, ok := c.pins.AnalogInputs[in] if !ok { return 0.0, errors.Errorf("no analog inputs for physical io (%s) in revision (%s)", in.String(), c.rev.String()) diff --git a/macformula/pinout/pinout.go b/macformula/pinout/pinout.go index 1197cad4..fa85ae9d 100644 --- a/macformula/pinout/pinout.go +++ b/macformula/pinout/pinout.go @@ -5,7 +5,6 @@ import ( // "github.com/macformula/hil/iocontrol/raspi" "github.com/macformula/hil/iocontrol/sil" "github.com/macformula/hil/iocontrol/speedgoat" - "github.com/pkg/errors" ) // DigitalPinout maps physical IO to digital pins. @@ -14,16 +13,64 @@ type DigitalPinout map[PhysicalIo]iocontrol.DigitalPin // AnalogPinout maps physical IO to analog pins. type AnalogPinout map[PhysicalIo]iocontrol.AnalogPin -// Revision-specific digital input pinouts -var _revisionDigitalInputPinout = map[Revision]DigitalPinout{ - Ev5: { +// These inputs and outputs are relative to the SIL/HIL (not the firmware!) +// Ex. A button is a DigitalInput to firmware but is a DigitalOutput of the HIL +type Pinout struct { + DigitalInputs DigitalPinout + DigitalOutputs DigitalPinout + AnalogInputs AnalogPinout + AnalogOutputs AnalogPinout +} + +var _ev5Pinout = Pinout{ + DigitalInputs: DigitalPinout{ InverterSwitchEn: speedgoat.NewDigitalPin(0), MotorControllerPrechargeEn: speedgoat.NewDigitalPin(1), ShutdownCircuitEn: speedgoat.NewDigitalPin(2), AccumulatorEn: speedgoat.NewDigitalPin(3), }, - MockTest: {}, - Sil: { + DigitalOutputs: DigitalPinout{ + GlvmsDisable: speedgoat.NewDigitalPin(8), + StartButtonN: speedgoat.NewDigitalPin(9), + HvilDisable: speedgoat.NewDigitalPin(10), + }, + AnalogInputs: AnalogPinout{ + HvilFeedback: speedgoat.NewAnalogPin(0), + LvController3v3RefVoltage: speedgoat.NewAnalogPin(1), + FrontController3v3RefVoltage: speedgoat.NewAnalogPin(2), + }, + AnalogOutputs: AnalogPinout{ + SteeringAngle: speedgoat.NewAnalogPin(8), + HvCurrentSense: speedgoat.NewAnalogPin(9), + AccelPedalPosition1: speedgoat.NewAnalogPin(10), + AccelPedalPosition2: speedgoat.NewAnalogPin(11), + }, +} + +var _mockPinout = Pinout{ + DigitalInputs: DigitalPinout{}, + DigitalOutputs: DigitalPinout{ + // StartButtonN: raspi.NewDigitalPin(), + }, + AnalogInputs: AnalogPinout{ + // LvController3v3RefVoltage: raspi.NewAnalogPin(), + }, + AnalogOutputs: AnalogPinout{ + // AccelPedalPosition1: raspi.NewAnalogPin(), + // AccelPedalPosition2: raspi.NewAnalogPin(), + // HvCurrentSense: raspi.NewAnalogPin(), + }, +} + +var _sgTestPinout = Pinout{ + DigitalInputs: DigitalPinout{}, + DigitalOutputs: DigitalPinout{}, + AnalogInputs: AnalogPinout{}, + AnalogOutputs: AnalogPinout{}, +} + +var _silPinout = Pinout{ + DigitalInputs: DigitalPinout{ IndicatorLed: sil.NewDigitalInputPin("DemoProject", IndicatorLed.String()), MotorControllerPrechargeEn: sil.NewDigitalInputPin("LvController", MotorControllerPrechargeEn.String()), InverterSwitchEn: sil.NewDigitalInputPin("LvController", InverterSwitchEn.String()), @@ -44,19 +91,7 @@ var _revisionDigitalInputPinout = map[Revision]DigitalPinout{ StatusLedEn: sil.NewDigitalInputPin("FrontController", StatusLedEn.String()), RtdsEn: sil.NewDigitalInputPin("FrontController", RtdsEn.String()), }, -} - -// Revision-specific digital output pinouts -var _revisionDigitalOutputPinout = map[Revision]DigitalPinout{ - Ev5: { - GlvmsDisable: speedgoat.NewDigitalPin(8), - StartButtonN: speedgoat.NewDigitalPin(9), - HvilDisable: speedgoat.NewDigitalPin(10), - }, - MockTest: { - // StartButtonN: raspi.NewDigitalPin(), - }, - Sil: { + DigitalOutputs: DigitalPinout{ IndicatorButton: sil.NewDigitalOutputPin("DemoProject", IndicatorButton.String()), StartButtonN: sil.NewDigitalOutputPin("FrontController", StartButtonN.String()), WheelSpeedLeftA: sil.NewDigitalOutputPin("FrontController", WheelSpeedLeftA.String()), @@ -66,121 +101,19 @@ var _revisionDigitalOutputPinout = map[Revision]DigitalPinout{ HvilDisable: sil.NewDigitalOutputPin("FrontController", HvilDisable.String()), GlvmsDisable: sil.NewDigitalOutputPin("LvController", GlvmsDisable.String()), }, -} - -// Revision-specific analog input pinouts -var _revisionAnalogInputPinout = map[Revision]AnalogPinout{ - Ev5: { - HvilFeedback: speedgoat.NewAnalogPin(0), - LvController3v3RefVoltage: speedgoat.NewAnalogPin(1), - FrontController3v3RefVoltage: speedgoat.NewAnalogPin(2), - }, - MockTest: { - // LvController3v3RefVoltage: raspi.NewAnalogPin(), - }, - Sil: { + AnalogInputs: AnalogPinout{ HvilFeedback: sil.NewAnalogInputPin("FrontController", HvilFeedback.String()), }, -} - -// Revision-specific analog output pinouts -var _revisionAnalogOutputPinout = map[Revision]AnalogPinout{ - Ev5: { - SteeringAngle: speedgoat.NewAnalogPin(8), - HvCurrentSense: speedgoat.NewAnalogPin(9), - AccelPedalPosition1: speedgoat.NewAnalogPin(10), - AccelPedalPosition2: speedgoat.NewAnalogPin(11), - }, - MockTest: { - // AccelPedalPosition1: raspi.NewAnalogPin(), - // AccelPedalPosition2: raspi.NewAnalogPin(), - // HvCurrentSense: raspi.NewAnalogPin(), - }, - Sil: { + AnalogOutputs: AnalogPinout{ AccelPedalPosition1: sil.NewAnalogOutputPin("FrontController", AccelPedalPosition1.String()), AccelPedalPosition2: sil.NewAnalogOutputPin("FrontController", AccelPedalPosition2.String()), SteeringAngle: sil.NewAnalogOutputPin("FrontController", SteeringAngle.String()), }, } -var SilDigitalInputPins = map[PhysicalIo]*sil.DigitalPin{ - IndicatorLed: sil.NewDigitalInputPin("DemoProject", IndicatorLed.String()), - MotorControllerPrechargeEn: sil.NewDigitalInputPin("LvController", MotorControllerPrechargeEn.String()), - InverterSwitchEn: sil.NewDigitalInputPin("LvController", InverterSwitchEn.String()), - AccumulatorEn: sil.NewDigitalInputPin("LvController", AccumulatorEn.String()), - ShutdownCircuitEn: sil.NewDigitalInputPin("LvController", ShutdownCircuitEn.String()), - TsalEn: sil.NewDigitalInputPin("LvController", TsalEn.String()), - RaspiEn: sil.NewDigitalInputPin("LvController", RaspiEn.String()), - FrontControllerEn: sil.NewDigitalInputPin("LvController", FrontControllerEn.String()), - SpeedgoatEn: sil.NewDigitalInputPin("LvController", SpeedgoatEn.String()), - MotorControllerEn: sil.NewDigitalInputPin("LvController", MotorControllerEn.String()), - ImuGpsEn: sil.NewDigitalInputPin("LvController", ImuGpsEn.String()), - DcdcEn: sil.NewDigitalInputPin("LvController", DcdcEn.String()), - DcdcValid: sil.NewDigitalInputPin("LvController", DcdcValid.String()), - DebugLedEn: sil.NewDigitalInputPin("FrontController", DebugLedEn.String()), - DashboardEn: sil.NewDigitalInputPin("FrontController", DashboardEn.String()), - HvilLedEn: sil.NewDigitalInputPin("FrontController", HvilLedEn.String()), - BrakeLightEn: sil.NewDigitalInputPin("FrontController", BrakeLightEn.String()), - StatusLedEn: sil.NewDigitalInputPin("FrontController", StatusLedEn.String()), - RtdsEn: sil.NewDigitalInputPin("FrontController", RtdsEn.String()), -} -var SilDigitalOutputPins = map[PhysicalIo]*sil.DigitalPin{ - IndicatorButton: sil.NewDigitalOutputPin("DemoProject", IndicatorButton.String()), - StartButtonN: sil.NewDigitalOutputPin("FrontController", StartButtonN.String()), - WheelSpeedLeftA: sil.NewDigitalOutputPin("FrontController", WheelSpeedLeftA.String()), - WheelSpeedLeftB: sil.NewDigitalOutputPin("FrontController", WheelSpeedLeftB.String()), - WheelSpeedRightA: sil.NewDigitalOutputPin("FrontController", WheelSpeedRightA.String()), - WheelSpeedRightB: sil.NewDigitalOutputPin("FrontController", WheelSpeedRightB.String()), - HvilDisable: sil.NewDigitalOutputPin("FrontController", HvilDisable.String()), - GlvmsDisable: sil.NewDigitalOutputPin("LvController", GlvmsDisable.String()), -} -var SilAnalogInputPins = map[PhysicalIo]*sil.AnalogPin{ - AccelPedalPosition1: sil.NewAnalogOutputPin("FrontController", AccelPedalPosition1.String()), - AccelPedalPosition2: sil.NewAnalogOutputPin("FrontController", AccelPedalPosition2.String()), - SteeringAngle: sil.NewAnalogOutputPin("FrontController", SteeringAngle.String()), -} -var SilAnalogOutputPins = map[PhysicalIo]*sil.AnalogPin{ - AccelPedalPosition1: sil.NewAnalogOutputPin("FrontController", AccelPedalPosition1.String()), - AccelPedalPosition2: sil.NewAnalogOutputPin("FrontController", AccelPedalPosition2.String()), - SteeringAngle: sil.NewAnalogOutputPin("FrontController", SteeringAngle.String()), -} - -// GetDigitalInputs returns a digital input pinout for the given revision. -func GetDigitalInputs(rev Revision) (DigitalPinout, error) { - ret, ok := _revisionDigitalInputPinout[rev] - if !ok { - return nil, errors.Errorf("no digital input pinout for revision (%s)", rev.String()) - } - - return ret, nil -} - -// GetDigitalOutputs returns a digital output pinout for the given revision. -func GetDigitalOutputs(rev Revision) (DigitalPinout, error) { - ret, ok := _revisionDigitalOutputPinout[rev] - if !ok { - return nil, errors.Errorf("no digital output pinout for revision (%s)", rev.String()) - } - - return ret, nil -} - -// GetAnalogInputs returns an analog input pinout for the given revision. -func GetAnalogInputs(rev Revision) (AnalogPinout, error) { - ret, ok := _revisionAnalogInputPinout[rev] - if !ok { - return nil, errors.Errorf("no analog input pinout for revision (%s)", rev.String()) - } - - return ret, nil -} - -// GetAnalogOutputs returns an analog output pinout for the given revision. -func GetAnalogOutputs(rev Revision) (AnalogPinout, error) { - ret, ok := _revisionAnalogOutputPinout[rev] - if !ok { - return nil, errors.Errorf("no analog output pinout for revision (%s)", rev.String()) - } - - return ret, nil +var Pinouts = map[Revision]Pinout{ + Ev5: _ev5Pinout, + MockTest: _mockPinout, + Sil: _silPinout, + SgTest: _sgTestPinout, }