gobot.io/x/gobot/v2@v2.1.0/system/digitalpin_sysfs.go (about)

     1  package system
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"strconv"
    10  	"syscall"
    11  	"time"
    12  
    13  	"gobot.io/x/gobot/v2"
    14  )
    15  
    16  const (
    17  	systemSysfsDebug = false
    18  	// gpioPath default linux sysfs gpio path
    19  	gpioPath = "/sys/class/gpio"
    20  )
    21  
    22  var errNotExported = errors.New("pin has not been exported")
    23  
    24  // digitalPin represents a digital pin
    25  type digitalPinSysfs struct {
    26  	pin string
    27  	*digitalPinConfig
    28  	fs filesystem
    29  
    30  	dirFile       File
    31  	valFile       File
    32  	activeLowFile File
    33  }
    34  
    35  // newDigitalPinSysfs returns a digital pin using for the given number. The name of the sysfs file will prepend "gpio"
    36  // to the pin number, eg. a pin number of 10 will have a name of "gpio10". The pin is handled by the sysfs Kernel ABI.
    37  func newDigitalPinSysfs(fs filesystem, pin string, options ...func(gobot.DigitalPinOptioner) bool) *digitalPinSysfs {
    38  	cfg := newDigitalPinConfig("gpio"+pin, options...)
    39  	d := &digitalPinSysfs{
    40  		pin:              pin,
    41  		digitalPinConfig: cfg,
    42  		fs:               fs,
    43  	}
    44  	return d
    45  }
    46  
    47  // ApplyOptions apply all given options to the pin immediately. Implements interface gobot.DigitalPinOptionApplier.
    48  func (d *digitalPinSysfs) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error {
    49  	anyChange := false
    50  	for _, option := range options {
    51  		anyChange = anyChange || option(d)
    52  	}
    53  	if anyChange {
    54  		return d.reconfigure()
    55  	}
    56  	return nil
    57  }
    58  
    59  // DirectionBehavior gets the direction behavior when the pin is used the next time. This means its possibly not in
    60  // this direction type at the moment. Implements the interface gobot.DigitalPinValuer, but should be rarely used.
    61  func (d *digitalPinSysfs) DirectionBehavior() string {
    62  	return d.direction
    63  }
    64  
    65  // Export sets the pin as exported with the configured direction
    66  func (d *digitalPinSysfs) Export() error {
    67  	err := d.reconfigure()
    68  	return err
    69  }
    70  
    71  // Unexport release the pin
    72  func (d *digitalPinSysfs) Unexport() error {
    73  	unexport, err := d.fs.openFile(gpioPath+"/unexport", os.O_WRONLY, 0644)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	defer unexport.Close()
    78  
    79  	if d.dirFile != nil {
    80  		d.dirFile.Close()
    81  		d.dirFile = nil
    82  	}
    83  	if d.valFile != nil {
    84  		d.valFile.Close()
    85  		d.valFile = nil
    86  	}
    87  	if d.activeLowFile != nil {
    88  		d.activeLowFile.Close()
    89  		d.activeLowFile = nil
    90  	}
    91  
    92  	_, err = writeFile(unexport, []byte(d.pin))
    93  	if err != nil {
    94  		// If EINVAL then the pin is reserved in the system and can't be unexported
    95  		e, ok := err.(*os.PathError)
    96  		if !ok || e.Err != syscall.EINVAL {
    97  			return err
    98  		}
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // Write writes the given value to the character device
   105  func (d *digitalPinSysfs) Write(b int) error {
   106  	_, err := writeFile(d.valFile, []byte(strconv.Itoa(b)))
   107  	return err
   108  }
   109  
   110  // Read reads the given value from character device
   111  func (d *digitalPinSysfs) Read() (int, error) {
   112  	buf, err := readFile(d.valFile)
   113  	if err != nil {
   114  		return 0, err
   115  	}
   116  	return strconv.Atoi(string(buf[0]))
   117  }
   118  
   119  func (d *digitalPinSysfs) reconfigure() error {
   120  	exportFile, err := d.fs.openFile(gpioPath+"/export", os.O_WRONLY, 0644)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	defer exportFile.Close()
   125  
   126  	_, err = writeFile(exportFile, []byte(d.pin))
   127  	if err != nil {
   128  		// If EBUSY then the pin has already been exported
   129  		e, ok := err.(*os.PathError)
   130  		if !ok || e.Err != syscall.EBUSY {
   131  			return err
   132  		}
   133  	}
   134  
   135  	if d.dirFile != nil {
   136  		d.dirFile.Close()
   137  	}
   138  
   139  	attempt := 0
   140  	for {
   141  		attempt++
   142  		d.dirFile, err = d.fs.openFile(fmt.Sprintf("%s/%s/direction", gpioPath, d.label), os.O_RDWR, 0644)
   143  		if err == nil {
   144  			break
   145  		}
   146  		if attempt > 10 {
   147  			return err
   148  		}
   149  		time.Sleep(10 * time.Millisecond)
   150  	}
   151  
   152  	if d.valFile != nil {
   153  		d.valFile.Close()
   154  	}
   155  	if err == nil {
   156  		d.valFile, err = d.fs.openFile(fmt.Sprintf("%s/%s/value", gpioPath, d.label), os.O_RDWR, 0644)
   157  	}
   158  
   159  	// configure direction
   160  	if err == nil {
   161  		err = d.writeDirectionWithInitialOutput()
   162  	}
   163  
   164  	// configure inverse logic
   165  	if err == nil {
   166  		if d.activeLow {
   167  			d.activeLowFile, err = d.fs.openFile(fmt.Sprintf("%s/%s/active_low", gpioPath, d.label), os.O_RDWR, 0644)
   168  			if err == nil {
   169  				_, err = writeFile(d.activeLowFile, []byte("1"))
   170  			}
   171  		}
   172  	}
   173  
   174  	// configure bias (unsupported)
   175  	if err == nil {
   176  		if d.bias != digitalPinBiasDefault && systemSysfsDebug {
   177  			log.Printf("bias options (%d) are not supported by sysfs, please use hardware resistors instead\n", d.bias)
   178  		}
   179  	}
   180  
   181  	// configure drive (unsupported)
   182  	if d.drive != digitalPinDrivePushPull && systemSysfsDebug {
   183  		log.Printf("drive options (%d) are not supported by sysfs\n", d.drive)
   184  	}
   185  
   186  	// configure debounce (unsupported)
   187  	if d.debouncePeriod != 0 && systemSysfsDebug {
   188  		log.Printf("debounce period option (%d) is not supported by sysfs\n", d.debouncePeriod)
   189  	}
   190  
   191  	// configure edge detection (not implemented)
   192  	if d.edge != 0 && systemSysfsDebug {
   193  		log.Printf("edge detect option (%d) is not implemented for sysfs\n", d.edge)
   194  	}
   195  
   196  	if err != nil {
   197  		d.Unexport()
   198  	}
   199  
   200  	return err
   201  }
   202  
   203  func (d *digitalPinSysfs) writeDirectionWithInitialOutput() error {
   204  	if _, err := writeFile(d.dirFile, []byte(d.direction)); err != nil || d.direction == IN {
   205  		return err
   206  	}
   207  	_, err := writeFile(d.valFile, []byte(strconv.Itoa(d.outInitialState)))
   208  	return err
   209  }
   210  
   211  // Linux sysfs / GPIO specific sysfs docs.
   212  //  https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
   213  //  https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
   214  
   215  var writeFile = func(f File, data []byte) (i int, err error) {
   216  	if f == nil {
   217  		return 0, errNotExported
   218  	}
   219  
   220  	// sysfs docs say:
   221  	// > When writing sysfs files, userspace processes should first read the
   222  	// > entire file, modify the values it wishes to change, then write the
   223  	// > entire buffer back.
   224  	// however, this seems outdated/inaccurate (docs are from back in the Kernel BitKeeper days).
   225  
   226  	i, err = f.Write(data)
   227  	return i, err
   228  }
   229  
   230  var readFile = func(f File) ([]byte, error) {
   231  	if f == nil {
   232  		return nil, errNotExported
   233  	}
   234  
   235  	// sysfs docs say:
   236  	// > If userspace seeks back to zero or does a pread(2) with an offset of '0' the [..] method will
   237  	// > be called again, rearmed, to fill the buffer.
   238  
   239  	// TODO: Examine if seek is needed if full buffer is read from sysfs file.
   240  
   241  	buf := make([]byte, 2)
   242  	_, err := f.Seek(0, io.SeekStart)
   243  	if err == nil {
   244  		_, err = f.Read(buf)
   245  	}
   246  	return buf, err
   247  }