gobot.io/x/gobot/v2@v2.1.0/platforms/chip/chip_adaptor.go (about)

     1  package chip
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  
    11  	multierror "github.com/hashicorp/go-multierror"
    12  	"gobot.io/x/gobot/v2"
    13  	"gobot.io/x/gobot/v2/platforms/adaptors"
    14  	"gobot.io/x/gobot/v2/system"
    15  )
    16  
    17  const defaultI2cBusNumber = 1
    18  
    19  // Valids pins are the XIO-P0 through XIO-P7 pins from the
    20  // extender (pins 13-20 on header 14), as well as the SoC pins
    21  // aka all the other pins.
    22  type sysfsPin struct {
    23  	pin    int
    24  	pwmPin int
    25  }
    26  
    27  // Adaptor represents a Gobot Adaptor for a C.H.I.P.
    28  type Adaptor struct {
    29  	name   string
    30  	sys    *system.Accesser
    31  	mutex  sync.Mutex
    32  	pinmap map[string]sysfsPin
    33  	*adaptors.DigitalPinsAdaptor
    34  	*adaptors.PWMPinsAdaptor
    35  	*adaptors.I2cBusAdaptor
    36  }
    37  
    38  // NewAdaptor creates a C.H.I.P. Adaptor
    39  //
    40  // Optional parameters:
    41  //
    42  //	adaptors.WithGpiodAccess():	use character device gpiod driver instead of sysfs
    43  //	adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso):	use GPIO's instead of /dev/spidev#.#
    44  func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
    45  	sys := system.NewAccesser()
    46  	c := &Adaptor{
    47  		name: gobot.DefaultName("CHIP"),
    48  		sys:  sys,
    49  	}
    50  
    51  	c.pinmap = chipPins
    52  	baseAddr, _ := getXIOBase()
    53  	for i := 0; i < 8; i++ {
    54  		pin := fmt.Sprintf("XIO-P%d", i)
    55  		c.pinmap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1}
    56  	}
    57  
    58  	c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
    59  	c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin)
    60  	c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
    61  	return c
    62  }
    63  
    64  // NewProAdaptor creates a C.H.I.P. Pro Adaptor
    65  func NewProAdaptor() *Adaptor {
    66  	c := NewAdaptor()
    67  	c.name = gobot.DefaultName("CHIP Pro")
    68  	c.pinmap = chipProPins
    69  	return c
    70  }
    71  
    72  // Name returns the name of the Adaptor
    73  func (c *Adaptor) Name() string { return c.name }
    74  
    75  // SetName sets the name of the Adaptor
    76  func (c *Adaptor) SetName(n string) { c.name = n }
    77  
    78  // Connect create new connection to board and pins.
    79  func (c *Adaptor) Connect() error {
    80  	c.mutex.Lock()
    81  	defer c.mutex.Unlock()
    82  
    83  	if err := c.I2cBusAdaptor.Connect(); err != nil {
    84  		return err
    85  	}
    86  
    87  	if err := c.PWMPinsAdaptor.Connect(); err != nil {
    88  		return err
    89  	}
    90  	return c.DigitalPinsAdaptor.Connect()
    91  }
    92  
    93  // Finalize closes connection to board and pins
    94  func (c *Adaptor) Finalize() error {
    95  	c.mutex.Lock()
    96  	defer c.mutex.Unlock()
    97  
    98  	err := c.DigitalPinsAdaptor.Finalize()
    99  
   100  	if e := c.PWMPinsAdaptor.Finalize(); e != nil {
   101  		err = multierror.Append(err, e)
   102  	}
   103  
   104  	if e := c.I2cBusAdaptor.Finalize(); e != nil {
   105  		err = multierror.Append(err, e)
   106  	}
   107  
   108  	return err
   109  }
   110  
   111  func getXIOBase() (baseAddr int, err error) {
   112  	// Default to original base from 4.3 kernel
   113  	baseAddr = 408
   114  	const expanderID = "pcf8574a"
   115  
   116  	labels, err := filepath.Glob("/sys/class/gpio/*/label")
   117  	if err != nil {
   118  		return
   119  	}
   120  
   121  	for _, labelPath := range labels {
   122  		label, err := ioutil.ReadFile(labelPath)
   123  		if err != nil {
   124  			return baseAddr, err
   125  		}
   126  		if strings.HasPrefix(string(label), expanderID) {
   127  			expanderPath, _ := filepath.Split(labelPath)
   128  			basePath := filepath.Join(expanderPath, "base")
   129  			base, err := ioutil.ReadFile(basePath)
   130  			if err != nil {
   131  				return baseAddr, err
   132  			}
   133  			baseAddr, _ = strconv.Atoi(strings.TrimSpace(string(base)))
   134  			break
   135  		}
   136  	}
   137  
   138  	return baseAddr, nil
   139  }
   140  
   141  func (c *Adaptor) validateI2cBusNumber(busNr int) error {
   142  	// Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2.
   143  	if (busNr < 0) || (busNr > 2) {
   144  		return fmt.Errorf("Bus number %d out of range", busNr)
   145  	}
   146  	return nil
   147  }
   148  
   149  func (c *Adaptor) translateDigitalPin(id string) (string, int, error) {
   150  	if val, ok := c.pinmap[id]; ok {
   151  		return "", val.pin, nil
   152  	}
   153  	return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id)
   154  }
   155  
   156  func (c *Adaptor) translatePWMPin(id string) (string, int, error) {
   157  	sysPin, ok := c.pinmap[id]
   158  	if !ok {
   159  		return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id)
   160  	}
   161  	if sysPin.pwmPin == -1 {
   162  		return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id)
   163  	}
   164  	return "/sys/class/pwm/pwmchip0", sysPin.pwmPin, nil
   165  }