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 }