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
14 # pylint: disable=unnecessary-pass
15 class GPIOError(IOError):
16 """Base class for GPIO errors."""
21 # pylint: enable=unnecessary-pass
25 """SysFS GPIO Pin Class"""
27 # Number of retries to check for GPIO export or direction write on open
28 GPIO_OPEN_RETRIES = 10
29 # Delay between check for GPIO export or direction write on open (100ms)
45 _sysfs_path = "/sys/class/gpio/"
46 _channel_path = "gpiochip{}"
49 _export_path = "export"
50 _unexport_path = "unexport"
53 def __init__(self, pin_id):
54 """Instantiate a Pin object and open the sysfs GPIO with the specified
58 pin_id (int): GPIO pin number.
61 SysfsGPIO: GPIO object.
64 GPIOError: if an I/O or OS error occurs.
65 TypeError: if `line` or `direction` types are invalid.
66 ValueError: if `direction` value is invalid.
67 TimeoutError: if waiting for GPIO export times out.
71 if not isinstance(pin_id, int):
72 raise TypeError("Invalid Pin ID, should be integer.")
85 def __exit__(self, t, value, traceback):
88 def init(self, mode=IN, pull=None):
89 """Initialize the Pin"""
94 elif mode == self.OUT:
98 raise RuntimeError("Invalid mode for pin: %s" % self.id)
101 if pull == self.PULL_UP:
102 raise NotImplementedError(
103 "Internal pullups not supported in periphery, "
104 "use physical resistor instead!"
106 if pull == self.PULL_DOWN:
107 raise NotImplementedError(
108 "Internal pulldowns not supported in periphery, "
109 "use physical resistor instead!"
111 raise RuntimeError("Invalid pull for pin: %s" % self.id)
113 def value(self, val=None):
114 """Set or return the Pin Value"""
124 raise RuntimeError("Invalid value for pin")
125 return self.HIGH if self._read() else self.LOW
127 # pylint: disable=too-many-branches
128 def _open(self, direction):
129 if not isinstance(direction, str):
130 raise TypeError("Invalid direction type, should be string.")
131 if direction.lower() not in ["in", "out", "high", "low"]:
132 raise ValueError('Invalid direction, can be: "in", "out", "high", "low".')
133 gpio_path = "/sys/class/gpio/gpio{:d}".format(self.id)
135 if not os.path.isdir(gpio_path):
138 with open("/sys/class/gpio/export", "w", encoding="utf-8") as f_export:
139 f_export.write("{:d}\n".format(self.id))
141 raise GPIOError(e.errno, "Exporting GPIO: " + e.strerror) from IOError
143 # Loop until GPIO is exported
145 for i in range(self.GPIO_OPEN_RETRIES):
146 if os.path.isdir(gpio_path):
150 time.sleep(self.GPIO_OPEN_DELAY)
154 'Exporting GPIO: waiting for "{:s}" timed out'.format(gpio_path)
157 # Write direction, looping in case of EACCES errors due to delayed udev
158 # permission rule application after export
159 for i in range(self.GPIO_OPEN_RETRIES):
162 os.path.join(gpio_path, "direction"), "w", encoding="utf-8"
164 f_direction.write(direction.lower() + "\n")
167 if e.errno != errno.EACCES or (
168 e.errno == errno.EACCES and i == self.GPIO_OPEN_RETRIES - 1
171 e.errno, "Setting GPIO direction: " + e.strerror
174 time.sleep(self.GPIO_OPEN_DELAY)
179 os.path.join(gpio_path, "direction"), "w", encoding="utf-8"
181 f_direction.write(direction.lower() + "\n")
184 e.errno, "Setting GPIO direction: " + e.strerror
189 self._fd = os.open(os.path.join(gpio_path, "value"), os.O_RDWR)
191 raise GPIOError(e.errno, "Opening GPIO: " + e.strerror) from OSError
193 self._path = gpio_path
195 # pylint: enable=too-many-branches
204 raise GPIOError(e.errno, "Closing GPIO: " + e.strerror) from OSError
210 unexport_fd = os.open("/sys/class/gpio/unexport", os.O_WRONLY)
211 os.write(unexport_fd, "{:d}\n".format(self.id).encode())
212 os.close(unexport_fd)
214 raise GPIOError(e.errno, "Unexporting GPIO: " + e.strerror) from OSError
219 buf = os.read(self._fd, 2)
221 raise GPIOError(e.errno, "Reading GPIO: " + e.strerror) from OSError
225 os.lseek(self._fd, 0, os.SEEK_SET)
227 raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) from OSError
229 if buf[0] == b"0"[0]:
231 if buf[0] == b"1"[0]:
234 raise GPIOError(None, "Unknown GPIO value: {}".format(buf))
236 def _write(self, value):
237 if not isinstance(value, bool):
238 raise TypeError("Invalid value type, should be bool.")
243 os.write(self._fd, b"1\n")
245 os.write(self._fd, b"0\n")
247 raise GPIOError(e.errno, "Writing GPIO: " + e.strerror) from OSError
251 os.lseek(self._fd, 0, os.SEEK_SET)
253 raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) from OSError
257 """Return the Chip Name"""
258 gpio_path = os.path.join(self._path, "device")
260 gpiochip_path = os.readlink(gpio_path)
262 if "/" not in gpiochip_path:
265 'Reading gpiochip name: invalid device symlink "{:s}"'.format(
270 return gpiochip_path.split("/")[-1]
273 def chip_label(self):
274 """Return the Chip Label"""
275 gpio_path = "/sys/class/gpio/{:s}/label".format(self.chip_name)
278 with open(gpio_path, "r", encoding="utf-8") as f_label:
279 label = f_label.read()
280 except (GPIOError, IOError) as e:
281 if isinstance(e, IOError):
283 e.errno, "Reading gpiochip label: " + e.strerror
287 None, "Reading gpiochip label: " + e.strerror
294 def _get_direction(self):
298 os.path.join(self._path, "direction"), "r", encoding="utf-8"
300 direction = f_direction.read()
303 e.errno, "Getting GPIO direction: " + e.strerror
306 return direction.strip()
308 def _set_direction(self, direction):
309 if not isinstance(direction, str):
310 raise TypeError("Invalid direction type, should be string.")
311 if direction.lower() not in ["in", "out", "high", "low"]:
312 raise ValueError('Invalid direction, can be: "in", "out", "high", "low".')
317 os.path.join(self._path, "direction"), "w", encoding="utf-8"
319 f_direction.write(direction.lower() + "\n")
322 e.errno, "Setting GPIO direction: " + e.strerror
325 direction = property(_get_direction, _set_direction)
329 str_direction = self.direction
331 str_direction = "<error>"
334 str_chip_name = self.chip_name
336 str_chip_name = "<error>"
339 str_chip_label = self.chip_label
341 str_chip_label = "<error>"
343 output = "Pin {:d} (dev={:s}, fd={:d}, dir={:s}, chip_name='{:s}', chip_label='{:s}')"
344 return output.format(
345 self.id, self._path, self._fd, str_direction, str_chip_name, str_chip_label