gobot.io/x/gobot/v2@v2.1.0/platforms/intel-iot/edison/edison_adaptor.go (about)

     1  package edison
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"sync"
     8  
     9  	multierror "github.com/hashicorp/go-multierror"
    10  	"gobot.io/x/gobot/v2"
    11  	"gobot.io/x/gobot/v2/platforms/adaptors"
    12  	"gobot.io/x/gobot/v2/system"
    13  )
    14  
    15  const (
    16  	defaultI2cBusNumber      = 6
    17  	defaultI2cBusNumberOther = 1
    18  )
    19  
    20  type mux struct {
    21  	pin   int
    22  	value int
    23  }
    24  
    25  type sysfsPin struct {
    26  	pin          int
    27  	resistor     int
    28  	levelShifter int
    29  	pwmPin       int
    30  	mux          []mux
    31  }
    32  
    33  // Adaptor represents a Gobot Adaptor for an Intel Edison
    34  type Adaptor struct {
    35  	name        string
    36  	board       string
    37  	sys         *system.Accesser
    38  	mutex       sync.Mutex
    39  	pinmap      map[string]sysfsPin
    40  	tristate    gobot.DigitalPinner
    41  	digitalPins map[int]gobot.DigitalPinner
    42  	*adaptors.PWMPinsAdaptor
    43  	*adaptors.I2cBusAdaptor
    44  	arduinoI2cInitialized bool
    45  }
    46  
    47  // NewAdaptor returns a new Edison Adaptor of the given type.
    48  // Supported types are: "arduino", "miniboard", "sparkfun", an empty string defaults to "arduino"
    49  func NewAdaptor(boardType ...string) *Adaptor {
    50  	sys := system.NewAccesser()
    51  	c := &Adaptor{
    52  		name:   gobot.DefaultName("Edison"),
    53  		board:  "arduino",
    54  		sys:    sys,
    55  		pinmap: arduinoPinMap,
    56  	}
    57  	if len(boardType) > 0 && boardType[0] != "" {
    58  		c.board = boardType[0]
    59  	}
    60  	c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translateAndMuxPWMPin,
    61  		adaptors.WithPWMPinInitializer(pwmPinInitializer))
    62  	defI2cBusNr := defaultI2cBusNumber
    63  	if c.board != "arduino" {
    64  		defI2cBusNr = defaultI2cBusNumberOther
    65  	}
    66  	c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateAndSetupI2cBusNumber, defI2cBusNr)
    67  	return c
    68  }
    69  
    70  // Name returns the Adaptors name
    71  func (c *Adaptor) Name() string { return c.name }
    72  
    73  // SetName sets the Adaptors name
    74  func (c *Adaptor) SetName(n string) { c.name = n }
    75  
    76  // Connect initializes the Edison for use with the Arduino breakout board
    77  func (c *Adaptor) Connect() error {
    78  	c.digitalPins = make(map[int]gobot.DigitalPinner)
    79  
    80  	if err := c.I2cBusAdaptor.Connect(); err != nil {
    81  		return err
    82  	}
    83  
    84  	if err := c.PWMPinsAdaptor.Connect(); err != nil {
    85  		return err
    86  	}
    87  
    88  	switch c.board {
    89  	case "sparkfun":
    90  		c.pinmap = sparkfunPinMap
    91  	case "arduino":
    92  		c.board = "arduino"
    93  		c.pinmap = arduinoPinMap
    94  		if err := c.arduinoSetup(); err != nil {
    95  			return err
    96  		}
    97  	case "miniboard":
    98  		c.pinmap = miniboardPinMap
    99  	default:
   100  		return fmt.Errorf("Unknown board type: %s", c.board)
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  // Finalize releases all i2c devices and exported analog, digital, pwm pins.
   107  func (c *Adaptor) Finalize() (err error) {
   108  	if c.tristate != nil {
   109  		if errs := c.tristate.Unexport(); errs != nil {
   110  			err = multierror.Append(err, errs)
   111  		}
   112  	}
   113  	c.tristate = nil
   114  
   115  	for _, pin := range c.digitalPins {
   116  		if pin != nil {
   117  			if errs := pin.Unexport(); errs != nil {
   118  				err = multierror.Append(err, errs)
   119  			}
   120  		}
   121  	}
   122  	c.digitalPins = nil
   123  
   124  	if e := c.PWMPinsAdaptor.Finalize(); e != nil {
   125  		err = multierror.Append(err, e)
   126  	}
   127  
   128  	if e := c.I2cBusAdaptor.Finalize(); e != nil {
   129  		err = multierror.Append(err, e)
   130  	}
   131  	c.arduinoI2cInitialized = false
   132  	return
   133  }
   134  
   135  // DigitalRead reads digital value from pin
   136  func (c *Adaptor) DigitalRead(pin string) (i int, err error) {
   137  	c.mutex.Lock()
   138  	defer c.mutex.Unlock()
   139  
   140  	sysPin, err := c.digitalPin(pin, system.WithPinDirectionInput())
   141  	if err != nil {
   142  		return
   143  	}
   144  	return sysPin.Read()
   145  }
   146  
   147  // DigitalWrite writes a value to the pin. Acceptable values are 1 or 0.
   148  func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) {
   149  	c.mutex.Lock()
   150  	defer c.mutex.Unlock()
   151  
   152  	return c.digitalWrite(pin, val)
   153  }
   154  
   155  // DigitalPin returns a digital pin. If the pin is initially acquired, it is an input.
   156  // Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time.
   157  func (c *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) {
   158  	c.mutex.Lock()
   159  	defer c.mutex.Unlock()
   160  
   161  	return c.digitalPin(id)
   162  }
   163  
   164  // AnalogRead returns value from analog reading of specified pin
   165  func (c *Adaptor) AnalogRead(pin string) (val int, err error) {
   166  	buf, err := c.readFile("/sys/bus/iio/devices/iio:device1/in_voltage" + pin + "_raw")
   167  	if err != nil {
   168  		return
   169  	}
   170  
   171  	val, err = strconv.Atoi(string(buf[0 : len(buf)-1]))
   172  
   173  	return val / 4, err
   174  }
   175  
   176  func (c *Adaptor) validateAndSetupI2cBusNumber(busNr int) error {
   177  	// Valid bus number is 6 for "arduino", otherwise 1.
   178  	if busNr == 6 && c.board == "arduino" {
   179  		if !c.arduinoI2cInitialized {
   180  			if err := c.arduinoI2CSetup(); err != nil {
   181  				return err
   182  			}
   183  			c.arduinoI2cInitialized = true
   184  			return nil
   185  		}
   186  		return nil
   187  	}
   188  
   189  	if busNr == 1 && c.board != "arduino" {
   190  		return nil
   191  	}
   192  
   193  	return fmt.Errorf("Unsupported I2C bus '%d'", busNr)
   194  }
   195  
   196  // arduinoSetup does needed setup for the Arduino compatible breakout board
   197  func (c *Adaptor) arduinoSetup() error {
   198  	// TODO: also check to see if device labels for
   199  	// /sys/class/gpio/gpiochip{200,216,232,248}/label == "pcal9555a"
   200  
   201  	tpin, err := c.newExportedDigitalPin(214, system.WithPinDirectionOutput(system.LOW))
   202  	if err != nil {
   203  		return err
   204  	}
   205  	c.tristate = tpin
   206  
   207  	for _, i := range []int{263, 262} {
   208  		if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionOutput(system.HIGH)); err != nil {
   209  			return err
   210  		}
   211  	}
   212  
   213  	for _, i := range []int{240, 241, 242, 243} {
   214  		if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionOutput(system.LOW)); err != nil {
   215  			return err
   216  		}
   217  	}
   218  
   219  	for _, i := range []string{"111", "115", "114", "109"} {
   220  		if err := c.changePinMode(i, "1"); err != nil {
   221  			return err
   222  		}
   223  	}
   224  
   225  	for _, i := range []string{"131", "129", "40"} {
   226  		if err := c.changePinMode(i, "0"); err != nil {
   227  			return err
   228  		}
   229  	}
   230  
   231  	return c.tristate.Write(system.HIGH)
   232  }
   233  
   234  func (c *Adaptor) arduinoI2CSetup() error {
   235  	if c.tristate == nil {
   236  		return fmt.Errorf("not connected")
   237  	}
   238  
   239  	if err := c.tristate.Write(system.LOW); err != nil {
   240  		return err
   241  	}
   242  
   243  	for _, i := range []int{14, 165, 212, 213} {
   244  		if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionInput()); err != nil {
   245  			return err
   246  		}
   247  	}
   248  
   249  	for _, i := range []int{236, 237, 204, 205} {
   250  		if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionOutput(system.LOW)); err != nil {
   251  			return err
   252  		}
   253  	}
   254  
   255  	for _, i := range []string{"28", "27"} {
   256  		if err := c.changePinMode(i, "1"); err != nil {
   257  			return err
   258  		}
   259  	}
   260  
   261  	return c.tristate.Write(system.HIGH)
   262  }
   263  
   264  func (c *Adaptor) readFile(path string) ([]byte, error) {
   265  	file, err := c.sys.OpenFile(path, os.O_RDONLY, 0644)
   266  	defer file.Close() //nolint:staticcheck // for historical reasons
   267  	if err != nil {
   268  		return make([]byte, 0), err
   269  	}
   270  
   271  	buf := make([]byte, 200)
   272  	var i int
   273  	i, err = file.Read(buf)
   274  	if i == 0 {
   275  		return buf, err
   276  	}
   277  	return buf[:i], err
   278  }
   279  
   280  func (c *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
   281  	i := c.pinmap[id]
   282  
   283  	err := c.ensureDigitalPin(i.pin, o...)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	pin := c.digitalPins[i.pin]
   288  	vpin, ok := pin.(gobot.DigitalPinValuer)
   289  	if !ok {
   290  		return nil, fmt.Errorf("can not determine the direction behavior")
   291  	}
   292  	dir := vpin.DirectionBehavior()
   293  	if i.resistor > 0 {
   294  		rop := system.WithPinDirectionOutput(system.LOW)
   295  		if dir == system.OUT {
   296  			rop = system.WithPinDirectionInput()
   297  		}
   298  		if err := c.ensureDigitalPin(i.resistor, rop); err != nil {
   299  			return nil, err
   300  		}
   301  	}
   302  
   303  	if i.levelShifter > 0 {
   304  		lop := system.WithPinDirectionOutput(system.LOW)
   305  		if dir == system.OUT {
   306  			lop = system.WithPinDirectionOutput(system.HIGH)
   307  		}
   308  		if err := c.ensureDigitalPin(i.levelShifter, lop); err != nil {
   309  			return nil, err
   310  		}
   311  	}
   312  
   313  	if len(i.mux) > 0 {
   314  		for _, mux := range i.mux {
   315  			if err := c.ensureDigitalPin(mux.pin, system.WithPinDirectionOutput(mux.value)); err != nil {
   316  				return nil, err
   317  			}
   318  		}
   319  	}
   320  
   321  	return pin, nil
   322  }
   323  
   324  func (c *Adaptor) ensureDigitalPin(idx int, o ...func(gobot.DigitalPinOptioner) bool) error {
   325  	pin := c.digitalPins[idx]
   326  	var err error
   327  	if pin == nil {
   328  		pin, err = c.newExportedDigitalPin(idx, o...)
   329  		if err != nil {
   330  			return err
   331  		}
   332  		c.digitalPins[idx] = pin
   333  	} else {
   334  		if err := pin.ApplyOptions(o...); err != nil {
   335  			return err
   336  		}
   337  	}
   338  	return nil
   339  }
   340  
   341  func pwmPinInitializer(pin gobot.PWMPinner) error {
   342  	if err := pin.Export(); err != nil {
   343  		return err
   344  	}
   345  	return pin.SetEnabled(true)
   346  }
   347  
   348  func (c *Adaptor) translateAndMuxPWMPin(id string) (string, int, error) {
   349  	sysPin, ok := c.pinmap[id]
   350  	if !ok {
   351  		return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id)
   352  	}
   353  	if sysPin.pwmPin == -1 {
   354  		return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id)
   355  	}
   356  	if err := c.digitalWrite(id, 1); err != nil {
   357  		return "", -1, err
   358  	}
   359  	if err := c.changePinMode(strconv.Itoa(int(sysPin.pin)), "1"); err != nil {
   360  		return "", -1, err
   361  	}
   362  	return "/sys/class/pwm/pwmchip0", sysPin.pwmPin, nil
   363  }
   364  
   365  func (c *Adaptor) newUnexportedDigitalPin(i int, o ...func(gobot.DigitalPinOptioner) bool) error {
   366  	io := c.sys.NewDigitalPin("", i, o...)
   367  	if err := io.Export(); err != nil {
   368  		return err
   369  	}
   370  	return io.Unexport()
   371  }
   372  
   373  func (c *Adaptor) newExportedDigitalPin(pin int, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
   374  	sysPin := c.sys.NewDigitalPin("", pin, o...)
   375  	err := sysPin.Export()
   376  	return sysPin, err
   377  }
   378  
   379  // changePinMode writes pin mode to current_pinmux file
   380  func (c *Adaptor) changePinMode(pin, mode string) error {
   381  	file, err := c.sys.OpenFile("/sys/kernel/debug/gpio_debug/gpio"+pin+"/current_pinmux", os.O_WRONLY, 0644)
   382  	defer file.Close() //nolint:staticcheck // for historical reasons
   383  	if err != nil {
   384  		return err
   385  	}
   386  	_, err = file.Write([]byte("mode" + mode))
   387  	return err
   388  }
   389  
   390  func (c *Adaptor) digitalWrite(pin string, val byte) (err error) {
   391  	sysPin, err := c.digitalPin(pin, system.WithPinDirectionOutput(int(val)))
   392  	if err != nil {
   393  		return
   394  	}
   395  	return sysPin.Write(int(val))
   396  }