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 }