hybridgroup.gobot/sysfs/digital_pin.go

212 lines
4.8 KiB
Go
Raw Normal View History

package sysfs
import (
"errors"
"fmt"
"os"
"strconv"
"syscall"
"time"
)
const (
// IN gpio direction
IN = "in"
// OUT gpio direction
OUT = "out"
// HIGH gpio level
HIGH = 1
// LOW gpio level
LOW = 0
// GPIOPATH default linux gpio path
2014-10-31 04:41:27 +08:00
GPIOPATH = "/sys/class/gpio"
)
var errNotExported = errors.New("pin has not been exported")
// DigitalPinner is the interface for sysfs gpio interactions
type DigitalPinner interface {
// Export exports the pin for use by the operating system
2014-11-04 11:02:20 +08:00
Export() error
// Unexport unexports the pin and releases the pin from the operating system
Unexport() error
// Direction sets the direction for the pin
2014-11-04 11:02:20 +08:00
Direction(string) error
// Read reads the current value of the pin
Read() (int, error)
// Write writes to the pin
2014-11-04 11:02:20 +08:00
Write(int) error
}
// DigitalPinnerProvider is the interface that an Adaptor should implement to allow
// clients to obtain access to any DigitalPin's available on that board.
type DigitalPinnerProvider interface {
DigitalPin(string, string) (DigitalPinner, error)
}
2022-11-05 14:42:28 +08:00
// DigitalPin represents a digital pin
type DigitalPin struct {
2014-11-04 11:02:20 +08:00
pin string
label string
value File
direction File
2022-11-05 14:42:28 +08:00
fs filesystem
}
2014-10-31 07:22:25 +08:00
// NewDigitalPin returns a DigitalPin given the pin number and an optional sysfs pin label.
// If no label is supplied the default label will prepend "gpio" to the pin number,
// eg. a pin number of 10 will have a label of "gpio10"
2022-11-05 14:42:28 +08:00
func (a *Accesser) NewDigitalPin(pin int, v ...string) *DigitalPin {
d := &DigitalPin{
pin: strconv.Itoa(pin),
fs: a.fs,
}
2014-10-31 06:26:31 +08:00
if len(v) > 0 {
d.label = v[0]
} else {
d.label = "gpio" + d.pin
}
return d
}
2014-10-31 04:41:27 +08:00
2022-11-05 14:42:28 +08:00
// Direction sets (writes) the direction of the digital pin
func (d *DigitalPin) Direction(dir string) error {
_, err := writeFile(d.direction, []byte(dir))
return err
}
2022-11-05 14:42:28 +08:00
// Write writes the given value to the character device
func (d *DigitalPin) Write(b int) error {
_, err := writeFile(d.value, []byte(strconv.Itoa(b)))
2014-10-31 04:41:27 +08:00
return err
}
2022-11-05 14:42:28 +08:00
// Read reads the given value from character device
func (d *DigitalPin) Read() (n int, err error) {
buf, err := readFile(d.value)
2014-10-31 04:41:27 +08:00
if err != nil {
2014-11-08 08:21:39 +08:00
return 0, err
2014-10-31 04:41:27 +08:00
}
return strconv.Atoi(string(buf[0]))
}
2022-11-05 14:42:28 +08:00
// Export sets the pin as exported
func (d *DigitalPin) Export() error {
2022-11-05 14:42:28 +08:00
export, err := d.fs.openFile(GPIOPATH+"/export", os.O_WRONLY, 0644)
if err != nil {
return err
}
defer export.Close()
_, err = writeFile(export, []byte(d.pin))
if err != nil {
// If EBUSY then the pin has already been exported
e, ok := err.(*os.PathError)
if !ok || e.Err != syscall.EBUSY {
return err
}
}
if d.direction != nil {
d.direction.Close()
}
attempt := 0
for {
attempt++
2022-11-05 14:42:28 +08:00
d.direction, err = d.fs.openFile(fmt.Sprintf("%v/%v/direction", GPIOPATH, d.label), os.O_RDWR, 0644)
if err == nil {
break
}
if attempt > 10 {
return err
}
time.Sleep(10 * time.Millisecond)
}
if d.value != nil {
d.value.Close()
}
if err == nil {
2022-11-05 14:42:28 +08:00
d.value, err = d.fs.openFile(fmt.Sprintf("%v/%v/value", GPIOPATH, d.label), os.O_RDWR, 0644)
}
if err != nil {
// Should we unexport here?
// If we don't unexport we should make sure to close d.direction and d.value here
d.Unexport()
}
return err
}
2022-11-05 14:42:28 +08:00
// Unexport sets the pin as unexported
func (d *DigitalPin) Unexport() error {
2022-11-05 14:42:28 +08:00
unexport, err := d.fs.openFile(GPIOPATH+"/unexport", os.O_WRONLY, 0644)
if err != nil {
return err
}
defer unexport.Close()
if d.direction != nil {
d.direction.Close()
d.direction = nil
}
if d.value != nil {
d.value.Close()
d.value = nil
}
_, err = writeFile(unexport, []byte(d.pin))
if err != nil {
// If EINVAL then the pin is reserved in the system and can't be unexported
e, ok := err.(*os.PathError)
if !ok || e.Err != syscall.EINVAL {
return err
}
}
return nil
}
2014-10-31 04:41:27 +08:00
// Linux sysfs / GPIO specific sysfs docs.
// https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
// https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
var writeFile = func(f File, data []byte) (i int, err error) {
if f == nil {
return 0, errNotExported
2014-10-31 04:41:27 +08:00
}
// sysfs docs say:
// > When writing sysfs files, userspace processes should first read the
// > entire file, modify the values it wishes to change, then write the
// > entire buffer back.
// however, this seems outdated/inaccurate (docs are from back in the Kernel BitKeeper days).
i, err = f.Write(data)
return i, err
2014-10-31 04:41:27 +08:00
}
2014-10-31 07:06:04 +08:00
var readFile = func(f File) ([]byte, error) {
if f == nil {
return nil, errNotExported
2014-11-08 08:21:39 +08:00
}
// sysfs docs say:
// > If userspace seeks back to zero or does a pread(2) with an offset of '0' the [..] method will
// > be called again, rearmed, to fill the buffer.
// TODO: Examine if seek is needed if full buffer is read from sysfs file.
2014-11-08 08:21:39 +08:00
buf := make([]byte, 2)
_, err := f.Seek(0, os.SEEK_SET)
if err == nil {
_, err = f.Read(buf)
}
2014-11-08 08:21:39 +08:00
return buf, err
2014-10-31 07:06:04 +08:00
}