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