gobot.io/x/gobot/v2@v2.1.0/platforms/beaglebone/beaglebone_adaptor.go (about)

     1  package beaglebone
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  
    10  	multierror "github.com/hashicorp/go-multierror"
    11  	"gobot.io/x/gobot/v2"
    12  	"gobot.io/x/gobot/v2/platforms/adaptors"
    13  	"gobot.io/x/gobot/v2/system"
    14  )
    15  
    16  type pwmPinData struct {
    17  	channel   int
    18  	dir       string
    19  	dirRegexp string
    20  }
    21  
    22  const (
    23  	pwmPeriodDefault = 500000 // 0.5 ms = 2 kHz
    24  
    25  	defaultI2cBusNumber = 2
    26  
    27  	defaultSpiBusNumber  = 0
    28  	defaultSpiChipNumber = 0
    29  	defaultSpiMode       = 0
    30  	defaultSpiBitsNumber = 8
    31  	defaultSpiMaxSpeed   = 500000
    32  )
    33  
    34  // Adaptor is the gobot.Adaptor representation for the Beaglebone Black/Green
    35  type Adaptor struct {
    36  	name  string
    37  	sys   *system.Accesser
    38  	mutex sync.Mutex
    39  	*adaptors.DigitalPinsAdaptor
    40  	*adaptors.PWMPinsAdaptor
    41  	*adaptors.I2cBusAdaptor
    42  	*adaptors.SpiBusAdaptor
    43  	usrLed       string
    44  	analogPath   string
    45  	pinMap       map[string]int
    46  	pwmPinMap    map[string]pwmPinData
    47  	analogPinMap map[string]string
    48  }
    49  
    50  // NewAdaptor returns a new Beaglebone Black/Green Adaptor
    51  //
    52  // Optional parameters:
    53  //
    54  //	adaptors.WithGpiodAccess():	use character device gpiod driver instead of sysfs
    55  //	adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso):	use GPIO's instead of /dev/spidev#.#
    56  func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
    57  	sys := system.NewAccesser()
    58  	c := &Adaptor{
    59  		name:         gobot.DefaultName("BeagleboneBlack"),
    60  		sys:          sys,
    61  		pinMap:       bbbPinMap,
    62  		pwmPinMap:    bbbPwmPinMap,
    63  		analogPinMap: bbbAnalogPinMap,
    64  		usrLed:       "/sys/class/leds/beaglebone:green:",
    65  		analogPath:   "/sys/bus/iio/devices/iio:device0",
    66  	}
    67  
    68  	c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateAndMuxDigitalPin, opts...)
    69  	c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translateAndMuxPWMPin,
    70  		adaptors.WithPWMPinDefaultPeriod(pwmPeriodDefault))
    71  	c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
    72  	c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,
    73  		defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed)
    74  	return c
    75  }
    76  
    77  // Name returns the Adaptor name
    78  func (c *Adaptor) Name() string { return c.name }
    79  
    80  // SetName sets the Adaptor name
    81  func (c *Adaptor) SetName(n string) { c.name = n }
    82  
    83  // Connect create new connection to board and pins.
    84  func (c *Adaptor) Connect() error {
    85  	c.mutex.Lock()
    86  	defer c.mutex.Unlock()
    87  
    88  	if err := c.SpiBusAdaptor.Connect(); err != nil {
    89  		return err
    90  	}
    91  
    92  	if err := c.I2cBusAdaptor.Connect(); err != nil {
    93  		return err
    94  	}
    95  
    96  	if err := c.PWMPinsAdaptor.Connect(); err != nil {
    97  		return err
    98  	}
    99  	return c.DigitalPinsAdaptor.Connect()
   100  }
   101  
   102  // Finalize releases all i2c devices and exported analog, digital, pwm pins.
   103  func (c *Adaptor) Finalize() error {
   104  	c.mutex.Lock()
   105  	defer c.mutex.Unlock()
   106  
   107  	err := c.DigitalPinsAdaptor.Finalize()
   108  
   109  	if e := c.PWMPinsAdaptor.Finalize(); e != nil {
   110  		err = multierror.Append(err, e)
   111  	}
   112  
   113  	if e := c.I2cBusAdaptor.Finalize(); e != nil {
   114  		err = multierror.Append(err, e)
   115  	}
   116  
   117  	if e := c.SpiBusAdaptor.Finalize(); e != nil {
   118  		err = multierror.Append(err, e)
   119  	}
   120  	return err
   121  }
   122  
   123  // DigitalWrite writes a digital value to specified pin.
   124  // valid usr pin values are usr0, usr1, usr2 and usr3
   125  func (c *Adaptor) DigitalWrite(id string, val byte) error {
   126  	c.mutex.Lock()
   127  	defer c.mutex.Unlock()
   128  
   129  	if strings.Contains(id, "usr") {
   130  		fi, e := c.sys.OpenFile(c.usrLed+id+"/brightness", os.O_WRONLY|os.O_APPEND, 0666)
   131  		defer fi.Close() //nolint:staticcheck // for historical reasons
   132  		if e != nil {
   133  			return e
   134  		}
   135  		_, err := fi.WriteString(strconv.Itoa(int(val)))
   136  		return err
   137  	}
   138  
   139  	return c.DigitalPinsAdaptor.DigitalWrite(id, val)
   140  }
   141  
   142  // AnalogRead returns an analog value from specified pin
   143  func (c *Adaptor) AnalogRead(pin string) (val int, err error) {
   144  	analogPin, err := c.translateAnalogPin(pin)
   145  	if err != nil {
   146  		return
   147  	}
   148  	fi, err := c.sys.OpenFile(fmt.Sprintf("%v/%v", c.analogPath, analogPin), os.O_RDONLY, 0644)
   149  	defer fi.Close() //nolint:staticcheck // for historical reasons
   150  
   151  	if err != nil {
   152  		return
   153  	}
   154  
   155  	var buf = make([]byte, 1024)
   156  	_, err = fi.Read(buf)
   157  	if err != nil {
   158  		return
   159  	}
   160  
   161  	val, _ = strconv.Atoi(strings.Split(string(buf), "\n")[0])
   162  	return
   163  }
   164  
   165  func (c *Adaptor) validateSpiBusNumber(busNr int) error {
   166  	// Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x.
   167  	// x is the chip number <255
   168  	if (busNr < 0) || (busNr > 1) {
   169  		return fmt.Errorf("Bus number %d out of range", busNr)
   170  	}
   171  	return nil
   172  }
   173  
   174  func (c *Adaptor) validateI2cBusNumber(busNr int) error {
   175  	// Valid bus number is either 0 or 2 which corresponds to /dev/i2c-0 or /dev/i2c-2.
   176  	if (busNr != 0) && (busNr != 2) {
   177  		return fmt.Errorf("Bus number %d out of range", busNr)
   178  	}
   179  	return nil
   180  }
   181  
   182  // translateAnalogPin converts analog pin name to pin position
   183  func (c *Adaptor) translateAnalogPin(pin string) (string, error) {
   184  	if val, ok := c.analogPinMap[pin]; ok {
   185  		return val, nil
   186  	}
   187  	return "", fmt.Errorf("Not a valid analog pin")
   188  }
   189  
   190  // translatePin converts digital pin name to pin position
   191  func (c *Adaptor) translateAndMuxDigitalPin(id string) (string, int, error) {
   192  	line, ok := c.pinMap[id]
   193  	if !ok {
   194  		return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id)
   195  	}
   196  	// mux is done by id, not by line
   197  	if err := c.muxPin(id, "gpio"); err != nil {
   198  		return "", -1, err
   199  	}
   200  	return "", line, nil
   201  }
   202  
   203  func (c *Adaptor) translateAndMuxPWMPin(id string) (string, int, error) {
   204  	pinInfo, ok := c.pwmPinMap[id]
   205  	if !ok {
   206  		return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id)
   207  	}
   208  
   209  	path, err := pinInfo.findPWMDir(c.sys)
   210  	if err != nil {
   211  		return "", -1, err
   212  	}
   213  
   214  	if err := c.muxPin(id, "pwm"); err != nil {
   215  		return "", -1, err
   216  	}
   217  
   218  	return path, pinInfo.channel, nil
   219  }
   220  
   221  func (p pwmPinData) findPWMDir(sys *system.Accesser) (dir string, err error) {
   222  	items, _ := sys.Find(p.dir, p.dirRegexp)
   223  	if len(items) == 0 {
   224  		return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'", p.dirRegexp, p.dir)
   225  	}
   226  
   227  	dir = items[0]
   228  	info, err := sys.Stat(dir)
   229  	if err != nil {
   230  		return "", fmt.Errorf("Error (%v) on access '%s'", err, dir)
   231  	}
   232  	if !info.IsDir() {
   233  		return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir)
   234  	}
   235  
   236  	return
   237  }
   238  
   239  func (c *Adaptor) muxPin(pin, cmd string) error {
   240  	path := fmt.Sprintf("/sys/devices/platform/ocp/ocp:%s_pinmux/state", pin)
   241  	fi, e := c.sys.OpenFile(path, os.O_WRONLY, 0666)
   242  	defer fi.Close() //nolint:staticcheck // for historical reasons
   243  	if e != nil {
   244  		return e
   245  	}
   246  	_, e = fi.WriteString(cmd)
   247  	return e
   248  }