gobot.io/x/gobot@v1.16.0/sysfs/digital_pin.go (about)

     1  package sysfs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  	"syscall"
     9  	"time"
    10  )
    11  
    12  const (
    13  	// IN gpio direction
    14  	IN = "in"
    15  	// OUT gpio direction
    16  	OUT = "out"
    17  	// HIGH gpio level
    18  	HIGH = 1
    19  	// LOW gpio level
    20  	LOW = 0
    21  	// GPIOPATH default linux gpio path
    22  	GPIOPATH = "/sys/class/gpio"
    23  )
    24  
    25  var errNotExported = errors.New("pin has not been exported")
    26  
    27  // DigitalPinner is the interface for sysfs gpio interactions
    28  type DigitalPinner interface {
    29  	// Export exports the pin for use by the operating system
    30  	Export() error
    31  	// Unexport unexports the pin and releases the pin from the operating system
    32  	Unexport() error
    33  	// Direction sets the direction for the pin
    34  	Direction(string) error
    35  	// Read reads the current value of the pin
    36  	Read() (int, error)
    37  	// Write writes to the pin
    38  	Write(int) error
    39  }
    40  
    41  // DigitalPinnerProvider is the interface that an Adaptor should implement to allow
    42  // clients to obtain access to any DigitalPin's available on that board.
    43  type DigitalPinnerProvider interface {
    44  	DigitalPin(string, string) (DigitalPinner, error)
    45  }
    46  
    47  type DigitalPin struct {
    48  	pin   string
    49  	label string
    50  
    51  	value     File
    52  	direction File
    53  }
    54  
    55  // NewDigitalPin returns a DigitalPin given the pin number and an optional sysfs pin label.
    56  // If no label is supplied the default label will prepend "gpio" to the pin number,
    57  // eg. a pin number of 10 will have a label of "gpio10"
    58  func NewDigitalPin(pin int, v ...string) *DigitalPin {
    59  	d := &DigitalPin{pin: strconv.Itoa(pin)}
    60  	if len(v) > 0 {
    61  		d.label = v[0]
    62  	} else {
    63  		d.label = "gpio" + d.pin
    64  	}
    65  
    66  	return d
    67  }
    68  
    69  func (d *DigitalPin) Direction(dir string) error {
    70  	_, err := writeFile(d.direction, []byte(dir))
    71  	return err
    72  }
    73  
    74  func (d *DigitalPin) Write(b int) error {
    75  	_, err := writeFile(d.value, []byte(strconv.Itoa(b)))
    76  	return err
    77  }
    78  
    79  func (d *DigitalPin) Read() (n int, err error) {
    80  	buf, err := readFile(d.value)
    81  	if err != nil {
    82  		return 0, err
    83  	}
    84  	return strconv.Atoi(string(buf[0]))
    85  }
    86  
    87  func (d *DigitalPin) Export() error {
    88  	export, err := fs.OpenFile(GPIOPATH+"/export", os.O_WRONLY, 0644)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	defer export.Close()
    93  
    94  	_, err = writeFile(export, []byte(d.pin))
    95  	if err != nil {
    96  		// If EBUSY then the pin has already been exported
    97  		e, ok := err.(*os.PathError)
    98  		if !ok || e.Err != syscall.EBUSY {
    99  			return err
   100  		}
   101  	}
   102  
   103  	if d.direction != nil {
   104  		d.direction.Close()
   105  	}
   106  
   107  	attempt := 0
   108  	for {
   109  		attempt++
   110  		d.direction, err = fs.OpenFile(fmt.Sprintf("%v/%v/direction", GPIOPATH, d.label), os.O_RDWR, 0644)
   111  		if err == nil {
   112  			break
   113  		}
   114  		if attempt > 10 {
   115  			return err
   116  		}
   117  		time.Sleep(10 * time.Millisecond)
   118  	}
   119  
   120  	if d.value != nil {
   121  		d.value.Close()
   122  	}
   123  	if err == nil {
   124  		d.value, err = fs.OpenFile(fmt.Sprintf("%v/%v/value", GPIOPATH, d.label), os.O_RDWR, 0644)
   125  	}
   126  
   127  	if err != nil {
   128  		// Should we unexport here?
   129  		// If we don't unexport we should make sure to close d.direction and d.value here
   130  		d.Unexport()
   131  	}
   132  
   133  	return err
   134  }
   135  
   136  func (d *DigitalPin) Unexport() error {
   137  	unexport, err := fs.OpenFile(GPIOPATH+"/unexport", os.O_WRONLY, 0644)
   138  	if err != nil {
   139  		return err
   140  	}
   141  	defer unexport.Close()
   142  
   143  	if d.direction != nil {
   144  		d.direction.Close()
   145  		d.direction = nil
   146  	}
   147  	if d.value != nil {
   148  		d.value.Close()
   149  		d.value = nil
   150  	}
   151  
   152  	_, err = writeFile(unexport, []byte(d.pin))
   153  	if err != nil {
   154  		// If EINVAL then the pin is reserved in the system and can't be unexported
   155  		e, ok := err.(*os.PathError)
   156  		if !ok || e.Err != syscall.EINVAL {
   157  			return err
   158  		}
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  // Linux sysfs / GPIO specific sysfs docs.
   165  //  https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
   166  //  https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
   167  
   168  var writeFile = func(f File, data []byte) (i int, err error) {
   169  	if f == nil {
   170  		return 0, errNotExported
   171  	}
   172  
   173  	// sysfs docs say:
   174  	// > When writing sysfs files, userspace processes should first read the
   175  	// > entire file, modify the values it wishes to change, then write the
   176  	// > entire buffer back.
   177  	// however, this seems outdated/inaccurate (docs are from back in the Kernel BitKeeper days).
   178  
   179  	i, err = f.Write(data)
   180  	return i, err
   181  }
   182  
   183  var readFile = func(f File) ([]byte, error) {
   184  	if f == nil {
   185  		return nil, errNotExported
   186  	}
   187  
   188  	// sysfs docs say:
   189  	// > If userspace seeks back to zero or does a pread(2) with an offset of '0' the [..] method will
   190  	// > be called again, rearmed, to fill the buffer.
   191  
   192  	// TODO: Examine if seek is needed if full buffer is read from sysfs file.
   193  
   194  	buf := make([]byte, 2)
   195  	_, err := f.Seek(0, os.SEEK_SET)
   196  	if err == nil {
   197  		_, err = f.Read(buf)
   198  	}
   199  	return buf, err
   200  }