gobot.io/x/gobot@v1.16.0/platforms/chip/chip_adaptor.go (about)

     1  package chip
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  
    12  	multierror "github.com/hashicorp/go-multierror"
    13  	"gobot.io/x/gobot"
    14  	"gobot.io/x/gobot/drivers/i2c"
    15  	"gobot.io/x/gobot/sysfs"
    16  )
    17  
    18  type sysfsPin struct {
    19  	pin    int
    20  	pwmPin int
    21  }
    22  
    23  // Adaptor represents a Gobot Adaptor for a C.H.I.P.
    24  type Adaptor struct {
    25  	name        string
    26  	board       string
    27  	pinmap      map[string]sysfsPin
    28  	digitalPins map[int]*sysfs.DigitalPin
    29  	pwmPins     map[int]*sysfs.PWMPin
    30  	i2cBuses    [3]i2c.I2cDevice
    31  	mutex       *sync.Mutex
    32  }
    33  
    34  // NewAdaptor creates a C.H.I.P. Adaptor
    35  func NewAdaptor() *Adaptor {
    36  	c := &Adaptor{
    37  		name:  gobot.DefaultName("CHIP"),
    38  		board: "chip",
    39  		mutex: &sync.Mutex{},
    40  	}
    41  
    42  	c.setPins()
    43  	return c
    44  }
    45  
    46  // NewAdaptor creates a C.H.I.P. Pro Adaptor
    47  func NewProAdaptor() *Adaptor {
    48  	c := &Adaptor{
    49  		name:  gobot.DefaultName("CHIP Pro"),
    50  		board: "pro",
    51  		mutex: &sync.Mutex{},
    52  	}
    53  
    54  	c.setPins()
    55  	return c
    56  }
    57  
    58  // Name returns the name of the Adaptor
    59  func (c *Adaptor) Name() string { return c.name }
    60  
    61  // SetName sets the name of the Adaptor
    62  func (c *Adaptor) SetName(n string) { c.name = n }
    63  
    64  // Connect initializes the board
    65  func (c *Adaptor) Connect() (err error) {
    66  	return nil
    67  }
    68  
    69  // Finalize closes connection to board and pins
    70  func (c *Adaptor) Finalize() (err error) {
    71  	c.mutex.Lock()
    72  	defer c.mutex.Unlock()
    73  
    74  	for _, pin := range c.digitalPins {
    75  		if pin != nil {
    76  			if e := pin.Unexport(); e != nil {
    77  				err = multierror.Append(err, e)
    78  			}
    79  		}
    80  	}
    81  	for _, pin := range c.pwmPins {
    82  		if pin != nil {
    83  			if errs := pin.Enable(false); errs != nil {
    84  				err = multierror.Append(err, errs)
    85  			}
    86  			if errs := pin.Unexport(); errs != nil {
    87  				err = multierror.Append(err, errs)
    88  			}
    89  		}
    90  	}
    91  	for _, bus := range c.i2cBuses {
    92  		if bus != nil {
    93  			if e := bus.Close(); e != nil {
    94  				err = multierror.Append(err, e)
    95  			}
    96  		}
    97  	}
    98  	return
    99  }
   100  
   101  // DigitalRead reads digital value from the specified pin.
   102  // Valids pins are the XIO-P0 through XIO-P7 pins from the
   103  // extender (pins 13-20 on header 14), as well as the SoC pins
   104  // aka all the other pins.
   105  func (c *Adaptor) DigitalRead(pin string) (val int, err error) {
   106  	sysfsPin, err := c.DigitalPin(pin, sysfs.IN)
   107  	if err != nil {
   108  		return
   109  	}
   110  	return sysfsPin.Read()
   111  }
   112  
   113  // DigitalWrite writes digital value to the specified pin.
   114  // Valids pins are the XIO-P0 through XIO-P7 pins from the
   115  // extender (pins 13-20 on header 14), as well as the SoC pins
   116  // aka all the other pins.
   117  func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
   118  	sysfsPin, err := c.DigitalPin(pin, sysfs.OUT)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	return sysfsPin.Write(int(val))
   123  }
   124  
   125  // GetConnection returns a connection to a device on a specified bus.
   126  // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2.
   127  func (c *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection, err error) {
   128  	c.mutex.Lock()
   129  	defer c.mutex.Unlock()
   130  
   131  	if (bus < 0) || (bus > 2) {
   132  		return nil, fmt.Errorf("Bus number %d out of range", bus)
   133  	}
   134  	if c.i2cBuses[bus] == nil {
   135  		c.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus))
   136  	}
   137  	return i2c.NewConnection(c.i2cBuses[bus], address), err
   138  }
   139  
   140  // GetDefaultBus returns the default i2c bus for this platform
   141  func (c *Adaptor) GetDefaultBus() int {
   142  	return 1
   143  }
   144  
   145  // digitalPin returns matched digitalPin for specified values
   146  func (c *Adaptor) DigitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPinner, err error) {
   147  	c.mutex.Lock()
   148  	defer c.mutex.Unlock()
   149  
   150  	i, err := c.translatePin(pin)
   151  
   152  	if err != nil {
   153  		return
   154  	}
   155  
   156  	if c.digitalPins[i] == nil {
   157  		c.digitalPins[i] = sysfs.NewDigitalPin(i)
   158  		if err = c.digitalPins[i].Export(); err != nil {
   159  			return
   160  		}
   161  	}
   162  
   163  	if err = c.digitalPins[i].Direction(dir); err != nil {
   164  		return
   165  	}
   166  
   167  	return c.digitalPins[i], nil
   168  }
   169  
   170  // pwmPin returns matched pwmPin for specified pin number
   171  func (c *Adaptor) PWMPin(pin string) (sysfsPin sysfs.PWMPinner, err error) {
   172  	c.mutex.Lock()
   173  	defer c.mutex.Unlock()
   174  
   175  	sysPin := c.pinmap[pin]
   176  	if sysPin.pwmPin != -1 {
   177  		if c.pwmPins[sysPin.pwmPin] == nil {
   178  			newPin := sysfs.NewPWMPin(sysPin.pwmPin)
   179  			if err = newPin.Export(); err != nil {
   180  				return
   181  			}
   182  			// Make sure pwm is disabled when setting polarity
   183  			if err = newPin.Enable(false); err != nil {
   184  				return
   185  			}
   186  			if err = newPin.InvertPolarity(false); err != nil {
   187  				return
   188  			}
   189  			if err = newPin.Enable(true); err != nil {
   190  				return
   191  			}
   192  			if err = newPin.SetPeriod(10000000); err != nil {
   193  				return
   194  			}
   195  			c.pwmPins[sysPin.pwmPin] = newPin
   196  		}
   197  
   198  		sysfsPin = c.pwmPins[sysPin.pwmPin]
   199  		return
   200  	}
   201  	err = errors.New("Not a PWM pin")
   202  	return
   203  }
   204  
   205  // PwmWrite writes a PWM signal to the specified pin
   206  func (c *Adaptor) PwmWrite(pin string, val byte) (err error) {
   207  	pwmPin, err := c.PWMPin(pin)
   208  	if err != nil {
   209  		return
   210  	}
   211  	period, err := pwmPin.Period()
   212  	if err != nil {
   213  		return err
   214  	}
   215  	duty := gobot.FromScale(float64(val), 0, 255.0)
   216  	return pwmPin.SetDutyCycle(uint32(float64(period) * duty))
   217  }
   218  
   219  // ServoWrite writes a servo signal to the specified pin
   220  func (c *Adaptor) ServoWrite(pin string, angle byte) (err error) {
   221  	pwmPin, err := c.PWMPin(pin)
   222  	if err != nil {
   223  		return
   224  	}
   225  
   226  	// 0.5 ms => -90
   227  	// 1.5 ms =>   0
   228  	// 2.0 ms =>  90
   229  	//
   230  	// Duty cycle is in nanos
   231  	const minDuty = 0.0005 * 1e9
   232  	const maxDuty = 0.0020 * 1e9
   233  	duty := uint32(gobot.ToScale(gobot.FromScale(float64(angle), 0, 180), minDuty, maxDuty))
   234  	return pwmPin.SetDutyCycle(duty)
   235  }
   236  
   237  // SetBoard sets the name of the type of board
   238  func (c *Adaptor) SetBoard(n string) (err error) {
   239  	if n == "chip" || n == "pro" {
   240  		c.board = n
   241  		c.setPins()
   242  		return
   243  	}
   244  	return errors.New("Invalid board type")
   245  }
   246  
   247  func (c *Adaptor) setPins() {
   248  	c.digitalPins = make(map[int]*sysfs.DigitalPin)
   249  	c.pwmPins = make(map[int]*sysfs.PWMPin)
   250  
   251  	if c.board == "pro" {
   252  		c.pinmap = chipProPins
   253  		return
   254  	}
   255  	// otherwise, original CHIP
   256  	c.pinmap = chipPins
   257  	baseAddr, _ := getXIOBase()
   258  	for i := 0; i < 8; i++ {
   259  		pin := fmt.Sprintf("XIO-P%d", i)
   260  		c.pinmap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1}
   261  	}
   262  }
   263  
   264  func getXIOBase() (baseAddr int, err error) {
   265  	// Default to original base from 4.3 kernel
   266  	baseAddr = 408
   267  	const expanderID = "pcf8574a"
   268  
   269  	labels, err := filepath.Glob("/sys/class/gpio/*/label")
   270  	if err != nil {
   271  		return
   272  	}
   273  
   274  	for _, labelPath := range labels {
   275  		label, err := ioutil.ReadFile(labelPath)
   276  		if err != nil {
   277  			return baseAddr, err
   278  		}
   279  		if strings.HasPrefix(string(label), expanderID) {
   280  			expanderPath, _ := filepath.Split(labelPath)
   281  			basePath := filepath.Join(expanderPath, "base")
   282  			base, err := ioutil.ReadFile(basePath)
   283  			if err != nil {
   284  				return baseAddr, err
   285  			}
   286  			baseAddr, _ = strconv.Atoi(strings.TrimSpace(string(base)))
   287  			break
   288  		}
   289  	}
   290  
   291  	return baseAddr, nil
   292  }
   293  
   294  func (c *Adaptor) translatePin(pin string) (i int, err error) {
   295  	if val, ok := c.pinmap[pin]; ok {
   296  		i = val.pin
   297  	} else {
   298  		err = errors.New("Not a valid pin")
   299  	}
   300  	return
   301  }