1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
3 # SPDX-License-Identifier: MIT
5 Much code from https://github.com/vsergeev/python-periphery/blob/master/periphery/gpio.py
6 Copyright (c) 2015-2019 vsergeev / Ivan (Vanya) A. Sergeev
15 # pylint: disable=unnecessary-pass
16 class GPIOError(IOError):
17 """Base class for GPIO errors."""
22 # pylint: enable=unnecessary-pass
26 """SysFS GPIO Pin Class"""
28 # Number of retries to check for GPIO export or direction write on open
29 GPIO_OPEN_RETRIES = 10
30 # Delay between check for GPIO export or direction write on open (100ms)
46 _sysfs_path = "/sys/class/gpio/"
47 _channel_path = "gpiochip{}"
50 _export_path = "export"
51 _unexport_path = "unexport"
54 def __init__(self, pin_id):
55 """Instantiate a Pin object and open the sysfs GPIO with the specified
59 pin_id (int): GPIO pin number.
62 SysfsGPIO: GPIO object.
65 GPIOError: if an I/O or OS error occurs.
66 TypeError: if `line` or `direction` types are invalid.
67 ValueError: if `direction` value is invalid.
68 TimeoutError: if waiting for GPIO export times out.
72 if not isinstance(pin_id, int):
73 raise TypeError("Invalid Pin ID, should be integer.")
86 def __exit__(self, t, value, traceback):
89 def init(self, mode=IN, pull=None):
90 """Initialize the Pin"""
95 elif mode == self.OUT:
99 raise RuntimeError("Invalid mode for pin: %s" % self.id)
102 if pull == self.PULL_UP:
103 raise NotImplementedError(
104 "Internal pullups not supported in periphery, "
105 "use physical resistor instead!"
107 if pull == self.PULL_DOWN:
108 raise NotImplementedError(
109 "Internal pulldowns not supported in periphery, "
110 "use physical resistor instead!"
112 raise RuntimeError("Invalid pull for pin: %s" % self.id)
114 def value(self, val=None):
115 """Set or return the Pin Value"""
125 raise RuntimeError("Invalid value for pin")
126 return self.HIGH if self._read() else self.LOW
128 # pylint: disable=too-many-branches
129 def _open(self, direction):
130 if not isinstance(direction, str):
131 raise TypeError("Invalid direction type, should be string.")
132 if direction.lower() not in ["in", "out", "high", "low"]:
133 raise ValueError('Invalid direction, can be: "in", "out", "high", "low".')
134 gpio_path = "/sys/class/gpio/gpio{:d}".format(self.id)
136 if not os.path.isdir(gpio_path):
139 with open("/sys/class/gpio/export", "w", encoding="utf-8") as f_export:
140 f_export.write("{:d}\n".format(self.id))
142 raise GPIOError(e.errno, "Exporting GPIO: " + e.strerror) from IOError
144 # Loop until GPIO is exported
146 for i in range(self.GPIO_OPEN_RETRIES):
147 if os.path.isdir(gpio_path):
151 time.sleep(self.GPIO_OPEN_DELAY)
155 'Exporting GPIO: waiting for "{:s}" timed out'.format(gpio_path)
158 # Write direction, looping in case of EACCES errors due to delayed udev
159 # permission rule application after export
160 for i in range(self.GPIO_OPEN_RETRIES):
163 os.path.join(gpio_path, "direction"), "w", encoding="utf-8"
165 f_direction.write(direction.lower() + "\n")
168 if e.errno != errno.EACCES or (
169 e.errno == errno.EACCES and i == self.GPIO_OPEN_RETRIES - 1
172 e.errno, "Setting GPIO direction: " + e.strerror
175 time.sleep(self.GPIO_OPEN_DELAY)
180 os.path.join(gpio_path, "direction"), "w", encoding="utf-8"
182 f_direction.write(direction.lower() + "\n")
185 e.errno, "Setting GPIO direction: " + e.strerror
190 self._fd = os.open(os.path.join(gpio_path, "value"), os.O_RDWR)
192 raise GPIOError(e.errno, "Opening GPIO: " + e.strerror) from OSError
194 self._path = gpio_path
196 # pylint: enable=too-many-branches
205 raise GPIOError(e.errno, "Closing GPIO: " + e.strerror) from OSError
211 unexport_fd = os.open("/sys/class/gpio/unexport", os.O_WRONLY)
212 os.write(unexport_fd, "{:d}\n".format(self.id).encode())
213 os.close(unexport_fd)
215 raise GPIOError(e.errno, "Unexporting GPIO: " + e.strerror) from OSError
220 buf = os.read(self._fd, 2)
222 raise GPIOError(e.errno, "Reading GPIO: " + e.strerror) from OSError
226 os.lseek(self._fd, 0, os.SEEK_SET)
228 raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) from OSError
230 if buf[0] == b"0"[0]:
232 if buf[0] == b"1"[0]:
235 raise GPIOError(None, "Unknown GPIO value: {}".format(buf))
237 def _write(self, value):
238 if not isinstance(value, bool):
239 raise TypeError("Invalid value type, should be bool.")
244 os.write(self._fd, b"1\n")
246 os.write(self._fd, b"0\n")
248 raise GPIOError(e.errno, "Writing GPIO: " + e.strerror) from OSError
252 os.lseek(self._fd, 0, os.SEEK_SET)
254 raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) from OSError
258 """Return the Chip Name"""
259 gpio_path = os.path.join(self._path, "device")
261 gpiochip_path = os.readlink(gpio_path)
263 if "/" not in gpiochip_path:
266 'Reading gpiochip name: invalid device symlink "{:s}"'.format(
271 return gpiochip_path.split("/")[-1]
274 def chip_label(self):
275 """Return the Chip Label"""
276 gpio_path = "/sys/class/gpio/{:s}/label".format(self.chip_name)
279 with open(gpio_path, "r", encoding="utf-8") as f_label:
280 label = f_label.read()
281 except (GPIOError, IOError) as e:
282 if isinstance(e, IOError):
284 e.errno, "Reading gpiochip label: " + e.strerror
288 None, "Reading gpiochip label: " + e.strerror
295 def _get_direction(self):
299 os.path.join(self._path, "direction"), "r", encoding="utf-8"
301 direction = f_direction.read()
304 e.errno, "Getting GPIO direction: " + e.strerror
307 return direction.strip()
309 def _set_direction(self, direction):
310 if not isinstance(direction, str):
311 raise TypeError("Invalid direction type, should be string.")
312 if direction.lower() not in ["in", "out", "high", "low"]:
313 raise ValueError('Invalid direction, can be: "in", "out", "high", "low".')
318 os.path.join(self._path, "direction"), "w", encoding="utf-8"
320 f_direction.write(direction.lower() + "\n")
323 e.errno, "Setting GPIO direction: " + e.strerror
326 direction = property(_get_direction, _set_direction)
330 str_direction = self.direction
332 str_direction = "<error>"
335 str_chip_name = self.chip_name
337 str_chip_name = "<error>"
340 str_chip_label = self.chip_label
342 str_chip_label = "<error>"
344 output = "Pin {:d} (dev={:s}, fd={:d}, dir={:s}, chip_name='{:s}', chip_label='{:s}')"
345 return output.format(
346 self.id, self._path, self._fd, str_direction, str_chip_name, str_chip_label