gobot.io/x/gobot/v2@v2.1.0/platforms/raspi/raspi_adaptor.go (about)

     1  package raspi
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     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 (
    18  	infoFile = "/proc/cpuinfo"
    19  
    20  	defaultSpiBusNumber  = 0
    21  	defaultSpiChipNumber = 0
    22  	defaultSpiMode       = 0
    23  	defaultSpiBitsNumber = 8
    24  	defaultSpiMaxSpeed   = 500000
    25  )
    26  
    27  // Adaptor is the Gobot Adaptor for the Raspberry Pi
    28  type Adaptor struct {
    29  	name     string
    30  	mutex    sync.Mutex
    31  	sys      *system.Accesser
    32  	revision string
    33  	pwmPins  map[string]gobot.PWMPinner
    34  	*adaptors.DigitalPinsAdaptor
    35  	*adaptors.I2cBusAdaptor
    36  	*adaptors.SpiBusAdaptor
    37  	spiDefaultMaxSpeed int64
    38  	PiBlasterPeriod    uint32
    39  }
    40  
    41  // NewAdaptor creates a Raspi Adaptor
    42  //
    43  // Optional parameters:
    44  //
    45  //			adaptors.WithGpiodAccess():	use character device gpiod driver instead of sysfs (still used by default)
    46  //			adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso):	use GPIO's instead of /dev/spidev#.#
    47  //	   adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
    48  //	   adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
    49  //	   adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior
    50  //	   adaptors.WithGpioDebounce(pin, period): sets the input debouncer
    51  //	   adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection
    52  func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
    53  	sys := system.NewAccesser(system.WithDigitalPinGpiodAccess())
    54  	c := &Adaptor{
    55  		name:            gobot.DefaultName("RaspberryPi"),
    56  		sys:             sys,
    57  		PiBlasterPeriod: 10000000,
    58  	}
    59  	c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.getPinTranslatorFunction(), opts...)
    60  	c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, 1)
    61  	c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,
    62  		defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed)
    63  	return c
    64  }
    65  
    66  // Name returns the Adaptor's name
    67  func (c *Adaptor) Name() string {
    68  	c.mutex.Lock()
    69  	defer c.mutex.Unlock()
    70  
    71  	return c.name
    72  }
    73  
    74  // SetName sets the Adaptor's name
    75  func (c *Adaptor) SetName(n string) {
    76  	c.mutex.Lock()
    77  	defer c.mutex.Unlock()
    78  
    79  	c.name = n
    80  }
    81  
    82  // Connect create new connection to board and pins.
    83  func (c *Adaptor) Connect() error {
    84  	c.mutex.Lock()
    85  	defer c.mutex.Unlock()
    86  
    87  	if err := c.SpiBusAdaptor.Connect(); err != nil {
    88  		return err
    89  	}
    90  
    91  	if err := c.I2cBusAdaptor.Connect(); err != nil {
    92  		return err
    93  	}
    94  
    95  	c.pwmPins = make(map[string]gobot.PWMPinner)
    96  	return c.DigitalPinsAdaptor.Connect()
    97  }
    98  
    99  // Finalize closes connection to board and pins
   100  func (c *Adaptor) Finalize() error {
   101  	c.mutex.Lock()
   102  	defer c.mutex.Unlock()
   103  
   104  	err := c.DigitalPinsAdaptor.Finalize()
   105  
   106  	for _, pin := range c.pwmPins {
   107  		if pin != nil {
   108  			if perr := pin.Unexport(); err != nil {
   109  				err = multierror.Append(err, perr)
   110  			}
   111  		}
   112  	}
   113  	c.pwmPins = nil
   114  
   115  	if e := c.I2cBusAdaptor.Finalize(); e != nil {
   116  		err = multierror.Append(err, e)
   117  	}
   118  
   119  	if e := c.SpiBusAdaptor.Finalize(); e != nil {
   120  		err = multierror.Append(err, e)
   121  	}
   122  	return err
   123  }
   124  
   125  // DefaultI2cBus returns the default i2c bus for this platform.
   126  // This overrides the base function due to the revision dependency.
   127  func (c *Adaptor) DefaultI2cBus() int {
   128  	rev := c.readRevision()
   129  	if rev == "2" || rev == "3" {
   130  		return 1
   131  	}
   132  	return 0
   133  }
   134  
   135  // PWMPin returns a raspi.PWMPin which provides the gobot.PWMPinner interface
   136  func (c *Adaptor) PWMPin(id string) (gobot.PWMPinner, error) {
   137  	c.mutex.Lock()
   138  	defer c.mutex.Unlock()
   139  
   140  	return c.pwmPin(id)
   141  }
   142  
   143  // PwmWrite writes a PWM signal to the specified pin
   144  func (c *Adaptor) PwmWrite(pin string, val byte) (err error) {
   145  	c.mutex.Lock()
   146  	defer c.mutex.Unlock()
   147  
   148  	sysPin, err := c.pwmPin(pin)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	duty := uint32(gobot.FromScale(float64(val), 0, 255) * float64(c.PiBlasterPeriod))
   154  	return sysPin.SetDutyCycle(duty)
   155  }
   156  
   157  // ServoWrite writes a servo signal to the specified pin
   158  func (c *Adaptor) ServoWrite(pin string, angle byte) (err error) {
   159  	c.mutex.Lock()
   160  	defer c.mutex.Unlock()
   161  
   162  	sysPin, err := c.pwmPin(pin)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	duty := uint32(gobot.FromScale(float64(angle), 0, 180) * float64(c.PiBlasterPeriod))
   168  	return sysPin.SetDutyCycle(duty)
   169  }
   170  
   171  func (c *Adaptor) validateSpiBusNumber(busNr int) error {
   172  	// Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x.
   173  	// x is the chip number <255
   174  	if (busNr < 0) || (busNr > 1) {
   175  		return fmt.Errorf("Bus number %d out of range", busNr)
   176  	}
   177  	return nil
   178  }
   179  
   180  func (c *Adaptor) validateI2cBusNumber(busNr int) error {
   181  	// Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1.
   182  	if (busNr < 0) || (busNr > 1) {
   183  		return fmt.Errorf("Bus number %d out of range", busNr)
   184  	}
   185  	return nil
   186  }
   187  
   188  func (c *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) {
   189  	return func(pin string) (chip string, line int, err error) {
   190  		if val, ok := pins[pin][c.readRevision()]; ok {
   191  			line = val
   192  		} else if val, ok := pins[pin]["*"]; ok {
   193  			line = val
   194  		} else {
   195  			err = errors.New("Not a valid pin")
   196  			return
   197  		}
   198  		// TODO: Pi1 model B has only this single "gpiochip0", a change of the translator is needed,
   199  		// to support different chips with different revisions
   200  		return "gpiochip0", line, nil
   201  	}
   202  }
   203  
   204  func (c *Adaptor) readRevision() string {
   205  	if c.revision == "" {
   206  		c.revision = "0"
   207  		content, err := c.sys.ReadFile(infoFile)
   208  		if err != nil {
   209  			return c.revision
   210  		}
   211  		for _, v := range strings.Split(string(content), "\n") {
   212  			if strings.Contains(v, "Revision") {
   213  				s := strings.Split(string(v), " ")
   214  				version, _ := strconv.ParseInt("0x"+s[len(s)-1], 0, 64)
   215  				if version <= 3 {
   216  					c.revision = "1"
   217  				} else if version <= 15 {
   218  					c.revision = "2"
   219  				} else {
   220  					c.revision = "3"
   221  				}
   222  			}
   223  		}
   224  	}
   225  
   226  	return c.revision
   227  }
   228  
   229  func (c *Adaptor) pwmPin(id string) (gobot.PWMPinner, error) {
   230  	pin := c.pwmPins[id]
   231  
   232  	if pin == nil {
   233  		tf := c.getPinTranslatorFunction()
   234  		_, i, err := tf(id)
   235  		if err != nil {
   236  			return nil, err
   237  		}
   238  		pin = NewPWMPin(c.sys, "/dev/pi-blaster", strconv.Itoa(i))
   239  		pin.SetPeriod(c.PiBlasterPeriod)
   240  		c.pwmPins[id] = pin
   241  	}
   242  
   243  	return pin, nil
   244  }