gobot.io/x/gobot/v2@v2.1.0/platforms/adaptors/digitalpinsadaptor.go (about)

     1  package adaptors
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  	"gobot.io/x/gobot/v2"
    10  	"gobot.io/x/gobot/v2/system"
    11  )
    12  
    13  type digitalPinTranslator func(pin string) (chip string, line int, err error)
    14  type digitalPinInitializer func(gobot.DigitalPinner) error
    15  
    16  type digitalPinsOptioner interface {
    17  	setDigitalPinInitializer(digitalPinInitializer)
    18  	setDigitalPinsForSystemGpiod()
    19  	setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin string)
    20  	prepareDigitalPinsActiveLow(pin string, otherPins ...string)
    21  	prepareDigitalPinsPullDown(pin string, otherPins ...string)
    22  	prepareDigitalPinsPullUp(pin string, otherPins ...string)
    23  	prepareDigitalPinsOpenDrain(pin string, otherPins ...string)
    24  	prepareDigitalPinsOpenSource(pin string, otherPins ...string)
    25  	prepareDigitalPinDebounce(pin string, period time.Duration)
    26  	prepareDigitalPinEventOnFallingEdge(pin string, handler func(lineOffset int, timestamp time.Duration,
    27  		detectedEdge string, seqno uint32, lseqno uint32))
    28  	prepareDigitalPinEventOnRisingEdge(pin string, handler func(lineOffset int, timestamp time.Duration,
    29  		detectedEdge string, seqno uint32, lseqno uint32))
    30  	prepareDigitalPinEventOnBothEdges(pin string, handler func(lineOffset int, timestamp time.Duration,
    31  		detectedEdge string, seqno uint32, lseqno uint32))
    32  }
    33  
    34  // DigitalPinsAdaptor is a adaptor for digital pins, normally used for composition in platforms.
    35  type DigitalPinsAdaptor struct {
    36  	sys        *system.Accesser
    37  	translate  digitalPinTranslator
    38  	initialize digitalPinInitializer
    39  	pins       map[string]gobot.DigitalPinner
    40  	pinOptions map[string][]func(gobot.DigitalPinOptioner) bool
    41  	mutex      sync.Mutex
    42  }
    43  
    44  // NewDigitalPinsAdaptor provides the access to digital pins of the board. It supports sysfs and gpiod system drivers.
    45  // This is decided by the given accesser. The translator is used to adapt the pin header naming, which is given by user,
    46  // to the internal file name or chip/line nomenclature. This varies by each platform. If for some reasons the default
    47  // initializer is not suitable, it can be given by the option "WithDigitalPinInitializer()". This is especially needed,
    48  // if some values needs to be adjusted after the pin was created but before the pin is exported.
    49  func NewDigitalPinsAdaptor(sys *system.Accesser, t digitalPinTranslator, options ...func(Optioner)) *DigitalPinsAdaptor {
    50  	a := &DigitalPinsAdaptor{
    51  		sys:        sys,
    52  		translate:  t,
    53  		initialize: func(pin gobot.DigitalPinner) error { return pin.Export() },
    54  	}
    55  	for _, option := range options {
    56  		option(a)
    57  	}
    58  	return a
    59  }
    60  
    61  // WithDigitalPinInitializer can be used to substitute the default initializer.
    62  func WithDigitalPinInitializer(pc digitalPinInitializer) func(Optioner) {
    63  	return func(o Optioner) {
    64  		a, ok := o.(digitalPinsOptioner)
    65  		if ok {
    66  			a.setDigitalPinInitializer(pc)
    67  		}
    68  	}
    69  }
    70  
    71  // WithGpiodAccess can be used to change the default sysfs implementation to the character device Kernel ABI.
    72  // The access is provided by the gpiod package.
    73  func WithGpiodAccess() func(Optioner) {
    74  	return func(o Optioner) {
    75  		a, ok := o.(digitalPinsOptioner)
    76  		if ok {
    77  			a.setDigitalPinsForSystemGpiod()
    78  		}
    79  	}
    80  }
    81  
    82  // WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage.
    83  func WithSpiGpioAccess(sclkPin, nssPin, mosiPin, misoPin string) func(Optioner) {
    84  	return func(o Optioner) {
    85  		a, ok := o.(digitalPinsOptioner)
    86  		if ok {
    87  			a.setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin)
    88  		}
    89  	}
    90  }
    91  
    92  // WithGpiosActiveLow prepares the given pins for inverse reaction on next initialize.
    93  // This is working for inputs and outputs.
    94  func WithGpiosActiveLow(pin string, otherPins ...string) func(Optioner) {
    95  	return func(o Optioner) {
    96  		a, ok := o.(digitalPinsOptioner)
    97  		if ok {
    98  			a.prepareDigitalPinsActiveLow(pin, otherPins...)
    99  		}
   100  	}
   101  }
   102  
   103  // WithGpiosPullDown prepares the given pins to be pulled down (high impedance to GND) on next initialize.
   104  // This is working for inputs and outputs since Kernel 5.5, but will be ignored with sysfs ABI.
   105  func WithGpiosPullDown(pin string, otherPins ...string) func(Optioner) {
   106  	return func(o Optioner) {
   107  		a, ok := o.(digitalPinsOptioner)
   108  		if ok {
   109  			a.prepareDigitalPinsPullDown(pin, otherPins...)
   110  		}
   111  	}
   112  }
   113  
   114  // WithGpiosPullUp prepares the given pins to be pulled up (high impedance to VDD) on next initialize.
   115  // This is working for inputs and outputs since Kernel 5.5, but will be ignored with sysfs ABI.
   116  func WithGpiosPullUp(pin string, otherPins ...string) func(Optioner) {
   117  	return func(o Optioner) {
   118  		a, ok := o.(digitalPinsOptioner)
   119  		if ok {
   120  			a.prepareDigitalPinsPullUp(pin, otherPins...)
   121  		}
   122  	}
   123  }
   124  
   125  // WithGpiosOpenDrain prepares the given output pins to be driven with open drain/collector on next initialize.
   126  // This will be ignored for inputs or with sysfs ABI.
   127  func WithGpiosOpenDrain(pin string, otherPins ...string) func(Optioner) {
   128  	return func(o Optioner) {
   129  		a, ok := o.(digitalPinsOptioner)
   130  		if ok {
   131  			a.prepareDigitalPinsOpenDrain(pin, otherPins...)
   132  		}
   133  	}
   134  }
   135  
   136  // WithGpiosOpenSource prepares the given output pins to be driven with open source/emitter on next initialize.
   137  // This will be ignored for inputs or with sysfs ABI.
   138  func WithGpiosOpenSource(pin string, otherPins ...string) func(Optioner) {
   139  	return func(o Optioner) {
   140  		a, ok := o.(digitalPinsOptioner)
   141  		if ok {
   142  			a.prepareDigitalPinsOpenSource(pin, otherPins...)
   143  		}
   144  	}
   145  }
   146  
   147  // WithGpioDebounce prepares the given input pin to be debounced on next initialize.
   148  // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI.
   149  func WithGpioDebounce(pin string, period time.Duration) func(Optioner) {
   150  	return func(o Optioner) {
   151  		a, ok := o.(digitalPinsOptioner)
   152  		if ok {
   153  			a.prepareDigitalPinDebounce(pin, period)
   154  		}
   155  	}
   156  }
   157  
   158  // WithGpioEventOnFallingEdge prepares the given input pin to be generate an event on falling edge.
   159  // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI.
   160  func WithGpioEventOnFallingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string,
   161  	seqno uint32, lseqno uint32)) func(Optioner) {
   162  	return func(o Optioner) {
   163  		a, ok := o.(digitalPinsOptioner)
   164  		if ok {
   165  			a.prepareDigitalPinEventOnFallingEdge(pin, handler)
   166  		}
   167  	}
   168  }
   169  
   170  // WithGpioEventOnRisingEdge prepares the given input pin to be generate an event on rising edge.
   171  // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI.
   172  func WithGpioEventOnRisingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string,
   173  	seqno uint32, lseqno uint32)) func(Optioner) {
   174  	return func(o Optioner) {
   175  		a, ok := o.(digitalPinsOptioner)
   176  		if ok {
   177  			a.prepareDigitalPinEventOnRisingEdge(pin, handler)
   178  		}
   179  	}
   180  }
   181  
   182  // WithGpioEventOnBothEdges prepares the given input pin to be generate an event on rising and falling edges.
   183  // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI.
   184  func WithGpioEventOnBothEdges(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string,
   185  	seqno uint32, lseqno uint32)) func(Optioner) {
   186  	return func(o Optioner) {
   187  		a, ok := o.(digitalPinsOptioner)
   188  		if ok {
   189  			a.prepareDigitalPinEventOnBothEdges(pin, handler)
   190  		}
   191  	}
   192  }
   193  
   194  // Connect prepare new connection to digital pins.
   195  func (a *DigitalPinsAdaptor) Connect() error {
   196  	a.mutex.Lock()
   197  	defer a.mutex.Unlock()
   198  
   199  	a.pins = make(map[string]gobot.DigitalPinner)
   200  	return nil
   201  }
   202  
   203  // Finalize closes connection to digital pins
   204  func (a *DigitalPinsAdaptor) Finalize() (err error) {
   205  	a.mutex.Lock()
   206  	defer a.mutex.Unlock()
   207  
   208  	for _, pin := range a.pins {
   209  		if pin != nil {
   210  			if e := pin.Unexport(); e != nil {
   211  				err = multierror.Append(err, e)
   212  			}
   213  		}
   214  	}
   215  	a.pins = nil
   216  	a.pinOptions = nil
   217  	return
   218  }
   219  
   220  // DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
   221  // Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
   222  func (a *DigitalPinsAdaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
   223  	a.mutex.Lock()
   224  	defer a.mutex.Unlock()
   225  
   226  	return a.digitalPin(id)
   227  }
   228  
   229  // DigitalRead reads digital value from pin
   230  func (a *DigitalPinsAdaptor) DigitalRead(id string) (int, error) {
   231  	a.mutex.Lock()
   232  	defer a.mutex.Unlock()
   233  
   234  	pin, err := a.digitalPin(id, system.WithPinDirectionInput())
   235  	if err != nil {
   236  		return 0, err
   237  	}
   238  	return pin.Read()
   239  }
   240  
   241  // DigitalWrite writes digital value to specified pin
   242  func (a *DigitalPinsAdaptor) DigitalWrite(id string, val byte) error {
   243  	a.mutex.Lock()
   244  	defer a.mutex.Unlock()
   245  
   246  	pin, err := a.digitalPin(id, system.WithPinDirectionOutput(int(val)))
   247  	if err != nil {
   248  		return err
   249  	}
   250  	return pin.Write(int(val))
   251  }
   252  
   253  func (a *DigitalPinsAdaptor) setDigitalPinInitializer(pinInit digitalPinInitializer) {
   254  	a.initialize = pinInit
   255  }
   256  
   257  func (a *DigitalPinsAdaptor) setDigitalPinsForSystemGpiod() {
   258  	system.WithDigitalPinGpiodAccess()(a.sys)
   259  }
   260  
   261  func (a *DigitalPinsAdaptor) setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin string) {
   262  	system.WithSpiGpioAccess(a, sclkPin, nssPin, mosiPin, misoPin)(a.sys)
   263  }
   264  
   265  func (a *DigitalPinsAdaptor) prepareDigitalPinsActiveLow(id string, otherIDs ...string) {
   266  	ids := []string{id}
   267  	ids = append(ids, otherIDs...)
   268  
   269  	if a.pinOptions == nil {
   270  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   271  	}
   272  
   273  	for _, i := range ids {
   274  		a.pinOptions[i] = append(a.pinOptions[i], system.WithPinActiveLow())
   275  	}
   276  }
   277  
   278  func (a *DigitalPinsAdaptor) prepareDigitalPinsPullDown(id string, otherIDs ...string) {
   279  	ids := []string{id}
   280  	ids = append(ids, otherIDs...)
   281  
   282  	if a.pinOptions == nil {
   283  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   284  	}
   285  
   286  	for _, i := range ids {
   287  		a.pinOptions[i] = append(a.pinOptions[i], system.WithPinPullDown())
   288  	}
   289  }
   290  
   291  func (a *DigitalPinsAdaptor) prepareDigitalPinsPullUp(id string, otherIDs ...string) {
   292  	ids := []string{id}
   293  	ids = append(ids, otherIDs...)
   294  
   295  	if a.pinOptions == nil {
   296  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   297  	}
   298  
   299  	for _, i := range ids {
   300  		a.pinOptions[i] = append(a.pinOptions[i], system.WithPinPullUp())
   301  	}
   302  }
   303  
   304  func (a *DigitalPinsAdaptor) prepareDigitalPinsOpenDrain(id string, otherIDs ...string) {
   305  	ids := []string{id}
   306  	ids = append(ids, otherIDs...)
   307  
   308  	if a.pinOptions == nil {
   309  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   310  	}
   311  
   312  	for _, i := range ids {
   313  		a.pinOptions[i] = append(a.pinOptions[i], system.WithPinOpenDrain())
   314  	}
   315  }
   316  
   317  func (a *DigitalPinsAdaptor) prepareDigitalPinsOpenSource(id string, otherIDs ...string) {
   318  	ids := []string{id}
   319  	ids = append(ids, otherIDs...)
   320  
   321  	if a.pinOptions == nil {
   322  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   323  	}
   324  
   325  	for _, i := range ids {
   326  		a.pinOptions[i] = append(a.pinOptions[i], system.WithPinOpenSource())
   327  	}
   328  }
   329  
   330  func (a *DigitalPinsAdaptor) prepareDigitalPinDebounce(id string, period time.Duration) {
   331  	if a.pinOptions == nil {
   332  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   333  	}
   334  
   335  	a.pinOptions[id] = append(a.pinOptions[id], system.WithPinDebounce(period))
   336  }
   337  
   338  func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnFallingEdge(id string, handler func(int, time.Duration, string,
   339  	uint32, uint32)) {
   340  	if a.pinOptions == nil {
   341  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   342  	}
   343  
   344  	a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnFallingEdge(handler))
   345  }
   346  
   347  func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnRisingEdge(id string, handler func(int, time.Duration, string,
   348  	uint32, uint32)) {
   349  	if a.pinOptions == nil {
   350  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   351  	}
   352  
   353  	a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnRisingEdge(handler))
   354  }
   355  
   356  func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnBothEdges(id string, handler func(int, time.Duration, string,
   357  	uint32, uint32)) {
   358  	if a.pinOptions == nil {
   359  		a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool)
   360  	}
   361  
   362  	a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnBothEdges(handler))
   363  }
   364  
   365  func (a *DigitalPinsAdaptor) digitalPin(id string, opts ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
   366  	if a.pins == nil {
   367  		return nil, fmt.Errorf("not connected for pin %s", id)
   368  	}
   369  
   370  	o := append(a.pinOptions[id], opts...)
   371  	pin := a.pins[id]
   372  
   373  	if pin == nil {
   374  		chip, line, err := a.translate(id)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  		pin = a.sys.NewDigitalPin(chip, line, o...)
   379  		if err = a.initialize(pin); err != nil {
   380  			return nil, err
   381  		}
   382  		a.pins[id] = pin
   383  	} else {
   384  		if err := pin.ApplyOptions(o...); err != nil {
   385  			return nil, err
   386  		}
   387  	}
   388  
   389  	return pin, nil
   390  }