# python-cc1101 - Python Library to Transmit RF Signals via CC1101 Transceivers # # Copyright (C) 2021 Fabian Peter Hammerle # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import ctypes import ctypes.util import datetime import errno import functools # could not find Debian's python3-libgpiod on pypi.org # https://salsa.debian.org/debian/libgpiod does not provide setup.py or setup.cfg @functools.lru_cache(maxsize=1) def _load_libgpiod() -> ctypes.CDLL: filename = ctypes.util.find_library("gpiod") if not filename: raise FileNotFoundError( "Failed to find libgpiod." "\nOn Debian-based systems, like Raspberry Pi OS / Raspbian," " libgpiod can be installed via" "\n\tsudo apt-get install --no-install-recommends libgpiod2" ) return ctypes.CDLL(filename, use_errno=True) class _c_timespec(ctypes.Structure): """ struct timespec { time_t tv_sec; long tv_nsec; }; """ # pylint: disable=too-few-public-methods,invalid-name; struct _fields_ = [("tv_sec", ctypes.c_long), ("tv_nsec", ctypes.c_long)] class GPIOLine: def __init__(self, pointer: ctypes.c_void_p) -> None: assert pointer != 0 self._pointer = pointer @classmethod def find(cls, name: bytes) -> "GPIOLine": # > If this routine succeeds, the user must manually close the GPIO chip # > owning this line to avoid memory leaks. pointer: int = _load_libgpiod().gpiod_line_find(name) # > If the line could not be found, this functions sets errno to ENOENT. if pointer == 0: err = ctypes.get_errno() if err == errno.EACCES: # > [PermissionError] corresponds to errno EACCES and EPERM. raise PermissionError( f"Failed to access GPIO line {name.decode()!r}." "\nVerify that the current user has read and write access for /dev/gpiochip*." "\nOn some systems, like Raspberry Pi OS / Raspbian," "\n\tsudo usermod -a -G gpio $USER" "\nfollowed by a re-login grants sufficient permissions." ) if err == errno.ENOENT: # > [FileNotFoundError] corresponds to errno ENOENT. # https://docs.python.org/3/library/exceptions.html#FileNotFoundError raise FileNotFoundError( f"GPIO line {name.decode()!r} does not exist." "\nRun command `gpioinfo` to get a list of all available GPIO lines." ) raise OSError( f"Failed to open GPIO line {name.decode()!r}: {errno.errorcode[err]}" ) return cls(pointer=ctypes.c_void_p(pointer)) def __del__(self): # > Close a GPIO chip owning this line and release all resources. # > After this function returns, the line must no longer be used. if self._pointer: _load_libgpiod().gpiod_line_close_chip(self._pointer) # might make debugging easier in case someone calls __del__ twice self._pointer = None def wait_for_rising_edge( self, *, consumer: bytes, timeout: datetime.timedelta ) -> bool: """ Return True, if an event occured; False on timeout. """ if ( _load_libgpiod().gpiod_line_request_rising_edge_events( self._pointer, consumer ) != 0 ): err = ctypes.get_errno() raise OSError( f"Request for rising edge event notifications failed ({errno.errorcode[err]})." + ("\nBlocked by another process?" if err == errno.EBUSY else "") ) timeout_timespec = _c_timespec( int(timeout.total_seconds()), timeout.microseconds * 1000 ) result: int = _load_libgpiod().gpiod_line_event_wait( self._pointer, ctypes.pointer(timeout_timespec) ) _load_libgpiod().gpiod_line_release(self._pointer) if result == -1: raise OSError("Failed to wait for rising edge event notification.") return result == 1