From 6aaccc13c05cee4b4d8dac407533e425403d36d5 Mon Sep 17 00:00:00 2001 From: Thomas Kohler Date: Sat, 4 Jan 2025 12:08:38 +0100 Subject: [PATCH] gpio(adaptors): fix so now gpiodev is used as default (#1112) --- examples/beaglepocket_direct_pin.go | 72 +++++ platforms/adaptors/analogpinsadaptor.go | 6 +- platforms/adaptors/analogpinsadaptor_test.go | 14 +- platforms/adaptors/analogpintranslator.go | 17 +- .../adaptors/analogpintranslator_test.go | 30 +-- platforms/adaptors/digitalpinsadaptor.go | 226 +++++++++++----- platforms/adaptors/digitalpinsadaptor_test.go | 44 +++- .../adaptors/digitalpinsadaptoroptions.go | 246 ++++++++++-------- .../digitalpinsadaptoroptions_test.go | 140 ++++++++-- .../adaptors/digitalpintranslator_test.go | 14 +- platforms/adaptors/pwmpinsadaptor.go | 2 +- platforms/beaglebone/README.md | 7 +- platforms/beaglebone/beaglebone_adaptor.go | 6 +- .../beaglebone/beaglebone_adaptor_test.go | 23 +- platforms/beaglebone/black_pins.go | 14 +- .../beaglebone/pocketbeagle_adaptor_test.go | 4 + platforms/beaglebone/pocketbeagle_pins.go | 12 +- platforms/chip/chip_adaptor.go | 26 +- platforms/chip/chip_adaptor_test.go | 51 ++-- platforms/chip/chippro_adaptor.go | 11 + platforms/chip/chippro_adaptor_test.go | 41 +++ platforms/dji/tello/driver_test.go | 2 +- platforms/dragonboard/dragonboard_adaptor.go | 6 +- .../dragonboard/dragonboard_adaptor_test.go | 25 +- platforms/intel-iot/edison/edison_adaptor.go | 21 +- .../intel-iot/edison/edison_adaptor_test.go | 66 ++--- platforms/intel-iot/joule/joule_adaptor.go | 8 +- .../intel-iot/joule/joule_adaptor_test.go | 26 +- platforms/jetson/jetson_adaptor.go | 8 +- platforms/jetson/jetson_adaptor_test.go | 21 +- platforms/nanopi/nanopi_adaptor.go | 10 +- platforms/nanopi/nanopi_adaptor_test.go | 120 ++++++--- platforms/nanopi/nanopineo_pin_map.go | 2 +- platforms/raspi/raspi_adaptor.go | 8 +- platforms/raspi/raspi_adaptor_test.go | 67 +++-- platforms/raspi/raspi_pin_map.go | 2 +- platforms/rockpi/rockpi_adaptor.go | 4 +- platforms/rockpi/rockpi_adaptor_test.go | 21 +- platforms/tinkerboard/adaptor.go | 10 +- platforms/tinkerboard/adaptor_test.go | 104 +++++--- platforms/tinkerboard/pin_map.go | 4 +- platforms/tinkerboard/tinkerboard2/adaptor.go | 13 +- .../tinkerboard/tinkerboard2/adaptor_test.go | 12 + platforms/upboard/up2/adaptor.go | 14 +- platforms/upboard/up2/adaptor_test.go | 23 +- system/digitalpin_access.go | 30 ++- system/digitalpin_mock.go | 145 +++++++++-- system/system.go | 54 ++-- system/system_options.go | 27 ++ system/system_test.go | 46 ++-- 50 files changed, 1279 insertions(+), 626 deletions(-) create mode 100644 examples/beaglepocket_direct_pin.go create mode 100644 platforms/chip/chippro_adaptor.go create mode 100644 platforms/chip/chippro_adaptor_test.go diff --git a/examples/beaglepocket_direct_pin.go b/examples/beaglepocket_direct_pin.go new file mode 100644 index 000000000..8c909c8c1 --- /dev/null +++ b/examples/beaglepocket_direct_pin.go @@ -0,0 +1,72 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/gpio" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/platforms/beaglebone" +) + +// Wiring +// PWR Pocket: P1.14, P2.23 (+3.3V, VCC); P1.15, P1.16, P1.22, P2.15, P2.21 (GND) +// GPIO Pocket: header pin P1.34 is input, pin P1.35 is normal output, pin P1.36 is inverted output +// Button: the input pin is wired with a button to GND, an external pull up resistor is needed (e.g. 2kOhm to VCC) +// LED's: the output pins are wired to the cathode of the LED, the anode is wired with a resistor (70-130Ohm for 20mA) +// to VCC +// Expected behavior: always one LED is on, the other in opposite state, if button is pressed the state changes +func main() { + const ( + inPinNum = "P1_34" + outPinNum = "P1_35" + outPinInvertedNum = "P1_36" + ) + + board := beaglebone.NewPocketBeagleAdaptor(adaptors.WithGpiosActiveLow(outPinInvertedNum)) + + inPin := gpio.NewDirectPinDriver(board, inPinNum) + outPin := gpio.NewDirectPinDriver(board, outPinNum) + outPinInverted := gpio.NewDirectPinDriver(board, outPinInvertedNum) + + work := func() { + gobot.Every(500*time.Millisecond, func() { + read, err := inPin.DigitalRead() + fmt.Printf("pin %s state is %d\n", inPinNum, read) + if err != nil { + fmt.Println(err) + } + + level := byte(read) + + err = outPin.DigitalWrite(level) + fmt.Printf("pin %s is now %d\n", outPinNum, level) + if err != nil { + fmt.Println(err) + } + + err = outPinInverted.DigitalWrite(level) + fmt.Printf("pin %s is now not %d\n", outPinInvertedNum, level) + if err != nil { + fmt.Println(err) + } + }) + } + + robot := gobot.NewRobot("pinBot", + []gobot.Connection{board}, + []gobot.Device{inPin, outPin, outPinInverted}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/platforms/adaptors/analogpinsadaptor.go b/platforms/adaptors/analogpinsadaptor.go index 59a210d14..99caf1c88 100644 --- a/platforms/adaptors/analogpinsadaptor.go +++ b/platforms/adaptors/analogpinsadaptor.go @@ -8,7 +8,7 @@ import ( "gobot.io/x/gobot/v2/system" ) -type analogPinTranslator func(pin string) (path string, r, w bool, bufLen uint16, err error) +type analogPinTranslator func(pin string) (path string, w bool, readBufLen uint16, err error) // AnalogPinsAdaptor is a adaptor for analog pins, normally used for composition in platforms. // It is also usable for general sysfs access. @@ -83,11 +83,11 @@ func (a *AnalogPinsAdaptor) analogPin(id string) (gobot.AnalogPinner, error) { pin := a.pins[id] if pin == nil { - path, r, w, bufLen, err := a.translate(id) + path, w, readBufLen, err := a.translate(id) if err != nil { return nil, err } - pin = a.sys.NewAnalogPin(path, r, w, bufLen) + pin = a.sys.NewAnalogPin(path, w, readBufLen) a.pins[id] = pin } diff --git a/platforms/adaptors/analogpinsadaptor_test.go b/platforms/adaptors/analogpinsadaptor_test.go index 0f78e4fa6..774aa1686 100644 --- a/platforms/adaptors/analogpinsadaptor_test.go +++ b/platforms/adaptors/analogpinsadaptor_test.go @@ -40,23 +40,23 @@ func initTestAnalogPinsAdaptorWithMockedFilesystem(mockPaths []string) (*AnalogP return a, fs } -func testAnalogPinTranslator(id string) (string, bool, bool, uint16, error) { +func testAnalogPinTranslator(id string) (string, bool, uint16, error) { switch id { case "read": - return analogReadPath, true, false, 10, nil + return analogReadPath, false, 10, nil case "write": - return analogWritePath, false, true, 11, nil + return analogWritePath, true, 0, nil case "read/write": - return analogReadWritePath, true, true, 12, nil + return analogReadWritePath, true, 12, nil case "read/write_string": - return analogReadWriteStringPath, true, true, 13, nil + return analogReadWriteStringPath, true, 13, nil } - return "", false, false, 0, fmt.Errorf("'%s' is not a valid id of an analog pin", id) + return "", false, 0, fmt.Errorf("'%s' is not a valid id of an analog pin", id) } func TestAnalogPinsConnect(t *testing.T) { - translate := func(id string) (path string, r, w bool, bufLen uint16, err error) { return } + translate := func(id string) (path string, w bool, bufLen uint16, err error) { return } a := NewAnalogPinsAdaptor(system.NewAccesser(), translate) assert.Equal(t, (map[string]gobot.AnalogPinner)(nil), a.pins) diff --git a/platforms/adaptors/analogpintranslator.go b/platforms/adaptors/analogpintranslator.go index 6cbf19bbe..c04962a7d 100644 --- a/platforms/adaptors/analogpintranslator.go +++ b/platforms/adaptors/analogpintranslator.go @@ -7,10 +7,9 @@ import ( ) type AnalogPinDefinition struct { - Path string - R bool // readable - W bool // writable - BufLen uint16 + Path string + W bool // writable + ReadBufLen uint16 // readable if buffer > 0 } type AnalogPinDefinitions map[string]AnalogPinDefinition @@ -26,20 +25,20 @@ func NewAnalogPinTranslator(sys *system.Accesser, pinDefinitions AnalogPinDefini } // Translate returns the sysfs path for the given id. -func (pt *AnalogPinTranslator) Translate(id string) (string, bool, bool, uint16, error) { +func (pt *AnalogPinTranslator) Translate(id string) (string, bool, uint16, error) { pinInfo, ok := pt.pinDefinitions[id] if !ok { - return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for an analog pin", id) + return "", false, 0, fmt.Errorf("'%s' is not a valid id for an analog pin", id) } path := pinInfo.Path info, err := pt.sys.Stat(path) if err != nil { - return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path) + return "", false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path) } if info.IsDir() { - return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path) + return "", false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path) } - return path, pinInfo.R, pinInfo.W, pinInfo.BufLen, nil + return path, pinInfo.W, pinInfo.ReadBufLen, nil } diff --git a/platforms/adaptors/analogpintranslator_test.go b/platforms/adaptors/analogpintranslator_test.go index 3a5c43991..a51914f4a 100644 --- a/platforms/adaptors/analogpintranslator_test.go +++ b/platforms/adaptors/analogpintranslator_test.go @@ -23,31 +23,28 @@ func TestNewAnalogPinTranslator(t *testing.T) { func TestAnalogPinTranslatorTranslate(t *testing.T) { pinDefinitions := AnalogPinDefinitions{ - "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, - "thermal_zone1": {Path: "/sys/class/thermal/thermal_zone1/temp", R: true, W: false, BufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", W: false, ReadBufLen: 7}, + "thermal_zone1": {Path: "/sys/class/thermal/thermal_zone1/temp", W: false, ReadBufLen: 7}, } mockedPaths := []string{ "/sys/class/thermal/thermal_zone0/temp", "/sys/class/thermal/thermal_zone1/temp", } tests := map[string]struct { - id string - wantPath string - wantReadable bool - wantBufLen uint16 - wantErr string + id string + wantPath string + wantBufLen uint16 + wantErr string }{ "translate_thermal_zone0": { - id: "thermal_zone0", - wantPath: "/sys/class/thermal/thermal_zone0/temp", - wantReadable: true, - wantBufLen: 7, + id: "thermal_zone0", + wantPath: "/sys/class/thermal/thermal_zone0/temp", + wantBufLen: 7, }, "translate_thermal_zone1": { - id: "thermal_zone1", - wantPath: "/sys/class/thermal/thermal_zone1/temp", - wantReadable: true, - wantBufLen: 7, + id: "thermal_zone1", + wantPath: "/sys/class/thermal/thermal_zone1/temp", + wantBufLen: 7, }, "unknown_id": { id: "99", @@ -61,7 +58,7 @@ func TestAnalogPinTranslatorTranslate(t *testing.T) { _ = sys.UseMockFilesystem(mockedPaths) pt := NewAnalogPinTranslator(sys, pinDefinitions) // act - path, r, w, buf, err := pt.Translate(tc.id) + path, w, buf, err := pt.Translate(tc.id) // assert if tc.wantErr != "" { require.EqualError(t, err, tc.wantErr) @@ -69,7 +66,6 @@ func TestAnalogPinTranslatorTranslate(t *testing.T) { require.NoError(t, err) } assert.Equal(t, tc.wantPath, path) - assert.Equal(t, tc.wantReadable, r) assert.False(t, w) assert.Equal(t, tc.wantBufLen, buf) }) diff --git a/platforms/adaptors/digitalpinsadaptor.go b/platforms/adaptors/digitalpinsadaptor.go index 943814b51..0c4b2d975 100644 --- a/platforms/adaptors/digitalpinsadaptor.go +++ b/platforms/adaptors/digitalpinsadaptor.go @@ -16,14 +16,54 @@ type ( digitalPinInitializer func(gobot.DigitalPinner) error ) +type digitalPinGpiosForSPI struct { + sclkPin string + ncsPin string + sdoPin string + sdiPin string +} + +type digitalPinsDebouncePin struct { + id string + period time.Duration +} + +type digitalPinsEventOnEdgePin struct { + id string + handler func(lineOffset int, timestamp time.Duration, detectedEdge string, seqno uint32, lseqno uint32) +} + +type digitalPinsPollForEdgeDetectionPin struct { + id string + pollInterval time.Duration + pollQuitChan chan struct{} +} + +// digitalPinsConfiguration contains all changeable attributes of the adaptor. +type digitalPinsConfiguration struct { + initialize digitalPinInitializer + useSysfs *bool + gpiosForSPI *digitalPinGpiosForSPI + activeLowPins []string + pullDownPins []string + pullUpPins []string + openDrainPins []string + openSourcePins []string + debouncePins []digitalPinsDebouncePin + eventOnFallingEdgePins []digitalPinsEventOnEdgePin + eventOnRisingEdgePins []digitalPinsEventOnEdgePin + eventOnBothEdgesPins []digitalPinsEventOnEdgePin + pollForEdgeDetectionPins []digitalPinsPollForEdgeDetectionPin +} + // DigitalPinsAdaptor is a adaptor for digital pins, normally used for composition in platforms. type DigitalPinsAdaptor struct { - sys *system.Accesser - translate digitalPinTranslator - initialize digitalPinInitializer - pins map[string]gobot.DigitalPinner - pinOptions map[string][]func(gobot.DigitalPinOptioner) bool - mutex sync.Mutex + sys *system.Accesser + digitalPinsCfg *digitalPinsConfiguration + translate digitalPinTranslator + pins map[string]gobot.DigitalPinner + pinOptions map[string][]func(gobot.DigitalPinOptioner) bool + mutex sync.Mutex } // NewDigitalPinsAdaptor provides the access to digital pins of the board. It supports sysfs and gpiod system drivers. @@ -34,117 +74,111 @@ type DigitalPinsAdaptor struct { func NewDigitalPinsAdaptor( sys *system.Accesser, t digitalPinTranslator, - options ...func(DigitalPinsOptioner), + opts ...DigitalPinsOptionApplier, ) *DigitalPinsAdaptor { a := &DigitalPinsAdaptor{ - sys: sys, - translate: t, - initialize: func(pin gobot.DigitalPinner) error { return pin.Export() }, + sys: sys, + digitalPinsCfg: &digitalPinsConfiguration{initialize: func(pin gobot.DigitalPinner) error { return pin.Export() }}, + translate: t, } - for _, option := range options { - option(a) + for _, o := range opts { + o.apply(a.digitalPinsCfg) } return a } // WithDigitalPinInitializer can be used to substitute the default initializer. -func WithDigitalPinInitializer(pc digitalPinInitializer) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.setDigitalPinInitializer(pc) - } +func WithDigitalPinInitializer(pc digitalPinInitializer) digitalPinsInitializeOption { + return digitalPinsInitializeOption(pc) } -// WithGpiodAccess can be used to change the default sysfs implementation to the character device Kernel ABI. -// The access is provided by the gpiod package. -func WithGpiodAccess() func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.setDigitalPinsForSystemGpiod() - } +// WithGpiodAccess can be used to change the default legacy sysfs implementation to the character device Kernel ABI, +// provided by the gpiod package. +func WithGpiodAccess() digitalPinsSystemSysfsOption { + return digitalPinsSystemSysfsOption(false) +} + +// WithSysfsAccess can be used to change the default character device implementation, provided by the gpiod package, to +// the legacy sysfs Kernel ABI. +func WithSysfsAccess() digitalPinsSystemSysfsOption { + return digitalPinsSystemSysfsOption(true) } // WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage. -func WithSpiGpioAccess(sclkPin, ncsPin, sdoPin, sdiPin string) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.setDigitalPinsForSystemSpi(sclkPin, ncsPin, sdoPin, sdiPin) +func WithSpiGpioAccess(sclkPin, ncsPin, sdoPin, sdiPin string) digitalPinsForSystemSpiOption { + o := digitalPinsForSystemSpiOption{ + sclkPin: sclkPin, + ncsPin: ncsPin, + sdoPin: sdoPin, + sdiPin: sdiPin, } + + return o } // WithGpiosActiveLow prepares the given pins for inverse reaction on next initialize. // This is working for inputs and outputs. -func WithGpiosActiveLow(pin string, otherPins ...string) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinsActiveLow(pin, otherPins...) - } +func WithGpiosActiveLow(pin string, otherPins ...string) digitalPinsActiveLowOption { + pins := append([]string{pin}, otherPins...) + return digitalPinsActiveLowOption(pins) } // WithGpiosPullDown prepares the given pins to be pulled down (high impedance to GND) on next initialize. // This is working for inputs and outputs since Kernel 5.5, but will be ignored with sysfs ABI. -func WithGpiosPullDown(pin string, otherPins ...string) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinsPullDown(pin, otherPins...) - } +func WithGpiosPullDown(pin string, otherPins ...string) digitalPinsPullDownOption { + pins := append([]string{pin}, otherPins...) + return digitalPinsPullDownOption(pins) } // WithGpiosPullUp prepares the given pins to be pulled up (high impedance to VDD) on next initialize. // This is working for inputs and outputs since Kernel 5.5, but will be ignored with sysfs ABI. -func WithGpiosPullUp(pin string, otherPins ...string) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinsPullUp(pin, otherPins...) - } +func WithGpiosPullUp(pin string, otherPins ...string) digitalPinsPullUpOption { + pins := append([]string{pin}, otherPins...) + return digitalPinsPullUpOption(pins) } // WithGpiosOpenDrain prepares the given output pins to be driven with open drain/collector on next initialize. // This will be ignored for inputs or with sysfs ABI. -func WithGpiosOpenDrain(pin string, otherPins ...string) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinsOpenDrain(pin, otherPins...) - } +func WithGpiosOpenDrain(pin string, otherPins ...string) digitalPinsOpenDrainOption { + pins := append([]string{pin}, otherPins...) + return digitalPinsOpenDrainOption(pins) } // WithGpiosOpenSource prepares the given output pins to be driven with open source/emitter on next initialize. // This will be ignored for inputs or with sysfs ABI. -func WithGpiosOpenSource(pin string, otherPins ...string) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinsOpenSource(pin, otherPins...) - } +func WithGpiosOpenSource(pin string, otherPins ...string) digitalPinsOpenSourceOption { + pins := append([]string{pin}, otherPins...) + return digitalPinsOpenSourceOption(pins) } // WithGpioDebounce prepares the given input pin to be debounced on next initialize. // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. -func WithGpioDebounce(pin string, period time.Duration) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinDebounce(pin, period) - } +func WithGpioDebounce(pin string, period time.Duration) digitalPinsDebounceOption { + return digitalPinsDebounceOption{id: pin, period: period} } // WithGpioEventOnFallingEdge prepares the given input pin to be generate an event on falling edge. // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. func WithGpioEventOnFallingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string, seqno uint32, lseqno uint32), -) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinEventOnFallingEdge(pin, handler) - } +) digitalPinsEventOnFallingEdgeOption { + return digitalPinsEventOnFallingEdgeOption{id: pin, handler: handler} } // WithGpioEventOnRisingEdge prepares the given input pin to be generate an event on rising edge. // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. func WithGpioEventOnRisingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string, seqno uint32, lseqno uint32), -) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinEventOnRisingEdge(pin, handler) - } +) digitalPinsEventOnRisingEdgeOption { + return digitalPinsEventOnRisingEdgeOption{id: pin, handler: handler} } // WithGpioEventOnBothEdges prepares the given input pin to be generate an event on rising and falling edges. // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. func WithGpioEventOnBothEdges(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string, seqno uint32, lseqno uint32), -) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinEventOnBothEdges(pin, handler) - } +) digitalPinsEventOnBothEdgesOption { + return digitalPinsEventOnBothEdgesOption{id: pin, handler: handler} } // WithGpioPollForEdgeDetection prepares the given input pin to use a discrete input pin polling function together with @@ -153,10 +187,8 @@ func WithGpioPollForEdgeDetection( pin string, pollInterval time.Duration, pollQuitChan chan struct{}, -) func(DigitalPinsOptioner) { - return func(o DigitalPinsOptioner) { - o.prepareDigitalPinPollForEdgeDetection(pin, pollInterval, pollQuitChan) - } +) digitalPinsPollForEdgeDetectionOption { + return digitalPinsPollForEdgeDetectionOption{id: pin, pollInterval: pollInterval, pollQuitChan: pollQuitChan} } // Connect prepare new connection to digital pins. @@ -164,6 +196,68 @@ func (a *DigitalPinsAdaptor) Connect() error { a.mutex.Lock() defer a.mutex.Unlock() + if a.pinOptions != nil { + return fmt.Errorf("digital pin adaptor already connected, please call Finalize() for re-connect") + } + + cfg := a.digitalPinsCfg + + if cfg.useSysfs != nil { + if *cfg.useSysfs { + system.WithDigitalPinSysfsAccess()(a.sys) + } else { + system.WithDigitalPinGpiodAccess()(a.sys) + } + } + + if cfg.gpiosForSPI != nil { + system.WithSpiGpioAccess(a, cfg.gpiosForSPI.sclkPin, cfg.gpiosForSPI.ncsPin, cfg.gpiosForSPI.sdoPin, + cfg.gpiosForSPI.sdiPin)(a.sys) + } + + a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) + + for _, pin := range cfg.activeLowPins { + a.pinOptions[pin] = append(a.pinOptions[pin], system.WithPinActiveLow()) + } + + for _, pin := range cfg.pullDownPins { + a.pinOptions[pin] = append(a.pinOptions[pin], system.WithPinPullDown()) + } + + for _, pin := range cfg.pullUpPins { + a.pinOptions[pin] = append(a.pinOptions[pin], system.WithPinPullUp()) + } + + for _, pin := range cfg.openDrainPins { + a.pinOptions[pin] = append(a.pinOptions[pin], system.WithPinOpenDrain()) + } + + for _, pin := range cfg.openSourcePins { + a.pinOptions[pin] = append(a.pinOptions[pin], system.WithPinOpenSource()) + } + + for _, pin := range cfg.debouncePins { + a.pinOptions[pin.id] = append(a.pinOptions[pin.id], system.WithPinDebounce(pin.period)) + } + + for _, pin := range cfg.eventOnFallingEdgePins { + a.pinOptions[pin.id] = append(a.pinOptions[pin.id], system.WithPinEventOnFallingEdge(pin.handler)) + } + + for _, pin := range cfg.eventOnRisingEdgePins { + a.pinOptions[pin.id] = append(a.pinOptions[pin.id], system.WithPinEventOnRisingEdge(pin.handler)) + } + + for _, pin := range cfg.eventOnBothEdgesPins { + a.pinOptions[pin.id] = append(a.pinOptions[pin.id], system.WithPinEventOnBothEdges(pin.handler)) + } + + for _, pin := range cfg.pollForEdgeDetectionPins { + a.pinOptions[pin.id] = append(a.pinOptions[pin.id], + system.WithPinPollForEdgeDetection(pin.pollInterval, pin.pollQuitChan)) + } + a.pins = make(map[string]gobot.DigitalPinner) return nil } @@ -236,7 +330,7 @@ func (a *DigitalPinsAdaptor) digitalPin( return nil, err } pin = a.sys.NewDigitalPin(chip, line, o...) - if err = a.initialize(pin); err != nil { + if err = a.digitalPinsCfg.initialize(pin); err != nil { return nil, err } a.pins[id] = pin diff --git a/platforms/adaptors/digitalpinsadaptor_test.go b/platforms/adaptors/digitalpinsadaptor_test.go index 55e16393b..d88a79781 100644 --- a/platforms/adaptors/digitalpinsadaptor_test.go +++ b/platforms/adaptors/digitalpinsadaptor_test.go @@ -23,16 +23,24 @@ var ( _ gpio.DigitalWriter = (*DigitalPinsAdaptor)(nil) ) -func initTestDigitalPinsAdaptorWithMockedFilesystem(mockPaths []string) (*DigitalPinsAdaptor, *system.MockFilesystem) { - sys := system.NewAccesser() - fs := sys.UseMockFilesystem(mockPaths) - a := NewDigitalPinsAdaptor(sys, testDigitalPinTranslator) +func initTestConnectedDigitalPinsAdaptorWithMockedFilesystem( + mockPaths []string, +) (*DigitalPinsAdaptor, *system.MockFilesystem) { + a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockPaths) if err := a.Connect(); err != nil { panic(err) } return a, fs } +func initTestDigitalPinsAdaptorWithMockedFilesystem(mockPaths []string) (*DigitalPinsAdaptor, *system.MockFilesystem) { + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) + fs := sys.UseMockFilesystem(mockPaths) + a := NewDigitalPinsAdaptor(sys, testDigitalPinTranslator) + + return a, fs +} + func testDigitalPinTranslator(pin string) (string, int, error) { line, err := strconv.Atoi(pin) if err != nil { @@ -42,9 +50,25 @@ func testDigitalPinTranslator(pin string) (string, int, error) { return "", line, err } +func TestNewDigitalPinsAdaptor(t *testing.T) { + // arrange + translate := func(string) (string, int, error) { return "", 0, nil } + sys := system.NewAccesser() + // act + a := NewDigitalPinsAdaptor(sys, translate) + // assert + assert.IsType(t, &DigitalPinsAdaptor{}, a) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.digitalPinsCfg) + assert.NotNil(t, a.translate) + assert.Nil(t, a.pins) // will be created on connect + assert.Nil(t, a.pinOptions) // will be created on connect + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) +} + func TestDigitalPinsConnect(t *testing.T) { translate := func(pin string) (chip string, line int, err error) { return } - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := NewDigitalPinsAdaptor(sys, translate) assert.Equal(t, (map[string]gobot.DigitalPinner)(nil), a.pins) @@ -69,7 +93,7 @@ func TestDigitalPinsFinalize(t *testing.T) { "/sys/class/gpio/gpio14/direction", "/sys/class/gpio/gpio14/value", } - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) fs := sys.UseMockFilesystem(mockedPaths) a := NewDigitalPinsAdaptor(sys, testDigitalPinTranslator) // assert that finalize before connect is working @@ -101,7 +125,7 @@ func TestDigitalPinsReConnect(t *testing.T) { "/sys/class/gpio/gpio15/direction", "/sys/class/gpio/gpio15/value", } - a, _ := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) + a, _ := initTestConnectedDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) require.NoError(t, a.DigitalWrite("4", 1)) assert.Len(t, a.pins, 1) require.NoError(t, a.Finalize()) @@ -120,7 +144,7 @@ func TestDigitalIO(t *testing.T) { "/sys/class/gpio/gpio25/value", "/sys/class/gpio/gpio25/direction", } - a, _ := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) + a, _ := initTestConnectedDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) err := a.DigitalWrite("14", 1) require.NoError(t, err) @@ -138,7 +162,7 @@ func TestDigitalRead(t *testing.T) { "/sys/class/gpio/gpio24/value", "/sys/class/gpio/gpio24/direction", } - a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) + a, fs := initTestConnectedDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) fs.Files["/sys/class/gpio/gpio24/value"].Contents = "1" // assert read correct value without error @@ -163,7 +187,7 @@ func TestDigitalPinConcurrency(t *testing.T) { defer runtime.GOMAXPROCS(oldProcs) translate := func(pin string) (string, int, error) { line, err := strconv.Atoi(pin); return "", line, err } - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) for retry := 0; retry < 20; retry++ { diff --git a/platforms/adaptors/digitalpinsadaptoroptions.go b/platforms/adaptors/digitalpinsadaptoroptions.go index b39339022..9c817fa99 100644 --- a/platforms/adaptors/digitalpinsadaptoroptions.go +++ b/platforms/adaptors/digitalpinsadaptoroptions.go @@ -2,156 +2,186 @@ package adaptors import ( "time" - - "gobot.io/x/gobot/v2" - "gobot.io/x/gobot/v2/system" ) -// DigitalPinsOptioner is the interface for digital adaptors options. This provides the possibility for change the +// DigitalPinsOptionApplier is the interface for digital adaptors options. This provides the possibility for change the // platform behavior by the user when creating the platform, e.g. by "NewAdaptor()". -// TODO: change to applier-architecture, see options of pwmpinsadaptor.go -type DigitalPinsOptioner interface { - setDigitalPinInitializer(initializer digitalPinInitializer) - setDigitalPinsForSystemGpiod() - setDigitalPinsForSystemSpi(sclkPin, ncsPin, sdoPin, sdiPin string) - prepareDigitalPinsActiveLow(pin string, otherPins ...string) - prepareDigitalPinsPullDown(pin string, otherPins ...string) - prepareDigitalPinsPullUp(pin string, otherPins ...string) - prepareDigitalPinsOpenDrain(pin string, otherPins ...string) - prepareDigitalPinsOpenSource(pin string, otherPins ...string) - prepareDigitalPinDebounce(pin string, period time.Duration) - prepareDigitalPinEventOnFallingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, - detectedEdge string, seqno uint32, lseqno uint32)) - prepareDigitalPinEventOnRisingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, - detectedEdge string, seqno uint32, lseqno uint32)) - prepareDigitalPinEventOnBothEdges(pin string, handler func(lineOffset int, timestamp time.Duration, - detectedEdge string, seqno uint32, lseqno uint32)) - prepareDigitalPinPollForEdgeDetection(pin string, pollInterval time.Duration, pollQuitChan chan struct{}) +// The interface needs to be implemented by each configurable option type. +type DigitalPinsOptionApplier interface { + apply(cfg *digitalPinsConfiguration) +} + +// digitalPinInitializeOption is the type for applying another than the default initializer +type digitalPinsInitializeOption digitalPinInitializer + +// digitalPinsSystemSysfsOption is the type to change the default character device implementation to the legacy +// sysfs Kernel ABI +type digitalPinsSystemSysfsOption bool + +// digitalPinsForSystemSpiOption is the type to switch the default SPI implementation to GPIO usage +type digitalPinsForSystemSpiOption digitalPinGpiosForSPI + +// digitalPinsActiveLowOption is the type to prepare the given pins for inverse reaction on next initialize +type digitalPinsActiveLowOption []string + +// digitalPinsPullDownOption is the type to prepare the given pins to be pulled down (high impedance to GND) +// on next initialize +type digitalPinsPullDownOption []string + +// digitalPinsPullUpOption is the type to prepare the given pins to be pulled up (high impedance to VDD) +// on next initialize +type digitalPinsPullUpOption []string + +// digitalPinsOpenDrainOption is the type to prepare the given output pins to be driven with open drain/collector +// on next initialize +type digitalPinsOpenDrainOption []string + +// digitalPinsOpenSourceOption is the type to prepares the given output pins to be driven with open source/emitter +// on next initialize +type digitalPinsOpenSourceOption []string + +// digitalPinsDebounceOption is the type to prepare the given input pin to be debounced on next initialize +type digitalPinsDebounceOption struct { + id string + period time.Duration +} + +// digitalPinsEventOnFallingEdgeOption is the type to prepare the given input pin to be generate an event +// on falling edge +type digitalPinsEventOnFallingEdgeOption struct { + id string + handler func(int, time.Duration, string, uint32, uint32) +} + +// digitalPinsEventOnRisingEdgeOption is the type to prepare the given input pin to be generate an event +// on rising edge +type digitalPinsEventOnRisingEdgeOption struct { + id string + handler func(int, time.Duration, string, uint32, uint32) } -func (a *DigitalPinsAdaptor) setDigitalPinInitializer(pinInit digitalPinInitializer) { - a.initialize = pinInit +// digitalPinsEventOnBothEdgesOption is the type to prepare the given input pin to be generate an event +// on rising and falling edges +type digitalPinsEventOnBothEdgesOption struct { + id string + handler func(int, time.Duration, string, uint32, uint32) } -func (a *DigitalPinsAdaptor) setDigitalPinsForSystemGpiod() { - system.WithDigitalPinGpiodAccess()(a.sys) +// digitalPinsPollForEdgeDetectionOption is the type to prepare the given input pin to use a discrete input pin polling +// function together with edge detection. +type digitalPinsPollForEdgeDetectionOption struct { + id string + pollInterval time.Duration + pollQuitChan chan struct{} } -func (a *DigitalPinsAdaptor) setDigitalPinsForSystemSpi(sclkPin, ncsPin, sdoPin, sdiPin string) { - system.WithSpiGpioAccess(a, sclkPin, ncsPin, sdoPin, sdiPin)(a.sys) +func (o digitalPinsInitializeOption) String() string { + return "pin initializer option for digital IO's" } -func (a *DigitalPinsAdaptor) prepareDigitalPinsActiveLow(id string, otherIDs ...string) { - ids := []string{id} - ids = append(ids, otherIDs...) +func (o digitalPinsSystemSysfsOption) String() string { + return "use gpiod system access option for digital pins" +} - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsForSystemSpiOption) String() string { + return "use digital pins for SPI option" +} - for _, i := range ids { - a.pinOptions[i] = append(a.pinOptions[i], system.WithPinActiveLow()) - } +func (o digitalPinsActiveLowOption) String() string { + return "digital pins set to active low option" } -func (a *DigitalPinsAdaptor) prepareDigitalPinsPullDown(id string, otherIDs ...string) { - ids := []string{id} - ids = append(ids, otherIDs...) +func (o digitalPinsPullDownOption) String() string { + return "digital pins set to pull down option" +} - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsPullUpOption) String() string { + return "digital pins set to pull up option" +} - for _, i := range ids { - a.pinOptions[i] = append(a.pinOptions[i], system.WithPinPullDown()) - } +func (o digitalPinsOpenDrainOption) String() string { + return "digital pins set to open drain option" } -func (a *DigitalPinsAdaptor) prepareDigitalPinsPullUp(id string, otherIDs ...string) { - ids := []string{id} - ids = append(ids, otherIDs...) +func (o digitalPinsOpenSourceOption) String() string { + return "digital pins set to open source option" +} - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsDebounceOption) String() string { + return "set debounce time for digital pin option" +} - for _, i := range ids { - a.pinOptions[i] = append(a.pinOptions[i], system.WithPinPullUp()) - } +func (o digitalPinsEventOnFallingEdgeOption) String() string { + return "set event on falling edge for digital pin option" } -func (a *DigitalPinsAdaptor) prepareDigitalPinsOpenDrain(id string, otherIDs ...string) { - ids := []string{id} - ids = append(ids, otherIDs...) +func (o digitalPinsEventOnRisingEdgeOption) String() string { + return "set event on rising edge for digital pin option" +} - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsEventOnBothEdgesOption) String() string { + return "set event on rising and falling edge for digital pin option" +} - for _, i := range ids { - a.pinOptions[i] = append(a.pinOptions[i], system.WithPinOpenDrain()) - } +func (o digitalPinsPollForEdgeDetectionOption) String() string { + return "discrete polling function for edge detection on digital pin option" } -func (a *DigitalPinsAdaptor) prepareDigitalPinsOpenSource(id string, otherIDs ...string) { - ids := []string{id} - ids = append(ids, otherIDs...) +func (o digitalPinsInitializeOption) apply(cfg *digitalPinsConfiguration) { + cfg.initialize = digitalPinInitializer(o) +} - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsSystemSysfsOption) apply(cfg *digitalPinsConfiguration) { + c := bool(o) + cfg.useSysfs = &c +} - for _, i := range ids { - a.pinOptions[i] = append(a.pinOptions[i], system.WithPinOpenSource()) - } +func (o digitalPinsForSystemSpiOption) apply(cfg *digitalPinsConfiguration) { + c := digitalPinGpiosForSPI(o) + cfg.gpiosForSPI = &c } -func (a *DigitalPinsAdaptor) prepareDigitalPinDebounce(id string, period time.Duration) { - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsActiveLowOption) apply(cfg *digitalPinsConfiguration) { + cfg.activeLowPins = append(cfg.activeLowPins, o...) +} - a.pinOptions[id] = append(a.pinOptions[id], system.WithPinDebounce(period)) +func (o digitalPinsPullDownOption) apply(cfg *digitalPinsConfiguration) { + cfg.pullDownPins = append(cfg.pullDownPins, o...) } -func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnFallingEdge(id string, handler func(int, time.Duration, string, - uint32, uint32), -) { - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsPullUpOption) apply(cfg *digitalPinsConfiguration) { + cfg.pullUpPins = append(cfg.pullUpPins, o...) +} - a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnFallingEdge(handler)) +func (o digitalPinsOpenDrainOption) apply(cfg *digitalPinsConfiguration) { + cfg.openDrainPins = append(cfg.openDrainPins, o...) } -func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnRisingEdge(id string, handler func(int, time.Duration, string, - uint32, uint32), -) { - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsOpenSourceOption) apply(cfg *digitalPinsConfiguration) { + cfg.openSourcePins = append(cfg.openSourcePins, o...) +} - a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnRisingEdge(handler)) +func (o digitalPinsDebounceOption) apply(cfg *digitalPinsConfiguration) { + pinCfg := digitalPinsDebouncePin(o) + cfg.debouncePins = append(cfg.debouncePins, pinCfg) } -func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnBothEdges(id string, handler func(int, time.Duration, string, - uint32, uint32), -) { - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsEventOnFallingEdgeOption) apply(cfg *digitalPinsConfiguration) { + pinCfg := digitalPinsEventOnEdgePin(o) + cfg.eventOnFallingEdgePins = append(cfg.eventOnFallingEdgePins, pinCfg) +} - a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnBothEdges(handler)) +func (o digitalPinsEventOnRisingEdgeOption) apply(cfg *digitalPinsConfiguration) { + pinCfg := digitalPinsEventOnEdgePin(o) + cfg.eventOnRisingEdgePins = append(cfg.eventOnRisingEdgePins, pinCfg) } -func (a *DigitalPinsAdaptor) prepareDigitalPinPollForEdgeDetection( - id string, - pollInterval time.Duration, - pollQuitChan chan struct{}, -) { - if a.pinOptions == nil { - a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) - } +func (o digitalPinsEventOnBothEdgesOption) apply(cfg *digitalPinsConfiguration) { + pinCfg := digitalPinsEventOnEdgePin(o) + cfg.eventOnBothEdgesPins = append(cfg.eventOnBothEdgesPins, pinCfg) +} - a.pinOptions[id] = append(a.pinOptions[id], system.WithPinPollForEdgeDetection(pollInterval, pollQuitChan)) +func (o digitalPinsPollForEdgeDetectionOption) apply(cfg *digitalPinsConfiguration) { + pinCfg := digitalPinsPollForEdgeDetectionPin(o) + cfg.pollForEdgeDetectionPins = append(cfg.pollForEdgeDetectionPins, pinCfg) } diff --git a/platforms/adaptors/digitalpinsadaptoroptions_test.go b/platforms/adaptors/digitalpinsadaptoroptions_test.go index a458de37e..61297a15e 100644 --- a/platforms/adaptors/digitalpinsadaptoroptions_test.go +++ b/platforms/adaptors/digitalpinsadaptoroptions_test.go @@ -1,4 +1,3 @@ -//nolint:nonamedreturns // ok for tests package adaptors import ( @@ -9,22 +8,77 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/system" ) func TestDigitalPinsWithGpiosActiveLow(t *testing.T) { // This is a general test, that options are applied in constructor. Further tests for options - // can also be done by call of "WithOption(val)(d)". + // can also be done by call of "WithOption(val).apply(cfg)". + // arrange & act, connect is mandatory to set options to the system + a := NewDigitalPinsAdaptor(system.NewAccesser(), nil, WithGpiosActiveLow("1", "12", "33")) + require.NoError(t, a.Connect()) + // assert + assert.Len(t, a.pinOptions, 3) +} + +func TestDigitalPinsWithDigitalPinInitializer(t *testing.T) { // arrange - translate := func(pin string) (chip string, line int, err error) { return } - sys := system.NewAccesser() + const ( + pinNo = "1" + translatedPinNo = "12" + ) + a := NewDigitalPinsAdaptor(system.NewAccesser(), testDigitalPinTranslator) + require.NoError(t, a.Connect()) + dpa := a.sys.UseMockDigitalPinAccess() + pin, err := a.DigitalPin(pinNo) + require.NoError(t, err) + require.Equal(t, 1, dpa.Exported("", translatedPinNo)) // original initializer called on DigitalPin() + require.NoError(t, a.digitalPinsCfg.initialize(pin)) + require.Equal(t, 2, dpa.Exported("", translatedPinNo)) + var called bool + anotherInitializer := func(pin gobot.DigitalPinner) error { + called = true + return nil + } + WithDigitalPinInitializer(anotherInitializer).apply(a.digitalPinsCfg) // act - a := NewDigitalPinsAdaptor(sys, translate, WithGpiosActiveLow("1", "12", "33")) + require.NoError(t, a.digitalPinsCfg.initialize(pin)) // assert - assert.Len(t, a.pinOptions, 3) + assert.Equal(t, 2, dpa.Exported("", translatedPinNo)) + assert.True(t, called) +} + +func TestDigitalPinsWithSysfsAccess(t *testing.T) { + // arrange + a := NewDigitalPinsAdaptor(system.NewAccesser(), nil) + require.NoError(t, a.Connect()) + require.True(t, a.sys.IsGpiodDigitalPinAccess()) + require.NoError(t, a.Finalize()) + // act, connect is mandatory to set options to the system + WithSysfsAccess().apply(a.digitalPinsCfg) + require.NoError(t, a.Connect()) + // assert + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) +} + +func TestDigitalPinsWithGpiodAccess(t *testing.T) { + // arrange + a := NewDigitalPinsAdaptor(system.NewAccesser(system.WithDigitalPinSysfsAccess()), nil) + require.NoError(t, a.Connect()) + require.True(t, a.sys.IsSysfsDigitalPinAccess()) + require.NoError(t, a.Finalize()) + // we have to mock the fs at this point to ensure the option can be applied on each test environment + a.sys.UseMockFilesystem([]string{"/dev/gpiochip0"}) + // act, connect is mandatory to set options to the system + WithGpiodAccess().apply(a.digitalPinsCfg) + require.NoError(t, a.Connect()) + // assert + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) } func TestDigitalReadWithGpiosActiveLow(t *testing.T) { + // arrange mockedPaths := []string{ "/sys/class/gpio/export", "/sys/class/gpio/unexport", @@ -35,17 +89,25 @@ func TestDigitalReadWithGpiosActiveLow(t *testing.T) { "/sys/class/gpio/gpio26/direction", } a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) + // arrange files for for pin 14 fs.Files["/sys/class/gpio/gpio25/value"].Contents = "1" fs.Files["/sys/class/gpio/gpio25/active_low"].Contents = "5" + // arrange value file and config for pin 15 fs.Files["/sys/class/gpio/gpio26/value"].Contents = "0" - WithGpiosActiveLow("14")(a) + WithGpiosActiveLow("14").apply(a.digitalPinsCfg) + require.NoError(t, a.Connect()) // creates a new pin without inverted logic if _, err := a.DigitalRead("15"); err != nil { panic(err) } + // assert for untouched content of pin 14 + assert.Equal(t, "5", fs.Files["/sys/class/gpio/gpio25/active_low"].Contents) + // arrange direction file and config for pin 15 fs.Add("/sys/class/gpio/gpio26/active_low") fs.Files["/sys/class/gpio/gpio26/active_low"].Contents = "6" - WithGpiosActiveLow("15")(a) + WithGpiosActiveLow("15").apply(a.digitalPinsCfg) + require.NoError(t, a.Finalize()) + require.NoError(t, a.Connect()) // act got1, err1 := a.DigitalRead("14") // for a new pin got2, err2 := a.DigitalRead("15") // for an existing pin (calls ApplyOptions()) @@ -69,19 +131,29 @@ func TestDigitalWriteWithOptions(t *testing.T) { a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) // assert write correct value without error and just ignore unsupported options - WithGpiosPullUp("7")(a) - WithGpiosOpenDrain("7")(a) - WithGpioEventOnFallingEdge("7", gpioEventHandler)(a) - WithGpioPollForEdgeDetection("7", 0, nil)(a) + WithGpiosPullUp("7").apply(a.digitalPinsCfg) + WithGpiosOpenDrain("7").apply(a.digitalPinsCfg) + WithGpioEventOnFallingEdge("7", gpioTestEventHandler).apply(a.digitalPinsCfg) + WithGpioPollForEdgeDetection("7", 0, nil).apply(a.digitalPinsCfg) + require.NoError(t, a.Connect()) err := a.DigitalWrite("7", 1) require.NoError(t, err) assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio18/value"].Contents) // assert second write to same pin without error and just ignore unsupported options - WithGpiosPullDown("7")(a) - WithGpiosOpenSource("7")(a) - WithGpioDebounce("7", 2*time.Second)(a) - WithGpioEventOnRisingEdge("7", gpioEventHandler)(a) + WithGpiosPullDown("7").apply(a.digitalPinsCfg) + WithGpiosOpenSource("7").apply(a.digitalPinsCfg) + WithGpioDebounce("7", 2*time.Second).apply(a.digitalPinsCfg) + WithGpioEventOnRisingEdge("7", gpioTestEventHandler).apply(a.digitalPinsCfg) + require.NoError(t, a.Finalize()) + require.NoError(t, a.Connect()) + err = a.DigitalWrite("7", 1) + require.NoError(t, err) + + // assert third write to same pin without error + WithGpioEventOnBothEdges("7", gpioTestEventHandler).apply(a.digitalPinsCfg) + require.NoError(t, a.Finalize()) + require.NoError(t, a.Connect()) err = a.DigitalWrite("7", 1) require.NoError(t, err) @@ -105,7 +177,8 @@ func TestDigitalWriteWithGpiosActiveLow(t *testing.T) { } a, fs := initTestDigitalPinsAdaptorWithMockedFilesystem(mockedPaths) fs.Files["/sys/class/gpio/gpio19/active_low"].Contents = "5" - WithGpiosActiveLow("8")(a) + WithGpiosActiveLow("8").apply(a.digitalPinsCfg) + require.NoError(t, a.Connect()) // act err := a.DigitalWrite("8", 2) // assert @@ -114,7 +187,38 @@ func TestDigitalWriteWithGpiosActiveLow(t *testing.T) { assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio19/active_low"].Contents) } -func gpioEventHandler(o int, t time.Duration, et string, sn uint32, lsn uint32) { +func TestDigitalPinsWithSpiGpioAccess(t *testing.T) { + // arrange + const ( + sclkPin = "1" + ncsPin = "2" + sdoPin = "3" + sdiPin = "4" + sclkPinTranslated = "12" + ncsPinTranslated = "13" + sdoPinTranslated = "14" + sdiPinTranslated = "15" + ) + a := NewDigitalPinsAdaptor(system.NewAccesser(), testDigitalPinTranslator) + dpa := a.sys.UseMockDigitalPinAccess() + // act + WithSpiGpioAccess(sclkPin, ncsPin, sdoPin, sdiPin).apply(a.digitalPinsCfg) + require.NoError(t, a.Connect()) + bus, err := a.sys.NewSpiDevice(0, 0, 0, 0, 1111) + // assert + require.NoError(t, err) + assert.NotNil(t, bus) + assert.Equal(t, 1, dpa.AppliedOptions("", sclkPinTranslated)) + assert.Equal(t, 1, dpa.AppliedOptions("", ncsPinTranslated)) + assert.Equal(t, 1, dpa.AppliedOptions("", sdoPinTranslated)) + assert.Equal(t, 0, dpa.AppliedOptions("", sdiPinTranslated)) // already input, so no option applied + assert.Equal(t, 1, dpa.Exported("", sclkPinTranslated)) + assert.Equal(t, 1, dpa.Exported("", ncsPinTranslated)) + assert.Equal(t, 1, dpa.Exported("", sdoPinTranslated)) + assert.Equal(t, 1, dpa.Exported("", sdiPinTranslated)) +} + +func gpioTestEventHandler(o int, t time.Duration, et string, sn uint32, lsn uint32) { // the handler should never execute, because used in outputs and not supported by sysfs panic(fmt.Sprintf("event handler was called (%d, %d) unexpected for line %d with '%s' at %s!", sn, lsn, o, t, et)) } diff --git a/platforms/adaptors/digitalpintranslator_test.go b/platforms/adaptors/digitalpintranslator_test.go index cce547790..65b54be70 100644 --- a/platforms/adaptors/digitalpintranslator_test.go +++ b/platforms/adaptors/digitalpintranslator_test.go @@ -28,44 +28,41 @@ func TestDigitalPinTranslatorTranslate(t *testing.T) { "5": {Sysfs: 253, Cdev: CdevPin{Chip: 8, Line: 5}}, } tests := map[string]struct { - access string + access func(system.Optioner) pin string wantChip string wantLine int wantErr error }{ "cdev_ok_7": { - access: "cdev", pin: "7", wantChip: "gpiochip0", wantLine: 17, }, "cdev_ok_22": { - access: "cdev", pin: "22", wantChip: "gpiochip5", wantLine: 19, }, "cdev_ok_5": { - access: "cdev", pin: "5", wantChip: "gpiochip8", wantLine: 5, }, "sysfs_ok_7": { - access: "sysfs", + access: system.WithDigitalPinSysfsAccess(), pin: "7", wantChip: "", wantLine: 17, }, "sysfs_ok_22": { - access: "sysfs", + access: system.WithDigitalPinSysfsAccess(), pin: "22", wantChip: "", wantLine: 171, }, "sysfs_ok_5": { - access: "sysfs", + access: system.WithDigitalPinSysfsAccess(), pin: "5", wantChip: "", wantLine: 253, @@ -80,8 +77,7 @@ func TestDigitalPinTranslatorTranslate(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange - sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) - sys.UseDigitalPinAccessWithMockFs(tc.access, []string{}) + sys := system.NewAccesser(tc.access) pt := NewDigitalPinTranslator(sys, pinDefinitions) // act chip, line, err := pt.Translate(tc.pin) diff --git a/platforms/adaptors/pwmpinsadaptor.go b/platforms/adaptors/pwmpinsadaptor.go index 25978a40c..8f2fa57ab 100644 --- a/platforms/adaptors/pwmpinsadaptor.go +++ b/platforms/adaptors/pwmpinsadaptor.go @@ -31,7 +31,7 @@ type pwmPinServoScale struct { minDuty, maxDuty time.Duration } -// pwmPinConfiguration contains all changeable attributes of the adaptor. +// pwmPinsConfiguration contains all changeable attributes of the adaptor. type pwmPinsConfiguration struct { initialize pwmPinInitializer usePiBlasterPin bool diff --git a/platforms/beaglebone/README.md b/platforms/beaglebone/README.md index b3a116e98..83fdd757f 100644 --- a/platforms/beaglebone/README.md +++ b/platforms/beaglebone/README.md @@ -25,8 +25,7 @@ transfer the final executable to your BeagleBone, and run the program on the Bea The pin numbering used by your Gobot program should match the way your board is labeled right on the board itself. -Gobot also has support for the four built-in LEDs on the BeagleBone Black, by referring to them as `usr0`, `usr1`, `usr2`, -and `usr3`. +Gobot also has support for the four built-in LEDs, by referring to them as `usr0`, `usr1`, `usr2` and `usr3`. ```go package main @@ -107,14 +106,14 @@ func main() { Compile your Gobot program on your workstation like this: ```sh -GOARM=7 GOARCH=arm GOOS=linux go build examples/beaglebone_blink.go +GOARM=7 GOARCH=arm GOOS=linux go build -o output/ examples/beaglebone_blink.go ``` Once you have compiled your code, you can you can upload your program and execute it on the BeagleBone from your workstation using the `scp` and `ssh` commands like this: ```sh -scp beaglebone_blink debian@192.168.7.2:/home/debian/ +scp output/beaglebone_blink debian@192.168.7.2:/home/debian/ ssh -t debian@192.168.7.2 "./beaglebone_blink" ``` diff --git a/platforms/beaglebone/beaglebone_adaptor.go b/platforms/beaglebone/beaglebone_adaptor.go index a8594e4e2..4d55bbc14 100644 --- a/platforms/beaglebone/beaglebone_adaptor.go +++ b/platforms/beaglebone/beaglebone_adaptor.go @@ -50,7 +50,7 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) pwmPinTranslator := adaptors.NewPWMPinTranslator(sys, bbbPwmPinMap) a := &Adaptor{ name: gobot.DefaultName("BeagleboneBlack"), @@ -61,11 +61,11 @@ func NewAdaptor(opts ...interface{}) *Adaptor { usrLed: "/sys/class/leds/beaglebone:green:", } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier pwmPinsOpts := []adaptors.PwmPinsOptionApplier{adaptors.WithPWMDefaultPeriod(pwmPeriodDefault)} for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) diff --git a/platforms/beaglebone/beaglebone_adaptor_test.go b/platforms/beaglebone/beaglebone_adaptor_test.go index 1929f8d99..36bc70987 100644 --- a/platforms/beaglebone/beaglebone_adaptor_test.go +++ b/platforms/beaglebone/beaglebone_adaptor_test.go @@ -32,7 +32,7 @@ var ( _ spi.Connector = (*Adaptor)(nil) ) -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) if err := a.Connect(); err != nil { @@ -92,6 +92,7 @@ func TestNewAdaptor(t *testing.T) { assert.Equal(t, bbbPinMap, a.pinMap) assert.NotNil(t, a.pwmPinTranslate) assert.Equal(t, "/sys/class/leds/beaglebone:green:", a.usrLed) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) @@ -99,7 +100,7 @@ func TestNewAdaptor(t *testing.T) { func TestPWMWrite(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) fs.Files[pwm1DutyCyclePath].Contents = "0" fs.Files[pwm1PeriodPath].Contents = "0" // act & assert wrong pin @@ -139,12 +140,12 @@ func TestServoWrite(t *testing.T) { require.NoError(t, a.Finalize()) } -func TestAnalog(t *testing.T) { +func TestAnalogRead(t *testing.T) { mockPaths := []string{ "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) fs.Files["/sys/bus/iio/devices/iio:device0/in_voltage1_raw"].Contents = "567\n" i, err := a.AnalogRead("P9_40") @@ -180,10 +181,10 @@ func TestDigitalIO(t *testing.T) { "/sys/class/gpio/gpio30/direction", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) // DigitalIO - _ = a.DigitalWrite("usr1", 1) + require.NoError(t, a.DigitalWrite("usr1", 1)) assert.Equal(t, "1", fs.Files["/sys/class/leds/beaglebone:green:usr1/brightness"].Contents, @@ -193,7 +194,7 @@ func TestDigitalIO(t *testing.T) { err := a.DigitalWrite("usr10101", 1) require.ErrorContains(t, err, " : /sys/class/leds/beaglebone:green:usr10101/brightness: no such file") - _ = a.DigitalWrite("P9_12", 1) + require.NoError(t, a.DigitalWrite("P9_12", 1)) assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio60/value"].Contents) require.ErrorContains(t, a.DigitalWrite("P9_99", 1), "'P9_99' is not a valid id for a digital pin") @@ -214,7 +215,7 @@ func TestAnalogReadFileError(t *testing.T) { "/sys/devices/platform/whatever", } - a, _ := initTestAdaptorWithMockedFilesystem(mockPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) _, err := a.AnalogRead("P9_40") require.ErrorContains(t, err, "/sys/bus/iio/devices/iio:device0/in_voltage1_raw: no such file") @@ -227,7 +228,7 @@ func TestDigitalPinDirectionFileError(t *testing.T) { "/sys/devices/platform/ocp/ocp:P9_12_pinmux/state", } - a, _ := initTestAdaptorWithMockedFilesystem(mockPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) err := a.DigitalWrite("P9_12", 1) require.ErrorContains(t, err, "/sys/class/gpio/gpio60/direction: no such file") @@ -245,7 +246,7 @@ func TestDigitalPinFinalizeFileError(t *testing.T) { "/sys/devices/platform/ocp/ocp:P9_12_pinmux/state", } - a, _ := initTestAdaptorWithMockedFilesystem(mockPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) err := a.DigitalWrite("P9_12", 1) require.NoError(t, err) @@ -294,7 +295,7 @@ func Test_translateAndMuxPWMPin(t *testing.T) { "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/pwmchip0/", "/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/pwmchip0/", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) tests := map[string]struct { wantDir string diff --git a/platforms/beaglebone/black_pins.go b/platforms/beaglebone/black_pins.go index ed6bec431..780a0a860 100644 --- a/platforms/beaglebone/black_pins.go +++ b/platforms/beaglebone/black_pins.go @@ -76,11 +76,11 @@ var bbbPwmPinMap = adaptors.PWMPinDefinitions{ } var bbbAnalogPinMap = adaptors.AnalogPinDefinitions{ - "P9_39": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", R: true, W: false, BufLen: 1024}, - "P9_40": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", R: true, W: false, BufLen: 1024}, - "P9_37": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", R: true, W: false, BufLen: 1024}, - "P9_38": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", R: true, W: false, BufLen: 1024}, - "P9_33": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", R: true, W: false, BufLen: 1024}, - "P9_36": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage5_raw", R: true, W: false, BufLen: 1024}, - "P9_35": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage6_raw", R: true, W: false, BufLen: 1024}, + "P9_39": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", W: false, ReadBufLen: 1024}, + "P9_40": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", W: false, ReadBufLen: 1024}, + "P9_37": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", W: false, ReadBufLen: 1024}, + "P9_38": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", W: false, ReadBufLen: 1024}, + "P9_33": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", W: false, ReadBufLen: 1024}, + "P9_36": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage5_raw", W: false, ReadBufLen: 1024}, + "P9_35": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage6_raw", W: false, ReadBufLen: 1024}, } diff --git a/platforms/beaglebone/pocketbeagle_adaptor_test.go b/platforms/beaglebone/pocketbeagle_adaptor_test.go index edcc1af62..3d6c1f06e 100644 --- a/platforms/beaglebone/pocketbeagle_adaptor_test.go +++ b/platforms/beaglebone/pocketbeagle_adaptor_test.go @@ -21,11 +21,15 @@ func TestNewPocketBeagleAdaptor(t *testing.T) { assert.NotNil(t, a.pwmPinTranslate) assert.Equal(t, pocketBeaglePinMap, a.pinMap) assert.Equal(t, "/sys/class/leds/beaglebone:green:", a.usrLed) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) } func TestNewPocketBeagleAdaptorWithOption(t *testing.T) { // arrange & act a := NewPocketBeagleAdaptor(adaptors.WithGpiodAccess()) + // we have to mock the fs at this point to ensure the option can be applied on each test environment + a.sys.UseMockFilesystem([]string{"/dev/gpiochip0"}) // assert require.NoError(t, a.Connect()) + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) } diff --git a/platforms/beaglebone/pocketbeagle_pins.go b/platforms/beaglebone/pocketbeagle_pins.go index ae9239311..27f72ff65 100644 --- a/platforms/beaglebone/pocketbeagle_pins.go +++ b/platforms/beaglebone/pocketbeagle_pins.go @@ -87,10 +87,10 @@ var pocketBeaglePwmPinMap = adaptors.PWMPinDefinitions{ } var pocketBeagleAnalogPinMap = adaptors.AnalogPinDefinitions{ - "P1_19": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", R: true, W: false, BufLen: 1024}, - "P1_21": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", R: true, W: false, BufLen: 1024}, - "P1_23": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", R: true, W: false, BufLen: 1024}, - "P1_25": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", R: true, W: false, BufLen: 1024}, - "P1_27": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", R: true, W: false, BufLen: 1024}, - "P2_36": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage7_raw", R: true, W: false, BufLen: 1024}, + "P1_19": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", W: false, ReadBufLen: 1024}, + "P1_21": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", W: false, ReadBufLen: 1024}, + "P1_23": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", W: false, ReadBufLen: 1024}, + "P1_25": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", W: false, ReadBufLen: 1024}, + "P1_27": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", W: false, ReadBufLen: 1024}, + "P2_36": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage7_raw", W: false, ReadBufLen: 1024}, } diff --git a/platforms/chip/chip_adaptor.go b/platforms/chip/chip_adaptor.go index b412d8282..dca340a94 100644 --- a/platforms/chip/chip_adaptor.go +++ b/platforms/chip/chip_adaptor.go @@ -28,9 +28,9 @@ type sysfsPin struct { // Adaptor represents a Gobot Adaptor for a C.H.I.P. type Adaptor struct { name string - sys *system.Accesser + sys *system.Accesser // used for unit tests only mutex sync.Mutex - pinmap map[string]sysfsPin + pinMap map[string]sysfsPin *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor *adaptors.I2cBusAdaptor @@ -45,24 +45,24 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("CHIP"), sys: sys, } - a.pinmap = chipPins + a.pinMap = chipPins baseAddr, _ := getXIOBase() for i := 0; i < 8; i++ { pin := fmt.Sprintf("XIO-P%d", i) - a.pinmap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1} + a.pinMap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1} } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) @@ -80,14 +80,6 @@ func NewAdaptor(opts ...interface{}) *Adaptor { return a } -// NewProAdaptor creates a C.H.I.P. Pro Adaptor -func NewProAdaptor() *Adaptor { - a := NewAdaptor() - a.name = gobot.DefaultName("CHIP Pro") - a.pinmap = chipProPins - return a -} - // Name returns the name of the Adaptor func (a *Adaptor) Name() string { return a.name } @@ -158,14 +150,14 @@ func getXIOBase() (int, error) { } func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { - if val, ok := a.pinmap[id]; ok { + if val, ok := a.pinMap[id]; ok { return "", val.pin, nil } return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) } func (a *Adaptor) translatePWMPin(id string) (string, int, error) { - sysPin, ok := a.pinmap[id] + sysPin, ok := a.pinMap[id] if !ok { return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id) } diff --git a/platforms/chip/chip_adaptor_test.go b/platforms/chip/chip_adaptor_test.go index ae61f1166..f8472137d 100644 --- a/platforms/chip/chip_adaptor_test.go +++ b/platforms/chip/chip_adaptor_test.go @@ -43,7 +43,7 @@ var mockPaths = []string{ "/sys/class/pwm/pwmchip0/pwm0/period", } -func initTestAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) if err := a.Connect(); err != nil { @@ -52,29 +52,25 @@ func initTestAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { return a, fs } -func initTestProAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { - a := NewProAdaptor() - fs := a.sys.UseMockFilesystem(mockPaths) - if err := a.Connect(); err != nil { - panic(err) - } - return a, fs -} - -func TestName(t *testing.T) { +func TestNewAdaptor(t *testing.T) { + // arrange & act a := NewAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "CHIP")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.pinMap) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } -func TestNewProAdaptor(t *testing.T) { - a := NewProAdaptor() - assert.True(t, strings.HasPrefix(a.Name(), "CHIP Pro")) -} - func TestFinalizeErrorAfterGPIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() require.NoError(t, a.DigitalWrite("CSID7", 1)) fs.WithWriteError = true @@ -84,7 +80,7 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } func TestFinalizeErrorAfterPWM(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() fs.Files["/sys/class/pwm/pwmchip0/pwm0/duty_cycle"].Contents = "0" fs.Files["/sys/class/pwm/pwmchip0/pwm0/period"].Contents = "0" @@ -97,7 +93,7 @@ func TestFinalizeErrorAfterPWM(t *testing.T) { } func TestDigitalIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() require.NoError(t, a.DigitalWrite("CSID7", 1)) assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio139/value"].Contents) @@ -110,24 +106,9 @@ func TestDigitalIO(t *testing.T) { require.NoError(t, a.Finalize()) } -func TestProDigitalIO(t *testing.T) { - a, fs := initTestProAdaptorWithMockedFilesystem() - _ = a.Connect() - - _ = a.DigitalWrite("CSID7", 1) - assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio139/value"].Contents) - - fs.Files["/sys/class/gpio/gpio50/value"].Contents = "1" - i, _ := a.DigitalRead("TWI2-SDA") - assert.Equal(t, 1, i) - - require.ErrorContains(t, a.DigitalWrite("XIO-P0", 1), "'XIO-P0' is not a valid id for a digital pin") - require.NoError(t, a.Finalize()) -} - func TestPWMWrite(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() fs.Files["/sys/class/pwm/pwmchip0/pwm0/duty_cycle"].Contents = "0" fs.Files["/sys/class/pwm/pwmchip0/pwm0/period"].Contents = "0" // act diff --git a/platforms/chip/chippro_adaptor.go b/platforms/chip/chippro_adaptor.go new file mode 100644 index 000000000..24e52cbba --- /dev/null +++ b/platforms/chip/chippro_adaptor.go @@ -0,0 +1,11 @@ +package chip + +import "gobot.io/x/gobot/v2" + +// NewProAdaptor creates a C.H.I.P. Pro Adaptor +func NewProAdaptor() *Adaptor { + a := NewAdaptor() + a.name = gobot.DefaultName("CHIP Pro") + a.pinMap = chipProPins + return a +} diff --git a/platforms/chip/chippro_adaptor_test.go b/platforms/chip/chippro_adaptor_test.go new file mode 100644 index 000000000..fed529bbe --- /dev/null +++ b/platforms/chip/chippro_adaptor_test.go @@ -0,0 +1,41 @@ +package chip + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2/system" +) + +func initConnectedTestProAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { + a := NewProAdaptor() + fs := a.sys.UseMockFilesystem(mockPaths) + if err := a.Connect(); err != nil { + panic(err) + } + return a, fs +} + +func TestNewProAdaptor(t *testing.T) { + a := NewProAdaptor() + assert.True(t, strings.HasPrefix(a.Name(), "CHIP Pro")) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) +} + +func TestProDigitalIO(t *testing.T) { + a, fs := initConnectedTestProAdaptorWithMockedFilesystem() + + require.NoError(t, a.DigitalWrite("CSID7", 1)) + assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio139/value"].Contents) + + fs.Files["/sys/class/gpio/gpio50/value"].Contents = "1" + i, err := a.DigitalRead("TWI2-SDA") + assert.Equal(t, 1, i) + require.NoError(t, err) + + require.ErrorContains(t, a.DigitalWrite("XIO-P0", 1), "'XIO-P0' is not a valid id for a digital pin") + require.NoError(t, a.Finalize()) +} diff --git a/platforms/dji/tello/driver_test.go b/platforms/dji/tello/driver_test.go index 90ec280d5..f2daf6b4d 100644 --- a/platforms/dji/tello/driver_test.go +++ b/platforms/dji/tello/driver_test.go @@ -130,7 +130,7 @@ func Test_handleResponse(t *testing.T) { if got != want { require.Fail(t, "\ngot: %s\nwant: %s\n", got, want) } - case <-time.After(time.Millisecond): + case <-time.After(5 * time.Millisecond): t.Error("subscription channel seems empty") } } diff --git a/platforms/dragonboard/dragonboard_adaptor.go b/platforms/dragonboard/dragonboard_adaptor.go index fe33ce93c..7fb4f0267 100644 --- a/platforms/dragonboard/dragonboard_adaptor.go +++ b/platforms/dragonboard/dragonboard_adaptor.go @@ -16,7 +16,7 @@ const defaultI2cBusNumber = 0 // Adaptor represents a Gobot Adaptor for a DragonBoard 410c type Adaptor struct { name string - sys *system.Accesser + sys *system.Accesser // used for unit tests only mutex sync.Mutex pinMap map[string]int *adaptors.DigitalPinsAdaptor @@ -50,8 +50,8 @@ var fixedPins = map[string]int{ // // adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# -func NewAdaptor(opts ...func(adaptors.DigitalPinsOptioner)) *Adaptor { - sys := system.NewAccesser() +func NewAdaptor(opts ...adaptors.DigitalPinsOptionApplier) *Adaptor { + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) c := &Adaptor{ name: gobot.DefaultName("DragonBoard"), sys: sys, diff --git a/platforms/dragonboard/dragonboard_adaptor_test.go b/platforms/dragonboard/dragonboard_adaptor_test.go index a70cf1961..24fcc7e8f 100644 --- a/platforms/dragonboard/dragonboard_adaptor_test.go +++ b/platforms/dragonboard/dragonboard_adaptor_test.go @@ -21,7 +21,7 @@ var ( _ i2c.Connector = (*Adaptor)(nil) ) -func initTestAdaptor() *Adaptor { +func initConnectedTestAdaptor() *Adaptor { a := NewAdaptor() if err := a.Connect(); err != nil { panic(err) @@ -29,15 +29,24 @@ func initTestAdaptor() *Adaptor { return a } -func TestName(t *testing.T) { - a := initTestAdaptor() +func TestNewAdaptor(t *testing.T) { + // arrange & act + a := NewAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "DragonBoard")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.pinMap) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } func TestDigitalIO(t *testing.T) { - a := initTestAdaptor() + a := initConnectedTestAdaptor() mockPaths := []string{ "/sys/class/gpio/export", "/sys/class/gpio/unexport", @@ -60,7 +69,7 @@ func TestDigitalIO(t *testing.T) { } func TestFinalizeErrorAfterGPIO(t *testing.T) { - a := initTestAdaptor() + a := initConnectedTestAdaptor() mockPaths := []string{ "/sys/class/gpio/export", "/sys/class/gpio/unexport", @@ -71,7 +80,6 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } fs := a.sys.UseMockFilesystem(mockPaths) - require.NoError(t, a.Connect()) require.NoError(t, a.DigitalWrite("GPIO_B", 1)) fs.WithWriteError = true @@ -81,16 +89,15 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } func TestI2cDefaultBus(t *testing.T) { - a := initTestAdaptor() + a := initConnectedTestAdaptor() assert.Equal(t, 0, a.DefaultI2cBus()) } func TestI2cFinalizeWithErrors(t *testing.T) { // arrange - a := NewAdaptor() + a := initConnectedTestAdaptor() a.sys.UseMockSyscall() fs := a.sys.UseMockFilesystem([]string{"/dev/i2c-1"}) - require.NoError(t, a.Connect()) con, err := a.GetI2cConnection(0xff, 1) require.NoError(t, err) _, err = con.Write([]byte{0xbf}) diff --git a/platforms/intel-iot/edison/edison_adaptor.go b/platforms/intel-iot/edison/edison_adaptor.go index fac93c0bd..caacdf84f 100644 --- a/platforms/intel-iot/edison/edison_adaptor.go +++ b/platforms/intel-iot/edison/edison_adaptor.go @@ -37,7 +37,7 @@ type Adaptor struct { board string sys *system.Accesser mutex sync.Mutex - pinmap map[string]sysfsPin + pinMap map[string]sysfsPin tristate gobot.DigitalPinner digitalPins map[int]gobot.DigitalPinner *adaptors.AnalogPinsAdaptor @@ -51,12 +51,12 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("Edison"), board: "arduino", sys: sys, - pinmap: arduinoPinMap, + pinMap: arduinoPinMap, } pwmPinsOpts := []adaptors.PwmPinsOptionApplier{adaptors.WithPWMPinInitializer(pwmPinInitializer)} @@ -107,15 +107,15 @@ func (a *Adaptor) Connect() error { switch a.board { case "sparkfun": - a.pinmap = sparkfunPinMap + a.pinMap = sparkfunPinMap case "arduino": a.board = "arduino" - a.pinmap = arduinoPinMap + a.pinMap = arduinoPinMap if err := a.arduinoSetup(); err != nil { return err } case "miniboard": - a.pinmap = miniboardPinMap + a.pinMap = miniboardPinMap default: return fmt.Errorf("Unknown board type: %s", a.board) } @@ -285,7 +285,7 @@ func (a *Adaptor) arduinoI2CSetup() error { } func (a *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) { - i := a.pinmap[id] + i := a.pinMap[id] err := a.ensureDigitalPin(i.pin, o...) if err != nil { @@ -352,18 +352,17 @@ func pwmPinInitializer(_ string, pin gobot.PWMPinner) error { return pin.SetEnabled(true) } -func (a *Adaptor) translateAnalogPin(pin string) (string, bool, bool, uint16, error) { +func (a *Adaptor) translateAnalogPin(pin string) (string, bool, uint16, error) { path := fmt.Sprintf("/sys/bus/iio/devices/iio:device1/in_voltage%s_raw", pin) const ( - read = true write = false readBufLen = 200 ) - return path, read, write, readBufLen, nil + return path, write, readBufLen, nil } func (a *Adaptor) translateAndMuxPWMPin(id string) (string, int, error) { - sysPin, ok := a.pinmap[id] + sysPin, ok := a.pinMap[id] if !ok { return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id) } diff --git a/platforms/intel-iot/edison/edison_adaptor_test.go b/platforms/intel-iot/edison/edison_adaptor_test.go index 7e132ea2f..e585b7e25 100644 --- a/platforms/intel-iot/edison/edison_adaptor_test.go +++ b/platforms/intel-iot/edison/edison_adaptor_test.go @@ -213,7 +213,7 @@ var pwmMockPathsMux40 = []string{ "/sys/class/gpio/gpio261/direction", } -func initTestAdaptorWithMockedFilesystem(boardType string) (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem(boardType string) (*Adaptor, *system.MockFilesystem) { a := NewAdaptor(boardType) fs := a.sys.UseMockFilesystem(testPinFiles) fs.Files["/sys/class/pwm/pwmchip0/pwm1/period"].Contents = "5000" @@ -223,16 +223,24 @@ func initTestAdaptorWithMockedFilesystem(boardType string) (*Adaptor, *system.Mo return a, fs } -func TestName(t *testing.T) { +func TestNewAdaptor(t *testing.T) { + // arrange & act a := NewAdaptor() - + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "Edison")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.AnalogPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } func TestConnect(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem("arduino") + a, _ := initConnectedTestAdaptorWithMockedFilesystem("arduino") assert.Equal(t, 6, a.DefaultI2cBus()) assert.Equal(t, "arduino", a.board) @@ -240,7 +248,7 @@ func TestConnect(t *testing.T) { } func TestArduinoSetupFail263(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") delete(fs.Files, "/sys/class/gpio/gpio263/direction") err := a.arduinoSetup() @@ -248,7 +256,7 @@ func TestArduinoSetupFail263(t *testing.T) { } func TestArduinoSetupFail240(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") delete(fs.Files, "/sys/class/gpio/gpio240/direction") err := a.arduinoSetup() @@ -256,7 +264,7 @@ func TestArduinoSetupFail240(t *testing.T) { } func TestArduinoSetupFail111(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") delete(fs.Files, "/sys/kernel/debug/gpio_debug/gpio111/current_pinmux") err := a.arduinoSetup() @@ -264,7 +272,7 @@ func TestArduinoSetupFail111(t *testing.T) { } func TestArduinoSetupFail131(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") delete(fs.Files, "/sys/kernel/debug/gpio_debug/gpio131/current_pinmux") err := a.arduinoSetup() @@ -272,7 +280,7 @@ func TestArduinoSetupFail131(t *testing.T) { } func TestArduinoI2CSetupFailTristate(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.arduinoSetup()) fs.WithWriteError = true @@ -281,7 +289,7 @@ func TestArduinoI2CSetupFailTristate(t *testing.T) { } func TestArduinoI2CSetupFail14(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.arduinoSetup()) delete(fs.Files, "/sys/class/gpio/gpio14/direction") @@ -291,7 +299,7 @@ func TestArduinoI2CSetupFail14(t *testing.T) { } func TestArduinoI2CSetupUnexportFail(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.arduinoSetup()) delete(fs.Files, "/sys/class/gpio/unexport") @@ -301,7 +309,7 @@ func TestArduinoI2CSetupUnexportFail(t *testing.T) { } func TestArduinoI2CSetupFail236(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.arduinoSetup()) delete(fs.Files, "/sys/class/gpio/gpio236/direction") @@ -311,7 +319,7 @@ func TestArduinoI2CSetupFail236(t *testing.T) { } func TestArduinoI2CSetupFail28(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.arduinoSetup()) delete(fs.Files, "/sys/kernel/debug/gpio_debug/gpio28/current_pinmux") @@ -321,7 +329,7 @@ func TestArduinoI2CSetupFail28(t *testing.T) { } func TestConnectArduinoError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithWriteError = true err := a.Connect() @@ -329,7 +337,7 @@ func TestConnectArduinoError(t *testing.T) { } func TestConnectArduinoWriteError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithWriteError = true err := a.Connect() @@ -337,7 +345,7 @@ func TestConnectArduinoWriteError(t *testing.T) { } func TestConnectSparkfun(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem("sparkfun") + a, _ := initConnectedTestAdaptorWithMockedFilesystem("sparkfun") require.NoError(t, a.Connect()) assert.Equal(t, 1, a.DefaultI2cBus()) @@ -345,7 +353,7 @@ func TestConnectSparkfun(t *testing.T) { } func TestConnectMiniboard(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem("miniboard") + a, _ := initConnectedTestAdaptorWithMockedFilesystem("miniboard") require.NoError(t, a.Connect()) assert.Equal(t, 1, a.DefaultI2cBus()) @@ -360,7 +368,7 @@ func TestConnectUnknown(t *testing.T) { } func TestFinalize(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") _ = a.DigitalWrite("3", 1) require.NoError(t, a.PwmWrite("5", 100)) @@ -381,7 +389,7 @@ func TestFinalize(t *testing.T) { } func TestFinalizeError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.PwmWrite("5", 100)) @@ -394,7 +402,7 @@ func TestFinalizeError(t *testing.T) { } func TestDigitalIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") require.NoError(t, a.DigitalWrite("13", 1)) assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio40/value"].Contents) @@ -450,7 +458,7 @@ func TestDigitalPinInMuxFileError(t *testing.T) { } func TestDigitalWriteError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithWriteError = true err := a.DigitalWrite("13", 1) @@ -458,7 +466,7 @@ func TestDigitalWriteError(t *testing.T) { } func TestDigitalReadWriteError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithWriteError = true _, err := a.DigitalRead("13") @@ -466,7 +474,7 @@ func TestDigitalReadWriteError(t *testing.T) { } func TestPwm(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") err := a.PwmWrite("5", 100) require.NoError(t, err) @@ -498,7 +506,7 @@ func TestPwmEnableError(t *testing.T) { } func TestPwmWritePinError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithWriteError = true err := a.PwmWrite("5", 100) @@ -506,7 +514,7 @@ func TestPwmWritePinError(t *testing.T) { } func TestPwmWriteError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithWriteError = true err := a.PwmWrite("5", 100) @@ -514,7 +522,7 @@ func TestPwmWriteError(t *testing.T) { } func TestPwmReadError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithReadError = true err := a.PwmWrite("5", 100) @@ -522,7 +530,7 @@ func TestPwmReadError(t *testing.T) { } func TestAnalog(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.Files["/sys/bus/iio/devices/iio:device1/in_voltage0_raw"].Contents = "1000\n" i, err := a.AnalogRead("0") @@ -531,7 +539,7 @@ func TestAnalog(t *testing.T) { } func TestAnalogError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem("arduino") + a, fs := initConnectedTestAdaptorWithMockedFilesystem("arduino") fs.WithReadError = true _, err := a.AnalogRead("0") @@ -539,7 +547,7 @@ func TestAnalogError(t *testing.T) { } func TestI2cWorkflow(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem("arduino") + a, _ := initConnectedTestAdaptorWithMockedFilesystem("arduino") a.sys.UseMockSyscall() con, err := a.GetI2cConnection(0xff, 6) diff --git a/platforms/intel-iot/joule/joule_adaptor.go b/platforms/intel-iot/joule/joule_adaptor.go index c6086071b..f5be9c784 100644 --- a/platforms/intel-iot/joule/joule_adaptor.go +++ b/platforms/intel-iot/joule/joule_adaptor.go @@ -21,7 +21,7 @@ type sysfsPin struct { // Adaptor represents an Intel Joule type Adaptor struct { name string - sys *system.Accesser + sys *system.Accesser // used for unit tests only mutex sync.Mutex *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor @@ -37,17 +37,17 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("Joule"), sys: sys, } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier pwmPinsOpts := []adaptors.PwmPinsOptionApplier{adaptors.WithPWMPinInitializer(pwmPinInitializer)} for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) diff --git a/platforms/intel-iot/joule/joule_adaptor_test.go b/platforms/intel-iot/joule/joule_adaptor_test.go index 2d6a73b72..6c9c4867d 100644 --- a/platforms/intel-iot/joule/joule_adaptor_test.go +++ b/platforms/intel-iot/joule/joule_adaptor_test.go @@ -24,7 +24,7 @@ var ( _ i2c.Connector = (*Adaptor)(nil) ) -func initTestAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() mockPaths := []string{ "/sys/class/pwm/pwmchip0/export", @@ -98,16 +98,24 @@ func initTestAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFilesystem) { return a, fs } -func TestName(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem() - +func TestNewAdaptor(t *testing.T) { + // arrange & act + a := NewAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "Joule")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } func TestFinalize(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem() + a, _ := initConnectedTestAdaptorWithMockedFilesystem() _ = a.DigitalWrite("J12_1", 1) _ = a.PwmWrite("J12_26", 100) @@ -122,7 +130,7 @@ func TestFinalize(t *testing.T) { } func TestDigitalIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() _ = a.DigitalWrite("J12_1", 1) assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio451/value"].Contents) @@ -138,7 +146,7 @@ func TestDigitalIO(t *testing.T) { } func TestPwm(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() err := a.PwmWrite("J12_26", 100) require.NoError(t, err) @@ -152,7 +160,7 @@ func TestPwm(t *testing.T) { } func TestPwmPinExportError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() delete(fs.Files, "/sys/class/pwm/pwmchip0/export") err := a.PwmWrite("J12_26", 100) @@ -160,7 +168,7 @@ func TestPwmPinExportError(t *testing.T) { } func TestPwmPinEnableError(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem() + a, fs := initConnectedTestAdaptorWithMockedFilesystem() delete(fs.Files, "/sys/class/pwm/pwmchip0/pwm0/enable") err := a.PwmWrite("J12_26", 100) diff --git a/platforms/jetson/jetson_adaptor.go b/platforms/jetson/jetson_adaptor.go index 78b508f4b..e2d07824d 100644 --- a/platforms/jetson/jetson_adaptor.go +++ b/platforms/jetson/jetson_adaptor.go @@ -28,7 +28,7 @@ const ( // Adaptor is the Gobot adaptor for the Jetson Nano type Adaptor struct { name string - sys *system.Accesser + sys *system.Accesser // used for unit tests only mutex *sync.Mutex *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor @@ -45,14 +45,14 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("JetsonNano"), sys: sys, mutex: &sync.Mutex{}, } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier pwmPinsOpts := []adaptors.PwmPinsOptionApplier{ adaptors.WithPWMDefaultPeriod(pwmPeriodDefault), adaptors.WithPWMMinimumPeriod(pwmPeriodMinimum), @@ -60,7 +60,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) diff --git a/platforms/jetson/jetson_adaptor_test.go b/platforms/jetson/jetson_adaptor_test.go index 9b2fd47ba..452f81054 100644 --- a/platforms/jetson/jetson_adaptor_test.go +++ b/platforms/jetson/jetson_adaptor_test.go @@ -28,7 +28,7 @@ var ( _ spi.Connector = (*Adaptor)(nil) ) -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) if err := a.Connect(); err != nil { @@ -38,10 +38,19 @@ func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system. } func TestNewAdaptor(t *testing.T) { + // arrange & act a := NewAdaptor() - + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "JetsonNano")) - + assert.NotNil(t, a.sys) + assert.NotNil(t, a.mutex) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } @@ -55,7 +64,7 @@ func TestFinalize(t *testing.T) { "/dev/spidev0.0", "/dev/spidev0.1", } - a, _ := initTestAdaptorWithMockedFilesystem(mockPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) _ = a.DigitalWrite("3", 1) @@ -83,7 +92,7 @@ func TestPWMPinsReConnect(t *testing.T) { "/sys/class/pwm/pwmchip0/pwm2/polarity", "/sys/class/pwm/pwmchip0/pwm2/enable", } - a, _ := initTestAdaptorWithMockedFilesystem(mockPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) require.NoError(t, a.PwmWrite("33", 1)) require.NoError(t, a.Finalize()) // act @@ -101,7 +110,7 @@ func TestDigitalIO(t *testing.T) { "/sys/class/gpio/gpio14/value", "/sys/class/gpio/gpio14/direction", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) err := a.DigitalWrite("7", 1) require.NoError(t, err) diff --git a/platforms/nanopi/nanopi_adaptor.go b/platforms/nanopi/nanopi_adaptor.go index e63e0fa94..b348e6795 100644 --- a/platforms/nanopi/nanopi_adaptor.go +++ b/platforms/nanopi/nanopi_adaptor.go @@ -24,7 +24,7 @@ const ( // Adaptor represents a Gobot Adaptor for the FriendlyARM NanoPi Boards type Adaptor struct { name string - sys *system.Accesser + sys *system.Accesser // used for unit tests only mutex sync.Mutex *adaptors.AnalogPinsAdaptor *adaptors.DigitalPinsAdaptor @@ -37,7 +37,7 @@ type Adaptor struct { // // Optional parameters: // -// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) +// adaptors.WithSysfsAccess(): use legacy sysfs driver instead of default character device gpiod // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor @@ -47,17 +47,17 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewNeoAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) + sys := system.NewAccesser() a := &Adaptor{ name: gobot.DefaultName("NanoPi NEO Board"), sys: sys, } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) diff --git a/platforms/nanopi/nanopi_adaptor_test.go b/platforms/nanopi/nanopi_adaptor_test.go index 7b72f9e4a..154c0f69a 100644 --- a/platforms/nanopi/nanopi_adaptor_test.go +++ b/platforms/nanopi/nanopi_adaptor_test.go @@ -17,11 +17,6 @@ import ( "gobot.io/x/gobot/v2/system" ) -const ( - gpio203Path = "/sys/class/gpio/gpio203/" - gpio199Path = "/sys/class/gpio/gpio199/" -) - const ( pwmDir = "/sys/devices/platform/soc/1c21400.pwm/pwm/pwmchip0/" //nolint:gosec // false positive pwmExportPath = pwmDir + "export" @@ -44,15 +39,6 @@ var pwmMockPaths = []string{ pwmPolarityPath, } -var gpioMockPaths = []string{ - "/sys/class/gpio/export", - "/sys/class/gpio/unexport", - gpio203Path + "value", - gpio203Path + "direction", - gpio199Path + "value", - gpio199Path + "direction", -} - // make sure that this Adaptor fulfills all the required interfaces var ( _ gobot.Adaptor = (*Adaptor)(nil) @@ -73,35 +59,91 @@ func preparePwmFs(fs *system.MockFilesystem) { fs.Files[pwmPolarityPath].Contents = pwmInvertedIdentifier } -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { - a := NewNeoAdaptor() +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { + a := initConnectedTestAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) + return a, fs +} + +func initConnectedTestAdaptor() *Adaptor { + a := NewNeoAdaptor() if err := a.Connect(); err != nil { panic(err) } - return a, fs + return a } -func TestName(t *testing.T) { +func TestNewAdaptor(t *testing.T) { + // arrange & act a := NewNeoAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "NanoPi NEO Board")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.AnalogPinsAdaptor) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } -func TestDigitalIO(t *testing.T) { - // only basic tests needed, further tests are done in "digitalpinsadaptor.go" - a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths) - - _ = a.DigitalWrite("7", 1) - assert.Equal(t, "1", fs.Files[gpio203Path+"value"].Contents) +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewNeoAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithSysfsAccess()) + // assert + require.NoError(t, a.Connect()) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) +} - fs.Files[gpio199Path+"value"].Contents = "1" - i, _ := a.DigitalRead("10") - assert.Equal(t, 1, i) +func TestDigitalIO(t *testing.T) { + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := initConnectedTestAdaptor() + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsGpiodDigitalPinAccess()) + // act & assert write + err := a.DigitalWrite("7", 1) + require.NoError(t, err) + assert.Equal(t, []int{1}, dpa.Written("gpiochip0", "203")) + // arrange, act & assert read + dpa.UseValue("gpiochip0", "199", 2) + i, err := a.DigitalRead("10") + require.NoError(t, err) + assert.Equal(t, 2, i) + // act and assert unknown pin + require.ErrorContains(t, a.DigitalWrite("99", 1), "'99' is not a valid id for a digital pin") + // act and assert finalize + require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("gpiochip0", "203")) + assert.Equal(t, 0, dpa.Exported("gpiochip0", "199")) +} +func TestDigitalIOSysfs(t *testing.T) { + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := NewNeoAdaptor(adaptors.WithSysfsAccess()) + require.NoError(t, a.Connect()) + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert write + err := a.DigitalWrite("7", 1) + require.NoError(t, err) + assert.Equal(t, []int{1}, dpa.Written("", "203")) + // arrange, act & assert read + dpa.UseValue("", "199", 2) + i, err := a.DigitalRead("10") + require.NoError(t, err) + assert.Equal(t, 2, i) + // act and assert unknown pin require.ErrorContains(t, a.DigitalWrite("99", 1), "'99' is not a valid id for a digital pin") + // act and assert finalize require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("", "203")) + assert.Equal(t, 0, dpa.Exported("", "199")) } func TestAnalog(t *testing.T) { @@ -109,7 +151,7 @@ func TestAnalog(t *testing.T) { "/sys/class/thermal/thermal_zone0/temp", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n" got, err := a.AnalogRead("thermal_zone0") @@ -128,7 +170,7 @@ func TestAnalog(t *testing.T) { } func TestInvalidPWMPin(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) err := a.PwmWrite("666", 42) @@ -146,7 +188,7 @@ func TestInvalidPWMPin(t *testing.T) { func TestPwmWrite(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) // act err := a.PwmWrite("PWM", 100) @@ -190,7 +232,7 @@ func TestServoWrite(t *testing.T) { func TestSetPeriod(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) newPeriod := uint32(2550000) @@ -229,18 +271,20 @@ func TestSetPeriod(t *testing.T) { } func TestFinalizeErrorAfterGPIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths) - + // arrange + a := initConnectedTestAdaptor() + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsGpiodDigitalPinAccess()) require.NoError(t, a.DigitalWrite("7", 1)) - - fs.WithWriteError = true - + dpa.UseUnexportError("gpiochip0", "203") + // act err := a.Finalize() - require.ErrorContains(t, err, "write error") + // assert + require.ErrorContains(t, err, "unexport error") } func TestFinalizeErrorAfterPWM(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) require.NoError(t, a.PwmWrite("PWM", 1)) diff --git a/platforms/nanopi/nanopineo_pin_map.go b/platforms/nanopi/nanopineo_pin_map.go index cf9398118..df1308065 100644 --- a/platforms/nanopi/nanopineo_pin_map.go +++ b/platforms/nanopi/nanopineo_pin_map.go @@ -28,5 +28,5 @@ var neoPWMPinDefinitions = adaptors.PWMPinDefinitions{ var analogPinDefinitions = adaptors.AnalogPinDefinitions{ // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius - "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", W: false, ReadBufLen: 7}, } diff --git a/platforms/raspi/raspi_adaptor.go b/platforms/raspi/raspi_adaptor.go index a41448f02..c41dacd46 100644 --- a/platforms/raspi/raspi_adaptor.go +++ b/platforms/raspi/raspi_adaptor.go @@ -40,7 +40,7 @@ type Adaptor struct { // // Optional parameters: // -// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) +// adaptors.WithSysfsAccess(): use legacy sysfs driver instead of default character device gpiod // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor @@ -48,17 +48,17 @@ type Adaptor struct { // adaptors.WithGpioDebounce(pin, period): sets the input debouncer // adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) + sys := system.NewAccesser() a := &Adaptor{ name: gobot.DefaultName("RaspberryPi"), sys: sys, } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) diff --git a/platforms/raspi/raspi_adaptor_test.go b/platforms/raspi/raspi_adaptor_test.go index cd63ab4c9..475f59b6f 100644 --- a/platforms/raspi/raspi_adaptor_test.go +++ b/platforms/raspi/raspi_adaptor_test.go @@ -63,7 +63,7 @@ func preparePwmFs(fs *system.MockFilesystem) { fs.Files[pwmPolarityPath].Contents = pwmInvertedIdentifier } -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) if err := a.Connect(); err != nil { @@ -72,14 +72,32 @@ func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system. return a, fs } -func TestName(t *testing.T) { +func TestNewAdaptor(t *testing.T) { + // arrange & act a := NewAdaptor() - + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "RaspberryPi")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.AnalogPinsAdaptor) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithSysfsAccess()) + // assert + require.NoError(t, a.Connect()) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) +} + func TestGetDefaultBus(t *testing.T) { const contentPattern = "Hardware : BCM2708\n%sSerial : 000000003bc748ea\n" tests := map[string]struct { @@ -133,7 +151,7 @@ func TestFinalize(t *testing.T) { "/dev/spidev0.0", "/dev/spidev0.1", } - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockedPaths) _ = a.DigitalWrite("3", 1) _ = a.PwmWrite("7", 255) @@ -147,7 +165,7 @@ func TestAnalog(t *testing.T) { "/sys/class/thermal/thermal_zone0/temp", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n" got, err := a.AnalogRead("thermal_zone0") @@ -167,7 +185,7 @@ func TestAnalog(t *testing.T) { func TestPwmWrite(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) // act err := a.PwmWrite("pwm0", 100) @@ -258,30 +276,29 @@ func TestPWM_piPlaster(t *testing.T) { } func TestDigitalIO(t *testing.T) { - mockedPaths := []string{ - "/sys/class/gpio/export", - "/sys/class/gpio/unexport", - "/sys/class/gpio/gpio4/value", - "/sys/class/gpio/gpio4/direction", - "/sys/class/gpio/gpio27/value", - "/sys/class/gpio/gpio27/direction", + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := NewAdaptor() + if err := a.Connect(); err != nil { + panic(err) } - a, fs := initTestAdaptorWithMockedFilesystem(mockedPaths) - - err := a.DigitalWrite("7", 1) - require.NoError(t, err) - assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio4/value"].Contents) - + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsGpiodDigitalPinAccess()) + // act & assert write + _ = a.DigitalWrite("7", 1) + assert.Equal(t, []int{1}, dpa.Written("gpiochip0", "4")) + // arrange, act & assert read a.revision = "2" - err = a.DigitalWrite("13", 1) - require.NoError(t, err) - + dpa.UseValue("gpiochip0", "27", 2) i, err := a.DigitalRead("13") require.NoError(t, err) - assert.Equal(t, 1, i) - + assert.Equal(t, 2, i) + // act and assert unknown pin require.ErrorContains(t, a.DigitalWrite("notexist", 1), "'notexist' is not a valid pin id for raspi revision 2") + // act and assert finalize require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("gpiochip0", "4")) + assert.Equal(t, 0, dpa.Exported("gpiochip0", "27")) } func TestDigitalPinConcurrency(t *testing.T) { @@ -318,7 +335,7 @@ func TestSpiDefaultValues(t *testing.T) { func TestI2cDefaultBus(t *testing.T) { mockedPaths := []string{"/dev/i2c-1"} - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) + a, _ := initConnectedTestAdaptorWithMockedFilesystem(mockedPaths) a.sys.UseMockSyscall() a.revision = "0" diff --git a/platforms/raspi/raspi_pin_map.go b/platforms/raspi/raspi_pin_map.go index 53d5147d2..47f0bfff6 100644 --- a/platforms/raspi/raspi_pin_map.go +++ b/platforms/raspi/raspi_pin_map.go @@ -97,5 +97,5 @@ var pins = map[string]map[string]int{ var analogPinDefinitions = adaptors.AnalogPinDefinitions{ // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius - "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", W: false, ReadBufLen: 7}, } diff --git a/platforms/rockpi/rockpi_adaptor.go b/platforms/rockpi/rockpi_adaptor.go index 4070a4fdf..17b598a59 100644 --- a/platforms/rockpi/rockpi_adaptor.go +++ b/platforms/rockpi/rockpi_adaptor.go @@ -43,8 +43,8 @@ type Adaptor struct { // adaptors.WithGpiodAccess(): use character device gpiod driver instead of the default sysfs (NOT work on RockPi4C+!) // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior -func NewAdaptor(opts ...func(adaptors.DigitalPinsOptioner)) *Adaptor { - sys := system.NewAccesser() +func NewAdaptor(opts ...adaptors.DigitalPinsOptionApplier) *Adaptor { + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("RockPi"), sys: sys, diff --git a/platforms/rockpi/rockpi_adaptor_test.go b/platforms/rockpi/rockpi_adaptor_test.go index 9534094da..9d9c9b92d 100644 --- a/platforms/rockpi/rockpi_adaptor_test.go +++ b/platforms/rockpi/rockpi_adaptor_test.go @@ -2,6 +2,7 @@ package rockpi import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -9,15 +10,31 @@ import ( "gobot.io/x/gobot/v2/system" ) -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) _ = a.Connect() return a, fs } +func TestNewAdaptor(t *testing.T) { + // arrange & act + a := NewAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) + assert.True(t, strings.HasPrefix(a.Name(), "RockPi")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert + a.SetName("NewName") + assert.Equal(t, "NewName", a.Name()) +} + func TestDefaultI2cBus(t *testing.T) { - a, _ := initTestAdaptorWithMockedFilesystem([]string{}) + a, _ := initConnectedTestAdaptorWithMockedFilesystem([]string{}) assert.Equal(t, 7, a.DefaultI2cBus()) } diff --git a/platforms/tinkerboard/adaptor.go b/platforms/tinkerboard/adaptor.go index 5fde72c42..10224bb97 100644 --- a/platforms/tinkerboard/adaptor.go +++ b/platforms/tinkerboard/adaptor.go @@ -24,7 +24,7 @@ const ( // Adaptor represents a Gobot Adaptor for the ASUS Tinker Board type Adaptor struct { name string - sys *system.Accesser + sys *system.Accesser // used for unit tests only mutex *sync.Mutex *adaptors.AnalogPinsAdaptor *adaptors.DigitalPinsAdaptor @@ -38,7 +38,7 @@ type Adaptor struct { // // Optional parameters: // -// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) +// adaptors.WithSysfsAccess(): use legacy sysfs driver instead of default character device gpiod // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor @@ -48,18 +48,18 @@ type Adaptor struct { // note from RK3288 datasheet: "The pull direction (pullup or pulldown) for all of GPIOs are software-programmable", but // the latter is not working for any pin (armbian 22.08.7) func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) + sys := system.NewAccesser() a := &Adaptor{ name: gobot.DefaultName("Tinker Board"), sys: sys, mutex: &sync.Mutex{}, } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) diff --git a/platforms/tinkerboard/adaptor_test.go b/platforms/tinkerboard/adaptor_test.go index 3b555b604..41c0c584e 100644 --- a/platforms/tinkerboard/adaptor_test.go +++ b/platforms/tinkerboard/adaptor_test.go @@ -17,11 +17,6 @@ import ( "gobot.io/x/gobot/v2/system" ) -const ( - gpio17Path = "/sys/class/gpio/gpio17/" - gpio160Path = "/sys/class/gpio/gpio160/" -) - const ( pwmDir = "/sys/devices/platform/ff680020.pwm/pwm/pwmchip2/" //nolint:gosec // false positive pwmPwmDir = pwmDir + "pwm0/" @@ -44,15 +39,6 @@ var pwmMockPaths = []string{ pwmPolarityPath, } -var gpioMockPaths = []string{ - "/sys/class/gpio/export", - "/sys/class/gpio/unexport", - gpio17Path + "value", - gpio17Path + "direction", - gpio160Path + "value", - gpio160Path + "direction", -} - // make sure that this Adaptor fulfills all the required interfaces var ( _ gobot.Adaptor = (*Adaptor)(nil) @@ -73,13 +59,18 @@ func preparePwmFs(fs *system.MockFilesystem) { fs.Files[pwmPolarityPath].Contents = pwmInvertedIdentifier } -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { - a := NewAdaptor() +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { + a := initConnectedTestAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) + return a, fs +} + +func initConnectedTestAdaptor() *Adaptor { + a := NewAdaptor() if err := a.Connect(); err != nil { panic(err) } - return a, fs + return a } func TestNewAdaptor(t *testing.T) { @@ -96,6 +87,7 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.I2cBusAdaptor) assert.NotNil(t, a.SpiBusAdaptor) assert.NotNil(t, a.OneWireBusAdaptor) + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) @@ -103,24 +95,57 @@ func TestNewAdaptor(t *testing.T) { func TestNewAdaptorWithOption(t *testing.T) { // arrange & act - a := NewAdaptor(adaptors.WithGpiosActiveLow("1")) + a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithSysfsAccess()) // assert require.NoError(t, a.Connect()) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) } func TestDigitalIO(t *testing.T) { - // only basic tests needed, further tests are done in "digitalpinsadaptor.go" - a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths) - - _ = a.DigitalWrite("7", 1) - assert.Equal(t, "1", fs.Files[gpio17Path+"value"].Contents) - - fs.Files[gpio160Path+"value"].Contents = "1" - i, _ := a.DigitalRead("10") - assert.Equal(t, 1, i) + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := initConnectedTestAdaptor() + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsGpiodDigitalPinAccess()) + // act & assert write + err := a.DigitalWrite("7", 1) + require.NoError(t, err) + assert.Equal(t, []int{1}, dpa.Written("gpiochip0", "17")) + // arrange, act & assert read + dpa.UseValue("gpiochip5", "8", 2) + i, err := a.DigitalRead("10") + require.NoError(t, err) + assert.Equal(t, 2, i) + // act and assert unknown pin + require.ErrorContains(t, a.DigitalWrite("99", 1), "'99' is not a valid id for a digital pin") + // act and assert finalize + require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("gpiochip0", "17")) + assert.Equal(t, 0, dpa.Exported("gpiochip5", "8")) +} +func TestDigitalIOSysfs(t *testing.T) { + // some basic tests, further tests are done in "digitalpinsadaptor.go" + // arrange + a := NewAdaptor(adaptors.WithSysfsAccess()) + require.NoError(t, a.Connect()) + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert write + err := a.DigitalWrite("7", 1) + require.NoError(t, err) + assert.Equal(t, []int{1}, dpa.Written("", "17")) + // arrange, act & assert read + dpa.UseValue("", "160", 2) + i, err := a.DigitalRead("10") + require.NoError(t, err) + assert.Equal(t, 2, i) + // act and assert unknown pin require.ErrorContains(t, a.DigitalWrite("99", 1), "'99' is not a valid id for a digital pin") + // act and assert finalize require.NoError(t, a.Finalize()) + assert.Equal(t, 0, dpa.Exported("", "17")) + assert.Equal(t, 0, dpa.Exported("", "160")) } func TestAnalogRead(t *testing.T) { @@ -128,7 +153,7 @@ func TestAnalogRead(t *testing.T) { "/sys/class/thermal/thermal_zone0/temp", } - a, fs := initTestAdaptorWithMockedFilesystem(mockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(mockPaths) fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n" got, err := a.AnalogRead("thermal_zone0") @@ -148,7 +173,7 @@ func TestAnalogRead(t *testing.T) { func TestPwmWrite(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) // act err := a.PwmWrite("33", 100) @@ -195,7 +220,7 @@ func TestServoWrite(t *testing.T) { func TestSetPeriod(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) newPeriod := uint32(2550000) @@ -234,18 +259,20 @@ func TestSetPeriod(t *testing.T) { } func TestFinalizeErrorAfterGPIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths) - + // arrange + a := initConnectedTestAdaptor() + dpa := a.sys.UseMockDigitalPinAccess() + require.True(t, a.sys.IsGpiodDigitalPinAccess()) require.NoError(t, a.DigitalWrite("7", 1)) - - fs.WithWriteError = true - + dpa.UseUnexportError("gpiochip0", "17") + // act err := a.Finalize() - require.ErrorContains(t, err, "write error") + // assert + require.ErrorContains(t, err, "unexport error") } func TestFinalizeErrorAfterPWM(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) preparePwmFs(fs) require.NoError(t, a.PwmWrite("33", 1)) @@ -273,10 +300,9 @@ func TestI2cDefaultBus(t *testing.T) { func TestI2cFinalizeWithErrors(t *testing.T) { // arrange - a := NewAdaptor() + a := initConnectedTestAdaptor() a.sys.UseMockSyscall() fs := a.sys.UseMockFilesystem([]string{"/dev/i2c-4"}) - require.NoError(t, a.Connect()) con, err := a.GetI2cConnection(0xff, 4) require.NoError(t, err) _, err = con.Write([]byte{0xbf}) diff --git a/platforms/tinkerboard/pin_map.go b/platforms/tinkerboard/pin_map.go index 5f3c6dcb1..6d899e25d 100644 --- a/platforms/tinkerboard/pin_map.go +++ b/platforms/tinkerboard/pin_map.go @@ -45,6 +45,6 @@ var pwmPinDefinitions = adaptors.PWMPinDefinitions{ var analogPinDefinitions = adaptors.AnalogPinDefinitions{ // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius - "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, - "thermal_zone1": {Path: "/sys/class/thermal/thermal_zone1/temp", R: true, W: false, BufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", W: false, ReadBufLen: 7}, + "thermal_zone1": {Path: "/sys/class/thermal/thermal_zone1/temp", W: false, ReadBufLen: 7}, } diff --git a/platforms/tinkerboard/tinkerboard2/adaptor.go b/platforms/tinkerboard/tinkerboard2/adaptor.go index 4a09f784b..564be5774 100644 --- a/platforms/tinkerboard/tinkerboard2/adaptor.go +++ b/platforms/tinkerboard/tinkerboard2/adaptor.go @@ -21,28 +21,29 @@ const ( type Tinkerboard2Adaptor struct { *tinkerboard.Adaptor + sys *system.Accesser // used for unit tests only } // NewAdaptor creates a Tinkerboard-2 Adaptor // // Optional parameters: // -// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) +// adaptors.WithSysfsAccess(): use legacy sysfs driver instead of default character device gpiod // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Tinkerboard2Adaptor { - sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) - a := tinkerboard.NewAdaptor() + sys := system.NewAccesser() + a := tinkerboard.NewAdaptor(opts...) a.SetName(gobot.DefaultName("Tinker Board 2")) - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) @@ -65,5 +66,5 @@ func NewAdaptor(opts ...interface{}) *Tinkerboard2Adaptor { a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) - return &Tinkerboard2Adaptor{Adaptor: a} + return &Tinkerboard2Adaptor{Adaptor: a, sys: sys} } diff --git a/platforms/tinkerboard/tinkerboard2/adaptor_test.go b/platforms/tinkerboard/tinkerboard2/adaptor_test.go index 10e02ee39..d9575ee50 100644 --- a/platforms/tinkerboard/tinkerboard2/adaptor_test.go +++ b/platforms/tinkerboard/tinkerboard2/adaptor_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2/platforms/adaptors" ) func TestNewAdaptor(t *testing.T) { @@ -18,7 +21,16 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.PWMPinsAdaptor) assert.NotNil(t, a.I2cBusAdaptor) assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.IsGpiodDigitalPinAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } + +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithSysfsAccess()) + // assert + require.NoError(t, a.Connect()) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) +} diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index bd1dfe824..bbc575286 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -42,7 +42,7 @@ type Adaptor struct { name string sys *system.Accesser mutex sync.Mutex - pinmap map[string]sysfsPin + pinMap map[string]sysfsPin ledPath string *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor @@ -59,19 +59,19 @@ type Adaptor struct { // // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { - sys := system.NewAccesser() + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("UP2"), sys: sys, ledPath: "/sys/class/leds/upboard:%s:/brightness", - pinmap: fixedPins, + pinMap: fixedPins, } - var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier for _, opt := range opts { switch o := opt.(type) { - case func(adaptors.DigitalPinsOptioner): + case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) @@ -161,14 +161,14 @@ func (a *Adaptor) DigitalWrite(id string, val byte) error { } func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { - if val, ok := a.pinmap[id]; ok { + if val, ok := a.pinMap[id]; ok { return "", val.pin, nil } return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) } func (a *Adaptor) translatePWMPin(id string) (string, int, error) { - sysPin, ok := a.pinmap[id] + sysPin, ok := a.pinMap[id] if !ok { return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id) } diff --git a/platforms/upboard/up2/adaptor_test.go b/platforms/upboard/up2/adaptor_test.go index 7f34998ca..597dd51aa 100644 --- a/platforms/upboard/up2/adaptor_test.go +++ b/platforms/upboard/up2/adaptor_test.go @@ -59,7 +59,7 @@ var gpioMockPaths = []string{ "/sys/class/leds/upboard:green:/brightness", } -func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { +func initConnectedTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) { a := NewAdaptor() fs := a.sys.UseMockFilesystem(mockPaths) if err := a.Connect(); err != nil { @@ -68,15 +68,26 @@ func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system. return a, fs } -func TestName(t *testing.T) { +func TestNewAdaptor(t *testing.T) { + // arrange & act a := NewAdaptor() + // assert + assert.IsType(t, &Adaptor{}, a) assert.True(t, strings.HasPrefix(a.Name(), "UP2")) + assert.NotNil(t, a.sys) + assert.Equal(t, "/sys/class/leds/upboard:%s:/brightness", a.ledPath) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } func TestDigitalIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(gpioMockPaths) _ = a.DigitalWrite("7", 1) assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio462/value"].Contents) @@ -97,7 +108,7 @@ func TestDigitalIO(t *testing.T) { func TestPWMWrite(t *testing.T) { // arrange - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) fs.Files[pwmDutyCyclePath].Contents = "0" fs.Files[pwmPeriodPath].Contents = "0" // act @@ -139,7 +150,7 @@ func TestServoWrite(t *testing.T) { } func TestFinalizeErrorAfterGPIO(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(gpioMockPaths) require.NoError(t, a.DigitalWrite("7", 1)) @@ -150,7 +161,7 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { } func TestFinalizeErrorAfterPWM(t *testing.T) { - a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) + a, fs := initConnectedTestAdaptorWithMockedFilesystem(pwmMockPaths) fs.Files[pwmDutyCyclePath].Contents = "0" fs.Files[pwmPeriodPath].Contents = "0" diff --git a/system/digitalpin_access.go b/system/digitalpin_access.go index fa2a3c536..31d9725f1 100644 --- a/system/digitalpin_access.go +++ b/system/digitalpin_access.go @@ -17,36 +17,44 @@ type gpiodDigitalPinAccess struct { chips []string } -func (h *sysfsDigitalPinAccess) isSupported() bool { +func (dpa *sysfsDigitalPinAccess) isType(accesserType digitalPinAccesserType) bool { + return accesserType == digitalPinAccesserTypeSysfs +} + +func (dpa *sysfsDigitalPinAccess) isSupported() bool { // currently this is supported by all Kernels return true } -func (h *sysfsDigitalPinAccess) createPin(chip string, pin int, +func (dpa *sysfsDigitalPinAccess) createPin(chip string, pin int, o ...func(gobot.DigitalPinOptioner) bool, ) gobot.DigitalPinner { - return newDigitalPinSysfs(h.sfa, strconv.Itoa(pin), o...) + return newDigitalPinSysfs(dpa.sfa, strconv.Itoa(pin), o...) +} + +func (dpa *sysfsDigitalPinAccess) setFs(fs filesystem) { + dpa.sfa = &sysfsFileAccess{fs: fs, readBufLen: 2} } -func (h *sysfsDigitalPinAccess) setFs(fs filesystem) { - h.sfa = &sysfsFileAccess{fs: fs, readBufLen: 2} +func (dpa *gpiodDigitalPinAccess) isType(accesserType digitalPinAccesserType) bool { + return accesserType == digitalPinAccesserTypeGpiod } -func (h *gpiodDigitalPinAccess) isSupported() bool { - chips, err := h.fs.find("/dev", "gpiochip") +func (dpa *gpiodDigitalPinAccess) isSupported() bool { + chips, err := dpa.fs.find("/dev", "gpiochip") if err != nil || len(chips) == 0 { return false } - h.chips = chips + dpa.chips = chips return true } -func (h *gpiodDigitalPinAccess) createPin(chip string, pin int, +func (dpa *gpiodDigitalPinAccess) createPin(chip string, pin int, o ...func(gobot.DigitalPinOptioner) bool, ) gobot.DigitalPinner { return newDigitalPinGpiod(chip, pin, o...) } -func (h *gpiodDigitalPinAccess) setFs(fs filesystem) { - h.fs = fs +func (dpa *gpiodDigitalPinAccess) setFs(fs filesystem) { + dpa.fs = fs } diff --git a/system/digitalpin_mock.go b/system/digitalpin_mock.go index c85fa345a..a8c66d66f 100644 --- a/system/digitalpin_mock.go +++ b/system/digitalpin_mock.go @@ -1,52 +1,167 @@ package system import ( + "errors" + "strconv" + "gobot.io/x/gobot/v2" ) +type simulateErrors struct { + applyOption bool + export bool + write bool + read bool + unexport bool +} + type mockDigitalPinAccess struct { - fs *MockFilesystem + underlyingDigitalPinAccess digitalPinAccesser + values map[string]int + simulateErrors map[string]simulateErrors // key is the pin-key + pins map[string]*digitalPinMock } -type digitalPinMock struct{} +type digitalPinMock struct { + chip string + pin int + appliedOptions int + exported int + written []int + value int + simulateErrors simulateErrors +} -func (h *mockDigitalPinAccess) isSupported() bool { return true } +func newMockDigitalPinAccess(underlyingDigitalPinAccess digitalPinAccesser) *mockDigitalPinAccess { + dpa := mockDigitalPinAccess{ + underlyingDigitalPinAccess: underlyingDigitalPinAccess, + values: make(map[string]int), + simulateErrors: make(map[string]simulateErrors), + pins: make(map[string]*digitalPinMock), + } + return &dpa +} -func (h *mockDigitalPinAccess) createPin(chip string, pin int, +func (dpa *mockDigitalPinAccess) isType(accesserType digitalPinAccesserType) bool { + return dpa.underlyingDigitalPinAccess.isType(accesserType) +} + +func (dpa *mockDigitalPinAccess) isSupported() bool { return true } + +func (dpa *mockDigitalPinAccess) createPin(chip string, pin int, o ...func(gobot.DigitalPinOptioner) bool, ) gobot.DigitalPinner { - dpm := &digitalPinMock{} + dpm := &digitalPinMock{chip: chip, pin: pin} + + key := getDigitalPinMockKey(chip, strconv.Itoa(pin)) + if v, ok := dpa.values[key]; ok { + dpm.value = v + } + + if v, ok := dpa.simulateErrors[key]; ok { + dpm.simulateErrors = v + } + + dpa.pins[key] = dpm return dpm } -func (h *mockDigitalPinAccess) setFs(fs filesystem) { - // do nothing +func (dpa *mockDigitalPinAccess) setFs(fs filesystem) { + panic("setFs() for mockDigitalPinAccess not supported") +} + +func (dpa *mockDigitalPinAccess) AppliedOptions(chip, pin string) int { + return dpa.pins[getDigitalPinMockKey(chip, pin)].appliedOptions +} + +func (dpa *mockDigitalPinAccess) Written(chip, pin string) []int { + return dpa.pins[getDigitalPinMockKey(chip, pin)].written +} + +func (dpa *mockDigitalPinAccess) Exported(chip, pin string) int { + return dpa.pins[getDigitalPinMockKey(chip, pin)].exported } -func (d *digitalPinMock) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error { +func (dpa *mockDigitalPinAccess) UseValue(chip, pin string, value int) { + key := getDigitalPinMockKey(chip, pin) + if pin, ok := dpa.pins[key]; ok { + pin.value = value + } + + // for creation and re-creation + dpa.values[key] = value +} + +func (dpa *mockDigitalPinAccess) UseUnexportError(chip, pin string) { + key := getDigitalPinMockKey(chip, pin) + if pin, ok := dpa.pins[key]; ok { + pin.simulateErrors.unexport = true + } + + // for creation and re-creation + simErrs, ok := dpa.simulateErrors[key] + if !ok { + simErrs = simulateErrors{} + } + + simErrs.unexport = true + dpa.simulateErrors[getDigitalPinMockKey(chip, pin)] = simErrs +} + +func (dp *digitalPinMock) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error { + dp.appliedOptions = dp.appliedOptions + len(options) + + if dp.simulateErrors.applyOption { + return errors.New("applyOption error") + } + return nil } -func (d *digitalPinMock) DirectionBehavior() string { - return "" +func (dp *digitalPinMock) DirectionBehavior() string { + panic("DirectionBehavior() for digitalPinMock needs do be implemented now") } // Write writes the given value to the character device -func (d *digitalPinMock) Write(b int) error { +func (dp *digitalPinMock) Write(b int) error { + dp.written = append(dp.written, b) + + if dp.simulateErrors.write { + return errors.New("write error") + } + return nil } // Read reads the given value from character device -func (d *digitalPinMock) Read() (int, error) { - return 0, nil +func (dp *digitalPinMock) Read() (int, error) { + if dp.simulateErrors.read { + return -1, errors.New("read error") + } + + return dp.value, nil } // Export sets the pin as exported with the configured direction -func (d *digitalPinMock) Export() error { +func (dp *digitalPinMock) Export() error { + dp.exported++ + if dp.simulateErrors.export { + return errors.New("export error") + } + return nil } // Unexport release the pin -func (d *digitalPinMock) Unexport() error { +func (dp *digitalPinMock) Unexport() error { + dp.exported-- + if dp.simulateErrors.unexport { + return errors.New("unexport error") + } + return nil } + +func getDigitalPinMockKey(chip, pin string) string { + return chip + "_" + pin +} diff --git a/system/system.go b/system/system.go index 63a865e9e..110c6d99f 100644 --- a/system/system.go +++ b/system/system.go @@ -10,6 +10,13 @@ import ( const systemDebug = false +type digitalPinAccesserType int + +const ( + digitalPinAccesserTypeGpiod digitalPinAccesserType = iota + digitalPinAccesserTypeSysfs +) + // A File represents basic IO interactions with the underlying file system type File interface { Write(b []byte) (n int, err error) @@ -49,6 +56,7 @@ type digitalPinAccesser interface { isSupported() bool createPin(chip string, pin int, o ...func(gobot.DigitalPinOptioner) bool) gobot.DigitalPinner setFs(fs filesystem) + isType(accesserType digitalPinAccesserType) bool } // spiAccesser represents unexposed interface to allow the switch between different implementations and a mocked one @@ -68,33 +76,26 @@ type Accesser struct { // NewAccesser returns a accesser to native system call, native file system and the chosen digital pin access. // Digital pin accesser can be empty or "sysfs", otherwise it will be automatically chosen. func NewAccesser(options ...func(Optioner)) *Accesser { - s := &Accesser{ + a := &Accesser{ sys: &nativeSyscall{}, fs: &nativeFilesystem{}, } - s.spiAccess = &periphioSpiAccess{fs: s.fs} - s.digitalPinAccess = &sysfsDigitalPinAccess{sfa: &sysfsFileAccess{fs: s.fs, readBufLen: 2}} + a.spiAccess = &periphioSpiAccess{fs: a.fs} + a.digitalPinAccess = &gpiodDigitalPinAccess{fs: a.fs} for _, option := range options { - option(s) + if option == nil { + continue + } + option(a) } - return s + return a } -// UseDigitalPinAccessWithMockFs sets the digital pin handler accesser to the chosen one. Used only for tests. -func (a *Accesser) UseDigitalPinAccessWithMockFs(digitalPinAccess string, files []string) digitalPinAccesser { - fs := newMockFilesystem(files) - var dph digitalPinAccesser - switch digitalPinAccess { - case "sysfs": - dph = &sysfsDigitalPinAccess{sfa: &sysfsFileAccess{fs: fs, readBufLen: 2}} - case "cdev": - dph = &gpiodDigitalPinAccess{fs: fs} - default: - dph = &mockDigitalPinAccess{fs: fs} - } - a.fs = fs - a.digitalPinAccess = dph - return dph +// UseMockDigitalPinAccess sets the digital pin handler accesser to the chosen one. Used only for tests. +func (a *Accesser) UseMockDigitalPinAccess() *mockDigitalPinAccess { + dpa := newMockDigitalPinAccess(a.digitalPinAccess) + a.digitalPinAccess = dpa + return dpa } // UseMockSyscall sets the Syscall implementation of the accesser to the mocked one. Used only for tests. @@ -128,10 +129,12 @@ func (a *Accesser) NewDigitalPin(chip string, pin int, // IsSysfsDigitalPinAccess returns whether the used digital pin accesser is a sysfs one. func (a *Accesser) IsSysfsDigitalPinAccess() bool { - if _, ok := a.digitalPinAccess.(*sysfsDigitalPinAccess); ok { - return true - } - return false + return a.digitalPinAccess.isType(digitalPinAccesserTypeSysfs) +} + +// IsGpiodDigitalPinAccess returns whether the used digital pin accesser is a sysfs one. +func (a *Accesser) IsGpiodDigitalPinAccess() bool { + return a.digitalPinAccess.isType(digitalPinAccesserTypeGpiod) } // NewPWMPin returns a new system PWM pin, according to the given pin number. @@ -140,7 +143,8 @@ func (a *Accesser) NewPWMPin(path string, pin int, polNormIdent string, polInvId return newPWMPinSysfs(sfa, path, pin, polNormIdent, polInvIdent) } -func (a *Accesser) NewAnalogPin(path string, r, w bool, readBufLen uint16) gobot.AnalogPinner { +func (a *Accesser) NewAnalogPin(path string, w bool, readBufLen uint16) gobot.AnalogPinner { + r := readBufLen > 0 if readBufLen == 0 { readBufLen = 32 // max. count of characters for int value is 20 } diff --git a/system/system_options.go b/system/system_options.go index bcabd4244..dbfbdf3d4 100644 --- a/system/system_options.go +++ b/system/system_options.go @@ -8,8 +8,10 @@ import ( // Optioner is the interface for system options. This provides the possibility for change the systems behavior by the // caller/user when creating the system access, e.g. by "NewAccesser()". +// TODO: change to applier-architecture, see options of pwmpinsadaptor.go type Optioner interface { setDigitalPinToGpiodAccess() + setDigitalPinToSysfsAccess() setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, ncsPin, sdoPin, sdiPin string) } @@ -21,6 +23,14 @@ func WithDigitalPinGpiodAccess() func(Optioner) { } } +// WithDigitalPinSysfsAccess can be used to change the default character device implementation for digital pins to the +// legacy sysfs Kernel ABI. +func WithDigitalPinSysfsAccess() func(Optioner) { + return func(s Optioner) { + s.setDigitalPinToSysfsAccess() + } +} + // WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage. func WithSpiGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, ncsPin, sdoPin, sdiPin string) func(Optioner) { return func(s Optioner) { @@ -35,6 +45,7 @@ func (a *Accesser) setDigitalPinToGpiodAccess() { if systemDebug { fmt.Printf("use gpiod driver for digital pins with this chips: %v\n", dpa.chips) } + return } if systemDebug { @@ -42,6 +53,21 @@ func (a *Accesser) setDigitalPinToGpiodAccess() { } } +func (a *Accesser) setDigitalPinToSysfsAccess() { + dpa := &sysfsDigitalPinAccess{sfa: &sysfsFileAccess{fs: a.fs, readBufLen: 2}} + if dpa.isSupported() { + a.digitalPinAccess = dpa + if systemDebug { + fmt.Println("use sysfs driver for digital pins") + } + + return + } + if systemDebug { + fmt.Println("sysfs driver not supported, fallback to gpiod") + } +} + func (a *Accesser) setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, ncsPin, sdoPin, sdiPin string) { cfg := spiGpioConfig{ pinProvider: p, @@ -56,6 +82,7 @@ func (a *Accesser) setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, nc if systemDebug { fmt.Printf("use gpio driver for SPI with this config: %s\n", gsa.cfg.String()) } + return } if systemDebug { diff --git a/system/system_test.go b/system/system_test.go index 78dadebd9..d67777cf1 100644 --- a/system/system_test.go +++ b/system/system_test.go @@ -15,10 +15,12 @@ func TestNewAccesser(t *testing.T) { nativeSys := a.sys.(*nativeSyscall) nativeFsSys := a.fs.(*nativeFilesystem) perphioSpi := a.spiAccess.(*periphioSpiAccess) + gpiodDigitalPin := a.digitalPinAccess.(*gpiodDigitalPinAccess) assert.NotNil(t, a) assert.NotNil(t, nativeSys) assert.NotNil(t, nativeFsSys) assert.NotNil(t, perphioSpi) + assert.NotNil(t, gpiodDigitalPin) } func TestNewAccesser_NewSpiDevice(t *testing.T) { @@ -47,47 +49,41 @@ func TestNewAccesser_NewSpiDevice(t *testing.T) { func TestNewAccesser_IsSysfsDigitalPinAccess(t *testing.T) { tests := map[string]struct { - gpiodAccesser bool - wantSys bool + sysfsAccesser bool + wantGpiod bool }{ - "default_accesser_sysfs": { - wantSys: true, + "default_accesser_gpiod": { + wantGpiod: true, }, "accesser_sysfs": { - wantSys: true, - }, - "accesser_gpiod": { - gpiodAccesser: true, - wantSys: false, + sysfsAccesser: true, + wantGpiod: false, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange a := NewAccesser() - if tc.gpiodAccesser { - // there is no mock at this level, so if the system do not support - // character device gpio, we skip the test - dpa := &gpiodDigitalPinAccess{fs: &nativeFilesystem{}} - if !dpa.isSupported() { - t.Skip() - } - WithDigitalPinGpiodAccess()(a) + if tc.sysfsAccesser { + WithDigitalPinSysfsAccess()(a) } // act - got := a.IsSysfsDigitalPinAccess() + gotGpiod := a.IsGpiodDigitalPinAccess() + gotSysfs := a.IsSysfsDigitalPinAccess() // assert assert.NotNil(t, a) - if tc.wantSys { - assert.True(t, got) - dpaSys := a.digitalPinAccess.(*sysfsDigitalPinAccess) - assert.NotNil(t, dpaSys) - assert.Equal(t, a.fs.(*nativeFilesystem), dpaSys.sfa.fs) - } else { - assert.False(t, got) + if tc.wantGpiod { + assert.True(t, gotGpiod) + assert.False(t, gotSysfs) dpaGpiod := a.digitalPinAccess.(*gpiodDigitalPinAccess) assert.NotNil(t, dpaGpiod) assert.Equal(t, a.fs.(*nativeFilesystem), dpaGpiod.fs) + } else { + assert.False(t, gotGpiod) + assert.True(t, gotSysfs) + dpaSys := a.digitalPinAccess.(*sysfsDigitalPinAccess) + assert.NotNil(t, dpaSys) + assert.Equal(t, a.fs.(*nativeFilesystem), dpaSys.sfa.fs) } }) }