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