diff -Nru python-periphery-2.3.0/CHANGELOG.md python-periphery-2.4.1/CHANGELOG.md --- python-periphery-2.3.0/CHANGELOG.md 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/CHANGELOG.md 2023-04-21 06:15:11.000000000 +0000 @@ -1,3 +1,17 @@ +* v2.4.1 - 04/21/2023 + * GPIO + * Fix realtime timestamp reporting for line events in gpio-cdev v2 + implementation. + +* v2.4.0 - 04/17/2023 + * GPIO + * Avoid writing `direction` and `inverted` on open to retain existing + state with sysfs GPIOs + * Add support for gpio-cdev v2 ABI. + * Add type stubs. + * Contributors + * Juho Kim, @AussieSeaweed - ea1ecc2 + * v2.3.0 - 02/14/2021 * GPIO * Add kernel version check for line bias support. diff -Nru python-periphery-2.3.0/debian/changelog python-periphery-2.4.1/debian/changelog --- python-periphery-2.3.0/debian/changelog 2022-11-27 14:00:43.000000000 +0000 +++ python-periphery-2.4.1/debian/changelog 2023-06-21 18:09:50.000000000 +0000 @@ -1,3 +1,13 @@ +python-periphery (2.4.1-1) unstable; urgency=medium + + * Use github tags instead of releases for d/watch. + * New upstream version 2.4.1 + * Bump Standards-Version to 4.6.2. + * Update d/copyright with new years. + * Build using pybuild-plugin-pyproject. + + -- Michael Fladischer Wed, 21 Jun 2023 18:09:50 +0000 + python-periphery (2.3.0-2) unstable; urgency=medium [ Debian Janitor ] diff -Nru python-periphery-2.3.0/debian/control python-periphery-2.4.1/debian/control --- python-periphery-2.3.0/debian/control 2022-11-27 14:00:43.000000000 +0000 +++ python-periphery-2.4.1/debian/control 2023-06-21 18:09:50.000000000 +0000 @@ -6,11 +6,12 @@ Build-Depends: debhelper-compat (= 13), dh-python, + pybuild-plugin-pyproject, python3-all, python3-setuptools, python3-sphinx, python3-sphinx-rtd-theme, -Standards-Version: 4.6.0 +Standards-Version: 4.6.2 Homepage: https://github.com/vsergeev/python-periphery/ Vcs-Browser: https://salsa.debian.org/python-team/packages/python-periphery Vcs-Git: https://salsa.debian.org/python-team/packages/python-periphery.git diff -Nru python-periphery-2.3.0/debian/copyright python-periphery-2.4.1/debian/copyright --- python-periphery-2.3.0/debian/copyright 2022-11-27 14:00:43.000000000 +0000 +++ python-periphery-2.4.1/debian/copyright 2023-06-21 18:09:50.000000000 +0000 @@ -8,7 +8,7 @@ License: Expat Files: debian/* -Copyright: 2019, Michael Fladischer +Copyright: 2019-2023, Michael Fladischer License: Expat License: Expat diff -Nru python-periphery-2.3.0/debian/watch python-periphery-2.4.1/debian/watch --- python-periphery-2.3.0/debian/watch 2022-11-27 14:00:43.000000000 +0000 +++ python-periphery-2.4.1/debian/watch 2023-06-21 18:09:50.000000000 +0000 @@ -1,4 +1,4 @@ version=4 opts=filenamemangle=s/.*\/v([\d\.]+.*)$/python-periphery-$1/ \ -https://github.com/vsergeev/python-periphery/releases \ +https://github.com/vsergeev/python-periphery/tags \ /vsergeev/python-periphery/archive/refs/tags/v([\d\.]+)\.tar\.gz diff -Nru python-periphery-2.3.0/docs/conf.py python-periphery-2.4.1/docs/conf.py --- python-periphery-2.3.0/docs/conf.py 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/docs/conf.py 2023-04-21 06:15:11.000000000 +0000 @@ -17,11 +17,11 @@ # -- Project information ----------------------------------------------------- project = u'python-periphery' -copyright = u'2015-2021, vsergeev / Ivan (Vanya) A. Sergeev' +copyright = u'2015-2023, vsergeev / Ivan (Vanya) A. Sergeev' author = u'Vanya A. Sergeev' # The short X.Y version. -version = '2.3.0' +version = '2.4.1' # The full version, including alpha/beta/rc tags. release = version diff -Nru python-periphery-2.3.0/.flake8 python-periphery-2.4.1/.flake8 --- python-periphery-2.3.0/.flake8 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/.flake8 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,2 @@ +[flake8] +extend-ignore = E501,F401,E402 diff -Nru python-periphery-2.3.0/LICENSE python-periphery-2.4.1/LICENSE --- python-periphery-2.3.0/LICENSE 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/LICENSE 2023-04-21 06:15:11.000000000 +0000 @@ -1,4 +1,4 @@ - Copyright (c) 2015-2021 vsergeev / Ivan (Vanya) A. Sergeev + Copyright (c) 2015-2023 vsergeev / Ivan (Vanya) A. Sergeev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff -Nru python-periphery-2.3.0/periphery/gpio_cdev1.py python-periphery-2.4.1/periphery/gpio_cdev1.py --- python-periphery-2.3.0/periphery/gpio_cdev1.py 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/gpio_cdev1.py 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,605 @@ +import platform +import ctypes +import fcntl +import os +import select + +from .gpio import GPIO, GPIOError, EdgeEvent + + +try: + KERNEL_VERSION = tuple([int(s) for s in platform.release().split(".")[:2]]) +except ValueError: + KERNEL_VERSION = (0, 0) + + +_GPIO_NAME_MAX_SIZE = 32 +_GPIOHANDLES_MAX = 64 + + +class _CGpiochipInfo(ctypes.Structure): + _fields_ = [ + ('name', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('label', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('lines', ctypes.c_uint32), + ] + + +class _CGpiolineInfo(ctypes.Structure): + _fields_ = [ + ('line_offset', ctypes.c_uint32), + ('flags', ctypes.c_uint32), + ('name', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('consumer', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ] + + +class _CGpiohandleRequest(ctypes.Structure): + _fields_ = [ + ('lineoffsets', ctypes.c_uint32 * _GPIOHANDLES_MAX), + ('flags', ctypes.c_uint32), + ('default_values', ctypes.c_uint8 * _GPIOHANDLES_MAX), + ('consumer_label', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('lines', ctypes.c_uint32), + ('fd', ctypes.c_int), + ] + + +class _CGpiohandleData(ctypes.Structure): + _fields_ = [ + ('values', ctypes.c_uint8 * _GPIOHANDLES_MAX), + ] + + +class _CGpioeventRequest(ctypes.Structure): + _fields_ = [ + ('lineoffset', ctypes.c_uint32), + ('handleflags', ctypes.c_uint32), + ('eventflags', ctypes.c_uint32), + ('consumer_label', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('fd', ctypes.c_int), + ] + + +class _CGpioeventData(ctypes.Structure): + _fields_ = [ + ('timestamp', ctypes.c_uint64), + ('id', ctypes.c_uint32), + ] + + +class Cdev1GPIO(GPIO): + # Constants scraped from + _GPIOHANDLE_GET_LINE_VALUES_IOCTL = 0xc040b408 + _GPIOHANDLE_SET_LINE_VALUES_IOCTL = 0xc040b409 + _GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 + _GPIO_GET_LINEINFO_IOCTL = 0xc048b402 + _GPIO_GET_LINEHANDLE_IOCTL = 0xc16cb403 + _GPIO_GET_LINEEVENT_IOCTL = 0xc030b404 + _GPIOHANDLE_REQUEST_INPUT = 0x1 + _GPIOHANDLE_REQUEST_OUTPUT = 0x2 + _GPIOHANDLE_REQUEST_ACTIVE_LOW = 0x4 + _GPIOHANDLE_REQUEST_OPEN_DRAIN = 0x8 + _GPIOHANDLE_REQUEST_OPEN_SOURCE = 0x10 + _GPIOHANDLE_REQUEST_BIAS_PULL_UP = 0x20 + _GPIOHANDLE_REQUEST_BIAS_PULL_DOWN = 0x40 + _GPIOHANDLE_REQUEST_BIAS_DISABLE = 0x80 + _GPIOEVENT_REQUEST_RISING_EDGE = 0x1 + _GPIOEVENT_REQUEST_FALLING_EDGE = 0x2 + _GPIOEVENT_REQUEST_BOTH_EDGES = 0x3 + _GPIOEVENT_EVENT_RISING_EDGE = 0x1 + _GPIOEVENT_EVENT_FALLING_EDGE = 0x2 + + _SUPPORTS_LINE_BIAS = KERNEL_VERSION >= (5, 5) + + def __init__(self, path, line, direction, edge="none", bias="default", drive="default", inverted=False, label=None): + """**Character device GPIO (ABI version 1)** + + Instantiate a GPIO object and open the character device GPIO with the + specified line and direction at the specified GPIO chip path (e.g. + "/dev/gpiochip0"). Defaults properties can be overridden with keyword + arguments. + + Args: + path (str): GPIO chip character device path. + line (int, str): GPIO line number or name. + direction (str): GPIO direction, can be "in", "out", "high", or + "low". + edge (str): GPIO interrupt edge, can be "none", "rising", + "falling", or "both". + bias (str): GPIO line bias, can be "default", "pull_up", + "pull_down", or "disable". + drive (str): GPIO line drive, can be "default", "open_drain", or + "open_source". + inverted (bool): GPIO is inverted (active low). + label (str, None): GPIO line consumer label. + + Returns: + Cdev1GPIO: GPIO object. + + Raises: + GPIOError: if an I/O or OS error occurs. + TypeError: if `path`, `line`, `direction`, `edge`, `bias`, `drive`, + `inverted`, or `label` types are invalid. + ValueError: if `direction`, `edge`, `bias`, or `drive` value is + invalid. + LookupError: if the GPIO line was not found by the provided name. + + """ + self._devpath = None + self._line = None + self._line_fd = None + self._chip_fd = None + self._direction = None + self._edge = None + self._bias = None + self._drive = None + self._inverted = None + self._label = None + + self._open(path, line, direction, edge, bias, drive, inverted, label) + + def __new__(self, path, line, direction, **kwargs): + return object.__new__(Cdev1GPIO) + + def _open(self, path, line, direction, edge, bias, drive, inverted, label): + if not isinstance(path, str): + raise TypeError("Invalid path type, should be string.") + + if not isinstance(line, (int, str)): + raise TypeError("Invalid line type, should be integer or string.") + + if not isinstance(direction, str): + raise TypeError("Invalid direction type, should be string.") + elif direction not in ["in", "out", "high", "low"]: + raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") + + if not isinstance(edge, str): + raise TypeError("Invalid edge type, should be string.") + elif edge not in ["none", "rising", "falling", "both"]: + raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") + + if not isinstance(bias, str): + raise TypeError("Invalid bias type, should be string.") + elif bias not in ["default", "pull_up", "pull_down", "disable"]: + raise ValueError("Invalid bias, can be: \"default\", \"pull_up\", \"pull_down\", \"disable\".") + + if not isinstance(drive, str): + raise TypeError("Invalid drive type, should be string.") + elif drive not in ["default", "open_drain", "open_source"]: + raise ValueError("Invalid drive, can be: \"default\", \"open_drain\", \"open_source\".") + + if not isinstance(inverted, bool): + raise TypeError("Invalid drive type, should be bool.") + + if not isinstance(label, (type(None), str)): + raise TypeError("Invalid label type, should be None or str.") + + if isinstance(line, str): + line = self._find_line_by_name(path, line) + + # Open GPIO chip + try: + self._chip_fd = os.open(path, 0) + except OSError as e: + raise GPIOError(e.errno, "Opening GPIO chip: " + e.strerror) + + self._devpath = path + self._line = line + self._label = label.encode() if label is not None else b"periphery" + + self._reopen(direction, edge, bias, drive, inverted) + + def _reopen(self, direction, edge, bias, drive, inverted): + flags = 0 + + if bias != "default" and not Cdev1GPIO._SUPPORTS_LINE_BIAS: + raise GPIOError(None, "Line bias configuration not supported by kernel version {}.{}.".format(*KERNEL_VERSION)) + elif bias == "pull_up": + flags |= Cdev1GPIO._GPIOHANDLE_REQUEST_BIAS_PULL_UP + elif bias == "pull_down": + flags |= Cdev1GPIO._GPIOHANDLE_REQUEST_BIAS_PULL_DOWN + elif bias == "disable": + flags |= Cdev1GPIO._GPIOHANDLE_REQUEST_BIAS_DISABLE + + if drive == "open_drain": + flags |= Cdev1GPIO._GPIOHANDLE_REQUEST_OPEN_DRAIN + elif drive == "open_source": + flags |= Cdev1GPIO._GPIOHANDLE_REQUEST_OPEN_SOURCE + + if inverted: + flags |= Cdev1GPIO._GPIOHANDLE_REQUEST_ACTIVE_LOW + + # FIXME this should really use GPIOHANDLE_SET_CONFIG_IOCTL instead of + # closing and reopening, especially to preserve output value on + # configuration changes + + # Close existing line + if self._line_fd is not None: + try: + os.close(self._line_fd) + except OSError as e: + raise GPIOError(e.errno, "Closing existing GPIO line: " + e.strerror) + + self._line_fd = None + + if direction == "in": + if edge == "none": + request = _CGpiohandleRequest() + + request.lineoffsets[0] = self._line + request.flags = flags | Cdev1GPIO._GPIOHANDLE_REQUEST_INPUT + request.consumer_label = self._label + request.lines = 1 + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_LINEHANDLE_IOCTL, request) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Opening input line handle: " + e.strerror) + + self._line_fd = request.fd + else: + request = _CGpioeventRequest() + + request.lineoffset = self._line + request.handleflags = flags | Cdev1GPIO._GPIOHANDLE_REQUEST_INPUT + request.eventflags = Cdev1GPIO._GPIOEVENT_REQUEST_RISING_EDGE if edge == "rising" else Cdev1GPIO._GPIOEVENT_REQUEST_FALLING_EDGE if edge == "falling" else Cdev1GPIO._GPIOEVENT_REQUEST_BOTH_EDGES + request.consumer_label = self._label + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_LINEEVENT_IOCTL, request) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Opening input line event handle: " + e.strerror) + + self._line_fd = request.fd + else: + request = _CGpiohandleRequest() + initial_value = True if direction == "high" else False + initial_value ^= inverted + + request.lineoffsets[0] = self._line + request.flags = flags | Cdev1GPIO._GPIOHANDLE_REQUEST_OUTPUT + request.default_values[0] = initial_value + request.consumer_label = self._label + request.lines = 1 + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_LINEHANDLE_IOCTL, request) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Opening output line handle: " + e.strerror) + + self._line_fd = request.fd + + self._direction = "in" if direction == "in" else "out" + self._edge = edge + self._bias = bias + self._drive = drive + self._inverted = inverted + + def _find_line_by_name(self, path, line): + # Open GPIO chip + try: + fd = os.open(path, 0) + except OSError as e: + raise GPIOError(e.errno, "Opening GPIO chip: " + e.strerror) + + # Get chip info for number of lines + chip_info = _CGpiochipInfo() + try: + fcntl.ioctl(fd, Cdev1GPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) + + # Get each line info + line_info = _CGpiolineInfo() + found = False + for i in range(chip_info.lines): + line_info.line_offset = i + try: + fcntl.ioctl(fd, Cdev1GPIO._GPIO_GET_LINEINFO_IOCTL, line_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) + + if line_info.name.decode() == line: + found = True + break + + try: + os.close(fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO chip: " + e.strerror) + + if found: + return i + + raise LookupError("Opening GPIO line: GPIO line \"{:s}\" not found by name.".format(line)) + + # Methods + + def read(self): + data = _CGpiohandleData() + + try: + fcntl.ioctl(self._line_fd, Cdev1GPIO._GPIOHANDLE_GET_LINE_VALUES_IOCTL, data) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Getting line value: " + e.strerror) + + return bool(data.values[0]) + + def write(self, value): + if not isinstance(value, bool): + raise TypeError("Invalid value type, should be bool.") + elif self._direction != "out": + raise GPIOError(None, "Invalid operation: cannot write to input GPIO") + + data = _CGpiohandleData() + + data.values[0] = value + + try: + fcntl.ioctl(self._line_fd, Cdev1GPIO._GPIOHANDLE_SET_LINE_VALUES_IOCTL, data) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Setting line value: " + e.strerror) + + def poll(self, timeout=None): + if not isinstance(timeout, (int, float, type(None))): + raise TypeError("Invalid timeout type, should be integer, float, or None.") + elif self._direction != "in": + raise GPIOError(None, "Invalid operation: cannot poll output GPIO") + + # Setup poll + p = select.poll() + p.register(self._line_fd, select.POLLIN | select.POLLPRI | select.POLLERR) + + # Scale timeout to milliseconds + if isinstance(timeout, (int, float)) and timeout > 0: + timeout *= 1000 + + # Poll + events = p.poll(timeout) + + return len(events) > 0 + + def read_event(self): + if self._direction != "in": + raise GPIOError(None, "Invalid operation: cannot read event of output GPIO") + elif self._edge == "none": + raise GPIOError(None, "Invalid operation: GPIO edge not set") + + try: + buf = os.read(self._line_fd, ctypes.sizeof(_CGpioeventData)) + except OSError as e: + raise GPIOError(e.errno, "Reading GPIO event: " + e.strerror) + + event_data = _CGpioeventData.from_buffer_copy(buf) + + if event_data.id == Cdev1GPIO._GPIOEVENT_EVENT_RISING_EDGE: + edge = "rising" + elif event_data.id == Cdev1GPIO._GPIOEVENT_EVENT_FALLING_EDGE: + edge = "falling" + else: + edge = "none" + + timestamp = event_data.timestamp + + return EdgeEvent(edge, timestamp) + + def close(self): + try: + if self._line_fd is not None: + os.close(self._line_fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO line: " + e.strerror) + + try: + if self._chip_fd is not None: + os.close(self._chip_fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO chip: " + e.strerror) + + self._line_fd = None + self._chip_fd = None + self._edge = "none" + self._direction = "in" + self._line = None + + # Immutable properties + + @property + def devpath(self): + return self._devpath + + @property + def fd(self): + return self._line_fd + + @property + def line(self): + return self._line + + @property + def name(self): + line_info = _CGpiolineInfo() + line_info.line_offset = self._line + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_LINEINFO_IOCTL, line_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) + + return line_info.name.decode() + + @property + def label(self): + line_info = _CGpiolineInfo() + line_info.line_offset = self._line + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_LINEINFO_IOCTL, line_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) + + return line_info.consumer.decode() + + @property + def chip_fd(self): + return self._chip_fd + + @property + def chip_name(self): + chip_info = _CGpiochipInfo() + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) + + return chip_info.name.decode() + + @property + def chip_label(self): + chip_info = _CGpiochipInfo() + + try: + fcntl.ioctl(self._chip_fd, Cdev1GPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) + + return chip_info.label.decode() + + # Mutable properties + + def _get_direction(self): + return self._direction + + def _set_direction(self, direction): + if not isinstance(direction, str): + raise TypeError("Invalid direction type, should be string.") + if direction not in ["in", "out", "high", "low"]: + raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") + + if self._direction == direction: + return + + self._reopen(direction, "none", self._bias, self._drive, self._inverted) + + direction = property(_get_direction, _set_direction) + + def _get_edge(self): + return self._edge + + def _set_edge(self, edge): + if not isinstance(edge, str): + raise TypeError("Invalid edge type, should be string.") + if edge not in ["none", "rising", "falling", "both"]: + raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") + + if self._direction != "in": + raise GPIOError(None, "Invalid operation: cannot set edge on output GPIO") + + if self._edge == edge: + return + + self._reopen(self._direction, edge, self._bias, self._drive, self._inverted) + + edge = property(_get_edge, _set_edge) + + def _get_bias(self): + return self._bias + + def _set_bias(self, bias): + if not isinstance(bias, str): + raise TypeError("Invalid bias type, should be string.") + if bias not in ["default", "pull_up", "pull_down", "disable"]: + raise ValueError("Invalid bias, can be: \"default\", \"pull_up\", \"pull_down\", \"disable\".") + + if self._bias == bias: + return + + self._reopen(self._direction, self._edge, bias, self._drive, self._inverted) + + bias = property(_get_bias, _set_bias) + + def _get_drive(self): + return self._drive + + def _set_drive(self, drive): + if not isinstance(drive, str): + raise TypeError("Invalid drive type, should be string.") + if drive not in ["default", "open_drain", "open_source"]: + raise ValueError("Invalid drive, can be: \"default\", \"open_drain\", \"open_source\".") + + if self._direction != "out" and drive != "default": + raise GPIOError(None, "Invalid operation: cannot set line drive on input GPIO") + + if self._drive == drive: + return + + self._reopen(self._direction, self._edge, self._bias, drive, self._inverted) + + drive = property(_get_drive, _set_drive) + + def _get_inverted(self): + return self._inverted + + def _set_inverted(self, inverted): + if not isinstance(inverted, bool): + raise TypeError("Invalid drive type, should be bool.") + + if self._inverted == inverted: + return + + self._reopen(self._direction, self._edge, self._bias, self._drive, inverted) + + inverted = property(_get_inverted, _set_inverted) + + # String representation + + def __str__(self): + try: + str_name = self.name + except GPIOError: + str_name = "" + + try: + str_label = self.label + except GPIOError: + str_label = "" + + try: + str_direction = self.direction + except GPIOError: + str_direction = "" + + try: + str_edge = self.edge + except GPIOError: + str_edge = "" + + try: + str_bias = self.bias + except GPIOError: + str_bias = "" + + try: + str_drive = self.drive + except GPIOError: + str_drive = "" + + try: + str_inverted = str(self.inverted) + except GPIOError: + str_inverted = "" + + try: + str_chip_name = self.chip_name + except GPIOError: + str_chip_name = "" + + try: + str_chip_label = self.chip_label + except GPIOError: + str_chip_label = "" + + return "GPIO {:d} (name=\"{:s}\", label=\"{:s}\", device={:s}, line_fd={:d}, chip_fd={:d}, direction={:s}, edge={:s}, bias={:s}, drive={:s}, inverted={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=cdev)" \ + .format(self._line, str_name, str_label, self._devpath, self._line_fd, self._chip_fd, str_direction, str_edge, str_bias, str_drive, str_inverted, str_chip_name, str_chip_label) diff -Nru python-periphery-2.3.0/periphery/gpio_cdev2.py python-periphery-2.4.1/periphery/gpio_cdev2.py --- python-periphery-2.3.0/periphery/gpio_cdev2.py 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/gpio_cdev2.py 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,627 @@ +import platform +import ctypes +import fcntl +import os +import select + +from .gpio import GPIO, GPIOError, EdgeEvent + + +try: + KERNEL_VERSION = tuple([int(s) for s in platform.release().split(".")[:2]]) +except ValueError: + KERNEL_VERSION = (0, 0) + + +_GPIO_NAME_MAX_SIZE = 32 +_GPIO_V2_LINES_MAX = 64 +_GPIO_V2_LINE_NUM_ATTRS_MAX = 10 + + +class _CGpiochipInfo(ctypes.Structure): + _fields_ = [ + ('name', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('label', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('lines', ctypes.c_uint32), + ] + + +class _CGpioV2LineValues(ctypes.Structure): + _fields_ = [ + ('bits', ctypes.c_uint64), + ('mask', ctypes.c_uint64), + ] + + +class _CGpioV2LineAttributeData(ctypes.Union): + _fields_ = [ + ('flags', ctypes.c_uint64), + ('values', ctypes.c_uint64), + ('debounce_period_us', ctypes.c_uint32), + ] + + +class _CGpioV2LineAttribute(ctypes.Structure): + _fields_ = [ + ('id', ctypes.c_uint32), + ('padding', ctypes.c_uint32), + ('data', _CGpioV2LineAttributeData), + ] + + +class _CGpioV2LineConfigAttribute(ctypes.Structure): + _fields_ = [ + ('attr', _CGpioV2LineAttribute), + ('mask', ctypes.c_uint64), + ] + + +class _CGpioV2LineConfig(ctypes.Structure): + _fields_ = [ + ('flags', ctypes.c_uint64), + ('num_attrs', ctypes.c_uint32), + ('padding', ctypes.c_uint32 * 5), + ('attrs', _CGpioV2LineConfigAttribute * _GPIO_V2_LINE_NUM_ATTRS_MAX), + ] + + +class _CGpioV2LineRequest(ctypes.Structure): + _fields_ = [ + ('offsets', ctypes.c_uint32 * _GPIO_V2_LINES_MAX), + ('consumer', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('config', _CGpioV2LineConfig), + ('num_lines', ctypes.c_uint32), + ('event_buffer_size', ctypes.c_uint32), + ('padding', ctypes.c_uint32 * 5), + ('fd', ctypes.c_int32), + ] + + +class _CGpioV2LineInfo(ctypes.Structure): + _fields_ = [ + ('name', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('consumer', ctypes.c_char * _GPIO_NAME_MAX_SIZE), + ('offset', ctypes.c_uint32), + ('num_attrs', ctypes.c_uint32), + ('flags', ctypes.c_uint64), + ('attrs', _CGpioV2LineAttribute * _GPIO_V2_LINE_NUM_ATTRS_MAX), + ('padding', ctypes.c_uint32 * 4), + ] + + +class _CGpioV2LineEvent(ctypes.Structure): + _fields_ = [ + ('timestamp_ns', ctypes.c_uint64), + ('id', ctypes.c_uint32), + ('offset', ctypes.c_uint32), + ('seqno', ctypes.c_uint32), + ('line_seqno', ctypes.c_uint32), + ('padding', ctypes.c_uint32 * 6), + ] + + +class Cdev2GPIO(GPIO): + # Constants scraped from + _GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 + _GPIO_V2_GET_LINEINFO_IOCTL = 0xc100b405 + _GPIO_V2_GET_LINE_IOCTL = 0xc250b407 + _GPIO_V2_LINE_GET_VALUES_IOCTL = 0xc010b40e + _GPIO_V2_LINE_SET_CONFIG_IOCTL = 0xc110b40d + _GPIO_V2_LINE_SET_VALUES_IOCTL = 0xc010b40f + _GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES = 0x2 + _GPIO_V2_LINE_EVENT_RISING_EDGE = 0x1 + _GPIO_V2_LINE_EVENT_FALLING_EDGE = 0x2 + _GPIO_V2_LINE_FLAG_ACTIVE_LOW = 0x2 + _GPIO_V2_LINE_FLAG_INPUT = 0x4 + _GPIO_V2_LINE_FLAG_OUTPUT = 0x8 + _GPIO_V2_LINE_FLAG_EDGE_RISING = 0x10 + _GPIO_V2_LINE_FLAG_EDGE_FALLING = 0x20 + _GPIO_V2_LINE_FLAG_OPEN_DRAIN = 0x40 + _GPIO_V2_LINE_FLAG_OPEN_SOURCE = 0x80 + _GPIO_V2_LINE_FLAG_BIAS_PULL_UP = 0x100 + _GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN = 0x200 + _GPIO_V2_LINE_FLAG_BIAS_DISABLED = 0x400 + _GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME = 0x800 + + SUPPORTED = KERNEL_VERSION >= (5, 10) + + def __init__(self, path, line, direction, edge="none", bias="default", drive="default", inverted=False, label=None): + """**Character device GPIO (ABI version 2)** + + Instantiate a GPIO object and open the character device GPIO with the + specified line and direction at the specified GPIO chip path (e.g. + "/dev/gpiochip0"). Defaults properties can be overridden with keyword + arguments. + + Args: + path (str): GPIO chip character device path. + line (int, str): GPIO line number or name. + direction (str): GPIO direction, can be "in", "out", "high", or + "low". + edge (str): GPIO interrupt edge, can be "none", "rising", + "falling", or "both". + bias (str): GPIO line bias, can be "default", "pull_up", + "pull_down", or "disable". + drive (str): GPIO line drive, can be "default", "open_drain", or + "open_source". + inverted (bool): GPIO is inverted (active low). + label (str, None): GPIO line consumer label. + + Returns: + Cdev2GPIO: GPIO object. + + Raises: + GPIOError: if an I/O or OS error occurs. + TypeError: if `path`, `line`, `direction`, `edge`, `bias`, `drive`, + `inverted`, or `label` types are invalid. + ValueError: if `direction`, `edge`, `bias`, or `drive` value is + invalid. + LookupError: if the GPIO line was not found by the provided name. + + """ + self._devpath = None + self._line = None + self._line_fd = None + self._chip_fd = None + self._direction = None + self._edge = None + self._bias = None + self._drive = None + self._inverted = None + self._label = None + + self._open(path, line, direction, edge, bias, drive, inverted, label) + + def __new__(self, path, line, direction, **kwargs): + return object.__new__(Cdev2GPIO) + + def _open(self, path, line, direction, edge, bias, drive, inverted, label): + if not isinstance(path, str): + raise TypeError("Invalid path type, should be string.") + + if not isinstance(line, (int, str)): + raise TypeError("Invalid line type, should be integer or string.") + + if not isinstance(direction, str): + raise TypeError("Invalid direction type, should be string.") + elif direction not in ["in", "out", "high", "low"]: + raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") + + if not isinstance(edge, str): + raise TypeError("Invalid edge type, should be string.") + elif edge not in ["none", "rising", "falling", "both"]: + raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") + + if not isinstance(bias, str): + raise TypeError("Invalid bias type, should be string.") + elif bias not in ["default", "pull_up", "pull_down", "disable"]: + raise ValueError("Invalid bias, can be: \"default\", \"pull_up\", \"pull_down\", \"disable\".") + + if not isinstance(drive, str): + raise TypeError("Invalid drive type, should be string.") + elif drive not in ["default", "open_drain", "open_source"]: + raise ValueError("Invalid drive, can be: \"default\", \"open_drain\", \"open_source\".") + + if not isinstance(inverted, bool): + raise TypeError("Invalid drive type, should be bool.") + + if not isinstance(label, (type(None), str)): + raise TypeError("Invalid label type, should be None or str.") + + if isinstance(line, str): + line = self._find_line_by_name(path, line) + + # Open GPIO chip + try: + self._chip_fd = os.open(path, 0) + except OSError as e: + raise GPIOError(e.errno, "Opening GPIO chip: " + e.strerror) + + self._devpath = path + self._line = line + self._label = label.encode() if label is not None else b"periphery" + + self._reopen(direction, edge, bias, drive, inverted) + + def _reopen(self, direction, edge, bias, drive, inverted): + flags = 0 + + if bias == "pull_up": + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_BIAS_PULL_UP + elif bias == "pull_down": + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN + elif bias == "disable": + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_BIAS_DISABLED + + if drive == "open_drain": + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_OPEN_DRAIN + elif drive == "open_source": + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_OPEN_SOURCE + + if inverted: + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_ACTIVE_LOW + + # FIXME this should really use GPIOHANDLE_SET_CONFIG_IOCTL instead of + # closing and reopening, especially to preserve output value on + # configuration changes + + # Close existing line + if self._line_fd is not None: + try: + os.close(self._line_fd) + except OSError as e: + raise GPIOError(e.errno, "Closing existing GPIO line: " + e.strerror) + + self._line_fd = None + + line_request = _CGpioV2LineRequest() + + if direction == "in": + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_EDGE_RISING if edge == "rising" else Cdev2GPIO._GPIO_V2_LINE_FLAG_EDGE_FALLING if edge == "falling" else (Cdev2GPIO._GPIO_V2_LINE_FLAG_EDGE_RISING | Cdev2GPIO._GPIO_V2_LINE_FLAG_EDGE_FALLING) if edge == "both" else 0 + flags |= Cdev2GPIO._GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME if edge != "none" else 0 + + line_request.offsets[0] = self._line + line_request.consumer = self._label + line_request.config.flags = flags | Cdev2GPIO._GPIO_V2_LINE_FLAG_INPUT + line_request.num_lines = 1 + + try: + fcntl.ioctl(self._chip_fd, Cdev2GPIO._GPIO_V2_GET_LINE_IOCTL, line_request) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Opening input line handle: " + e.strerror) + else: + initial_value = True if direction == "high" else False + initial_value ^= inverted + + line_request.offsets[0] = self._line + line_request.consumer = self._label + line_request.config.flags = flags | Cdev2GPIO._GPIO_V2_LINE_FLAG_OUTPUT + line_request.config.num_attrs = 1 + line_request.config.attrs[0].attr.id = Cdev2GPIO._GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES + line_request.config.attrs[0].attr.data.values = int(initial_value) + line_request.config.attrs[0].mask = 0x1 + line_request.num_lines = 1 + + try: + fcntl.ioctl(self._chip_fd, Cdev2GPIO._GPIO_V2_GET_LINE_IOCTL, line_request) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Opening output line handle: " + e.strerror) + + self._line_fd = line_request.fd + + self._direction = "in" if direction == "in" else "out" + self._edge = edge + self._bias = bias + self._drive = drive + self._inverted = inverted + + def _find_line_by_name(self, path, line): + # Open GPIO chip + try: + fd = os.open(path, 0) + except OSError as e: + raise GPIOError(e.errno, "Opening GPIO chip: " + e.strerror) + + # Get chip info for number of lines + chip_info = _CGpiochipInfo() + try: + fcntl.ioctl(fd, Cdev2GPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) + + # Get each line info + line_info = _CGpioV2LineInfo() + found = False + for i in range(chip_info.lines): + line_info.offset = i + try: + fcntl.ioctl(fd, Cdev2GPIO._GPIO_V2_GET_LINEINFO_IOCTL, line_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) + + if line_info.name.decode() == line: + found = True + break + + try: + os.close(fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO chip: " + e.strerror) + + if found: + return i + + raise LookupError("Opening GPIO line: GPIO line \"{:s}\" not found by name.".format(line)) + + # Methods + + def read(self): + data = _CGpioV2LineValues() + + data.mask = 0x1 + + try: + fcntl.ioctl(self._line_fd, Cdev2GPIO._GPIO_V2_LINE_GET_VALUES_IOCTL, data) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Getting line value: " + e.strerror) + + return bool(data.bits & 0x1) + + def write(self, value): + if not isinstance(value, bool): + raise TypeError("Invalid value type, should be bool.") + elif self._direction != "out": + raise GPIOError(None, "Invalid operation: cannot write to input GPIO") + + data = _CGpioV2LineValues() + + data.mask = 0x1 + data.bits = int(value) + + try: + fcntl.ioctl(self._line_fd, Cdev2GPIO._GPIO_V2_LINE_SET_VALUES_IOCTL, data) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Setting line value: " + e.strerror) + + def poll(self, timeout=None): + if not isinstance(timeout, (int, float, type(None))): + raise TypeError("Invalid timeout type, should be integer, float, or None.") + elif self._direction != "in": + raise GPIOError(None, "Invalid operation: cannot poll output GPIO") + + # Setup poll + p = select.poll() + p.register(self._line_fd, select.POLLIN | select.POLLPRI | select.POLLERR) + + # Scale timeout to milliseconds + if isinstance(timeout, (int, float)) and timeout > 0: + timeout *= 1000 + + # Poll + events = p.poll(timeout) + + return len(events) > 0 + + def read_event(self): + if self._direction != "in": + raise GPIOError(None, "Invalid operation: cannot read event of output GPIO") + elif self._edge == "none": + raise GPIOError(None, "Invalid operation: GPIO edge not set") + + try: + buf = os.read(self._line_fd, ctypes.sizeof(_CGpioV2LineEvent)) + except OSError as e: + raise GPIOError(e.errno, "Reading GPIO event: " + e.strerror) + + line_event = _CGpioV2LineEvent.from_buffer_copy(buf) + + if line_event.id == Cdev2GPIO._GPIO_V2_LINE_EVENT_RISING_EDGE: + edge = "rising" + elif line_event.id == Cdev2GPIO._GPIO_V2_LINE_EVENT_FALLING_EDGE: + edge = "falling" + else: + edge = "none" + + timestamp = line_event.timestamp_ns + + return EdgeEvent(edge, timestamp) + + def close(self): + try: + if self._line_fd is not None: + os.close(self._line_fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO line: " + e.strerror) + + try: + if self._chip_fd is not None: + os.close(self._chip_fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO chip: " + e.strerror) + + self._line_fd = None + self._chip_fd = None + self._edge = "none" + self._direction = "in" + self._line = None + + # Immutable properties + + @property + def devpath(self): + return self._devpath + + @property + def fd(self): + return self._line_fd + + @property + def line(self): + return self._line + + @property + def name(self): + line_info = _CGpioV2LineInfo() + line_info.offset = self._line + + try: + fcntl.ioctl(self._chip_fd, Cdev2GPIO._GPIO_V2_GET_LINEINFO_IOCTL, line_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) + + return line_info.name.decode() + + @property + def label(self): + line_info = _CGpioV2LineInfo() + line_info.offset = self._line + + try: + fcntl.ioctl(self._chip_fd, Cdev2GPIO._GPIO_V2_GET_LINEINFO_IOCTL, line_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) + + return line_info.consumer.decode() + + @property + def chip_fd(self): + return self._chip_fd + + @property + def chip_name(self): + chip_info = _CGpiochipInfo() + + try: + fcntl.ioctl(self._chip_fd, Cdev2GPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) + + return chip_info.name.decode() + + @property + def chip_label(self): + chip_info = _CGpiochipInfo() + + try: + fcntl.ioctl(self._chip_fd, Cdev2GPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) + except (OSError, IOError) as e: + raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) + + return chip_info.label.decode() + + # Mutable properties + + def _get_direction(self): + return self._direction + + def _set_direction(self, direction): + if not isinstance(direction, str): + raise TypeError("Invalid direction type, should be string.") + if direction not in ["in", "out", "high", "low"]: + raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") + + if self._direction == direction: + return + + self._reopen(direction, "none", self._bias, self._drive, self._inverted) + + direction = property(_get_direction, _set_direction) + + def _get_edge(self): + return self._edge + + def _set_edge(self, edge): + if not isinstance(edge, str): + raise TypeError("Invalid edge type, should be string.") + if edge not in ["none", "rising", "falling", "both"]: + raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") + + if self._direction != "in": + raise GPIOError(None, "Invalid operation: cannot set edge on output GPIO") + + if self._edge == edge: + return + + self._reopen(self._direction, edge, self._bias, self._drive, self._inverted) + + edge = property(_get_edge, _set_edge) + + def _get_bias(self): + return self._bias + + def _set_bias(self, bias): + if not isinstance(bias, str): + raise TypeError("Invalid bias type, should be string.") + if bias not in ["default", "pull_up", "pull_down", "disable"]: + raise ValueError("Invalid bias, can be: \"default\", \"pull_up\", \"pull_down\", \"disable\".") + + if self._bias == bias: + return + + self._reopen(self._direction, self._edge, bias, self._drive, self._inverted) + + bias = property(_get_bias, _set_bias) + + def _get_drive(self): + return self._drive + + def _set_drive(self, drive): + if not isinstance(drive, str): + raise TypeError("Invalid drive type, should be string.") + if drive not in ["default", "open_drain", "open_source"]: + raise ValueError("Invalid drive, can be: \"default\", \"open_drain\", \"open_source\".") + + if self._direction != "out" and drive != "default": + raise GPIOError(None, "Invalid operation: cannot set line drive on input GPIO") + + if self._drive == drive: + return + + self._reopen(self._direction, self._edge, self._bias, drive, self._inverted) + + drive = property(_get_drive, _set_drive) + + def _get_inverted(self): + return self._inverted + + def _set_inverted(self, inverted): + if not isinstance(inverted, bool): + raise TypeError("Invalid drive type, should be bool.") + + if self._inverted == inverted: + return + + self._reopen(self._direction, self._edge, self._bias, self._drive, inverted) + + inverted = property(_get_inverted, _set_inverted) + + # String representation + + def __str__(self): + try: + str_name = self.name + except GPIOError: + str_name = "" + + try: + str_label = self.label + except GPIOError: + str_label = "" + + try: + str_direction = self.direction + except GPIOError: + str_direction = "" + + try: + str_edge = self.edge + except GPIOError: + str_edge = "" + + try: + str_bias = self.bias + except GPIOError: + str_bias = "" + + try: + str_drive = self.drive + except GPIOError: + str_drive = "" + + try: + str_inverted = str(self.inverted) + except GPIOError: + str_inverted = "" + + try: + str_chip_name = self.chip_name + except GPIOError: + str_chip_name = "" + + try: + str_chip_label = self.chip_label + except GPIOError: + str_chip_label = "" + + return "GPIO {:d} (name=\"{:s}\", label=\"{:s}\", device={:s}, line_fd={:d}, chip_fd={:d}, direction={:s}, edge={:s}, bias={:s}, drive={:s}, inverted={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=cdev)" \ + .format(self._line, str_name, str_label, self._devpath, self._line_fd, self._chip_fd, str_direction, str_edge, str_bias, str_drive, str_inverted, str_chip_name, str_chip_label) diff -Nru python-periphery-2.3.0/periphery/gpio.py python-periphery-2.4.1/periphery/gpio.py --- python-periphery-2.3.0/periphery/gpio.py 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/periphery/gpio.py 2023-04-21 06:15:11.000000000 +0000 @@ -1,18 +1,6 @@ import collections -import ctypes -import errno -import fcntl import os -import os.path -import platform import select -import time - - -try: - KERNEL_VERSION = tuple([int(s) for s in platform.release().split(".")[:2]]) -except ValueError: - KERNEL_VERSION = (0, 0) class GPIOError(IOError): @@ -373,958 +361,10 @@ raise NotImplementedError() -class _CGpiochipInfo(ctypes.Structure): - _fields_ = [ - ('name', ctypes.c_char * 32), - ('label', ctypes.c_char * 32), - ('lines', ctypes.c_uint32), - ] - - -class _CGpiolineInfo(ctypes.Structure): - _fields_ = [ - ('line_offset', ctypes.c_uint32), - ('flags', ctypes.c_uint32), - ('name', ctypes.c_char * 32), - ('consumer', ctypes.c_char * 32), - ] - - -class _CGpiohandleRequest(ctypes.Structure): - _fields_ = [ - ('lineoffsets', ctypes.c_uint32 * 64), - ('flags', ctypes.c_uint32), - ('default_values', ctypes.c_uint8 * 64), - ('consumer_label', ctypes.c_char * 32), - ('lines', ctypes.c_uint32), - ('fd', ctypes.c_int), - ] - - -class _CGpiohandleData(ctypes.Structure): - _fields_ = [ - ('values', ctypes.c_uint8 * 64), - ] - - -class _CGpioeventRequest(ctypes.Structure): - _fields_ = [ - ('lineoffset', ctypes.c_uint32), - ('handleflags', ctypes.c_uint32), - ('eventflags', ctypes.c_uint32), - ('consumer_label', ctypes.c_char * 32), - ('fd', ctypes.c_int), - ] - - -class _CGpioeventData(ctypes.Structure): - _fields_ = [ - ('timestamp', ctypes.c_uint64), - ('id', ctypes.c_uint32), - ] - - -class CdevGPIO(GPIO): - # Constants scraped from - _GPIOHANDLE_GET_LINE_VALUES_IOCTL = 0xc040b408 - _GPIOHANDLE_SET_LINE_VALUES_IOCTL = 0xc040b409 - _GPIO_GET_CHIPINFO_IOCTL = 0x8044b401 - _GPIO_GET_LINEINFO_IOCTL = 0xc048b402 - _GPIO_GET_LINEHANDLE_IOCTL = 0xc16cb403 - _GPIO_GET_LINEEVENT_IOCTL = 0xc030b404 - _GPIOHANDLE_REQUEST_INPUT = 0x1 - _GPIOHANDLE_REQUEST_OUTPUT = 0x2 - _GPIOHANDLE_REQUEST_ACTIVE_LOW = 0x4 - _GPIOHANDLE_REQUEST_OPEN_DRAIN = 0x8 - _GPIOHANDLE_REQUEST_OPEN_SOURCE = 0x10 - _GPIOHANDLE_REQUEST_BIAS_PULL_UP = 0x20 - _GPIOHANDLE_REQUEST_BIAS_PULL_DOWN = 0x40 - _GPIOHANDLE_REQUEST_BIAS_DISABLE = 0x80 - _GPIOEVENT_REQUEST_RISING_EDGE = 0x1 - _GPIOEVENT_REQUEST_FALLING_EDGE = 0x2 - _GPIOEVENT_REQUEST_BOTH_EDGES = 0x3 - _GPIOEVENT_EVENT_RISING_EDGE = 0x1 - _GPIOEVENT_EVENT_FALLING_EDGE = 0x2 - - _SUPPORTS_LINE_BIAS = KERNEL_VERSION >= (5, 5) - - def __init__(self, path, line, direction, edge="none", bias="default", drive="default", inverted=False, label=None): - """**Character device GPIO** - - Instantiate a GPIO object and open the character device GPIO with the - specified line and direction at the specified GPIO chip path (e.g. - "/dev/gpiochip0"). Defaults properties can be overridden with keyword - arguments. - - Args: - path (str): GPIO chip character device path. - line (int, str): GPIO line number or name. - direction (str): GPIO direction, can be "in", "out", "high", or - "low". - edge (str): GPIO interrupt edge, can be "none", "rising", - "falling", or "both". - bias (str): GPIO line bias, can be "default", "pull_up", - "pull_down", or "disable". - drive (str): GPIO line drive, can be "default", "open_drain", or - "open_source". - inverted (bool): GPIO is inverted (active low). - label (str, None): GPIO line consumer label. - - Returns: - CdevGPIO: GPIO object. - - Raises: - GPIOError: if an I/O or OS error occurs. - TypeError: if `path`, `line`, `direction`, `edge`, `bias`, `drive`, - `inverted`, or `label` types are invalid. - ValueError: if `direction`, `edge`, `bias`, or `drive` value is - invalid. - LookupError: if the GPIO line was not found by the provided name. - - """ - self._devpath = None - self._line = None - self._line_fd = None - self._chip_fd = None - self._direction = None - self._edge = None - self._bias = None - self._drive = None - self._inverted = None - self._label = None - - self._open(path, line, direction, edge, bias, drive, inverted, label) - - def __new__(self, path, line, direction, **kwargs): - return object.__new__(CdevGPIO) - - def _open(self, path, line, direction, edge, bias, drive, inverted, label): - if not isinstance(path, str): - raise TypeError("Invalid path type, should be string.") - - if not isinstance(line, (int, str)): - raise TypeError("Invalid line type, should be integer or string.") - - if not isinstance(direction, str): - raise TypeError("Invalid direction type, should be string.") - elif direction not in ["in", "out", "high", "low"]: - raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") - - if not isinstance(edge, str): - raise TypeError("Invalid edge type, should be string.") - elif edge not in ["none", "rising", "falling", "both"]: - raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") - - if not isinstance(bias, str): - raise TypeError("Invalid bias type, should be string.") - elif bias not in ["default", "pull_up", "pull_down", "disable"]: - raise ValueError("Invalid bias, can be: \"default\", \"pull_up\", \"pull_down\", \"disable\".") - - if not isinstance(drive, str): - raise TypeError("Invalid drive type, should be string.") - elif drive not in ["default", "open_drain", "open_source"]: - raise ValueError("Invalid drive, can be: \"default\", \"open_drain\", \"open_source\".") - - if not isinstance(inverted, bool): - raise TypeError("Invalid drive type, should be bool.") - - if not isinstance(label, (type(None), str)): - raise TypeError("Invalid label type, should be None or str.") - - if isinstance(line, str): - line = self._find_line_by_name(path, line) - - # Open GPIO chip - try: - self._chip_fd = os.open(path, 0) - except OSError as e: - raise GPIOError(e.errno, "Opening GPIO chip: " + e.strerror) - - self._devpath = path - self._line = line - self._label = label.encode() if label is not None else b"periphery" - - self._reopen(direction, edge, bias, drive, inverted) - - def _reopen(self, direction, edge, bias, drive, inverted): - flags = 0 - - if bias != "default" and not CdevGPIO._SUPPORTS_LINE_BIAS: - raise GPIOError(None, "Line bias configuration not supported by kernel version {}.{}.".format(*KERNEL_VERSION)) - elif bias == "pull_up": - flags |= CdevGPIO._GPIOHANDLE_REQUEST_BIAS_PULL_UP - elif bias == "pull_down": - flags |= CdevGPIO._GPIOHANDLE_REQUEST_BIAS_PULL_DOWN - elif bias == "disable": - flags |= CdevGPIO._GPIOHANDLE_REQUEST_BIAS_DISABLE - - if drive == "open_drain": - flags |= CdevGPIO._GPIOHANDLE_REQUEST_OPEN_DRAIN - elif drive == "open_source": - flags |= CdevGPIO._GPIOHANDLE_REQUEST_OPEN_SOURCE - - if inverted: - flags |= CdevGPIO._GPIOHANDLE_REQUEST_ACTIVE_LOW - - # FIXME this should really use GPIOHANDLE_SET_CONFIG_IOCTL instead of - # closing and reopening, especially to preserve output value on - # configuration changes - - # Close existing line - if self._line_fd is not None: - try: - os.close(self._line_fd) - except OSError as e: - raise GPIOError(e.errno, "Closing existing GPIO line: " + e.strerror) - - self._line_fd = None - - if direction == "in": - if edge == "none": - request = _CGpiohandleRequest() - - request.lineoffsets[0] = self._line - request.flags = flags | CdevGPIO._GPIOHANDLE_REQUEST_INPUT - request.consumer_label = self._label - request.lines = 1 - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_LINEHANDLE_IOCTL, request) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Opening input line handle: " + e.strerror) - - self._line_fd = request.fd - else: - request = _CGpioeventRequest() - - request.lineoffset = self._line - request.handleflags = flags | CdevGPIO._GPIOHANDLE_REQUEST_INPUT - request.eventflags = CdevGPIO._GPIOEVENT_REQUEST_RISING_EDGE if edge == "rising" else CdevGPIO._GPIOEVENT_REQUEST_FALLING_EDGE if edge == "falling" else CdevGPIO._GPIOEVENT_REQUEST_BOTH_EDGES - request.consumer_label = self._label - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_LINEEVENT_IOCTL, request) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Opening input line event handle: " + e.strerror) - - self._line_fd = request.fd - else: - request = _CGpiohandleRequest() - initial_value = True if direction == "high" else False - initial_value ^= inverted - - request.lineoffsets[0] = self._line - request.flags = flags | CdevGPIO._GPIOHANDLE_REQUEST_OUTPUT - request.default_values[0] = initial_value - request.consumer_label = self._label - request.lines = 1 - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_LINEHANDLE_IOCTL, request) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Opening output line handle: " + e.strerror) - - self._line_fd = request.fd - - self._direction = "in" if direction == "in" else "out" - self._edge = edge - self._bias = bias - self._drive = drive - self._inverted = inverted - - def _find_line_by_name(self, path, line): - # Open GPIO chip - try: - fd = os.open(path, 0) - except OSError as e: - raise GPIOError(e.errno, "Opening GPIO chip: " + e.strerror) - - # Get chip info for number of lines - chip_info = _CGpiochipInfo() - try: - fcntl.ioctl(fd, CdevGPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) - - # Get each line info - line_info = _CGpiolineInfo() - found = False - for i in range(chip_info.lines): - line_info.line_offset = i - try: - fcntl.ioctl(fd, CdevGPIO._GPIO_GET_LINEINFO_IOCTL, line_info) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) - - if line_info.name.decode() == line: - found = True - break - - try: - os.close(fd) - except OSError as e: - raise GPIOError(e.errno, "Closing GPIO chip: " + e.strerror) - - if found: - return i - - raise LookupError("Opening GPIO line: GPIO line \"{:s}\" not found by name.".format(line)) - - # Methods - - def read(self): - data = _CGpiohandleData() - - try: - fcntl.ioctl(self._line_fd, CdevGPIO._GPIOHANDLE_GET_LINE_VALUES_IOCTL, data) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Getting line value: " + e.strerror) - - return bool(data.values[0]) - - def write(self, value): - if not isinstance(value, bool): - raise TypeError("Invalid value type, should be bool.") - elif self._direction != "out": - raise GPIOError(None, "Invalid operation: cannot write to input GPIO") - - data = _CGpiohandleData() - - data.values[0] = value - - try: - fcntl.ioctl(self._line_fd, CdevGPIO._GPIOHANDLE_SET_LINE_VALUES_IOCTL, data) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Setting line value: " + e.strerror) - - def poll(self, timeout=None): - if not isinstance(timeout, (int, float, type(None))): - raise TypeError("Invalid timeout type, should be integer, float, or None.") - elif self._direction != "in": - raise GPIOError(None, "Invalid operation: cannot poll output GPIO") - - # Setup poll - p = select.poll() - p.register(self._line_fd, select.POLLIN | select.POLLPRI | select.POLLERR) - - # Scale timeout to milliseconds - if isinstance(timeout, (int, float)) and timeout > 0: - timeout *= 1000 - - # Poll - events = p.poll(timeout) - - return len(events) > 0 - - def read_event(self): - if self._direction != "in": - raise GPIOError(None, "Invalid operation: cannot read event of output GPIO") - elif self._edge == "none": - raise GPIOError(None, "Invalid operation: GPIO edge not set") - - try: - buf = os.read(self._line_fd, ctypes.sizeof(_CGpioeventData)) - except OSError as e: - raise GPIOError(e.errno, "Reading GPIO event: " + e.strerror) - - event_data = _CGpioeventData.from_buffer_copy(buf) - - if event_data.id == CdevGPIO._GPIOEVENT_EVENT_RISING_EDGE: - edge = "rising" - elif event_data.id == CdevGPIO._GPIOEVENT_EVENT_FALLING_EDGE: - edge = "falling" - else: - edge = "none" - - timestamp = event_data.timestamp - - return EdgeEvent(edge, timestamp) - - def close(self): - try: - if self._line_fd is not None: - os.close(self._line_fd) - except OSError as e: - raise GPIOError(e.errno, "Closing GPIO line: " + e.strerror) - - try: - if self._chip_fd is not None: - os.close(self._chip_fd) - except OSError as e: - raise GPIOError(e.errno, "Closing GPIO chip: " + e.strerror) - - self._line_fd = None - self._chip_fd = None - self._edge = "none" - self._direction = "in" - self._line = None - - # Immutable properties - - @property - def devpath(self): - return self._devpath - - @property - def fd(self): - return self._line_fd - - @property - def line(self): - return self._line - - @property - def name(self): - line_info = _CGpiolineInfo() - line_info.line_offset = self._line - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_LINEINFO_IOCTL, line_info) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) - - return line_info.name.decode() - - @property - def label(self): - line_info = _CGpiolineInfo() - line_info.line_offset = self._line - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_LINEINFO_IOCTL, line_info) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Querying GPIO line info: " + e.strerror) - - return line_info.consumer.decode() - - @property - def chip_fd(self): - return self._chip_fd - - @property - def chip_name(self): - chip_info = _CGpiochipInfo() - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) - - return chip_info.name.decode() - - @property - def chip_label(self): - chip_info = _CGpiochipInfo() - - try: - fcntl.ioctl(self._chip_fd, CdevGPIO._GPIO_GET_CHIPINFO_IOCTL, chip_info) - except (OSError, IOError) as e: - raise GPIOError(e.errno, "Querying GPIO chip info: " + e.strerror) - - return chip_info.label.decode() - - # Mutable properties - - def _get_direction(self): - return self._direction - - def _set_direction(self, direction): - if not isinstance(direction, str): - raise TypeError("Invalid direction type, should be string.") - if direction not in ["in", "out", "high", "low"]: - raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") - - if self._direction == direction: - return - - self._reopen(direction, "none", self._bias, self._drive, self._inverted) - - direction = property(_get_direction, _set_direction) - - def _get_edge(self): - return self._edge - - def _set_edge(self, edge): - if not isinstance(edge, str): - raise TypeError("Invalid edge type, should be string.") - if edge not in ["none", "rising", "falling", "both"]: - raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") - - if self._direction != "in": - raise GPIOError(None, "Invalid operation: cannot set edge on output GPIO") - - if self._edge == edge: - return - - self._reopen(self._direction, edge, self._bias, self._drive, self._inverted) - - edge = property(_get_edge, _set_edge) - - def _get_bias(self): - return self._bias - - def _set_bias(self, bias): - if not isinstance(bias, str): - raise TypeError("Invalid bias type, should be string.") - if bias not in ["default", "pull_up", "pull_down", "disable"]: - raise ValueError("Invalid bias, can be: \"default\", \"pull_up\", \"pull_down\", \"disable\".") - - if self._bias == bias: - return - - self._reopen(self._direction, self._edge, bias, self._drive, self._inverted) - - bias = property(_get_bias, _set_bias) - - def _get_drive(self): - return self._drive - - def _set_drive(self, drive): - if not isinstance(drive, str): - raise TypeError("Invalid drive type, should be string.") - if drive not in ["default", "open_drain", "open_source"]: - raise ValueError("Invalid drive, can be: \"default\", \"open_drain\", \"open_source\".") - - if self._direction != "out" and drive != "default": - raise GPIOError(None, "Invalid operation: cannot set line drive on input GPIO") - - if self._drive == drive: - return - - self._reopen(self._direction, self._edge, self._bias, drive, self._inverted) - - drive = property(_get_drive, _set_drive) - - def _get_inverted(self): - return self._inverted - - def _set_inverted(self, inverted): - if not isinstance(inverted, bool): - raise TypeError("Invalid drive type, should be bool.") - - if self._inverted == inverted: - return - - self._reopen(self._direction, self._edge, self._bias, self._drive, inverted) - - inverted = property(_get_inverted, _set_inverted) - - # String representation - - def __str__(self): - try: - str_name = self.name - except GPIOError: - str_name = "" - - try: - str_label = self.label - except GPIOError: - str_label = "" - - try: - str_direction = self.direction - except GPIOError: - str_direction = "" - - try: - str_edge = self.edge - except GPIOError: - str_edge = "" - - try: - str_bias = self.bias - except GPIOError: - str_bias = "" - - try: - str_drive = self.drive - except GPIOError: - str_drive = "" - - try: - str_inverted = str(self.inverted) - except GPIOError: - str_inverted = "" - - try: - str_chip_name = self.chip_name - except GPIOError: - str_chip_name = "" - - try: - str_chip_label = self.chip_label - except GPIOError: - str_chip_label = "" - - return "GPIO {:d} (name=\"{:s}\", label=\"{:s}\", device={:s}, line_fd={:d}, chip_fd={:d}, direction={:s}, edge={:s}, bias={:s}, drive={:s}, inverted={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=cdev)" \ - .format(self._line, str_name, str_label, self._devpath, self._line_fd, self._chip_fd, str_direction, str_edge, str_bias, str_drive, str_inverted, str_chip_name, str_chip_label) - - -class SysfsGPIO(GPIO): - # Number of retries to check for GPIO export or direction write on open - GPIO_OPEN_RETRIES = 10 - # Delay between check for GPIO export or direction write on open (100ms) - GPIO_OPEN_DELAY = 0.1 - - def __init__(self, line, direction): - """**Sysfs GPIO** - - Instantiate a GPIO object and open the sysfs GPIO with the specified - line and direction. - - `direction` can be "in" for input; "out" for output, initialized to - low; "high" for output, initialized to high; or "low" for output, - initialized to low. - - Args: - line (int): GPIO line number. - direction (str): GPIO direction, can be "in", "out", "high", or - "low", - - Returns: - SysfsGPIO: GPIO object. - - Raises: - GPIOError: if an I/O or OS error occurs. - TypeError: if `line` or `direction` types are invalid. - ValueError: if `direction` value is invalid. - TimeoutError: if waiting for GPIO export times out. - - """ - self._fd = None - self._line = None - self._exported = False - - self._open(line, direction) - - def __new__(self, line, direction): - return object.__new__(SysfsGPIO) - - def _open(self, line, direction): - if not isinstance(line, int): - raise TypeError("Invalid line type, should be integer.") - if not isinstance(direction, str): - raise TypeError("Invalid direction type, should be string.") - if direction.lower() not in ["in", "out", "high", "low"]: - raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") - - gpio_path = "/sys/class/gpio/gpio{:d}".format(line) - - if not os.path.isdir(gpio_path): - # Export the line - try: - with open("/sys/class/gpio/export", "w") as f_export: - f_export.write("{:d}\n".format(line)) - except IOError as e: - raise GPIOError(e.errno, "Exporting GPIO: " + e.strerror) - - # Loop until GPIO is exported - for i in range(SysfsGPIO.GPIO_OPEN_RETRIES): - if os.path.isdir(gpio_path): - self._exported = True - break - - time.sleep(SysfsGPIO.GPIO_OPEN_DELAY) - - if not self._exported: - raise TimeoutError("Exporting GPIO: waiting for \"{:s}\" timed out".format(gpio_path)) - - # Write direction, looping in case of EACCES errors due to delayed udev - # permission rule application after export - for i in range(SysfsGPIO.GPIO_OPEN_RETRIES): - try: - with open(os.path.join(gpio_path, "direction"), "w") as f_direction: - f_direction.write(direction.lower() + "\n") - break - except IOError as e: - if e.errno != errno.EACCES or (e.errno == errno.EACCES and i == SysfsGPIO.GPIO_OPEN_RETRIES - 1): - raise GPIOError(e.errno, "Setting GPIO direction: " + e.strerror) - - time.sleep(SysfsGPIO.GPIO_OPEN_DELAY) - - # Open value - try: - self._fd = os.open(os.path.join(gpio_path, "value"), os.O_RDWR) - except OSError as e: - raise GPIOError(e.errno, "Opening GPIO: " + e.strerror) - - self._line = line - self._path = gpio_path - - # Initialize direction - if not self._exported: - self.direction = direction - - # Initialize inverted - self.inverted = False - - # Methods - - def read(self): - # Read value - try: - buf = os.read(self._fd, 2) - except OSError as e: - raise GPIOError(e.errno, "Reading GPIO: " + e.strerror) - - # Rewind - try: - os.lseek(self._fd, 0, os.SEEK_SET) - except OSError as e: - raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) - - if buf[0] == b"0"[0]: - return False - elif buf[0] == b"1"[0]: - return True - - raise GPIOError(None, "Unknown GPIO value: {}".format(buf)) - - def write(self, value): - if not isinstance(value, bool): - raise TypeError("Invalid value type, should be bool.") - - # Write value - try: - if value: - os.write(self._fd, b"1\n") - else: - os.write(self._fd, b"0\n") - except OSError as e: - raise GPIOError(e.errno, "Writing GPIO: " + e.strerror) - - # Rewind - try: - os.lseek(self._fd, 0, os.SEEK_SET) - except OSError as e: - raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) - - def poll(self, timeout=None): - if not isinstance(timeout, (int, float, type(None))): - raise TypeError("Invalid timeout type, should be integer, float, or None.") - - # Setup poll - p = select.poll() - p.register(self._fd, select.POLLPRI | select.POLLERR) - - # Scale timeout to milliseconds - if isinstance(timeout, (int, float)) and timeout > 0: - timeout *= 1000 - - # Poll - events = p.poll(timeout) - - # If GPIO edge interrupt occurred - if events: - # Rewind - try: - os.lseek(self._fd, 0, os.SEEK_SET) - except OSError as e: - raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) - - return True - - return False - - def read_event(self): - raise NotImplementedError() - - def close(self): - if self._fd is None: - return - - try: - os.close(self._fd) - except OSError as e: - raise GPIOError(e.errno, "Closing GPIO: " + e.strerror) - - self._fd = None - - if self._exported: - # Unexport the line - try: - unexport_fd = os.open("/sys/class/gpio/unexport", os.O_WRONLY) - os.write(unexport_fd, "{:d}\n".format(self._line).encode()) - os.close(unexport_fd) - except OSError as e: - raise GPIOError(e.errno, "Unexporting GPIO: " + e.strerror) - - # Immutable properties - - @property - def devpath(self): - return self._path - - @property - def fd(self): - return self._fd - - @property - def line(self): - return self._line - - @property - def name(self): - return "" - - @property - def label(self): - return "" - - @property - def chip_fd(self): - raise NotImplementedError("Sysfs GPIO does not have a gpiochip file descriptor.") - - @property - def chip_name(self): - gpio_path = os.path.join(self._path, "device") - - gpiochip_path = os.readlink(gpio_path) - - if '/' not in gpiochip_path: - raise GPIOError(None, "Reading gpiochip name: invalid device symlink \"{:s}\"".format(gpiochip_path)) - - return gpiochip_path.split('/')[-1] - - @property - def chip_label(self): - gpio_path = "/sys/class/gpio/{:s}/label".format(self.chip_name) - - try: - with open(gpio_path, "r") as f_label: - label = f_label.read() - except (GPIOError, IOError) as e: - if isinstance(e, IOError): - raise GPIOError(e.errno, "Reading gpiochip label: " + e.strerror) - - raise GPIOError(None, "Reading gpiochip label: " + e.strerror) - - return label.strip() - - # Mutable properties - - def _get_direction(self): - # Read direction - try: - with open(os.path.join(self._path, "direction"), "r") as f_direction: - direction = f_direction.read() - except IOError as e: - raise GPIOError(e.errno, "Getting GPIO direction: " + e.strerror) - - return direction.strip() - - def _set_direction(self, direction): - if not isinstance(direction, str): - raise TypeError("Invalid direction type, should be string.") - if direction.lower() not in ["in", "out", "high", "low"]: - raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") - - # Write direction - try: - with open(os.path.join(self._path, "direction"), "w") as f_direction: - f_direction.write(direction.lower() + "\n") - except IOError as e: - raise GPIOError(e.errno, "Setting GPIO direction: " + e.strerror) - - direction = property(_get_direction, _set_direction) - - def _get_edge(self): - # Read edge - try: - with open(os.path.join(self._path, "edge"), "r") as f_edge: - edge = f_edge.read() - except IOError as e: - raise GPIOError(e.errno, "Getting GPIO edge: " + e.strerror) - - return edge.strip() - - def _set_edge(self, edge): - if not isinstance(edge, str): - raise TypeError("Invalid edge type, should be string.") - if edge.lower() not in ["none", "rising", "falling", "both"]: - raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") - - # Write edge - try: - with open(os.path.join(self._path, "edge"), "w") as f_edge: - f_edge.write(edge.lower() + "\n") - except IOError as e: - raise GPIOError(e.errno, "Setting GPIO edge: " + e.strerror) - - edge = property(_get_edge, _set_edge) - - def _get_bias(self): - raise NotImplementedError("Sysfs GPIO does not support line bias property.") - - def _set_bias(self, bias): - raise NotImplementedError("Sysfs GPIO does not support line bias property.") - - bias = property(_get_bias, _set_bias) - - def _get_drive(self): - raise NotImplementedError("Sysfs GPIO does not support line drive property.") - - def _set_drive(self, drive): - raise NotImplementedError("Sysfs GPIO does not support line drive property.") - - drive = property(_get_drive, _set_drive) - - def _get_inverted(self): - # Read active_low - try: - with open(os.path.join(self._path, "active_low"), "r") as f_inverted: - inverted = f_inverted.read().strip() - except IOError as e: - raise GPIOError(e.errno, "Getting GPIO active_low: " + e.strerror) - - if inverted == "0": - return False - elif inverted == "1": - return True - - raise GPIOError(None, "Unknown GPIO active_low value: {}".format(inverted)) - - def _set_inverted(self, inverted): - if not isinstance(inverted, bool): - raise TypeError("Invalid drive type, should be bool.") - - # Write active_low - try: - with open(os.path.join(self._path, "active_low"), "w") as f_active_low: - f_active_low.write("1\n" if inverted else "0\n") - except IOError as e: - raise GPIOError(e.errno, "Setting GPIO active_low: " + e.strerror) - - inverted = property(_get_inverted, _set_inverted) - - # String representation - - def __str__(self): - try: - str_direction = self.direction - except GPIOError: - str_direction = "" - - try: - str_edge = self.edge - except GPIOError: - str_edge = "" - - try: - str_chip_name = self.chip_name - except GPIOError: - str_chip_name = "" - - try: - str_chip_label = self.chip_label - except GPIOError: - str_chip_label = "" - - try: - str_inverted = str(self.inverted) - except GPIOError: - str_inverted = "" +# Assign GPIO classes +from . import gpio_cdev1 +from . import gpio_cdev2 +from . import gpio_sysfs - return "GPIO {:d} (device={:s}, fd={:d}, direction={:s}, edge={:s}, inverted={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=sysfs)" \ - .format(self._line, self._path, self._fd, str_direction, str_edge, str_inverted, str_chip_name, str_chip_label) +CdevGPIO = gpio_cdev2.Cdev2GPIO if gpio_cdev2.Cdev2GPIO.SUPPORTED else gpio_cdev1.Cdev1GPIO +SysfsGPIO = gpio_sysfs.SysfsGPIO diff -Nru python-periphery-2.3.0/periphery/gpio.pyi python-periphery-2.4.1/periphery/gpio.pyi --- python-periphery-2.3.0/periphery/gpio.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/gpio.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,61 @@ +from types import TracebackType +from typing import Any + +KERNEL_VERSION: tuple[int, int] + +class GPIOError(IOError): ... + +class EdgeEvent: + def __new__(cls, edge: str, timestamp: int) -> EdgeEvent: ... # noqa: Y034 + +class GPIO: + def __new__(cls, *args: Any, **kwargs: Any) -> GPIO: ... # noqa: Y034 + def __del__(self) -> None: ... + def __enter__(self) -> GPIO: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def read(self) -> bool: ... + def write(self, value: bool) -> None: ... + def poll(self, timeout: float | None = ...) -> bool: ... + def read_event(self) -> EdgeEvent: ... + @staticmethod + def poll_multiple(gpios: list[GPIO], timeout: float | None = ...) -> list[GPIO]: ... + def close(self) -> None: ... + @property + def devpath(self) -> str: ... + @property + def fd(self) -> int: ... + @property + def line(self) -> int: ... + @property + def name(self) -> str: ... + @property + def label(self) -> str: ... + @property + def chip_fd(self) -> int: ... + @property + def chip_name(self) -> str: ... + @property + def chip_label(self) -> str: ... + direction: property + edge: property + bias: property + drive: property + inverted: property + +class CdevGPIO(GPIO): + def __init__( # pyright: ignore [reportInconsistentConstructor] + self, + path: str, + line: int | str, + direction: str, + edge: str = ..., + bias: str = ..., + drive: str = ..., + inverted: bool = ..., + label: str | None = ..., + ) -> None: ... + def __new__(self, path: str, line: int | str, direction: str, **kwargs: Any) -> CdevGPIO: ... # noqa: Y034 + +class SysfsGPIO(GPIO): + def __init__(self, line: int, direction: str) -> None: ... + def __new__(self, line: int, direction: str) -> SysfsGPIO: ... # noqa: Y034 diff -Nru python-periphery-2.3.0/periphery/gpio_sysfs.py python-periphery-2.4.1/periphery/gpio_sysfs.py --- python-periphery-2.3.0/periphery/gpio_sysfs.py 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/gpio_sysfs.py 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,372 @@ +import errno +import os +import os.path +import select +import time + +from .gpio import GPIO, GPIOError + + +class SysfsGPIO(GPIO): + # Number of retries to check for GPIO export or direction on open + _GPIO_STAT_RETRIES = 10 + # Delay between check for GPIO export or direction write on open (100ms) + _GPIO_STAT_DELAY = 0.1 + + def __init__(self, line, direction): + """**Sysfs GPIO** + + Instantiate a GPIO object and open the sysfs GPIO with the specified + line and direction. + + `direction` can be "in" for input; "out" for output, initialized to + low; "high" for output, initialized to high; or "low" for output, + initialized to low. + + Args: + line (int): GPIO line number. + direction (str): GPIO direction, can be "in", "out", "high", or + "low", + + Returns: + SysfsGPIO: GPIO object. + + Raises: + GPIOError: if an I/O or OS error occurs. + TypeError: if `line` or `direction` types are invalid. + ValueError: if `direction` value is invalid. + TimeoutError: if waiting for GPIO export times out. + + """ + self._fd = None + self._line = None + self._exported = False + + self._open(line, direction) + + def __new__(self, line, direction): + return object.__new__(SysfsGPIO) + + def _open(self, line, direction): + if not isinstance(line, int): + raise TypeError("Invalid line type, should be integer.") + if not isinstance(direction, str): + raise TypeError("Invalid direction type, should be string.") + if direction.lower() not in ["in", "out", "high", "low"]: + raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") + + gpio_path = "/sys/class/gpio/gpio{:d}".format(line) + + if not os.path.isdir(gpio_path): + # Export the line + try: + with open("/sys/class/gpio/export", "w") as f_export: + f_export.write("{:d}\n".format(line)) + except IOError as e: + raise GPIOError(e.errno, "Exporting GPIO: " + e.strerror) + + # Loop until GPIO is exported + for i in range(SysfsGPIO._GPIO_STAT_RETRIES): + if os.path.isdir(gpio_path): + self._exported = True + break + + time.sleep(SysfsGPIO._GPIO_STAT_DELAY) + + if not self._exported: + raise TimeoutError("Exporting GPIO: waiting for \"{:s}\" timed out".format(gpio_path)) + + # Loop until direction is writable. This could take some time after + # export as application of udev rules after export is asynchronous. + for i in range(SysfsGPIO._GPIO_STAT_RETRIES): + try: + with open(os.path.join(gpio_path, "direction"), 'w'): + break + except IOError as e: + if e.errno != errno.EACCES or (e.errno == errno.EACCES and i == SysfsGPIO._GPIO_STAT_RETRIES - 1): + raise GPIOError(e.errno, "Opening GPIO direction: " + e.strerror) + + time.sleep(SysfsGPIO._GPIO_STAT_DELAY) + + # Open value + try: + self._fd = os.open(os.path.join(gpio_path, "value"), os.O_RDWR) + except OSError as e: + raise GPIOError(e.errno, "Opening GPIO: " + e.strerror) + + self._line = line + self._path = gpio_path + + # Initialize direction + if self.direction != direction.lower(): + self.direction = direction + + # Methods + + def read(self): + # Read value + try: + buf = os.read(self._fd, 2) + except OSError as e: + raise GPIOError(e.errno, "Reading GPIO: " + e.strerror) + + # Rewind + try: + os.lseek(self._fd, 0, os.SEEK_SET) + except OSError as e: + raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) + + if buf[0] == b"0"[0]: + return False + elif buf[0] == b"1"[0]: + return True + + raise GPIOError(None, "Unknown GPIO value: {}".format(buf)) + + def write(self, value): + if not isinstance(value, bool): + raise TypeError("Invalid value type, should be bool.") + + # Write value + try: + if value: + os.write(self._fd, b"1\n") + else: + os.write(self._fd, b"0\n") + except OSError as e: + raise GPIOError(e.errno, "Writing GPIO: " + e.strerror) + + # Rewind + try: + os.lseek(self._fd, 0, os.SEEK_SET) + except OSError as e: + raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) + + def poll(self, timeout=None): + if not isinstance(timeout, (int, float, type(None))): + raise TypeError("Invalid timeout type, should be integer, float, or None.") + + # Setup poll + p = select.poll() + p.register(self._fd, select.POLLPRI | select.POLLERR) + + # Scale timeout to milliseconds + if isinstance(timeout, (int, float)) and timeout > 0: + timeout *= 1000 + + # Poll + events = p.poll(timeout) + + # If GPIO edge interrupt occurred + if events: + # Rewind + try: + os.lseek(self._fd, 0, os.SEEK_SET) + except OSError as e: + raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) + + return True + + return False + + def read_event(self): + raise NotImplementedError() + + def close(self): + if self._fd is None: + return + + try: + os.close(self._fd) + except OSError as e: + raise GPIOError(e.errno, "Closing GPIO: " + e.strerror) + + self._fd = None + + if self._exported: + # Unexport the line + try: + unexport_fd = os.open("/sys/class/gpio/unexport", os.O_WRONLY) + os.write(unexport_fd, "{:d}\n".format(self._line).encode()) + os.close(unexport_fd) + except OSError as e: + raise GPIOError(e.errno, "Unexporting GPIO: " + e.strerror) + + # Immutable properties + + @property + def devpath(self): + return self._path + + @property + def fd(self): + return self._fd + + @property + def line(self): + return self._line + + @property + def name(self): + return "" + + @property + def label(self): + return "" + + @property + def chip_fd(self): + raise NotImplementedError("Sysfs GPIO does not have a gpiochip file descriptor.") + + @property + def chip_name(self): + gpio_path = os.path.join(self._path, "device") + + gpiochip_path = os.readlink(gpio_path) + + if '/' not in gpiochip_path: + raise GPIOError(None, "Reading gpiochip name: invalid device symlink \"{:s}\"".format(gpiochip_path)) + + return gpiochip_path.split('/')[-1] + + @property + def chip_label(self): + gpio_path = "/sys/class/gpio/{:s}/label".format(self.chip_name) + + try: + with open(gpio_path, "r") as f_label: + label = f_label.read() + except (GPIOError, IOError) as e: + if isinstance(e, IOError): + raise GPIOError(e.errno, "Reading gpiochip label: " + e.strerror) + + raise GPIOError(None, "Reading gpiochip label: " + e.strerror) + + return label.strip() + + # Mutable properties + + def _get_direction(self): + # Read direction + try: + with open(os.path.join(self._path, "direction"), "r") as f_direction: + direction = f_direction.read() + except IOError as e: + raise GPIOError(e.errno, "Getting GPIO direction: " + e.strerror) + + return direction.strip() + + def _set_direction(self, direction): + if not isinstance(direction, str): + raise TypeError("Invalid direction type, should be string.") + if direction.lower() not in ["in", "out", "high", "low"]: + raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") + + # Write direction + try: + with open(os.path.join(self._path, "direction"), "w") as f_direction: + f_direction.write(direction.lower() + "\n") + except IOError as e: + raise GPIOError(e.errno, "Setting GPIO direction: " + e.strerror) + + direction = property(_get_direction, _set_direction) + + def _get_edge(self): + # Read edge + try: + with open(os.path.join(self._path, "edge"), "r") as f_edge: + edge = f_edge.read() + except IOError as e: + raise GPIOError(e.errno, "Getting GPIO edge: " + e.strerror) + + return edge.strip() + + def _set_edge(self, edge): + if not isinstance(edge, str): + raise TypeError("Invalid edge type, should be string.") + if edge.lower() not in ["none", "rising", "falling", "both"]: + raise ValueError("Invalid edge, can be: \"none\", \"rising\", \"falling\", \"both\".") + + # Write edge + try: + with open(os.path.join(self._path, "edge"), "w") as f_edge: + f_edge.write(edge.lower() + "\n") + except IOError as e: + raise GPIOError(e.errno, "Setting GPIO edge: " + e.strerror) + + edge = property(_get_edge, _set_edge) + + def _get_bias(self): + raise NotImplementedError("Sysfs GPIO does not support line bias property.") + + def _set_bias(self, bias): + raise NotImplementedError("Sysfs GPIO does not support line bias property.") + + bias = property(_get_bias, _set_bias) + + def _get_drive(self): + raise NotImplementedError("Sysfs GPIO does not support line drive property.") + + def _set_drive(self, drive): + raise NotImplementedError("Sysfs GPIO does not support line drive property.") + + drive = property(_get_drive, _set_drive) + + def _get_inverted(self): + # Read active_low + try: + with open(os.path.join(self._path, "active_low"), "r") as f_inverted: + inverted = f_inverted.read().strip() + except IOError as e: + raise GPIOError(e.errno, "Getting GPIO active_low: " + e.strerror) + + if inverted == "0": + return False + elif inverted == "1": + return True + + raise GPIOError(None, "Unknown GPIO active_low value: {}".format(inverted)) + + def _set_inverted(self, inverted): + if not isinstance(inverted, bool): + raise TypeError("Invalid drive type, should be bool.") + + # Write active_low + try: + with open(os.path.join(self._path, "active_low"), "w") as f_active_low: + f_active_low.write("1\n" if inverted else "0\n") + except IOError as e: + raise GPIOError(e.errno, "Setting GPIO active_low: " + e.strerror) + + inverted = property(_get_inverted, _set_inverted) + + # String representation + + def __str__(self): + try: + str_direction = self.direction + except GPIOError: + str_direction = "" + + try: + str_edge = self.edge + except GPIOError: + str_edge = "" + + try: + str_chip_name = self.chip_name + except GPIOError: + str_chip_name = "" + + try: + str_chip_label = self.chip_label + except GPIOError: + str_chip_label = "" + + try: + str_inverted = str(self.inverted) + except GPIOError: + str_inverted = "" + + return "GPIO {:d} (device={:s}, fd={:d}, direction={:s}, edge={:s}, inverted={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=sysfs)" \ + .format(self._line, self._path, self._fd, str_direction, str_edge, str_inverted, str_chip_name, str_chip_label) diff -Nru python-periphery-2.3.0/periphery/i2c.pyi python-periphery-2.4.1/periphery/i2c.pyi --- python-periphery-2.3.0/periphery/i2c.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/i2c.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,21 @@ +from types import TracebackType + +class I2CError(IOError): ... + +class I2C: + class Message: + data: bytes | bytearray | list[int] + read: bool + flags: int + def __init__(self, data: bytes | bytearray | list[int], read: bool = ..., flags: int = ...) -> None: ... + + def __init__(self, devpath: str) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> I2C: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def transfer(self, address: int, messages: list[Message]) -> None: ... + def close(self) -> None: ... + @property + def fd(self) -> int: ... + @property + def devpath(self) -> str: ... diff -Nru python-periphery-2.3.0/periphery/__init__.py python-periphery-2.4.1/periphery/__init__.py --- python-periphery-2.3.0/periphery/__init__.py 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/periphery/__init__.py 2023-04-21 06:15:11.000000000 +0000 @@ -1,9 +1,9 @@ import time -__version__ = "2.3.0" +__version__ = "2.4.1" "Module version string." -version = (2, 3, 0) +version = (2, 4, 1) "Module version tuple." diff -Nru python-periphery-2.3.0/periphery/__init__.pyi python-periphery-2.4.1/periphery/__init__.pyi --- python-periphery-2.3.0/periphery/__init__.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/__init__.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,19 @@ +from periphery.gpio import ( + GPIO as GPIO, + CdevGPIO as CdevGPIO, + EdgeEvent as EdgeEvent, + GPIOError as GPIOError, + SysfsGPIO as SysfsGPIO, +) +from periphery.i2c import I2C as I2C, I2CError as I2CError +from periphery.led import LED as LED, LEDError as LEDError +from periphery.mmio import MMIO as MMIO, MMIOError as MMIOError +from periphery.pwm import PWM as PWM, PWMError as PWMError +from periphery.serial import Serial as Serial, SerialError as SerialError +from periphery.spi import SPI as SPI, SPIError as SPIError + +version: tuple[int, int, int] + +def sleep(seconds: float) -> None: ... +def sleep_ms(milliseconds: float) -> None: ... +def sleep_us(microseconds: float) -> None: ... diff -Nru python-periphery-2.3.0/periphery/led.pyi python-periphery-2.4.1/periphery/led.pyi --- python-periphery-2.3.0/periphery/led.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/led.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,21 @@ +from types import TracebackType + +class LEDError(IOError): ... + +class LED: + def __init__(self, name: str, brightness: bool | int | None = ...) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> LED: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def read(self) -> int: ... + def write(self, brightness: bool | int) -> None: ... + def close(self) -> None: ... + @property + def devpath(self) -> str: ... + @property + def fd(self) -> int: ... + @property + def name(self) -> str: ... + @property + def max_brightness(self) -> int: ... + brightness: int diff -Nru python-periphery-2.3.0/periphery/mmio.py python-periphery-2.4.1/periphery/mmio.py --- python-periphery-2.3.0/periphery/mmio.py 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/periphery/mmio.py 2023-04-21 06:15:11.000000000 +0000 @@ -2,7 +2,6 @@ import os import mmap import ctypes -import struct # Alias long to int on Python 3 diff -Nru python-periphery-2.3.0/periphery/mmio.pyi python-periphery-2.4.1/periphery/mmio.pyi --- python-periphery-2.3.0/periphery/mmio.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/mmio.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,25 @@ +from ctypes import c_void_p +from types import TracebackType + +class MMIOError(IOError): ... + +class MMIO: + def __init__(self, physaddr: int, size: int, path: str = ...) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> MMIO: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def read32(self, offset: int) -> int: ... + def read16(self, offset: int) -> int: ... + def read8(self, offset: int) -> int: ... + def read(self, offset: int, length: int) -> bytes: ... + def write32(self, offset: int, value: int) -> None: ... + def write16(self, offset: int, value: int) -> None: ... + def write8(self, offset: int, value: int) -> None: ... + def write(self, offset: int, data: bytes | bytearray | list[int]) -> None: ... + def close(self) -> None: ... + @property + def base(self) -> int: ... + @property + def size(self) -> int: ... + @property + def pointer(self) -> c_void_p: ... diff -Nru python-periphery-2.3.0/periphery/pwm.py python-periphery-2.4.1/periphery/pwm.py --- python-periphery-2.3.0/periphery/pwm.py 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/periphery/pwm.py 2023-04-21 06:15:11.000000000 +0000 @@ -10,9 +10,9 @@ class PWM(object): # Number of retries to check for successful PWM export on open - PWM_STAT_RETRIES = 10 + _PWM_STAT_RETRIES = 10 # Delay between check for scucessful PWM export on open (100ms) - PWM_STAT_DELAY = 0.1 + _PWM_STAT_DELAY = 0.1 def __init__(self, chip, channel): """Instantiate a PWM object and open the sysfs PWM corresponding to the @@ -69,27 +69,27 @@ # Loop until PWM is exported exported = False - for i in range(PWM.PWM_STAT_RETRIES): + for i in range(PWM._PWM_STAT_RETRIES): if os.path.isdir(channel_path): exported = True break - time.sleep(PWM.PWM_STAT_DELAY) + time.sleep(PWM._PWM_STAT_DELAY) if not exported: raise TimeoutError("Exporting PWM: waiting for \"{:s}\" timed out".format(channel_path)) # Loop until period is writable. This could take some time after # export as application of udev rules after export is asynchronous. - for i in range(PWM.PWM_STAT_RETRIES): + for i in range(PWM._PWM_STAT_RETRIES): try: with open(os.path.join(channel_path, "period"), 'w'): break except IOError as e: - if e.errno != errno.EACCES or (e.errno == errno.EACCES and i == PWM.PWM_STAT_RETRIES - 1): + if e.errno != errno.EACCES or (e.errno == errno.EACCES and i == PWM._PWM_STAT_RETRIES - 1): raise PWMError(e.errno, "Opening PWM period: " + e.strerror) - time.sleep(PWM.PWM_STAT_DELAY) + time.sleep(PWM._PWM_STAT_DELAY) self._chip = chip self._channel = channel diff -Nru python-periphery-2.3.0/periphery/pwm.pyi python-periphery-2.4.1/periphery/pwm.pyi --- python-periphery-2.3.0/periphery/pwm.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/pwm.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,25 @@ +from types import TracebackType + +class PWMError(IOError): ... + +class PWM: + def __init__(self, chip: int, channel: int) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> PWM: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def close(self) -> None: ... + enabled: bool + def enable(self) -> None: ... + def disable(self) -> None: ... + @property + def devpath(self) -> str: ... + @property + def chip(self) -> int: ... + @property + def channel(self) -> int: ... + period_ns: int + duty_cycle_ns: int + period: int | float + duty_cycle: int | float + frequency: int | float + polarity: str diff -Nru python-periphery-2.3.0/periphery/serial.pyi python-periphery-2.4.1/periphery/serial.pyi --- python-periphery-2.3.0/periphery/serial.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/serial.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,37 @@ +from types import TracebackType + +class SerialError(IOError): ... + +class Serial: + def __init__( + self, + devpath: str, + baudrate: int, + databits: int = ..., + parity: str = ..., + stopbits: int = ..., + xonxoff: bool = ..., + rtscts: bool = ..., + ) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> Serial: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def read(self, length: int, timeout: float | None = ...) -> bytes: ... + def write(self, data: bytes | bytearray | list[int]) -> int: ... + def poll(self, timeout: float | None = ...) -> bool: ... + def flush(self) -> None: ... + def input_waiting(self) -> int: ... + def output_waiting(self) -> int: ... + def close(self) -> None: ... + @property + def fd(self) -> int: ... + @property + def devpath(self) -> str: ... + baudrate: int + databits: int + parity: str + stopbits: int + xonxoff: bool + rtscts: bool + vmin: int + vtime: float diff -Nru python-periphery-2.3.0/periphery/spi.pyi python-periphery-2.4.1/periphery/spi.pyi --- python-periphery-2.3.0/periphery/spi.pyi 1970-01-01 00:00:00.000000000 +0000 +++ python-periphery-2.4.1/periphery/spi.pyi 2023-04-21 06:15:11.000000000 +0000 @@ -0,0 +1,24 @@ +from types import TracebackType + +KERNEL_VERSION: tuple[int, int] + +class SPIError(IOError): ... + +class SPI: + def __init__( + self, devpath: str, mode: int, max_speed: float, bit_order: str = ..., bits_per_word: int = ..., extra_flags: int = ... + ) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> SPI: ... # noqa: Y034 + def __exit__(self, t: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None) -> None: ... + def transfer(self, data: bytes | bytearray | list[int]) -> bytes | bytearray | list[int]: ... + def close(self) -> None: ... + @property + def fd(self) -> int: ... + @property + def devpath(self) -> str: ... + mode: int + max_speed: float + bit_order: str + bits_per_word: int + extra_flags: int diff -Nru python-periphery-2.3.0/README.md python-periphery-2.4.1/README.md --- python-periphery-2.3.0/README.md 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/README.md 2023-04-21 06:15:11.000000000 +0000 @@ -1,4 +1,4 @@ -# python-periphery [![Build Status](https://travis-ci.org/vsergeev/python-periphery.svg?branch=master)](https://travis-ci.org/vsergeev/python-periphery) [![Docs Status](https://readthedocs.org/projects/python-periphery/badge/)](https://python-periphery.readthedocs.io/en/latest/) [![GitHub release](https://img.shields.io/github/release/vsergeev/python-periphery.svg?maxAge=7200)](https://github.com/vsergeev/python-periphery) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vsergeev/python-periphery/blob/master/LICENSE) +# python-periphery [![Build Status](https://app.travis-ci.com/vsergeev/python-periphery.svg?branch=master)](https://app.travis-ci.com/github/vsergeev/python-periphery) [![Docs Status](https://readthedocs.org/projects/python-periphery/badge/)](https://python-periphery.readthedocs.io/en/latest/) [![GitHub release](https://img.shields.io/github/release/vsergeev/python-periphery.svg?maxAge=7200)](https://github.com/vsergeev/python-periphery) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vsergeev/python-periphery/blob/master/LICENSE) ## Linux Peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) with Python 2 & 3 @@ -6,7 +6,7 @@ Using Lua or C? Check out the [lua-periphery](https://github.com/vsergeev/lua-periphery) and [c-periphery](https://github.com/vsergeev/c-periphery) projects. -Contributed libraries: [java-periphery](https://github.com/sgjava/java-periphery) +Contributed libraries: [java-periphery](https://github.com/sgjava/java-periphery), [dart_periphery](https://github.com/pezi/dart_periphery) ## Installation diff -Nru python-periphery-2.3.0/setup.py python-periphery-2.4.1/setup.py --- python-periphery-2.3.0/setup.py 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/setup.py 2023-04-21 06:15:11.000000000 +0000 @@ -5,12 +5,13 @@ setup( name='python-periphery', - version='2.3.0', + version='2.4.1', description='A pure Python 2/3 library for peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) in Linux.', author='vsergeev', author_email='v@sergeev.io', url='https://github.com/vsergeev/python-periphery', packages=['periphery'], + package_data={'periphery': ['*.pyi', 'py.typed']}, long_description="""python-periphery is a pure Python library for GPIO, LED, PWM, SPI, I2C, MMIO, and Serial peripheral I/O interface access in userspace Linux. It is useful in embedded Linux environments (including Raspberry Pi, BeagleBone, etc. platforms) for interfacing with external peripherals. python-periphery is compatible with Python 2 and Python 3, is written in pure Python, and is MIT licensed. See https://github.com/vsergeev/python-periphery for more information.""", classifiers=[ 'Development Status :: 5 - Production/Stable', diff -Nru python-periphery-2.3.0/.travis.yml python-periphery-2.4.1/.travis.yml --- python-periphery-2.3.0/.travis.yml 2021-02-15 04:38:06.000000000 +0000 +++ python-periphery-2.4.1/.travis.yml 2023-04-21 06:15:11.000000000 +0000 @@ -8,7 +8,11 @@ - "pypy" - "pypy3" +install: + - if [[ $TRAVIS_PYTHON_VERSION =~ ^3.[67]$ ]]; then pip install mypy; fi + script: + - if [[ $TRAVIS_PYTHON_VERSION =~ ^3.[67]$ ]]; then mypy periphery; fi - python -m tests.test_gpio - python -m tests.test_gpio_sysfs - python -m tests.test_spi