diff --git a/MIGRATION.md b/MIGRATION.md index 114d2fbb6..903d76126 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -46,7 +46,7 @@ suitable: Also those findings needs to be replaced, which usually affects developers, but not users: * `system.WithDigitalPinGpiodAccess()` --> `system.WithDigitalPinCdevAccess()` -* `IsGpiodDigitalPinAccess()` --> `IsCdevDigitalPinAccess()` +* `IsGpiodDigitalPinAccess()` --> `HasDigitalPinCdevAccess()` ### PocketBeagle adaptor goes cdev diff --git a/platforms/adaptors/analogpinsadaptor.go b/platforms/adaptors/analogpinsadaptor.go index 99caf1c88..4e266fc7a 100644 --- a/platforms/adaptors/analogpinsadaptor.go +++ b/platforms/adaptors/analogpinsadaptor.go @@ -27,6 +27,9 @@ func NewAnalogPinsAdaptor(sys *system.Accesser, t analogPinTranslator) *AnalogPi sys: sys, translate: t, } + + sys.AddAnalogSupport() + return &a } diff --git a/platforms/adaptors/digitalpinsadaptor.go b/platforms/adaptors/digitalpinsadaptor.go index c24c1e125..d087192cc 100644 --- a/platforms/adaptors/digitalpinsadaptor.go +++ b/platforms/adaptors/digitalpinsadaptor.go @@ -16,44 +16,12 @@ 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 + debug bool + initialize digitalPinInitializer + systemOptions []system.AccesserOptionApplier + pinOptions map[string][]func(gobot.DigitalPinOptioner) bool } // DigitalPinsAdaptor is a adaptor for digital pins, normally used for composition in platforms. @@ -62,7 +30,6 @@ type DigitalPinsAdaptor struct { digitalPinsCfg *digitalPinsConfiguration translate digitalPinTranslator pins map[string]gobot.DigitalPinner - pinOptions map[string][]func(gobot.DigitalPinOptioner) bool mutex sync.Mutex } @@ -76,15 +43,27 @@ func NewDigitalPinsAdaptor( t digitalPinTranslator, opts ...DigitalPinsOptionApplier, ) *DigitalPinsAdaptor { - a := &DigitalPinsAdaptor{ - sys: sys, - digitalPinsCfg: &digitalPinsConfiguration{initialize: func(pin gobot.DigitalPinner) error { return pin.Export() }}, - translate: t, + a := DigitalPinsAdaptor{ + sys: sys, + digitalPinsCfg: &digitalPinsConfiguration{ + initialize: func(pin gobot.DigitalPinner) error { return pin.Export() }, + pinOptions: make(map[string][]func(gobot.DigitalPinOptioner) bool), + }, + translate: t, } + for _, o := range opts { o.apply(a.digitalPinsCfg) } - return a + + a.sys.AddDigitalPinSupport(a.digitalPinsCfg.systemOptions...) + + return &a +} + +// WithDigitalPinDebug can be used to switch on debugging for SPI implementation. +func WithDigitalPinDebug() digitalPinsDebugOption { + return digitalPinsDebugOption(true) } // WithDigitalPinInitializer can be used to substitute the default initializer. @@ -104,18 +83,6 @@ func WithGpioSysfsAccess() digitalPinsSystemSysfsOption { return digitalPinsSystemSysfsOption(true) } -// WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage. -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) digitalPinsActiveLowOption { @@ -196,69 +163,16 @@ 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.WithDigitalPinCdevAccess()(a.sys) - } - } - - if cfg.gpiosForSPI != nil { - system.WithSpiGpioAccess(a, cfg.gpiosForSPI.sclkPin, cfg.gpiosForSPI.ncsPin, cfg.gpiosForSPI.sdoPin, - cfg.gpiosForSPI.sdiPin)(a.sys) + if a.digitalPinsCfg.debug { + fmt.Println("connect the digital pins adaptor") } - 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)) + if a.pins != nil { + return fmt.Errorf("digital pin adaptor already connected, please call Finalize() for re-connect") } a.pins = make(map[string]gobot.DigitalPinner) + return nil } @@ -267,6 +181,10 @@ func (a *DigitalPinsAdaptor) Finalize() error { a.mutex.Lock() defer a.mutex.Unlock() + if a.digitalPinsCfg.debug { + fmt.Println("finalize the digital pins adaptor") + } + var err error for _, pin := range a.pins { if pin != nil { @@ -276,7 +194,7 @@ func (a *DigitalPinsAdaptor) Finalize() error { } } a.pins = nil - a.pinOptions = nil + return err } @@ -321,7 +239,7 @@ func (a *DigitalPinsAdaptor) digitalPin( return nil, fmt.Errorf("not connected for pin %s", id) } - o := append(a.pinOptions[id], opts...) + o := append(a.digitalPinsCfg.pinOptions[id], opts...) pin := a.pins[id] if pin == nil { diff --git a/platforms/adaptors/digitalpinsadaptor_test.go b/platforms/adaptors/digitalpinsadaptor_test.go index cb863cbdf..63d849eb0 100644 --- a/platforms/adaptors/digitalpinsadaptor_test.go +++ b/platforms/adaptors/digitalpinsadaptor_test.go @@ -54,6 +54,8 @@ func TestNewDigitalPinsAdaptor(t *testing.T) { // arrange translate := func(string) (string, int, error) { return "", 0, nil } sys := system.NewAccesser() + // arrange for cdev needed + sys.UseMockFilesystem([]string{"/dev/gpiochip"}) // act a := NewDigitalPinsAdaptor(sys, translate) // assert @@ -61,9 +63,8 @@ func TestNewDigitalPinsAdaptor(t *testing.T) { 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.IsCdevDigitalPinAccess()) + assert.Nil(t, a.pins) // will be created on connect + assert.True(t, a.sys.HasDigitalPinCdevAccess()) } func TestDigitalPinsConnect(t *testing.T) { diff --git a/platforms/adaptors/digitalpinsadaptoroptions.go b/platforms/adaptors/digitalpinsadaptoroptions.go index 5fcb29548..b23036b85 100644 --- a/platforms/adaptors/digitalpinsadaptoroptions.go +++ b/platforms/adaptors/digitalpinsadaptoroptions.go @@ -2,6 +2,8 @@ package adaptors import ( "time" + + "gobot.io/x/gobot/v2/system" ) // DigitalPinsOptionApplier is the interface for digital adaptors options. This provides the possibility for change the @@ -11,6 +13,9 @@ type DigitalPinsOptionApplier interface { apply(cfg *digitalPinsConfiguration) } +// digitalPinsDebugOption is the type to switch on digital pin related debug messages. +type digitalPinsDebugOption bool + // digitalPinInitializeOption is the type for applying another than the default initializer type digitalPinsInitializeOption digitalPinInitializer @@ -18,9 +23,6 @@ type digitalPinsInitializeOption digitalPinInitializer // 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 @@ -75,6 +77,10 @@ type digitalPinsPollForEdgeDetectionOption struct { pollQuitChan chan struct{} } +func (o digitalPinsDebugOption) String() string { + return "switch on debugging for digital pins option" +} + func (o digitalPinsInitializeOption) String() string { return "pin initializer option for digital IO's" } @@ -83,10 +89,6 @@ func (o digitalPinsSystemSysfsOption) String() string { return "use sysfs vs. cdev system access option for digital pins" } -func (o digitalPinsForSystemSpiOption) String() string { - return "use digital pins for SPI option" -} - func (o digitalPinsActiveLowOption) String() string { return "digital pins set to active low option" } @@ -127,61 +129,72 @@ func (o digitalPinsPollForEdgeDetectionOption) String() string { return "discrete polling function for edge detection on digital pin option" } +func (o digitalPinsDebugOption) apply(cfg *digitalPinsConfiguration) { + cfg.debug = bool(o) + cfg.systemOptions = append(cfg.systemOptions, system.WithDigitalPinDebug()) +} + func (o digitalPinsInitializeOption) apply(cfg *digitalPinsConfiguration) { cfg.initialize = digitalPinInitializer(o) } func (o digitalPinsSystemSysfsOption) apply(cfg *digitalPinsConfiguration) { - c := bool(o) - cfg.useSysfs = &c -} + useSysFs := bool(o) -func (o digitalPinsForSystemSpiOption) apply(cfg *digitalPinsConfiguration) { - c := digitalPinGpiosForSPI(o) - cfg.gpiosForSPI = &c + if useSysFs { + cfg.systemOptions = append(cfg.systemOptions, system.WithDigitalPinSysfsAccess()) + } else { + cfg.systemOptions = append(cfg.systemOptions, system.WithDigitalPinCdevAccess()) + } } func (o digitalPinsActiveLowOption) apply(cfg *digitalPinsConfiguration) { - cfg.activeLowPins = append(cfg.activeLowPins, o...) + for _, pin := range o { + cfg.pinOptions[pin] = append(cfg.pinOptions[pin], system.WithPinActiveLow()) + } } func (o digitalPinsPullDownOption) apply(cfg *digitalPinsConfiguration) { - cfg.pullDownPins = append(cfg.pullDownPins, o...) + for _, pin := range o { + cfg.pinOptions[pin] = append(cfg.pinOptions[pin], system.WithPinPullDown()) + } } func (o digitalPinsPullUpOption) apply(cfg *digitalPinsConfiguration) { - cfg.pullUpPins = append(cfg.pullUpPins, o...) + for _, pin := range o { + cfg.pinOptions[pin] = append(cfg.pinOptions[pin], system.WithPinPullUp()) + } } func (o digitalPinsOpenDrainOption) apply(cfg *digitalPinsConfiguration) { - cfg.openDrainPins = append(cfg.openDrainPins, o...) + for _, pin := range o { + cfg.pinOptions[pin] = append(cfg.pinOptions[pin], system.WithPinOpenDrain()) + } } func (o digitalPinsOpenSourceOption) apply(cfg *digitalPinsConfiguration) { - cfg.openSourcePins = append(cfg.openSourcePins, o...) + for _, pin := range o { + cfg.pinOptions[pin] = append(cfg.pinOptions[pin], system.WithPinOpenSource()) + } } func (o digitalPinsDebounceOption) apply(cfg *digitalPinsConfiguration) { - pinCfg := digitalPinsDebouncePin(o) - cfg.debouncePins = append(cfg.debouncePins, pinCfg) + cfg.pinOptions[o.id] = append(cfg.pinOptions[o.id], system.WithPinDebounce(o.period)) } func (o digitalPinsEventOnFallingEdgeOption) apply(cfg *digitalPinsConfiguration) { - pinCfg := digitalPinsEventOnEdgePin(o) - cfg.eventOnFallingEdgePins = append(cfg.eventOnFallingEdgePins, pinCfg) + cfg.pinOptions[o.id] = append(cfg.pinOptions[o.id], system.WithPinEventOnFallingEdge(o.handler)) } func (o digitalPinsEventOnRisingEdgeOption) apply(cfg *digitalPinsConfiguration) { - pinCfg := digitalPinsEventOnEdgePin(o) - cfg.eventOnRisingEdgePins = append(cfg.eventOnRisingEdgePins, pinCfg) + cfg.pinOptions[o.id] = append(cfg.pinOptions[o.id], system.WithPinEventOnRisingEdge(o.handler)) } func (o digitalPinsEventOnBothEdgesOption) apply(cfg *digitalPinsConfiguration) { - pinCfg := digitalPinsEventOnEdgePin(o) - cfg.eventOnBothEdgesPins = append(cfg.eventOnBothEdgesPins, pinCfg) + cfg.pinOptions[o.id] = append(cfg.pinOptions[o.id], system.WithPinEventOnBothEdges(o.handler)) } func (o digitalPinsPollForEdgeDetectionOption) apply(cfg *digitalPinsConfiguration) { - pinCfg := digitalPinsPollForEdgeDetectionPin(o) - cfg.pollForEdgeDetectionPins = append(cfg.pollForEdgeDetectionPins, pinCfg) + cfg.pinOptions[o.id] = append(cfg.pinOptions[o.id], + system.WithPinPollForEdgeDetection(o.pollInterval, o.pollQuitChan)) } diff --git a/platforms/adaptors/digitalpinsadaptoroptions_test.go b/platforms/adaptors/digitalpinsadaptoroptions_test.go index a57a4899f..0e53ca13d 100644 --- a/platforms/adaptors/digitalpinsadaptoroptions_test.go +++ b/platforms/adaptors/digitalpinsadaptoroptions_test.go @@ -19,7 +19,7 @@ func TestDigitalPinsWithGpiosActiveLow(t *testing.T) { a := NewDigitalPinsAdaptor(system.NewAccesser(), nil, WithGpiosActiveLow("1", "12", "33")) require.NoError(t, a.Connect()) // assert - assert.Len(t, a.pinOptions, 3) + assert.Len(t, a.digitalPinsCfg.pinOptions, 3) } func TestDigitalPinsWithDigitalPinInitializer(t *testing.T) { @@ -51,30 +51,18 @@ func TestDigitalPinsWithDigitalPinInitializer(t *testing.T) { func TestDigitalPinsWithSysfsAccess(t *testing.T) { // arrange - a := NewDigitalPinsAdaptor(system.NewAccesser(), nil) - require.NoError(t, a.Connect()) - require.True(t, a.sys.IsCdevDigitalPinAccess()) - require.NoError(t, a.Finalize()) - // act, connect is mandatory to set options to the system - WithGpioSysfsAccess().apply(a.digitalPinsCfg) - require.NoError(t, a.Connect()) + a := NewDigitalPinsAdaptor(system.NewAccesser(), nil, WithGpioSysfsAccess()) // assert - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } func TestDigitalPinsWithCdevAccess(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 - WithGpioCdevAccess().apply(a.digitalPinsCfg) - require.NoError(t, a.Connect()) + sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) + sys.UseMockFilesystem([]string{"/dev/gpiochip0"}) + a := NewDigitalPinsAdaptor(sys, nil, WithGpioCdevAccess()) // assert - assert.True(t, a.sys.IsCdevDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) } func TestDigitalReadWithGpiosActiveLow(t *testing.T) { @@ -187,37 +175,6 @@ func TestDigitalWriteWithGpiosActiveLow(t *testing.T) { assert.Equal(t, "1", fs.Files["/sys/class/gpio/gpio19/active_low"].Contents) } -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.go b/platforms/adaptors/digitalpintranslator.go index 8e99ae663..f87759403 100644 --- a/platforms/adaptors/digitalpintranslator.go +++ b/platforms/adaptors/digitalpintranslator.go @@ -34,7 +34,7 @@ func (pt *DigitalPinTranslator) Translate(id string) (string, int, error) { if !ok { return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) } - if pt.sys.IsSysfsDigitalPinAccess() { + if pt.sys.HasDigitalPinSysfsAccess() { return "", pindef.Sysfs, nil } chip := fmt.Sprintf("gpiochip%d", pindef.Cdev.Chip) diff --git a/platforms/adaptors/digitalpintranslator_test.go b/platforms/adaptors/digitalpintranslator_test.go index 65b54be70..f6898cd10 100644 --- a/platforms/adaptors/digitalpintranslator_test.go +++ b/platforms/adaptors/digitalpintranslator_test.go @@ -28,7 +28,7 @@ func TestDigitalPinTranslatorTranslate(t *testing.T) { "5": {Sysfs: 253, Cdev: CdevPin{Chip: 8, Line: 5}}, } tests := map[string]struct { - access func(system.Optioner) + access system.AccesserOptionApplier pin string wantChip string wantLine int @@ -77,7 +77,10 @@ func TestDigitalPinTranslatorTranslate(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange - sys := system.NewAccesser(tc.access) + sys := system.NewAccesser() + // arrange for cdev needed + sys.UseMockFilesystem([]string{"/dev/gpiochip"}) + sys.AddDigitalPinSupport(tc.access) pt := NewDigitalPinTranslator(sys, pinDefinitions) // act chip, line, err := pt.Translate(tc.pin) diff --git a/platforms/adaptors/i2cbusadaptor.go b/platforms/adaptors/i2cbusadaptor.go index 401d6cf73..235ccdcd2 100644 --- a/platforms/adaptors/i2cbusadaptor.go +++ b/platforms/adaptors/i2cbusadaptor.go @@ -25,12 +25,15 @@ type I2cBusAdaptor struct { // NewI2cBusAdaptor provides the access to i2c buses of the board. The validator is used to check the bus number, // which is given by user, to the abilities of the board. func NewI2cBusAdaptor(sys *system.Accesser, v i2cBusNumberValidator, defaultBusNr int) *I2cBusAdaptor { - a := &I2cBusAdaptor{ + a := I2cBusAdaptor{ sys: sys, validateNumber: v, defaultBusNumber: defaultBusNr, } - return a + + sys.AddI2CSupport() + + return &a } // Connect prepares the connection to i2c buses. diff --git a/platforms/adaptors/i2cbusadaptor_test.go b/platforms/adaptors/i2cbusadaptor_test.go index 70d7ab8b8..b07e5301f 100644 --- a/platforms/adaptors/i2cbusadaptor_test.go +++ b/platforms/adaptors/i2cbusadaptor_test.go @@ -17,16 +17,15 @@ var _ i2c.Connector = (*I2cBusAdaptor)(nil) const i2cBus1 = "/dev/i2c-1" func initTestI2cAdaptorWithMockedFilesystem(mockPaths []string) (*I2cBusAdaptor, *system.MockFilesystem) { - sys := system.NewAccesser() - sys.UseMockSyscall() - fs := sys.UseMockFilesystem(mockPaths) validator := func(busNr int) error { if busNr > 1 { return fmt.Errorf("%d not valid", busNr) } return nil } - a := NewI2cBusAdaptor(sys, validator, 1) + a := NewI2cBusAdaptor(system.NewAccesser(), validator, 1) + a.sys.UseMockSyscall() + fs := a.sys.UseMockFilesystem(mockPaths) if err := a.Connect(); err != nil { panic(err) } @@ -110,6 +109,6 @@ func TestI2cReConnect(t *testing.T) { } func TestI2cGetDefaultBus(t *testing.T) { - a := NewI2cBusAdaptor(nil, nil, 2) + a := NewI2cBusAdaptor(system.NewAccesser(), nil, 2) assert.Equal(t, 2, a.DefaultI2cBus()) } diff --git a/platforms/adaptors/onewirebusadaptor.go b/platforms/adaptors/onewirebusadaptor.go index c24391cb4..2332589ee 100644 --- a/platforms/adaptors/onewirebusadaptor.go +++ b/platforms/adaptors/onewirebusadaptor.go @@ -21,8 +21,10 @@ type OneWireBusAdaptor struct { // NewOneWireBusAdaptor provides the access to 1-wire devices of the board. func NewOneWireBusAdaptor(sys *system.Accesser) *OneWireBusAdaptor { - a := &OneWireBusAdaptor{sys: sys, mutex: &sync.Mutex{}} - return a + a := OneWireBusAdaptor{sys: sys, mutex: &sync.Mutex{}} + sys.AddOneWireSupport() + + return &a } // Connect prepares the connection to 1-wire devices. diff --git a/platforms/adaptors/pwmpinsadaptor.go b/platforms/adaptors/pwmpinsadaptor.go index 8f2fa57ab..a76fab4d2 100644 --- a/platforms/adaptors/pwmpinsadaptor.go +++ b/platforms/adaptors/pwmpinsadaptor.go @@ -68,7 +68,7 @@ type PWMPinsAdaptor struct { // "WithPWMServoDutyCycleRangeForPin" // "WithPWMServoAngleRangeForPin" func NewPWMPinsAdaptor(sys *system.Accesser, t pwmPinTranslator, opts ...PwmPinsOptionApplier) *PWMPinsAdaptor { - a := &PWMPinsAdaptor{ + a := PWMPinsAdaptor{ sys: sys, translate: t, pwmPinsCfg: &pwmPinsConfiguration{ @@ -86,7 +86,9 @@ func NewPWMPinsAdaptor(sys *system.Accesser, t pwmPinTranslator, opts ...PwmPins o.apply(a.pwmPinsCfg) } - return a + sys.AddPWMSupport() + + return &a } // WithPWMPinInitializer substitute the default initializer. diff --git a/platforms/adaptors/pwmpinsadaptor_test.go b/platforms/adaptors/pwmpinsadaptor_test.go index 400674d97..674756c59 100644 --- a/platforms/adaptors/pwmpinsadaptor_test.go +++ b/platforms/adaptors/pwmpinsadaptor_test.go @@ -461,7 +461,7 @@ func TestPWMPinConcurrency(t *testing.T) { for retry := 0; retry < 20; retry++ { a := NewPWMPinsAdaptor(sys, translate) - _ = a.Connect() + require.NoError(t, a.Connect()) var wg sync.WaitGroup for i := 0; i < 20; i++ { diff --git a/platforms/adaptors/spibusadaptor.go b/platforms/adaptors/spibusadaptor.go index 2672fc9e4..d9e0e1791 100644 --- a/platforms/adaptors/spibusadaptor.go +++ b/platforms/adaptors/spibusadaptor.go @@ -6,12 +6,20 @@ import ( multierror "github.com/hashicorp/go-multierror" + "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/spi" "gobot.io/x/gobot/v2/system" ) type spiBusNumberValidator func(busNumber int) error +// spiBusConfiguration contains all changeable attributes of the adaptor. +type spiBusConfiguration struct { + debug bool + spiGpioPinnerProvider gobot.DigitalPinnerProvider + systemOptions []system.AccesserOptionApplier +} + // SpiBusAdaptor is a adaptor for SPI bus, normally used for composition in platforms. type SpiBusAdaptor struct { sys *system.Accesser @@ -20,17 +28,23 @@ type SpiBusAdaptor struct { defaultChipNumber int defaultMode int defaultBitCount int - defaultMaxSpeed int64 + defaultMaxSpeed int64 // Hz + spiBusCfg *spiBusConfiguration mutex sync.Mutex connections map[string]spi.Connection } // NewSpiBusAdaptor provides the access to SPI buses of the board. The validator is used to check the // bus number (given by user) to the abilities of the board. -func NewSpiBusAdaptor(sys *system.Accesser, v spiBusNumberValidator, busNum, chipNum, mode, bits int, +func NewSpiBusAdaptor( + sys *system.Accesser, + v spiBusNumberValidator, + busNum, chipNum, mode, bits int, maxSpeed int64, + spiGpioPinnerProvider gobot.DigitalPinnerProvider, + opts ...SpiBusOptionApplier, ) *SpiBusAdaptor { - a := &SpiBusAdaptor{ + a := SpiBusAdaptor{ sys: sys, validateBusNumber: v, defaultBusNumber: busNum, @@ -38,8 +52,33 @@ func NewSpiBusAdaptor(sys *system.Accesser, v spiBusNumberValidator, busNum, chi defaultMode: mode, defaultBitCount: bits, defaultMaxSpeed: maxSpeed, + spiBusCfg: &spiBusConfiguration{spiGpioPinnerProvider: spiGpioPinnerProvider}, + } + + for _, o := range opts { + o.apply(a.spiBusCfg) + } + + sys.AddSPISupport(a.spiBusCfg.systemOptions...) + + return &a +} + +// WithSpiDebug can be used to switch on debugging for SPI implementation. +func WithSpiDebug() spiBusDebugOption { + return spiBusDebugOption(true) +} + +// WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage. +func WithSpiGpioAccess(sclkPin, ncsPin, sdoPin, sdiPin string) spiBusDigitalPinsForSystemSpiOption { + o := spiBusDigitalPinsForSystemSpiOption{ + sclkPin: sclkPin, + ncsPin: ncsPin, + sdoPin: sdoPin, + sdiPin: sdiPin, } - return a + + return o } // Connect prepares the connection to SPI buses. @@ -47,6 +86,10 @@ func (a *SpiBusAdaptor) Connect() error { a.mutex.Lock() defer a.mutex.Unlock() + if a.spiBusCfg.debug { + fmt.Println("connect the SPI bus adaptor") + } + a.connections = make(map[string]spi.Connection) return nil } @@ -56,6 +99,10 @@ func (a *SpiBusAdaptor) Finalize() error { a.mutex.Lock() defer a.mutex.Unlock() + if a.spiBusCfg.debug { + fmt.Println("finalize the SPI bus adaptor") + } + var err error for _, con := range a.connections { if con != nil { @@ -74,6 +121,10 @@ func (a *SpiBusAdaptor) GetSpiConnection(busNum, chipNum, mode, bits int, maxSpe a.mutex.Lock() defer a.mutex.Unlock() + if a.spiBusCfg.debug { + fmt.Println("get SPI connection") + } + if a.connections == nil { return nil, fmt.Errorf("not connected") } diff --git a/platforms/adaptors/spibusadaptor_test.go b/platforms/adaptors/spibusadaptor_test.go index df4375b62..1437592fe 100644 --- a/platforms/adaptors/spibusadaptor_test.go +++ b/platforms/adaptors/spibusadaptor_test.go @@ -24,8 +24,11 @@ func initTestSpiBusAdaptorWithMockedSpi() (*SpiBusAdaptor, *system.MockSpiAccess return nil } sys := system.NewAccesser() - spi := sys.UseMockSpi() - a := NewSpiBusAdaptor(sys, validator, 1, 2, 3, 4, 5) + // arrange for periphio needed + sys.UseMockFilesystem([]string{"/dev/spidev"}) + dpa := sys.UseMockDigitalPinAccess() + a := NewSpiBusAdaptor(sys, validator, 1, 2, 3, 4, 5, dpa) + spi := a.sys.UseMockSpi() if err := a.Connect(); err != nil { panic(err) } @@ -34,7 +37,11 @@ func initTestSpiBusAdaptorWithMockedSpi() (*SpiBusAdaptor, *system.MockSpiAccess func TestNewSpiAdaptor(t *testing.T) { // arrange - a := NewSpiBusAdaptor(nil, nil, 1, 2, 3, 4, 5) + sys := system.NewAccesser() + // arrange for periphio needed + sys.UseMockFilesystem([]string{"/dev/spidev"}) + dpa := sys.UseMockDigitalPinAccess() + a := NewSpiBusAdaptor(sys, nil, 1, 2, 3, 4, 5, dpa) // act & assert assert.Equal(t, 1, a.SpiDefaultBusNumber()) assert.Equal(t, 2, a.SpiDefaultChipNumber()) diff --git a/platforms/adaptors/spibusadaptoroptions.go b/platforms/adaptors/spibusadaptoroptions.go new file mode 100644 index 000000000..3873d8b14 --- /dev/null +++ b/platforms/adaptors/spibusadaptoroptions.go @@ -0,0 +1,39 @@ +package adaptors + +import "gobot.io/x/gobot/v2/system" + +// SpiBusOptionApplier is the interface for spi bus adaptor options. This provides the possibility for change the +// platform behavior by the user when creating the platform, e.g. by "NewAdaptor()". +// The interface needs to be implemented by each configurable option type. +type SpiBusOptionApplier interface { + apply(cfg *spiBusConfiguration) +} + +// spiBusDebugOption is the type to switch on SPI related debug messages. +type spiBusDebugOption bool + +// spiBusDigitalPinsForSystemSpiOption is the type to switch the default SPI implementation to GPIO usage +type spiBusDigitalPinsForSystemSpiOption struct { + sclkPin string + ncsPin string + sdoPin string + sdiPin string +} + +func (o spiBusDebugOption) String() string { + return "switch on debugging for SPI option" +} + +func (o spiBusDigitalPinsForSystemSpiOption) String() string { + return "use digital pins for SPI option" +} + +func (o spiBusDebugOption) apply(cfg *spiBusConfiguration) { + cfg.debug = bool(o) + cfg.systemOptions = append(cfg.systemOptions, system.WithSpiDebug()) +} + +func (o spiBusDigitalPinsForSystemSpiOption) apply(cfg *spiBusConfiguration) { + cfg.systemOptions = append(cfg.systemOptions, system.WithSpiGpioAccess(cfg.spiGpioPinnerProvider, o.sclkPin, o.ncsPin, + o.sdoPin, o.sdiPin)) +} diff --git a/platforms/adaptors/spibusadaptoroptions_test.go b/platforms/adaptors/spibusadaptoroptions_test.go new file mode 100644 index 000000000..99e7e5fcc --- /dev/null +++ b/platforms/adaptors/spibusadaptoroptions_test.go @@ -0,0 +1,38 @@ +package adaptors + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2/system" +) + +func TestNewSpiBusAdaptorWithSpiGpioAccess(t *testing.T) { + // arrange + const ( + sclkPin = "1" + ncsPin = "2" + sdoPin = "3" + sdiPin = "4" + sclkPinTranslated = "1" + ncsPinTranslated = "2" + sdoPinTranslated = "3" + sdiPinTranslated = "4" + ) + sys := system.NewAccesser() + dpa := sys.UseMockDigitalPinAccess() + a := NewSpiBusAdaptor(sys, nil, 1, 2, 3, 4, 5, dpa, WithSpiGpioAccess(sclkPin, ncsPin, sdoPin, sdiPin)) + // act + 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.True(t, a.sys.HasSpiGpioAccess()) + 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 +} diff --git a/platforms/beagleboard/beaglebone/beaglebone_adaptor.go b/platforms/beagleboard/beaglebone/beaglebone_adaptor.go index faeaeb8e1..d57100db7 100644 --- a/platforms/beagleboard/beaglebone/beaglebone_adaptor.go +++ b/platforms/beagleboard/beaglebone/beaglebone_adaptor.go @@ -59,14 +59,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier pwmPinsOpts := []adaptors.PwmPinsOptionApplier{adaptors.WithPWMDefaultPeriod(pwmPeriodDefault)} + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) - case func(system.Optioner): - o(sys) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -85,7 +86,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return a } diff --git a/platforms/beagleboard/beaglebone/beaglebone_adaptor_test.go b/platforms/beagleboard/beaglebone/beaglebone_adaptor_test.go index b625282de..16acbc570 100644 --- a/platforms/beagleboard/beaglebone/beaglebone_adaptor_test.go +++ b/platforms/beagleboard/beaglebone/beaglebone_adaptor_test.go @@ -90,7 +90,7 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.I2cBusAdaptor) assert.NotNil(t, a.SpiBusAdaptor) assert.Equal(t, "/sys/class/leds/beaglebone:green:", a.usrLed) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) diff --git a/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor.go b/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor.go index 359967d50..77227c34a 100644 --- a/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor.go +++ b/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor.go @@ -49,8 +49,6 @@ func NewAdaptor(opts ...interface{}) *PocketBeagleAdaptor { digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) - case func(system.Optioner): - // do nothing, already applied on NewAdaptor(opts) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.Name())) } @@ -73,7 +71,7 @@ func (a *PocketBeagleAdaptor) getTranslateAndMuxDigitalPinFunc( digitalPinTranslate func(id string) (string, int, error), ) func(id string) (string, int, error) { return func(id string) (string, int, error) { - if a.sys.IsSysfsDigitalPinAccess() { + if a.sys.HasDigitalPinSysfsAccess() { // mux is done by id, not by line if err := a.muxPin(id, "gpio"); err != nil { return "", -1, err diff --git a/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor_test.go b/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor_test.go index 40897bc58..5c4f8dbb2 100644 --- a/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor_test.go +++ b/platforms/beagleboard/pocketbeagle/pocketbeagle_adaptor_test.go @@ -22,7 +22,7 @@ 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.IsCdevDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) } func TestNewAdaptorWithOption(t *testing.T) { @@ -30,7 +30,7 @@ func TestNewAdaptorWithOption(t *testing.T) { a := NewAdaptor(adaptors.WithGpioSysfsAccess()) // assert require.NoError(t, a.Connect()) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } func TestDigitalIO(t *testing.T) { @@ -39,7 +39,7 @@ func TestDigitalIO(t *testing.T) { a := NewAdaptor() require.NoError(t, a.Connect()) dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsCdevDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert write err := a.DigitalWrite("P1_02", 1) require.NoError(t, err) diff --git a/platforms/chip/chip_adaptor.go b/platforms/chip/chip_adaptor.go index 02a45e6d4..7bff03b2b 100644 --- a/platforms/chip/chip_adaptor.go +++ b/platforms/chip/chip_adaptor.go @@ -15,7 +15,11 @@ import ( "gobot.io/x/gobot/v2/system" ) -const defaultI2cBusNumber = 1 +const ( + defaultI2cBusNumber = 1 + + defaultSpiMaxSpeed = 5000 // 5kHz (more than 15kHz not possible with SPI over GPIO) +) // Valids pins are the XIO-P0 through XIO-P7 pins from the // extender (pins 13-20 on header 14), as well as the SoC pins @@ -34,6 +38,7 @@ type Adaptor struct { *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor *adaptors.I2cBusAdaptor + *adaptors.SpiBusAdaptor // for usage of "adaptors.WithSpiGpioAccess()" } // NewAdaptor creates a C.H.I.P. Adaptor @@ -60,12 +65,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -77,6 +85,13 @@ func NewAdaptor(opts ...interface{}) *Adaptor { a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + + // SPI is only supported when "adaptors.WithSpiGpioAccess()" is given + if len(spiBusOpts) > 0 { + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, func(int) error { return nil }, 0, 0, 0, 0, defaultSpiMaxSpeed, + a.DigitalPinsAdaptor, spiBusOpts...) + } + return a } diff --git a/platforms/chip/chip_adaptor_test.go b/platforms/chip/chip_adaptor_test.go index f8472137d..bf2f4ac45 100644 --- a/platforms/chip/chip_adaptor_test.go +++ b/platforms/chip/chip_adaptor_test.go @@ -63,12 +63,28 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.DigitalPinsAdaptor) assert.NotNil(t, a.PWMPinsAdaptor) assert.NotNil(t, a.I2cBusAdaptor) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.Nil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewAdaptor(adaptors.WithSpiGpioAccess("1", "2", "3", "4")) + // 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.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) +} + func TestFinalizeErrorAfterGPIO(t *testing.T) { a, fs := initConnectedTestAdaptorWithMockedFilesystem() require.NoError(t, a.DigitalWrite("CSID7", 1)) diff --git a/platforms/chip/chippro_adaptor_test.go b/platforms/chip/chippro_adaptor_test.go index fed529bbe..241261763 100644 --- a/platforms/chip/chippro_adaptor_test.go +++ b/platforms/chip/chippro_adaptor_test.go @@ -22,7 +22,7 @@ func initConnectedTestProAdaptorWithMockedFilesystem() (*Adaptor, *system.MockFi func TestNewProAdaptor(t *testing.T) { a := NewProAdaptor() assert.True(t, strings.HasPrefix(a.Name(), "CHIP Pro")) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } func TestProDigitalIO(t *testing.T) { diff --git a/platforms/dragonboard/dragonboard_adaptor.go b/platforms/dragonboard/dragonboard_adaptor.go index 450b64f83..6a071e43c 100644 --- a/platforms/dragonboard/dragonboard_adaptor.go +++ b/platforms/dragonboard/dragonboard_adaptor.go @@ -11,7 +11,11 @@ import ( "gobot.io/x/gobot/v2/system" ) -const defaultI2cBusNumber = 0 +const ( + defaultI2cBusNumber = 0 + + defaultSpiMaxSpeed = 5000 // 5kHz (more than 15kHz not possible with SPI over GPIO) +) // Adaptor represents a Gobot Adaptor for a DragonBoard 410c type Adaptor struct { @@ -21,6 +25,7 @@ type Adaptor struct { pinMap map[string]int *adaptors.DigitalPinsAdaptor *adaptors.I2cBusAdaptor + *adaptors.SpiBusAdaptor // for usage of "adaptors.WithSpiGpioAccess()" } // Valid pins are the GPIO_A through GPIO_L pins from the @@ -50,60 +55,80 @@ var fixedPins = map[string]int{ // // adaptors.WithGpioCdevAccess(): use character device driver instead of sysfs // adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# -func NewAdaptor(opts ...adaptors.DigitalPinsOptionApplier) *Adaptor { +func NewAdaptor(opts ...interface{}) *Adaptor { sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) - c := &Adaptor{ + a := &Adaptor{ name: gobot.DefaultName("DragonBoard"), sys: sys, } + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier + for _, opt := range opts { + switch o := opt.(type) { + case adaptors.DigitalPinsOptionApplier: + digitalPinsOpts = append(digitalPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) + default: + panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) + } + } + // Valid bus numbers are [0,1] which corresponds to /dev/i2c-0 through /dev/i2c-1. i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) - c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...) - c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) - c.pinMap = fixedPins + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + + // SPI is only supported when "adaptors.WithSpiGpioAccess()" is given + if len(spiBusOpts) > 0 { + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, func(int) error { return nil }, 0, 0, 0, 0, defaultSpiMaxSpeed, + a.DigitalPinsAdaptor, spiBusOpts...) + } + + a.pinMap = fixedPins for i := 0; i < 122; i++ { pin := fmt.Sprintf("GPIO_%d", i) - c.pinMap[pin] = i + a.pinMap[pin] = i } - return c + return a } // Name returns the name of the Adaptor -func (c *Adaptor) Name() string { return c.name } +func (a *Adaptor) Name() string { return a.name } // SetName sets the name of the Adaptor -func (c *Adaptor) SetName(n string) { c.name = n } +func (a *Adaptor) SetName(n string) { a.name = n } // Connect create new connection to board and pins. -func (c *Adaptor) Connect() error { - c.mutex.Lock() - defer c.mutex.Unlock() +func (a *Adaptor) Connect() error { + a.mutex.Lock() + defer a.mutex.Unlock() - if err := c.I2cBusAdaptor.Connect(); err != nil { + if err := a.I2cBusAdaptor.Connect(); err != nil { return err } - return c.DigitalPinsAdaptor.Connect() + return a.DigitalPinsAdaptor.Connect() } // Finalize closes connection to board and pins -func (c *Adaptor) Finalize() error { - c.mutex.Lock() - defer c.mutex.Unlock() +func (a *Adaptor) Finalize() error { + a.mutex.Lock() + defer a.mutex.Unlock() - err := c.DigitalPinsAdaptor.Finalize() + err := a.DigitalPinsAdaptor.Finalize() - if e := c.I2cBusAdaptor.Finalize(); e != nil { + if e := a.I2cBusAdaptor.Finalize(); e != nil { err = multierror.Append(err, e) } return err } -func (c *Adaptor) translateDigitalPin(id string) (string, int, error) { - if line, ok := c.pinMap[id]; ok { +func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { + if line, ok := a.pinMap[id]; ok { return "", line, nil } return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) diff --git a/platforms/dragonboard/dragonboard_adaptor_test.go b/platforms/dragonboard/dragonboard_adaptor_test.go index 24fcc7e8f..4f1158112 100644 --- a/platforms/dragonboard/dragonboard_adaptor_test.go +++ b/platforms/dragonboard/dragonboard_adaptor_test.go @@ -10,6 +10,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/gpio" "gobot.io/x/gobot/v2/drivers/i2c" + "gobot.io/x/gobot/v2/platforms/adaptors" ) // make sure that this Adaptor fulfills all the required interfaces @@ -39,12 +40,27 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.pinMap) assert.NotNil(t, a.DigitalPinsAdaptor) assert.NotNil(t, a.I2cBusAdaptor) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.Nil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewAdaptor(adaptors.WithSpiGpioAccess("1", "2", "3", "4")) + // 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.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) +} + func TestDigitalIO(t *testing.T) { a := initConnectedTestAdaptor() mockPaths := []string{ diff --git a/platforms/intel-iot/edison/edison_adaptor.go b/platforms/intel-iot/edison/edison_adaptor.go index caacdf84f..325404d17 100644 --- a/platforms/intel-iot/edison/edison_adaptor.go +++ b/platforms/intel-iot/edison/edison_adaptor.go @@ -52,6 +52,7 @@ type Adaptor struct { // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) + sys.AddDigitalPinSupport() a := &Adaptor{ name: gobot.DefaultName("Edison"), board: "arduino", diff --git a/platforms/intel-iot/edison/edison_adaptor_test.go b/platforms/intel-iot/edison/edison_adaptor_test.go index e585b7e25..ff803ad37 100644 --- a/platforms/intel-iot/edison/edison_adaptor_test.go +++ b/platforms/intel-iot/edison/edison_adaptor_test.go @@ -233,7 +233,7 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.AnalogPinsAdaptor) assert.NotNil(t, a.PWMPinsAdaptor) assert.NotNil(t, a.I2cBusAdaptor) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) diff --git a/platforms/intel-iot/joule/joule_adaptor.go b/platforms/intel-iot/joule/joule_adaptor.go index 9a4ccf8dc..894505934 100644 --- a/platforms/intel-iot/joule/joule_adaptor.go +++ b/platforms/intel-iot/joule/joule_adaptor.go @@ -11,7 +11,11 @@ import ( "gobot.io/x/gobot/v2/system" ) -const defaultI2cBusNumber = 0 +const ( + defaultI2cBusNumber = 0 + + defaultSpiMaxSpeed = 5000 // 5kHz (more than 15kHz not possible with SPI over GPIO) +) type sysfsPin struct { pin int @@ -26,6 +30,7 @@ type Adaptor struct { *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor *adaptors.I2cBusAdaptor + *adaptors.SpiBusAdaptor // for usage of "adaptors.WithSpiGpioAccess()" } // NewAdaptor returns a new Joule Adaptor @@ -45,12 +50,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier pwmPinsOpts := []adaptors.PwmPinsOptionApplier{adaptors.WithPWMPinInitializer(pwmPinInitializer)} + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -62,6 +70,13 @@ func NewAdaptor(opts ...interface{}) *Adaptor { a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + + // SPI is only supported when "adaptors.WithSpiGpioAccess()" is given + if len(spiBusOpts) > 0 { + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, func(int) error { return nil }, 0, 0, 0, 0, defaultSpiMaxSpeed, + a.DigitalPinsAdaptor, spiBusOpts...) + } + return a } diff --git a/platforms/intel-iot/joule/joule_adaptor_test.go b/platforms/intel-iot/joule/joule_adaptor_test.go index 6c9c4867d..513401821 100644 --- a/platforms/intel-iot/joule/joule_adaptor_test.go +++ b/platforms/intel-iot/joule/joule_adaptor_test.go @@ -10,6 +10,7 @@ import ( "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/gpio" "gobot.io/x/gobot/v2/drivers/i2c" + "gobot.io/x/gobot/v2/platforms/adaptors" "gobot.io/x/gobot/v2/system" ) @@ -108,12 +109,27 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.DigitalPinsAdaptor) assert.NotNil(t, a.PWMPinsAdaptor) assert.NotNil(t, a.I2cBusAdaptor) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.Nil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } +func TestNewAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewAdaptor(adaptors.WithSpiGpioAccess("1", "2", "3", "4")) + // 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.NotNil(t, a.SpiBusAdaptor) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) +} + func TestFinalize(t *testing.T) { a, _ := initConnectedTestAdaptorWithMockedFilesystem() diff --git a/platforms/jetson/jetson_adaptor.go b/platforms/jetson/jetson_adaptor.go index 73d64f32d..128f9a941 100644 --- a/platforms/jetson/jetson_adaptor.go +++ b/platforms/jetson/jetson_adaptor.go @@ -58,12 +58,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { adaptors.WithPWMMinimumPeriod(pwmPeriodMinimum), adaptors.WithPWMMinimumDutyRate(pwmDutyRateMinimum), } + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -79,7 +82,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return a } diff --git a/platforms/jetson/jetson_adaptor_test.go b/platforms/jetson/jetson_adaptor_test.go index 452f81054..2f922c6f2 100644 --- a/platforms/jetson/jetson_adaptor_test.go +++ b/platforms/jetson/jetson_adaptor_test.go @@ -49,7 +49,7 @@ 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.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) diff --git a/platforms/nanopi/nanopi_adaptor.go b/platforms/nanopi/nanopi_adaptor.go index 3ffc152c9..5bc06ce0d 100644 --- a/platforms/nanopi/nanopi_adaptor.go +++ b/platforms/nanopi/nanopi_adaptor.go @@ -55,12 +55,15 @@ func NewNeoAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -80,7 +83,7 @@ func NewNeoAdaptor(opts ...interface{}) *Adaptor { a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return a } diff --git a/platforms/nanopi/nanopi_adaptor_test.go b/platforms/nanopi/nanopi_adaptor_test.go index 46034a4d0..186006290 100644 --- a/platforms/nanopi/nanopi_adaptor_test.go +++ b/platforms/nanopi/nanopi_adaptor_test.go @@ -85,7 +85,7 @@ 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.IsCdevDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) @@ -96,7 +96,7 @@ func TestNewAdaptorWithOption(t *testing.T) { a := NewNeoAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithGpioSysfsAccess()) // assert require.NoError(t, a.Connect()) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } func TestDigitalIO(t *testing.T) { @@ -104,7 +104,7 @@ func TestDigitalIO(t *testing.T) { // arrange a := initConnectedTestAdaptor() dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsCdevDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert write err := a.DigitalWrite("7", 1) require.NoError(t, err) @@ -128,7 +128,7 @@ func TestDigitalIOSysfs(t *testing.T) { a := NewNeoAdaptor(adaptors.WithGpioSysfsAccess()) require.NoError(t, a.Connect()) dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsSysfsDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert write err := a.DigitalWrite("7", 1) require.NoError(t, err) @@ -274,7 +274,7 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { // arrange a := initConnectedTestAdaptor() dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsCdevDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinCdevAccess()) require.NoError(t, a.DigitalWrite("7", 1)) dpa.UseUnexportError("gpiochip0", "203") // act diff --git a/platforms/raspi/raspi_adaptor.go b/platforms/raspi/raspi_adaptor.go index 6af1c52e6..c36abf2d7 100644 --- a/platforms/raspi/raspi_adaptor.go +++ b/platforms/raspi/raspi_adaptor.go @@ -56,12 +56,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -79,7 +82,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.getPinTranslatorFunction(), pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, 1) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return a } diff --git a/platforms/raspi/raspi_adaptor_test.go b/platforms/raspi/raspi_adaptor_test.go index 7f6984680..70d5ce010 100644 --- a/platforms/raspi/raspi_adaptor_test.go +++ b/platforms/raspi/raspi_adaptor_test.go @@ -84,7 +84,7 @@ 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.IsCdevDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) @@ -95,7 +95,7 @@ func TestNewAdaptorWithOption(t *testing.T) { a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithGpioSysfsAccess()) // assert require.NoError(t, a.Connect()) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } func TestGetDefaultBus(t *testing.T) { @@ -283,7 +283,7 @@ func TestDigitalIO(t *testing.T) { panic(err) } dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsCdevDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert write _ = a.DigitalWrite("7", 1) assert.Equal(t, []int{1}, dpa.Written("gpiochip0", "4")) diff --git a/platforms/rockpi/rockpi_adaptor.go b/platforms/rockpi/rockpi_adaptor.go index 4370c4477..7b8b02003 100644 --- a/platforms/rockpi/rockpi_adaptor.go +++ b/platforms/rockpi/rockpi_adaptor.go @@ -2,6 +2,7 @@ package rockpi import ( "errors" + "fmt" "sync" multierror "github.com/hashicorp/go-multierror" @@ -43,13 +44,26 @@ type Adaptor struct { // adaptors.WithGpioCdevAccess(): use character device 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 ...adaptors.DigitalPinsOptionApplier) *Adaptor { +func NewAdaptor(opts ...interface{}) *Adaptor { sys := system.NewAccesser(system.WithDigitalPinSysfsAccess()) a := &Adaptor{ name: gobot.DefaultName("RockPi"), sys: sys, } + var digitalPinsOpts []adaptors.DigitalPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier + for _, opt := range opts { + switch o := opt.(type) { + case adaptors.DigitalPinsOptionApplier: + digitalPinsOpts = append(digitalPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) + default: + panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) + } + } + // The RockPi4 has 3 I2C buses: 2, 6, 7. See https://wiki.radxa.com/Rock4/hardware/gpio // This could change in the future with other revisions! i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{2, 6, 7}) @@ -57,10 +71,10 @@ func NewAdaptor(opts ...adaptors.DigitalPinsOptionApplier) *Adaptor { // This could change in the future with other revisions! spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{1, 2}) - a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.getPinTranslatorFunction(), opts...) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.getPinTranslatorFunction(), digitalPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return a } diff --git a/platforms/rockpi/rockpi_adaptor_test.go b/platforms/rockpi/rockpi_adaptor_test.go index 9d9c9b92d..d7ed355fa 100644 --- a/platforms/rockpi/rockpi_adaptor_test.go +++ b/platforms/rockpi/rockpi_adaptor_test.go @@ -27,7 +27,7 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.DigitalPinsAdaptor) assert.NotNil(t, a.I2cBusAdaptor) assert.NotNil(t, a.SpiBusAdaptor) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) diff --git a/platforms/tinkerboard/adaptor.go b/platforms/tinkerboard/adaptor.go index 443ba974f..c4e1df6e1 100644 --- a/platforms/tinkerboard/adaptor.go +++ b/platforms/tinkerboard/adaptor.go @@ -57,12 +57,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -83,7 +86,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) a.OneWireBusAdaptor = adaptors.NewOneWireBusAdaptor(sys) return a diff --git a/platforms/tinkerboard/adaptor_test.go b/platforms/tinkerboard/adaptor_test.go index 3fa997f6f..6c666e94b 100644 --- a/platforms/tinkerboard/adaptor_test.go +++ b/platforms/tinkerboard/adaptor_test.go @@ -87,7 +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.IsCdevDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) @@ -98,7 +98,7 @@ func TestNewAdaptorWithOption(t *testing.T) { a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithGpioSysfsAccess()) // assert require.NoError(t, a.Connect()) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } func TestDigitalIO(t *testing.T) { @@ -106,7 +106,7 @@ func TestDigitalIO(t *testing.T) { // arrange a := initConnectedTestAdaptor() dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsCdevDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert write err := a.DigitalWrite("7", 1) require.NoError(t, err) @@ -130,7 +130,7 @@ func TestDigitalIOSysfs(t *testing.T) { a := NewAdaptor(adaptors.WithGpioSysfsAccess()) require.NoError(t, a.Connect()) dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsSysfsDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert write err := a.DigitalWrite("7", 1) require.NoError(t, err) @@ -262,7 +262,7 @@ func TestFinalizeErrorAfterGPIO(t *testing.T) { // arrange a := initConnectedTestAdaptor() dpa := a.sys.UseMockDigitalPinAccess() - require.True(t, a.sys.IsCdevDigitalPinAccess()) + require.True(t, a.sys.HasDigitalPinCdevAccess()) require.NoError(t, a.DigitalWrite("7", 1)) dpa.UseUnexportError("gpiochip0", "17") // act diff --git a/platforms/tinkerboard/tinkerboard2/adaptor.go b/platforms/tinkerboard/tinkerboard2/adaptor.go index d3d113ca3..ff7890fac 100644 --- a/platforms/tinkerboard/tinkerboard2/adaptor.go +++ b/platforms/tinkerboard/tinkerboard2/adaptor.go @@ -44,12 +44,15 @@ func NewAdaptor(opts ...interface{}) *Tinkerboard2Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.Name())) } @@ -67,7 +70,7 @@ func NewAdaptor(opts ...interface{}) *Tinkerboard2Adaptor { a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return &a } diff --git a/platforms/tinkerboard/tinkerboard2/adaptor_test.go b/platforms/tinkerboard/tinkerboard2/adaptor_test.go index 5e2efa972..a078c989c 100644 --- a/platforms/tinkerboard/tinkerboard2/adaptor_test.go +++ b/platforms/tinkerboard/tinkerboard2/adaptor_test.go @@ -21,7 +21,7 @@ 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.IsCdevDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinCdevAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) @@ -32,5 +32,5 @@ func TestNewAdaptorWithOption(t *testing.T) { a := NewAdaptor(adaptors.WithGpiosActiveLow("1"), adaptors.WithGpioSysfsAccess()) // assert require.NoError(t, a.Connect()) - assert.True(t, a.sys.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) } diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index 2a80cf7dd..a1d98ce50 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -69,12 +69,15 @@ func NewAdaptor(opts ...interface{}) *Adaptor { var digitalPinsOpts []adaptors.DigitalPinsOptionApplier var pwmPinsOpts []adaptors.PwmPinsOptionApplier + var spiBusOpts []adaptors.SpiBusOptionApplier for _, opt := range opts { switch o := opt.(type) { case adaptors.DigitalPinsOptionApplier: digitalPinsOpts = append(digitalPinsOpts, o) case adaptors.PwmPinsOptionApplier: pwmPinsOpts = append(pwmPinsOpts, o) + case adaptors.SpiBusOptionApplier: + spiBusOpts = append(spiBusOpts, o) default: panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.name)) } @@ -90,7 +93,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor { a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, - defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed, a.DigitalPinsAdaptor, spiBusOpts...) return a } diff --git a/platforms/upboard/up2/adaptor_test.go b/platforms/upboard/up2/adaptor_test.go index 597dd51aa..c1a233fd3 100644 --- a/platforms/upboard/up2/adaptor_test.go +++ b/platforms/upboard/up2/adaptor_test.go @@ -80,7 +80,7 @@ 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.IsSysfsDigitalPinAccess()) + assert.True(t, a.sys.HasDigitalPinSysfsAccess()) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) diff --git a/system/digitalpin_mock.go b/system/digitalpin_mock.go index a8c66d66f..fc2cf02d7 100644 --- a/system/digitalpin_mock.go +++ b/system/digitalpin_mock.go @@ -70,6 +70,11 @@ func (dpa *mockDigitalPinAccess) setFs(fs filesystem) { panic("setFs() for mockDigitalPinAccess not supported") } +func (dpa *mockDigitalPinAccess) DigitalPin(id string) (gobot.DigitalPinner, error) { + pin, err := strconv.Atoi(id) + return dpa.createPin("", pin), err +} + func (dpa *mockDigitalPinAccess) AppliedOptions(chip, pin string) int { return dpa.pins[getDigitalPinMockKey(chip, pin)].appliedOptions } diff --git a/system/digitalpin_poll.go b/system/digitalpin_poll.go index 7516d0dc9..20d6ef835 100644 --- a/system/digitalpin_poll.go +++ b/system/digitalpin_poll.go @@ -55,7 +55,7 @@ func startEdgePolling( readStart = time.Now() readValue, err := pinReadFunc() if err != nil { - fmt.Printf("edge polling error occurred while reading the pin %s: %v", pinLabel, err) + fmt.Printf("edge polling error occurred while reading the pin %s: %v\n", pinLabel, err) readValue = oldState // keep the value } if readValue != oldState { diff --git a/system/digitalpin_access.go b/system/digitalpinaccess.go similarity index 100% rename from system/digitalpin_access.go rename to system/digitalpinaccess.go diff --git a/system/digitalpin_access_test.go b/system/digitalpinaccess_test.go similarity index 100% rename from system/digitalpin_access_test.go rename to system/digitalpinaccess_test.go diff --git a/system/digitalpin_config.go b/system/digitalpinoptions.go similarity index 100% rename from system/digitalpin_config.go rename to system/digitalpinoptions.go diff --git a/system/digitalpin_config_test.go b/system/digitalpinoptions_test.go similarity index 100% rename from system/digitalpin_config_test.go rename to system/digitalpinoptions_test.go diff --git a/system/onewiredevice_sysfs_test.go b/system/onewiredevice_sysfs_test.go new file mode 100644 index 000000000..575694e7c --- /dev/null +++ b/system/onewiredevice_sysfs_test.go @@ -0,0 +1,20 @@ +package system + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_newOneWireDeviceSysfs(t *testing.T) { + // arrange + m := &MockFilesystem{} + sfa := sysfsFileAccess{fs: m, readBufLen: 2} + const id = "0815" + // act + d := newOneWireDeviceSysfs(&sfa, id) + // assert + assert.Equal(t, "/sys/bus/w1/devices/"+id, d.sysfsPath) + assert.Equal(t, &sfa, d.sfa) + assert.Equal(t, id, d.ID()) +} diff --git a/system/spi_gpio.go b/system/spi_gpio.go index 5ec70d9b9..1472adcfb 100644 --- a/system/spi_gpio.go +++ b/system/spi_gpio.go @@ -30,24 +30,24 @@ type spiGpio struct { } // newSpiGpio creates and returns a new SPI connection based on given GPIO's. -func newSpiGpio(cfg spiGpioConfig, maxSpeed int64) (*spiGpio, error) { +func newSpiGpio(cfg spiGpioConfig, maxSpeedHz int64) (*spiGpio, error) { spi := &spiGpio{cfg: cfg} - spi.initializeTime(maxSpeed) + spi.initializeTime(maxSpeedHz) return spi, spi.initializeGpios() } -func (s *spiGpio) initializeTime(maxSpeed int64) { - // maxSpeed is given in Hz, tclk is half the cycle time, tclk=1/(2*f), tclk[ns]=1 000 000 000/(2*maxSpeed) +func (s *spiGpio) initializeTime(maxSpeedHz int64) { + // maxSpeedHz is given in Hz, tclk is half the cycle time, tclk=1/(2*f), tclk[ns]=1 000 000 000/(2*maxSpeed) // but with gpio's a speed of more than ~15kHz is most likely not possible, so we limit to 10kHz - if maxSpeed > 10000 { + if maxSpeedHz > 10000 { if s.cfg.debug { fmt.Printf("reduce SPI speed for GPIO usage to 10Khz") } - maxSpeed = 10000 + maxSpeedHz = 10000 } - tclk := time.Duration(1000000000/2/maxSpeed) * time.Nanosecond + s.tclk = time.Duration(1000000000/2/maxSpeedHz) * time.Nanosecond if s.cfg.debug { - fmt.Println("clk", tclk) + fmt.Println("clk", s.tclk) } } diff --git a/system/spi_gpio_test.go b/system/spi_gpio_test.go new file mode 100644 index 000000000..f87d39547 --- /dev/null +++ b/system/spi_gpio_test.go @@ -0,0 +1,31 @@ +package system + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_newSpiGpio(t *testing.T) { + // arrange + dpa := newMockDigitalPinAccess(nil) + cfg := spiGpioConfig{ + pinProvider: dpa, + sclkPinID: "1", + ncsPinID: "2", + sdoPinID: "3", + sdiPinID: "4", + } + // act + d, err := newSpiGpio(cfg, 10001) + // assert + require.NoError(t, err) + assert.Equal(t, cfg, d.cfg) + assert.Equal(t, 50*time.Microsecond, d.tclk) + assert.NotNil(t, d.sclkPin) + assert.NotNil(t, d.ncsPin) + assert.NotNil(t, d.sdoPin) + assert.NotNil(t, d.sdiPin) +} diff --git a/system/spi_mock.go b/system/spi_mock.go index b4c63b14d..97b97cdcf 100644 --- a/system/spi_mock.go +++ b/system/spi_mock.go @@ -6,15 +6,19 @@ import ( "gobot.io/x/gobot/v2" ) -// MockSpiAccess contains parameters of mocked SPI access type MockSpiAccess struct { - CreateError bool - busNum int - chipNum int - mode int - bits int - maxSpeed int64 - sysdev *spiMock + underlyingSpiAccess spiAccesser + CreateError bool + busNum int + chipNum int + mode int + bits int + maxSpeed int64 + sysdev *spiMock +} + +func newMockSpiAccess(underlyingSpiAccess spiAccesser) *MockSpiAccess { + return &MockSpiAccess{underlyingSpiAccess: underlyingSpiAccess} } func (spi *MockSpiAccess) createDevice( @@ -34,6 +38,10 @@ func (spi *MockSpiAccess) createDevice( return spi.sysdev, err } +func (spi *MockSpiAccess) isType(accesserType spiBusAccesserType) bool { + return spi.underlyingSpiAccess.isType(accesserType) +} + func (*MockSpiAccess) isSupported() bool { return true } diff --git a/system/spi_access.go b/system/spiaccess.go similarity index 74% rename from system/spi_access.go rename to system/spiaccess.go index d7fdd1bef..8e934c9d5 100644 --- a/system/spi_access.go +++ b/system/spiaccess.go @@ -12,11 +12,8 @@ type gpioSpiAccess struct { cfg spiGpioConfig } -func (*periphioSpiAccess) createDevice( - busNum, chipNum, mode, bits int, - maxSpeed int64, -) (gobot.SpiSystemDevicer, error) { - return newSpiPeriphIo(busNum, chipNum, mode, bits, maxSpeed) +func (psa *periphioSpiAccess) isType(accesserType spiBusAccesserType) bool { + return accesserType == spiBusAccesserTypePeriphio } func (psa *periphioSpiAccess) isSupported() bool { @@ -27,13 +24,24 @@ func (psa *periphioSpiAccess) isSupported() bool { return true } -func (gsa *gpioSpiAccess) createDevice( +func (*periphioSpiAccess) createDevice( busNum, chipNum, mode, bits int, maxSpeed int64, ) (gobot.SpiSystemDevicer, error) { - return newSpiGpio(gsa.cfg, maxSpeed) + return newSpiPeriphIo(busNum, chipNum, mode, bits, maxSpeed) +} + +func (gsa *gpioSpiAccess) isType(accesserType spiBusAccesserType) bool { + return accesserType == spiBusAccesserTypeGPIO } func (gsa *gpioSpiAccess) isSupported() bool { return true } + +func (gsa *gpioSpiAccess) createDevice( + busNum, chipNum, mode, bits int, + maxSpeed int64, +) (gobot.SpiSystemDevicer, error) { + return newSpiGpio(gsa.cfg, maxSpeed) +} diff --git a/system/spi_access_test.go b/system/spiaccess_test.go similarity index 62% rename from system/spi_access_test.go rename to system/spiaccess_test.go index e6f0266fc..f02660887 100644 --- a/system/spi_access_test.go +++ b/system/spiaccess_test.go @@ -6,6 +6,24 @@ import ( "github.com/stretchr/testify/assert" ) +func TestGpioSpi_isType(t *testing.T) { + // arrange + gsa := gpioSpiAccess{} + // act & assert GPIO + assert.True(t, gsa.isType(spiBusAccesserTypeGPIO)) + // act & assert Periphio + assert.False(t, gsa.isType(spiBusAccesserTypePeriphio)) +} + +func TestPeriphioSpi_isType(t *testing.T) { + // arrange + psa := periphioSpiAccess{} + // act & assert Periphio + assert.True(t, psa.isType(spiBusAccesserTypePeriphio)) + // act & assert GPIO + assert.False(t, psa.isType(spiBusAccesserTypeGPIO)) +} + func TestGpioSpi_isSupported(t *testing.T) { // arrange gsa := gpioSpiAccess{} diff --git a/system/sysfsfile_access.go b/system/sysfsfileaccess.go similarity index 100% rename from system/sysfsfile_access.go rename to system/sysfsfileaccess.go diff --git a/system/system.go b/system/system.go index eb4775f22..e2c16cf29 100644 --- a/system/system.go +++ b/system/system.go @@ -15,6 +15,13 @@ const ( digitalPinAccesserTypeSysfs ) +type spiBusAccesserType int + +const ( + spiBusAccesserTypePeriphio spiBusAccesserType = iota + spiBusAccesserTypeGPIO +) + // A File represents basic IO interactions with the underlying file system type File interface { Write(b []byte) (n int, err error) @@ -51,45 +58,186 @@ type systemCaller interface { // digitalPinAccesser represents unexposed interface to allow the switch between different implementations and // a mocked one type digitalPinAccesser interface { + isType(accesserType digitalPinAccesserType) bool 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 type spiAccesser interface { + isType(accesserType spiBusAccesserType) bool isSupported() bool createDevice(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiSystemDevicer, error) } +type accesserConfiguration struct { + debug bool + debugSpi bool + debugDigitalPin bool + useGpioSysfs *bool + spiGpioConfig *spiGpioConfig +} + // Accesser provides access to system calls, filesystem, implementation for digital pin and SPI type Accesser struct { + accesserCfg *accesserConfiguration sys systemCaller fs filesystem digitalPinAccess digitalPinAccesser spiAccess spiAccesser - debug bool } // 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 { +func NewAccesser(options ...AccesserOptionApplier) *Accesser { a := &Accesser{ - sys: &nativeSyscall{}, - fs: &nativeFilesystem{}, + accesserCfg: &accesserConfiguration{}, } - a.spiAccess = &periphioSpiAccess{fs: a.fs} - a.digitalPinAccess = &cdevDigitalPinAccess{fs: a.fs} - for _, option := range options { - if option == nil { + + for _, o := range options { + if o == nil { continue } - option(a) + o.apply(a.accesserCfg) } return a } +// AddAnalogSupport adds the support to access the analog features of the system, usually by sysfs. +func (a *Accesser) AddAnalogSupport() { + if a.fs == nil { + a.fs = &nativeFilesystem{} // for sysfs access + } +} + +// AddPWMSupport adds the support to access the PWM features of the system, usually by sysfs. +func (a *Accesser) AddPWMSupport() { + if a.fs == nil { + a.fs = &nativeFilesystem{} // for sysfs access + } +} + +// AddDigitalPinSupport adds the support to access the GPIO features of the system. Usually by character device or +// sysfs Kernel API. Related options can be applied here. +func (a *Accesser) AddDigitalPinSupport(options ...AccesserOptionApplier) { + for _, o := range options { + if o == nil { + continue + } + o.apply(a.accesserCfg) + } + + if a.fs == nil { + a.fs = &nativeFilesystem{} // for sysfs access or check for /dev/gpiochip* in cdev + } + + if a.accesserCfg.useGpioSysfs == nil || !*a.accesserCfg.useGpioSysfs { + dpa := &cdevDigitalPinAccess{fs: a.fs} + + if dpa.isSupported() || a.accesserCfg.useGpioSysfs == nil { + a.digitalPinAccess = dpa + + if a.accesserCfg.debug || a.accesserCfg.debugDigitalPin { + fmt.Printf("use cdev driver for digital pins with this chips: %v\n", dpa.chips) + } + + return + } + + if a.accesserCfg.debug || a.accesserCfg.debugDigitalPin { + fmt.Println("cdev driver not supported, fallback to sysfs driver") + } + } + + // currently sysfs is supported by all Kernels + dpa := &sysfsDigitalPinAccess{sfa: &sysfsFileAccess{fs: a.fs, readBufLen: 2}} + a.digitalPinAccess = dpa + if a.accesserCfg.debug || a.accesserCfg.debugDigitalPin { + fmt.Println("use sysfs driver for digital pins") + } +} + +// HasDigitalPinSysfsAccess returns whether the used digital pin accesser is a sysfs one. +// If no digital pin accesser is defined, returns false. +func (a *Accesser) HasDigitalPinSysfsAccess() bool { + return a.digitalPinAccess != nil && a.digitalPinAccess.isType(digitalPinAccesserTypeSysfs) +} + +// HasDigitalPinCdevAccess returns whether the used digital pin accesser is a sysfs one. +// If no digital pin accesser is defined, returns false. +func (a *Accesser) HasDigitalPinCdevAccess() bool { + return a.digitalPinAccess != nil && a.digitalPinAccess.isType(digitalPinAccesserTypeCdev) +} + +// AddI2CSupport adds the support to access the I2C features of the system, usually by syscall with character device. +func (a *Accesser) AddI2CSupport() { + if a.fs == nil { + a.fs = &nativeFilesystem{} // for access to the i2c character device, e.g. /dev/i2c-2 + } + + a.sys = &nativeSyscall{} +} + +// AddSPISupport adds the support to access the SPI features of the system, usually by character device or GPIOs. +// Related options can be applied here. +func (a *Accesser) AddSPISupport(options ...AccesserOptionApplier) { + for _, o := range options { + if o == nil { + continue + } + o.apply(a.accesserCfg) + } + + if a.fs == nil { + a.fs = &nativeFilesystem{} // to check for "/dev/spidev*" or access by GPIO (see AddDigitalPinSupport()) + } + + if a.accesserCfg.spiGpioConfig != nil { + // currently GPIO SPI access is always supported + a.accesserCfg.spiGpioConfig.debug = a.accesserCfg.debugSpi + a.spiAccess = &gpioSpiAccess{cfg: *a.accesserCfg.spiGpioConfig} + + if a.accesserCfg.debug || a.accesserCfg.debugSpi { + fmt.Printf("use gpio driver for SPI with this config: %s\n", a.accesserCfg.spiGpioConfig.String()) + } + + return + } + + gsa := &periphioSpiAccess{fs: a.fs} + if !gsa.isSupported() { + if a.accesserCfg.debug || a.accesserCfg.debugSpi { + fmt.Println("periphio driver not supported for SPI, please activate SPI or try to use GPIOs") + } + return + } + + a.spiAccess = gsa + if a.accesserCfg.debug || a.accesserCfg.debugSpi { + fmt.Println("use periphio driver for SPI") + } +} + +// HasSpiPeriphioAccess returns whether the used SPI accesser is periphio based. +// If SPI accesser is defined, returns false. +func (a *Accesser) HasSpiPeriphioAccess() bool { + return a.spiAccess != nil && a.spiAccess.isType(spiBusAccesserTypePeriphio) +} + +// HasSpiGpioAccess returns whether the used SPI accesser is GPIO based. +// If SPI accesser is defined, returns false. +func (a *Accesser) HasSpiGpioAccess() bool { + return a.spiAccess != nil && a.spiAccess.isType(spiBusAccesserTypeGPIO) +} + +// AddOneWireSupport adds the support to access the one wire features of the system, usually by sysfs. +func (a *Accesser) AddOneWireSupport() { + if a.fs == nil { + a.fs = &nativeFilesystem{} // for sysfs access + } +} + // UseMockDigitalPinAccess sets the digital pin handler accesser to the chosen one. Used only for tests. func (a *Accesser) UseMockDigitalPinAccess() *mockDigitalPinAccess { dpa := newMockDigitalPinAccess(a.digitalPinAccess) @@ -108,13 +256,16 @@ func (a *Accesser) UseMockSyscall() *mockSyscall { func (a *Accesser) UseMockFilesystem(files []string) *MockFilesystem { fs := newMockFilesystem(files) a.fs = fs - a.digitalPinAccess.setFs(fs) + if a.digitalPinAccess != nil { + a.digitalPinAccess.setFs(fs) + } + return fs } // UseMockSpi sets the SPI implementation of the accesser to the mocked one. Used only for tests. func (a *Accesser) UseMockSpi() *MockSpiAccess { - msc := &MockSpiAccess{} + msc := newMockSpiAccess(a.spiAccess) a.spiAccess = msc return msc } @@ -126,16 +277,6 @@ func (a *Accesser) NewDigitalPin(chip string, pin int, return a.digitalPinAccess.createPin(chip, pin, o...) } -// IsSysfsDigitalPinAccess returns whether the used digital pin accesser is a sysfs one. -func (a *Accesser) IsSysfsDigitalPinAccess() bool { - return a.digitalPinAccess.isType(digitalPinAccesserTypeSysfs) -} - -// IsCdevDigitalPinAccess returns whether the used digital pin accesser is a sysfs one. -func (a *Accesser) IsCdevDigitalPinAccess() bool { - return a.digitalPinAccess.isType(digitalPinAccesserTypeCdev) -} - // NewPWMPin returns a new system PWM pin, according to the given pin number. func (a *Accesser) NewPWMPin(path string, pin int, polNormIdent string, polInvIdent string) gobot.PWMPinner { sfa := &sysfsFileAccess{fs: a.fs, readBufLen: 200} diff --git a/system/system_options.go b/system/system_options.go deleted file mode 100644 index 0508c5511..000000000 --- a/system/system_options.go +++ /dev/null @@ -1,104 +0,0 @@ -package system - -import ( - "fmt" - - "gobot.io/x/gobot/v2" -) - -// 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 { - setSystemAccesserDebug(on bool) - setDigitalPinToCdevAccess() - setDigitalPinToSysfsAccess() - setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, ncsPin, sdoPin, sdiPin string) -} - -// WithSystemAccesserDebug can be used to switch on debug messages. -func WithSystemAccesserDebug() func(Optioner) { - return func(s Optioner) { - s.setSystemAccesserDebug(true) - } -} - -// WithDigitalPinCdevAccess can be used to change the default sysfs implementation for digital pins to the character -// device Kernel ABI. The access is provided by the go-gpiocdev package. -func WithDigitalPinCdevAccess() func(Optioner) { - return func(s Optioner) { - s.setDigitalPinToCdevAccess() - } -} - -// 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) { - s.setSpiToGpioAccess(p, sclkPin, ncsPin, sdoPin, sdiPin) - } -} - -func (a *Accesser) setSystemAccesserDebug(on bool) { - a.debug = on - fmt.Println("system accesser debug is now on") -} - -func (a *Accesser) setDigitalPinToCdevAccess() { - dpa := &cdevDigitalPinAccess{fs: a.fs} - if dpa.isSupported() { - a.digitalPinAccess = dpa - if a.debug { - fmt.Printf("use cdev driver for digital pins with this chips: %v\n", dpa.chips) - } - - return - } - if a.debug { - fmt.Println("cdev driver not supported, fallback to sysfs driver") - } -} - -func (a *Accesser) setDigitalPinToSysfsAccess() { - dpa := &sysfsDigitalPinAccess{sfa: &sysfsFileAccess{fs: a.fs, readBufLen: 2}} - if dpa.isSupported() { - a.digitalPinAccess = dpa - if a.debug { - fmt.Println("use sysfs driver for digital pins") - } - - return - } - if a.debug { - fmt.Println("sysfs driver not supported, fallback to cdev driver") - } -} - -func (a *Accesser) setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, ncsPin, sdoPin, sdiPin string) { - cfg := spiGpioConfig{ - pinProvider: p, - sclkPinID: sclkPin, - ncsPinID: ncsPin, - sdoPinID: sdoPin, - sdiPinID: sdiPin, - } - gsa := &gpioSpiAccess{cfg: cfg} - if gsa.isSupported() { - a.spiAccess = gsa - if a.debug { - fmt.Printf("use gpio driver for SPI with this config: %s\n", gsa.cfg.String()) - } - - return - } - if a.debug { - fmt.Println("gpio driver not supported for SPI, fallback to periphio") - } -} diff --git a/system/system_test.go b/system/system_test.go index 6459ed9cb..6cbc477c5 100644 --- a/system/system_test.go +++ b/system/system_test.go @@ -1,4 +1,3 @@ -//nolint:forcetypeassert // ok here package system import ( @@ -9,23 +8,92 @@ import ( ) func TestNewAccesser(t *testing.T) { - // act + // arrange & act a := NewAccesser() // assert - nativeSys := a.sys.(*nativeSyscall) - nativeFsSys := a.fs.(*nativeFilesystem) - perphioSpi := a.spiAccess.(*periphioSpiAccess) - gpiodDigitalPin := a.digitalPinAccess.(*cdevDigitalPinAccess) assert.NotNil(t, a) - assert.NotNil(t, nativeSys) - assert.NotNil(t, nativeFsSys) - assert.NotNil(t, perphioSpi) - assert.NotNil(t, gpiodDigitalPin) + assert.NotNil(t, a.accesserCfg) + assert.Nil(t, a.sys) + assert.Nil(t, a.fs) + assert.Nil(t, a.digitalPinAccess) + assert.Nil(t, a.spiAccess) } -func TestNewAccesser_NewSpiDevice(t *testing.T) { +func TestAccesserAddAnalogSupport(t *testing.T) { // arrange + a := NewAccesser() + // act + a.AddAnalogSupport() + // assert + assert.Nil(t, a.sys) + assert.Nil(t, a.digitalPinAccess) + assert.Nil(t, a.spiAccess) + require.NotNil(t, a.fs) + assert.IsType(t, &nativeFilesystem{}, a.fs) +} + +func TestAccesserAddDigitalPinSupport(t *testing.T) { + // arrange + a := NewAccesser() + // act + a.AddDigitalPinSupport() + // assert + assert.Nil(t, a.sys) + assert.Nil(t, a.spiAccess) + require.NotNil(t, a.fs) + assert.IsType(t, &nativeFilesystem{}, a.fs) + require.NotNil(t, a.digitalPinAccess) + assert.IsType(t, &cdevDigitalPinAccess{}, a.digitalPinAccess) +} + +func TestAccesserAddI2CSupport(t *testing.T) { + // assert + a := NewAccesser() + // act + a.AddI2CSupport() + // assert + assert.Nil(t, a.digitalPinAccess) + assert.Nil(t, a.spiAccess) + require.NotNil(t, a.fs) + assert.IsType(t, &nativeFilesystem{}, a.fs) + require.NotNil(t, a.sys) + assert.IsType(t, &nativeSyscall{}, a.sys) +} +func TestAccesserAddSPISupport(t *testing.T) { + // arrange + a := NewAccesser() + // act + a.AddSPISupport() // this writes a message, but we need this test case to assert a.fs + // assert + assert.Nil(t, a.sys) + assert.Nil(t, a.digitalPinAccess) + require.NotNil(t, a.fs) + assert.IsType(t, &nativeFilesystem{}, a.fs) + // arrange for periphio needed + a.UseMockFilesystem([]string{"/dev/spidev"}) + // act for apply periphio + a.AddSPISupport() + // assert + require.NotNil(t, a.spiAccess) + assert.IsType(t, &periphioSpiAccess{}, a.spiAccess) +} + +func TestAccesserAddOneWireSupport(t *testing.T) { + // arrange + a := NewAccesser() + // act + a.AddOneWireSupport() + // assert + assert.Nil(t, a.sys) + assert.Nil(t, a.digitalPinAccess) + assert.Nil(t, a.spiAccess) + require.NotNil(t, a.fs) + assert.IsType(t, &nativeFilesystem{}, a.fs) +} + +func TestNewAccesser_NewSpiDevice(t *testing.T) { + // arrange const ( busNum = 15 chipNum = 14 @@ -46,45 +114,3 @@ func TestNewAccesser_NewSpiDevice(t *testing.T) { assert.Equal(t, bits, spi.bits) assert.Equal(t, maxSpeed, spi.maxSpeed) } - -func TestNewAccesser_IsSysfsDigitalPinAccess(t *testing.T) { - tests := map[string]struct { - sysfsAccesser bool - wantCdev bool - }{ - "default_accesser_gpiod": { - wantCdev: true, - }, - "accesser_sysfs": { - sysfsAccesser: true, - wantCdev: false, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAccesser() - if tc.sysfsAccesser { - WithDigitalPinSysfsAccess()(a) - } - // act - gotCdev := a.IsCdevDigitalPinAccess() - gotSysfs := a.IsSysfsDigitalPinAccess() - // assert - assert.NotNil(t, a) - if tc.wantCdev { - assert.True(t, gotCdev) - assert.False(t, gotSysfs) - dpaGpioCdev := a.digitalPinAccess.(*cdevDigitalPinAccess) - assert.NotNil(t, dpaGpioCdev) - assert.Equal(t, a.fs.(*nativeFilesystem), dpaGpioCdev.fs) - } else { - assert.False(t, gotCdev) - assert.True(t, gotSysfs) - dpaSys := a.digitalPinAccess.(*sysfsDigitalPinAccess) - assert.NotNil(t, dpaSys) - assert.Equal(t, a.fs.(*nativeFilesystem), dpaSys.sfa.fs) - } - }) - } -} diff --git a/system/systemoptions.go b/system/systemoptions.go new file mode 100644 index 000000000..c71cdc29a --- /dev/null +++ b/system/systemoptions.go @@ -0,0 +1,104 @@ +package system + +import ( + "gobot.io/x/gobot/v2" +) + +// accesserOptionApplier 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().Add..". +// The interface needs to be implemented by each configurable option type. +type AccesserOptionApplier interface { + apply(cfg *accesserConfiguration) +} + +type systemAccesserDebugOption bool + +type systemDigitalPinDebugOption bool + +type systemSpiDebugOption bool + +type systemUseDigitalPinSysfsOption bool + +type systemUseSpiGpioOption spiGpioConfig + +// WithSystemAccesserDebug can be used to switch on debug messages. +func WithSystemAccesserDebug() systemAccesserDebugOption { + return systemAccesserDebugOption(true) +} + +// WithDigitalPinDebug can be used to switch on debug messages for digital pins. +func WithDigitalPinDebug() systemDigitalPinDebugOption { + return systemDigitalPinDebugOption(true) +} + +// WithSpiDebug can be used to switch on debug messages for SPI. +func WithSpiDebug() systemSpiDebugOption { + return systemSpiDebugOption(true) +} + +// WithDigitalPinSysfsAccess can be used to change the default character device implementation for digital pins to the +// legacy sysfs Kernel ABI. +func WithDigitalPinSysfsAccess() systemUseDigitalPinSysfsOption { + return systemUseDigitalPinSysfsOption(true) +} + +// WithDigitalPinCdevAccess can be used to change the default sysfs implementation for digital pins in old platforms to +// test the character device Kernel ABI. The access is provided by the go-gpiocdev package. +func WithDigitalPinCdevAccess() systemUseDigitalPinSysfsOption { + return systemUseDigitalPinSysfsOption(false) +} + +// WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage. +func WithSpiGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, ncsPin, sdoPin, sdiPin string) systemUseSpiGpioOption { + o := systemUseSpiGpioOption{ + pinProvider: p, + sclkPinID: sclkPin, + ncsPinID: ncsPin, + sdoPinID: sdoPin, + sdiPinID: sdiPin, + } + + return o +} + +func (o systemAccesserDebugOption) String() string { + return "switch on system accesser debugging option" +} + +func (o systemDigitalPinDebugOption) String() string { + return "switch on system digital pin debugging option" +} + +func (o systemSpiDebugOption) String() string { + return "switch on system SPI debugging option" +} + +func (o systemUseDigitalPinSysfsOption) String() string { + return "system accesser use sysfs vs. cdev for digital pins option" +} + +func (o systemUseSpiGpioOption) String() string { + return "system accesser use discrete GPIOs for SPI option" +} + +func (o systemAccesserDebugOption) apply(cfg *accesserConfiguration) { + cfg.debug = bool(o) +} + +func (o systemDigitalPinDebugOption) apply(cfg *accesserConfiguration) { + cfg.debugDigitalPin = bool(o) +} + +func (o systemSpiDebugOption) apply(cfg *accesserConfiguration) { + cfg.debugSpi = bool(o) +} + +func (o systemUseDigitalPinSysfsOption) apply(cfg *accesserConfiguration) { + c := bool(o) + cfg.useGpioSysfs = &c +} + +func (o systemUseSpiGpioOption) apply(cfg *accesserConfiguration) { + c := spiGpioConfig(o) + cfg.spiGpioConfig = &c +} diff --git a/system/systemoptions_test.go b/system/systemoptions_test.go new file mode 100644 index 000000000..811c2b6f9 --- /dev/null +++ b/system/systemoptions_test.go @@ -0,0 +1,96 @@ +//nolint:forcetypeassert // ok here +package system + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWithDigitalPinOptionAccess_HasDigitalPinCdevAccess_HasDigitalPinSysfsAccess(t *testing.T) { + // note: default accesser already tested on tests with AddDigitalPinSupport() + tests := map[string]struct { + initialAccesser digitalPinAccesser + option AccesserOptionApplier + wantCdev bool + }{ + "with_cdev": { + initialAccesser: &sysfsDigitalPinAccess{}, + option: WithDigitalPinCdevAccess(), + wantCdev: true, + }, + "with_sysfs": { + initialAccesser: &cdevDigitalPinAccess{}, + option: WithDigitalPinSysfsAccess(), + wantCdev: false, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + a := NewAccesser() + a.digitalPinAccess = tc.initialAccesser + a.AddDigitalPinSupport(tc.option) + // act + gotCdev := a.HasDigitalPinCdevAccess() + gotSysfs := a.HasDigitalPinSysfsAccess() + // assert + if tc.wantCdev { + assert.True(t, gotCdev) + assert.False(t, gotSysfs) + assert.IsType(t, &cdevDigitalPinAccess{}, a.digitalPinAccess) + assert.IsType(t, &nativeFilesystem{}, a.digitalPinAccess.(*cdevDigitalPinAccess).fs) + } else { + assert.False(t, gotCdev) + assert.True(t, gotSysfs) + assert.IsType(t, &sysfsDigitalPinAccess{}, a.digitalPinAccess) + assert.IsType(t, &nativeFilesystem{}, a.digitalPinAccess.(*sysfsDigitalPinAccess).sfa.fs) + } + }) + } +} + +func TestWithSpiOptionAccess_HasSpiPeriphioAccess_HasSpiGpioAccess(t *testing.T) { + // note: default accesser already tested on tests with AddSPISupport() + tests := map[string]struct { + initialAccesser spiAccesser + option AccesserOptionApplier + wantPeriphio bool + }{ + "with_periphio": { + initialAccesser: &gpioSpiAccess{}, + wantPeriphio: true, + }, + "withr_sysfs": { + initialAccesser: &periphioSpiAccess{}, + option: WithSpiGpioAccess(nil, "2", "3", "4", "5"), + wantPeriphio: false, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + a := NewAccesser() + a.spiAccess = tc.initialAccesser + // arrange for periphio needed + a.UseMockFilesystem([]string{"/dev/spidev"}) + a.AddDigitalPinSupport() + a.AddSPISupport(tc.option) + // act + gotPeriphio := a.HasSpiPeriphioAccess() + gotGpio := a.HasSpiGpioAccess() + // assert + require.NotNil(t, a.spiAccess) + if tc.wantPeriphio { + assert.True(t, gotPeriphio) + assert.False(t, gotGpio) + assert.IsType(t, &periphioSpiAccess{}, a.spiAccess) + } else { + assert.False(t, gotPeriphio) + assert.True(t, gotGpio) + assert.IsType(t, &gpioSpiAccess{}, a.spiAccess) + } + }) + } +}