diff --git a/cmd/hilapp/main.go b/cmd/hilapp/main.go index 205bcd0..be23837 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() @@ -153,21 +166,40 @@ 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)) + 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") } - // 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 1589ee2..11ad3c8 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 3154ff2..6af2cf4 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 1149cd2..1c580ac 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 418ccb8..18f70f7 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 d25893b..48396db 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 759d493..16e5841 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 ddceea8..2c87912 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 c83890f..0000000 --- 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 067bc78..5171f41 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, @@ -77,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 } @@ -353,7 +343,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 af0b2a6..a7772bd 100644 --- a/macformula/pinout/controller.go +++ b/macformula/pinout/controller.go @@ -14,17 +14,14 @@ 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 - digitalInputs DigitalPinout - analogOutputs AnalogPinout - analogInputs AnalogPinout + pins Pinout } // 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, @@ -34,40 +31,26 @@ func NewController(rev Revision, ioController *iocontrol.IOControl, l *zap.Logge // 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()) } - err := c.ioController.SetDigital(digitalOutput, level) + err := c.ioController.WriteDigital(digitalOutput, level) if err != nil { return errors.Wrap(err, "set digital") } @@ -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 1197cad..fa85ae9 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, }