pax_global_header00006660000000000000000000000064140123752560014517gustar00rootroot0000000000000052 comment=741652ae79d6b90933a47c41389a4c395bd57236 python-periphery-2.3.0/000077500000000000000000000000001401237525600150475ustar00rootroot00000000000000python-periphery-2.3.0/.gitignore000066400000000000000000000001011401237525600170270ustar00rootroot00000000000000*.swp *.pyc docs/_build/ build/ dist/ python_periphery.egg-info/ python-periphery-2.3.0/.travis.yml000066400000000000000000000005371401237525600171650ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" - "3.7" - "pypy" - "pypy3" script: - python -m tests.test_gpio - python -m tests.test_gpio_sysfs - python -m tests.test_spi - python -m tests.test_i2c - python -m tests.test_mmio - python -m tests.test_serial - python -m tests.test_led - python -m tests.test_pwm python-periphery-2.3.0/CHANGELOG.md000066400000000000000000000077761401237525600167010ustar00rootroot00000000000000* v2.3.0 - 02/14/2021 * GPIO * Add kernel version check for line bias support. * Fix docstring for `close()`. * SPI * Add kernel version check for 32-bit mode support. * MMIO * Fix duplicate transactions in integral read and write methods. * Fix memory offset of `pointer` property. * Contributors * Michael Murton, @CrazyIvan359 - 9c1a4f3 * @paul-demo - b318a6a * v2.2.0 - 12/16/2020 * MMIO * Add `path` keyword argument to constructor for use with alternate memory character devices (e.g. `/dev/gpiomem`). * SPI * Add support for 32-bit flags to `extra_flags` property and constructor. * v2.1.1 - 11/19/2020 * GPIO * Add direction checks for improved error reporting to `write()`, `read_event()`, and `poll()` for character device GPIOs. * Contributors * Michael Murton, @CrazyIvan359 - 69bd36e * v2.1.0 - 05/29/2020 * GPIO * Add `poll_multiple()` static method. * Add line consumer `label` property. * Add line `bias`, line `drive`, and `inverted` properties. * Add additional properties as keyword arguments to constructor for character device GPIOs. * Only unexport GPIO in `close()` if exported in open for sysfs GPIOs. * Improve wording and fix typos in docstrings. * Serial * Fix performance of blocking read in `read()`. * Raise exception on unexpected empty read in `read()`, which may be caused by a serial port disconnect. * Add `vmin` and `vtime` properties for the corresponding termios settings. * Add support for termios timeout with `read()`. * Improve wording in docstrings. * Contributors * @xrombik - 444f778 * Alexander Steffen, @webmeister - f0403da * v2.0.1 - 01/08/2020 * PWM * Add retry loop for opening PWM period file after export to accommodate delayed udev permission rule application. * Contributors * Jonas Larsson, @jonasl - 28653d4 * v2.0.0 - 10/28/2019 * GPIO * Add support for character device GPIOs. * Remove support for preserve direction from GPIO constructor. * Add retry loop to direction write after export to accommodate delayed udev permission rule application for sysfs GPIOs. * Unexport GPIO line on close for sysfs GPIOs. * Fix handling of `timeout=None` with sysfs GPIO `poll()`. * Add `devpath` property. * PWM * Fix chip and channel argument names in PWM constructor and documentation. * Add retry loop to PWM open after export to accommodate delayed creation of sysfs files by kernel driver. * Unexport PWM channel on close. * Add nanosecond `period_ns` and `duty_cycle_ns` properties. * Add `devpath` property. * LED * Raise `LookupError` instead of `ValueError` if LED name is not found during open. * Add `devpath` property. * Fix exception handling for Python 2 with `ioctl()` operations in Serial, SPI, and I2C modules. * Fix `with` statement context manager support for all modules. * Update tests with running hints for Raspberry Pi 3. * Contributors * Uwe Kleine-König, @ukleinek - 0005260 * Heath Robinson, @ubiquitousthey - ac457d6 * v1.1.2 - 06/25/2019 * Add LICENSE file to packaging. * v1.1.1 - 04/03/2018 * Fix handling of delayed pin directory export when opening a GPIO. * v1.1.0 - 10/24/2016 * Add support for preserving pin direction when opening GPIO. * Improve GPIO poll() implementation to work with more platforms. * Improve atomicity of MMIO fixed width writes. * Add PWM module. * Add LED module. * Add support for universal wheel packaging. * Contributors * Sanket Dasgupta - 8ac7b40 * Joseph Kogut - 022ef29, d2e9132 * Hector Martin - 1e3343a * Francesco Valla - 34b3877 * v1.0.0 - 06/25/2015 * Initial release. python-periphery-2.3.0/LICENSE000066400000000000000000000021131401237525600160510ustar00rootroot00000000000000 Copyright (c) 2015-2021 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 in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-periphery-2.3.0/README.md000066400000000000000000000126711401237525600163350ustar00rootroot00000000000000# 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) ## Linux Peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) with Python 2 & 3 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. 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) ## Installation With pip: ``` text pip install python-periphery ``` With easy_install: ``` text easy_install python-periphery ``` With setup.py: ``` text git clone https://github.com/vsergeev/python-periphery.git cd python-periphery python setup.py install ``` ## Examples ### GPIO ``` python from periphery import GPIO # Open GPIO /dev/gpiochip0 line 10 with input direction gpio_in = GPIO("/dev/gpiochip0", 10, "in") # Open GPIO /dev/gpiochip0 line 12 with output direction gpio_out = GPIO("/dev/gpiochip0", 12, "out") value = gpio_in.read() gpio_out.write(not value) gpio_in.close() gpio_out.close() ``` [Go to GPIO documentation.](https://python-periphery.readthedocs.io/en/latest/gpio.html) ### LED ``` python from periphery import LED # Open LED "led0" with initial state off led0 = LED("led0", False) # Open LED "led1" with initial state on led1 = LED("led1", True) value = led0.read() led1.write(value) # Set custom brightness level led1.write(led1.max_brightness / 2) led0.close() led1.close() ``` [Go to LED documentation.](https://python-periphery.readthedocs.io/en/latest/led.html) ### PWM ``` python from periphery import PWM # Open PWM chip 0, channel 10 pwm = PWM(0, 10) # Set frequency to 1 kHz pwm.frequency = 1e3 # Set duty cycle to 75% pwm.duty_cycle = 0.75 pwm.enable() # Change duty cycle to 50% pwm.duty_cycle = 0.50 pwm.close() ``` [Go to PWM documentation.](https://python-periphery.readthedocs.io/en/latest/pwm.html) ### SPI ``` python from periphery import SPI # Open spidev1.0 with mode 0 and max speed 1MHz spi = SPI("/dev/spidev1.0", 0, 1000000) data_out = [0xaa, 0xbb, 0xcc, 0xdd] data_in = spi.transfer(data_out) print("shifted out [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*data_out)) print("shifted in [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*data_in)) spi.close() ``` [Go to SPI documentation.](https://python-periphery.readthedocs.io/en/latest/spi.html) ### I2C ``` python from periphery import I2C # Open i2c-0 controller i2c = I2C("/dev/i2c-0") # Read byte at address 0x100 of EEPROM at 0x50 msgs = [I2C.Message([0x01, 0x00]), I2C.Message([0x00], read=True)] i2c.transfer(0x50, msgs) print("0x100: 0x{:02x}".format(msgs[1].data[0])) i2c.close() ``` [Go to I2C documentation.](https://python-periphery.readthedocs.io/en/latest/i2c.html) ### MMIO ``` python from periphery import MMIO # Open am335x real-time clock subsystem page rtc_mmio = MMIO(0x44E3E000, 0x1000) # Read current time rtc_secs = rtc_mmio.read32(0x00) rtc_mins = rtc_mmio.read32(0x04) rtc_hrs = rtc_mmio.read32(0x08) print("hours: {:02x} minutes: {:02x} seconds: {:02x}".format(rtc_hrs, rtc_mins, rtc_secs)) rtc_mmio.close() # Open am335x control module page ctrl_mmio = MMIO(0x44E10000, 0x1000) # Read MAC address mac_id0_lo = ctrl_mmio.read32(0x630) mac_id0_hi = ctrl_mmio.read32(0x634) print("MAC address: {:04x}{:08x}".format(mac_id0_lo, mac_id0_hi)) ctrl_mmio.close() ``` [Go to MMIO documentation.](https://python-periphery.readthedocs.io/en/latest/mmio.html) ### Serial ``` python from periphery import Serial # Open /dev/ttyUSB0 with baudrate 115200, and defaults of 8N1, no flow control serial = Serial("/dev/ttyUSB0", 115200) serial.write(b"Hello World!") # Read up to 128 bytes with 500ms timeout buf = serial.read(128, 0.5) print("read {:d} bytes: _{:s}_".format(len(buf), buf)) serial.close() ``` [Go to Serial documentation.](https://python-periphery.readthedocs.io/en/latest/serial.html) ## Documentation Documentation is hosted at [https://python-periphery.readthedocs.io](https://python-periphery.readthedocs.io). To build documentation locally with Sphinx, run: ``` cd docs make html ``` Sphinx will produce the HTML documentation in `docs/_build/html/`. Run `make help` to see other output targets (LaTeX, man, text, etc.). ## Testing The tests located in the [tests](tests/) folder may be run under Python to test the correctness and functionality of python-periphery. Some tests require interactive probing (e.g. with an oscilloscope), the installation of a physical loopback, or the existence of a particular device on a bus. See the usage of each test for more details on the required setup. ## License python-periphery is MIT licensed. See the included [LICENSE](LICENSE) file. python-periphery-2.3.0/docs/000077500000000000000000000000001401237525600157775ustar00rootroot00000000000000python-periphery-2.3.0/docs/Makefile000066400000000000000000000011721401237525600174400ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) python-periphery-2.3.0/docs/conf.py000066400000000000000000000045511401237525600173030ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import os import sys sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = u'python-periphery' copyright = u'2015-2021, vsergeev / Ivan (Vanya) A. Sergeev' author = u'Vanya A. Sergeev' # The short X.Y version. version = '2.3.0' # The full version, including alpha/beta/rc tags. release = version # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', ] autoclass_content = 'init' autodoc_member_order = 'bysource' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The master toctree document. master_doc = 'index' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If true, links to the reST sources are added to the pages. html_show_sourcelink = False python-periphery-2.3.0/docs/gpio.rst000066400000000000000000000015601401237525600174710ustar00rootroot00000000000000GPIO ==== Code Example ------------ .. code-block:: python from periphery import GPIO # Open GPIO /dev/gpiochip0 line 10 with input direction gpio_in = GPIO("/dev/gpiochip0", 10, "in") # Open GPIO /dev/gpiochip0 line 12 with output direction gpio_out = GPIO("/dev/gpiochip0", 12, "out") value = gpio_in.read() gpio_out.write(not value) gpio_in.close() gpio_out.close() API --- .. class:: periphery.GPIO(path, line, direction) .. autoclass:: periphery.CdevGPIO .. class:: periphery.GPIO(line, direction) .. autoclass:: periphery.SysfsGPIO .. autoclass:: periphery.GPIO :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.EdgeEvent :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.GPIOError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/i2c.rst000066400000000000000000000011401401237525600172020ustar00rootroot00000000000000I2C === Code Example ------------ .. code-block:: python from periphery import I2C # Open i2c-0 controller i2c = I2C("/dev/i2c-0") # Read byte at address 0x100 of EEPROM at 0x50 msgs = [I2C.Message([0x01, 0x00]), I2C.Message([0x00], read=True)] i2c.transfer(0x50, msgs) print("0x100: 0x{:02x}".format(msgs[1].data[0])) i2c.close() API --- .. autoclass:: periphery.I2C :members: transfer, close, fd, devpath, Message :undoc-members: :show-inheritance: .. autoclass:: periphery.I2CError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/index.rst000066400000000000000000000014601401237525600176410ustar00rootroot00000000000000.. periphery documentation master file, created by sphinx-quickstart2 on Sat Jun 20 18:30:50 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to python-periphery's documentation! ============================================ 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. Contents -------- .. toctree:: :maxdepth: 4 gpio led pwm spi i2c mmio serial version python-periphery-2.3.0/docs/led.rst000066400000000000000000000011321401237525600172720ustar00rootroot00000000000000LED ==== Code Example ------------ .. code-block:: python from periphery import LED # Open LED "led0" with initial state off led0 = LED("led0", False) # Open LED "led1" with initial state on led1 = LED("led1", True) value = led0.read() led1.write(value) # Set custom brightness level led1.write(led1.max_brightness / 2) led0.close() led1.close() API --- .. autoclass:: periphery.LED :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.LEDError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/mmio.rst000066400000000000000000000016531401237525600174770ustar00rootroot00000000000000MMIO ==== Code Example ------------ .. code-block:: python from periphery import MMIO # Open am335x real-time clock subsystem page rtc_mmio = MMIO(0x44E3E000, 0x1000) # Read current time rtc_secs = rtc_mmio.read32(0x00) rtc_mins = rtc_mmio.read32(0x04) rtc_hrs = rtc_mmio.read32(0x08) print("hours: {:02x} minutes: {:02x} seconds: {:02x}".format(rtc_hrs, rtc_mins, rtc_secs)) rtc_mmio.close() # Open am335x control module page ctrl_mmio = MMIO(0x44E10000, 0x1000) # Read MAC address mac_id0_lo = ctrl_mmio.read32(0x630) mac_id0_hi = ctrl_mmio.read32(0x634) print("MAC address: {:04x}{:08x}".format(mac_id0_lo, mac_id0_hi)) ctrl_mmio.close() API --- .. autoclass:: periphery.MMIO :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.MMIOError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/pwm.rst000066400000000000000000000010501401237525600173300ustar00rootroot00000000000000PWM === Code Example ------------ .. code-block:: python from periphery import PWM # Open PWM chip 0, channel 10 pwm = PWM(0, 10) # Set frequency to 1 kHz pwm.frequency = 1e3 # Set duty cycle to 75% pwm.duty_cycle = 0.75 pwm.enable() # Change duty cycle to 50% pwm.duty_cycle = 0.50 pwm.close() API --- .. autoclass:: periphery.PWM :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.PWMError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/serial.rst000066400000000000000000000011671401237525600200150ustar00rootroot00000000000000Serial ====== Code Example ------------ .. code-block:: python from periphery import Serial # Open /dev/ttyUSB0 with baudrate 115200, and defaults of 8N1, no flow control serial = Serial("/dev/ttyUSB0", 115200) serial.write(b"Hello World!") # Read up to 128 bytes with 500ms timeout buf = serial.read(128, 0.5) print("read {:d} bytes: _{:s}_".format(len(buf), buf)) serial.close() API --- .. autoclass:: periphery.Serial :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.SerialError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/spi.rst000066400000000000000000000012171401237525600173250ustar00rootroot00000000000000SPI === Code Example ------------ .. code-block:: python from periphery import SPI # Open spidev1.0 with mode 0 and max speed 1MHz spi = SPI("/dev/spidev1.0", 0, 1000000) data_out = [0xaa, 0xbb, 0xcc, 0xdd] data_in = spi.transfer(data_out) print("shifted out [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*data_out)) print("shifted in [0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}]".format(*data_in)) spi.close() API --- .. autoclass:: periphery.SPI :members: :undoc-members: :show-inheritance: .. autoclass:: periphery.SPIError :members: :undoc-members: :show-inheritance: python-periphery-2.3.0/docs/version.rst000066400000000000000000000002771401237525600202240ustar00rootroot00000000000000Version and Helper Functions ---------------------------- .. automodule:: periphery :members: __version__, version, sleep, sleep_ms, sleep_us :undoc-members: :show-inheritance: python-periphery-2.3.0/periphery/000077500000000000000000000000001401237525600170565ustar00rootroot00000000000000python-periphery-2.3.0/periphery/__init__.py000066400000000000000000000020061401237525600211650ustar00rootroot00000000000000import time __version__ = "2.3.0" "Module version string." version = (2, 3, 0) "Module version tuple." def sleep(seconds): """Sleep for the specified number of seconds. Args: seconds (int, long, float): duration in seconds. """ time.sleep(seconds) def sleep_ms(milliseconds): """Sleep for the specified number of milliseconds. Args: milliseconds (int, long, float): duration in milliseconds. """ time.sleep(milliseconds / 1000.0) def sleep_us(microseconds): """Sleep for the specified number of microseconds. Args: microseconds (int, long, float): duration in microseconds. """ time.sleep(microseconds / 1000000.0) from periphery.gpio import GPIO, SysfsGPIO, CdevGPIO, EdgeEvent, GPIOError from periphery.led import LED, LEDError from periphery.pwm import PWM, PWMError from periphery.spi import SPI, SPIError from periphery.i2c import I2C, I2CError from periphery.mmio import MMIO, MMIOError from periphery.serial import Serial, SerialError python-periphery-2.3.0/periphery/gpio.py000066400000000000000000001220651401237525600203740ustar00rootroot00000000000000import 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): """Base class for GPIO errors.""" pass class EdgeEvent(collections.namedtuple('EdgeEvent', ['edge', 'timestamp'])): def __new__(cls, edge, timestamp): """EdgeEvent containing the event edge and event time reported by Linux. Args: edge (str): event edge, either "rising" or "falling". timestamp (int): event time in nanoseconds. """ return super(EdgeEvent, cls).__new__(cls, edge, timestamp) class GPIO(object): def __new__(cls, *args, **kwargs): if len(args) > 2: return CdevGPIO.__new__(cls, *args, **kwargs) else: return SysfsGPIO.__new__(cls, *args, **kwargs) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() # Methods def read(self): """Read the state of the GPIO. Returns: bool: ``True`` for high state, ``False`` for low state. Raises: GPIOError: if an I/O or OS error occurs. """ raise NotImplementedError() def write(self, value): """Set the state of the GPIO to `value`. Args: value (bool): ``True`` for high state, ``False`` for low state. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `value` type is not bool. """ raise NotImplementedError() def poll(self, timeout=None): """Poll a GPIO for the edge event configured with the .edge property with an optional timeout. For character device GPIOs, the edge event should be consumed with `read_event()`. For sysfs GPIOs, the edge event should be consumed with `read()`. `timeout` can be a positive number for a timeout in seconds, zero for a non-blocking poll, or negative or None for a blocking poll. Default is a blocking poll. Args: timeout (int, float, None): timeout duration in seconds. Returns: bool: ``True`` if an edge event occurred, ``False`` on timeout. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `timeout` type is not None or int. """ raise NotImplementedError() def read_event(self): """Read the edge event that occurred with the GPIO. This method is intended for use with character device GPIOs and is unsupported by sysfs GPIOs. Returns: EdgeEvent: a namedtuple containing the string edge event that occurred (either ``"rising"`` or ``"falling"``), and the event time reported by Linux in nanoseconds. Raises: GPIOError: if an I/O or OS error occurs. NotImplementedError: if called on a sysfs GPIO. """ raise NotImplementedError() @staticmethod def poll_multiple(gpios, timeout=None): """Poll multiple GPIOs for the edge event configured with the .edge property with an optional timeout. For character device GPIOs, the edge event should be consumed with `read_event()`. For sysfs GPIOs, the edge event should be consumed with `read()`. `timeout` can be a positive number for a timeout in seconds, zero for a non-blocking poll, or negative or None for a blocking poll. Default is a blocking poll. Args: gpios (list): list of GPIO objects to poll. timeout (int, float, None): timeout duration in seconds. Returns: list: list of GPIO objects for which an edge event occurred. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `timeout` type is not None or int. """ if not isinstance(timeout, (int, float, type(None))): raise TypeError("Invalid timeout type, should be integer, float, or None.") # Setup poll p = select.poll() # Register GPIO file descriptors and build map of fd to object fd_gpio_map = {} for gpio in gpios: if isinstance(gpio, SysfsGPIO): p.register(gpio.fd, select.POLLPRI | select.POLLERR) else: p.register(gpio.fd, select.POLLIN | select.POLLRDNORM) fd_gpio_map[gpio.fd] = gpio # Scale timeout to milliseconds if isinstance(timeout, (int, float)) and timeout > 0: timeout *= 1000 # Poll events = p.poll(timeout) # Gather GPIOs that had edge events occur results = [] for (fd, _) in events: gpio = fd_gpio_map[fd] results.append(gpio) if isinstance(gpio, SysfsGPIO): # Rewind for read try: os.lseek(fd, 0, os.SEEK_SET) except OSError as e: raise GPIOError(e.errno, "Rewinding GPIO: " + e.strerror) return results def close(self): """Close the GPIO. Raises: GPIOError: if an I/O or OS error occurs. """ raise NotImplementedError() # Immutable properties @property def devpath(self): """Get the device path of the underlying GPIO device. :type: str """ raise NotImplementedError() @property def fd(self): """Get the line file descriptor of the GPIO object. :type: int """ raise NotImplementedError() @property def line(self): """Get the GPIO object's line number. :type: int """ raise NotImplementedError() @property def name(self): """Get the line name of the GPIO. This method is intended for use with character device GPIOs and always returns the empty string for sysfs GPIOs. :type: str """ raise NotImplementedError() @property def label(self): """Get the line consumer label of the GPIO. This method is intended for use with character device GPIOs and always returns the empty string for sysfs GPIOs. :type: str """ raise NotImplementedError() @property def chip_fd(self): """Get the GPIO chip file descriptor of the GPIO object. This method is intended for use with character device GPIOs and is unsupported by sysfs GPIOs. Raises: NotImplementedError: if accessed on a sysfs GPIO. :type: int """ raise NotImplementedError() @property def chip_name(self): """Get the name of the GPIO chip associated with the GPIO. :type: str """ raise NotImplementedError() @property def chip_label(self): """Get the label of the GPIO chip associated with the GPIO. :type: str """ raise NotImplementedError() # Mutable properties def _get_direction(self): raise NotImplementedError() def _set_direction(self, direction): raise NotImplementedError() direction = property(_get_direction, _set_direction) """Get or set the GPIO's direction. Can be "in", "out", "high", "low". Direction "in" is input; "out" is output, initialized to low; "high" is output, initialized to high; and "low" is output, initialized to low. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `direction` type is not str. ValueError: if `direction` value is invalid. :type: str """ def _get_edge(self): raise NotImplementedError() def _set_edge(self, edge): raise NotImplementedError() edge = property(_get_edge, _set_edge) """Get or set the GPIO's interrupt edge. Can be "none", "rising", "falling", "both". Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `edge` type is not str. ValueError: if `edge` value is invalid. :type: str """ def _get_bias(self): raise NotImplementedError() def _set_bias(self, bias): raise NotImplementedError() bias = property(_get_bias, _set_bias) """Get or set the GPIO's line bias. Can be "default", "pull_up", "pull_down", "disable". This property is not supported by sysfs GPIOs. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `bias` type is not str. ValueError: if `bias` value is invalid. :type: str """ def _get_drive(self): raise NotImplementedError() def _set_drive(self, drive): raise NotImplementedError() drive = property(_get_drive, _set_drive) """Get or set the GPIO's line drive. Can be "default" (for push-pull), "open_drain", "open_source". This property is not supported by sysfs GPIOs. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `drive` type is not str. ValueError: if `drive` value is invalid. :type: str """ def _get_inverted(self): raise NotImplementedError() def _set_inverted(self, inverted): raise NotImplementedError() inverted = property(_get_inverted, _set_inverted) """Get or set the GPIO's inverted (active low) property. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `inverted` type is not bool. :type: bool """ # String representation def __str__(self): """Get the string representation of the GPIO. :type: str """ 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 = "" 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) python-periphery-2.3.0/periphery/i2c.py000066400000000000000000000151611401237525600201110ustar00rootroot00000000000000import os import ctypes import array import fcntl class I2CError(IOError): """Base class for I2C errors.""" pass class _CI2CMessage(ctypes.Structure): _fields_ = [ ("addr", ctypes.c_ushort), ("flags", ctypes.c_ushort), ("len", ctypes.c_ushort), ("buf", ctypes.POINTER(ctypes.c_ubyte)), ] class _CI2CIocTransfer(ctypes.Structure): _fields_ = [ ("msgs", ctypes.POINTER(_CI2CMessage)), ("nmsgs", ctypes.c_uint), ] class I2C(object): # Constants scraped from and _I2C_IOC_FUNCS = 0x705 _I2C_IOC_RDWR = 0x707 _I2C_FUNC_I2C = 0x1 _I2C_M_TEN = 0x0010 _I2C_M_RD = 0x0001 _I2C_M_STOP = 0x8000 _I2C_M_NOSTART = 0x4000 _I2C_M_REV_DIR_ADDR = 0x2000 _I2C_M_IGNORE_NAK = 0x1000 _I2C_M_NO_RD_ACK = 0x0800 _I2C_M_RECV_LEN = 0x0400 def __init__(self, devpath): """Instantiate an I2C object and open the i2c-dev device at the specified path. Args: devpath (str): i2c-dev device path. Returns: I2C: I2C object. Raises: I2CError: if an I/O or OS error occurs. """ self._fd = None self._devpath = None self._open(devpath) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, devpath): # Open i2c device try: self._fd = os.open(devpath, os.O_RDWR) except OSError as e: raise I2CError(e.errno, "Opening I2C device: " + e.strerror) self._devpath = devpath # Query supported functions buf = array.array('I', [0]) try: fcntl.ioctl(self._fd, I2C._I2C_IOC_FUNCS, buf, True) except (OSError, IOError) as e: self.close() raise I2CError(e.errno, "Querying supported functions: " + e.strerror) # Check that I2C_RDWR ioctl() is supported on this device if (buf[0] & I2C._I2C_FUNC_I2C) == 0: self.close() raise I2CError(None, "I2C not supported on device \"{:s}\"".format(devpath)) # Methods def transfer(self, address, messages): """Transfer `messages` to the specified I2C `address`. Modifies the `messages` array with the results of any read transactions. Args: address (int): I2C address. messages (list): list of I2C.Message messages. Raises: I2CError: if an I/O or OS error occurs. TypeError: if `messages` type is not list. ValueError: if `messages` length is zero, or if message data is not valid bytes. """ if not isinstance(messages, list): raise TypeError("Invalid messages type, should be list of I2C.Message.") elif len(messages) == 0: raise ValueError("Invalid messages data, should be non-zero length.") # Convert I2C.Message messages to _CI2CMessage messages cmessages = (_CI2CMessage * len(messages))() for i in range(len(messages)): # Convert I2C.Message data to bytes if isinstance(messages[i].data, bytes): data = messages[i].data elif isinstance(messages[i].data, bytearray): data = bytes(messages[i].data) elif isinstance(messages[i].data, list): data = bytes(bytearray(messages[i].data)) cmessages[i].addr = address cmessages[i].flags = messages[i].flags | (I2C._I2C_M_RD if messages[i].read else 0) cmessages[i].len = len(data) cmessages[i].buf = ctypes.cast(ctypes.create_string_buffer(data, len(data)), ctypes.POINTER(ctypes.c_ubyte)) # Prepare transfer structure i2c_xfer = _CI2CIocTransfer() i2c_xfer.nmsgs = len(cmessages) i2c_xfer.msgs = cmessages # Transfer try: fcntl.ioctl(self._fd, I2C._I2C_IOC_RDWR, i2c_xfer, False) except (OSError, IOError) as e: raise I2CError(e.errno, "I2C transfer: " + e.strerror) # Update any read I2C.Message messages for i in range(len(messages)): if messages[i].read: data = [cmessages[i].buf[j] for j in range(cmessages[i].len)] # Convert read data to type used in I2C.Message messages if isinstance(messages[i].data, list): messages[i].data = data elif isinstance(messages[i].data, bytearray): messages[i].data = bytearray(data) elif isinstance(messages[i].data, bytes): messages[i].data = bytes(bytearray(data)) def close(self): """Close the i2c-dev I2C device. Raises: I2CError: if an I/O or OS error occurs. """ if self._fd is None: return try: os.close(self._fd) except OSError as e: raise I2CError(e.errno, "Closing I2C device: " + e.strerror) self._fd = None # Immutable properties @property def fd(self): """Get the file descriptor of the underlying i2c-dev device. :type: int """ return self._fd @property def devpath(self): """Get the device path of the underlying i2c-dev device. :type: str """ return self._devpath # String representation def __str__(self): return "I2C (device={:s}, fd={:d})".format(self.devpath, self.fd) class Message: def __init__(self, data, read=False, flags=0): """Instantiate an I2C Message object. Args: data (bytes, bytearray, list): a byte array or list of 8-bit integers to write. read (bool): specify this as a read message, where `data` serves as placeholder bytes for the read. flags (int): additional i2c-dev flags for this message. Returns: Message: Message object. Raises: TypeError: if `data`, `read`, or `flags` types are invalid. """ if not isinstance(data, (bytes, bytearray, list)): raise TypeError("Invalid data type, should be bytes, bytearray, or list.") if not isinstance(read, bool): raise TypeError("Invalid read type, should be boolean.") if not isinstance(flags, int): raise TypeError("Invalid flags type, should be integer.") self.data = data self.read = read self.flags = flags python-periphery-2.3.0/periphery/led.py000066400000000000000000000135551401237525600202050ustar00rootroot00000000000000import os import os.path class LEDError(IOError): """Base class for LED errors.""" pass class LED(object): def __init__(self, name, brightness=None): """Instantiate an LED object and open the sysfs LED corresponding to the specified name. `brightness` can be a boolean for on/off, integer value for a specific brightness, or None to preserve existing brightness. Default is preserve existing brightness. Args: name (str): Linux led name. brightness (bool, int, None): Initial brightness. Returns: LED: LED object. Raises: LEDError: if an I/O or OS error occurs. TypeError: if `name` or `brightness` types are invalid. LookupError: if LED name does not exist. ValueError: if `brightness` value is invalid. """ self._path = None self._fd = None self._name = None self._max_brightness = None self._open(name, brightness) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, name, brightness): if not isinstance(name, str): raise TypeError("Invalid name type, should be string.") if not isinstance(brightness, (bool, int, type(None))): raise TypeError("Invalid brightness type, should be bool, int, or None.") led_path = "/sys/class/leds/{:s}".format(name) if not os.path.isdir(led_path): raise LookupError("Opening LED: LED \"{:s}\" not found.".format(name)) # Read max brightness try: with open(os.path.join(led_path, "max_brightness"), "r") as f_max_brightness: self._max_brightness = int(f_max_brightness.read()) except IOError as e: raise LEDError(e.errno, "Reading LED max brightness: " + e.strerror) # Open brightness try: self._fd = os.open(os.path.join(led_path, "brightness"), os.O_RDWR) except OSError as e: raise LEDError(e.errno, "Opening LED brightness: " + e.strerror) self._name = name self._path = led_path # Set initial brightness if brightness: self.write(brightness) # Methods def read(self): """Read the brightness of the LED. Returns: int: Current brightness. Raises: LEDError: if an I/O or OS error occurs. """ # Read value try: buf = os.read(self._fd, 8) except OSError as e: raise LEDError(e.errno, "Reading LED brightness: " + e.strerror) # Rewind try: os.lseek(self._fd, 0, os.SEEK_SET) except OSError as e: raise LEDError(e.errno, "Rewinding LED brightness: " + e.strerror) return int(buf) def write(self, brightness): """Set the brightness of the LED to `brightness`. `brightness` can be a boolean for on/off, or integer value for a specific brightness. Args: brightness (bool, int): Brightness value to set. Raises: LEDError: if an I/O or OS error occurs. TypeError: if `brightness` type is not bool or int. """ if not isinstance(brightness, (bool, int)): raise TypeError("Invalid brightness type, should be bool or int.") if isinstance(brightness, bool): brightness = self._max_brightness if brightness else 0 else: if not 0 <= brightness <= self._max_brightness: raise ValueError("Invalid brightness value: should be between 0 and {:d}".format(self._max_brightness)) # Write value try: os.write(self._fd, "{:d}\n".format(brightness).encode()) except OSError as e: raise LEDError(e.errno, "Writing LED brightness: " + e.strerror) # Rewind try: os.lseek(self._fd, 0, os.SEEK_SET) except OSError as e: raise LEDError(e.errno, "Rewinding LED brightness: " + e.strerror) def close(self): """Close the sysfs LED. Raises: LEDError: if an I/O or OS error occurs. """ if self._fd is None: return try: os.close(self._fd) except OSError as e: raise LEDError(e.errno, "Closing LED: " + e.strerror) self._fd = None # Immutable properties @property def devpath(self): """Get the device path of the underlying sysfs LED device. :type: str """ return self._path @property def fd(self): """Get the file descriptor for the underlying sysfs LED "brightness" file of the LED object. :type: int """ return self._fd @property def name(self): """Get the sysfs LED name. :type: str """ return self._name @property def max_brightness(self): """Get the LED's max brightness. :type: int """ return self._max_brightness # Mutable properties def _get_brightness(self): # Read brightness return self.read() def _set_brightness(self, brightness): return self.write(brightness) brightness = property(_get_brightness, _set_brightness) """Get or set the LED's brightness. Value can be a boolean for on/off, or integer value a for specific brightness. Raises: LEDError: if an I/O or OS error occurs. TypeError: if `brightness` type is not bool or int. ValueError: if `brightness` value is invalid. :type: int """ # String representation def __str__(self): return "LED {:s} (device={:s}, fd={:d}, max_brightness={:d})".format(self._name, self._path, self._fd, self._max_brightness) python-periphery-2.3.0/periphery/mmio.py000066400000000000000000000243631401237525600204010ustar00rootroot00000000000000import sys import os import mmap import ctypes import struct # Alias long to int on Python 3 if sys.version_info[0] >= 3: long = int class MMIOError(IOError): """Base class for MMIO errors.""" pass class MMIO(object): def __init__(self, physaddr, size, path="/dev/mem"): """Instantiate an MMIO object and map the region of physical memory specified by the `physaddr` base physical address and `size` size in bytes. The default memory character device "/dev/mem" can be overridden with the keyword argument `path`, for use with sandboxed memory character devices, e.g. "/dev/gpiomem". Args: physaddr (int, long): base physical address of memory region. size (int, long): size of memory region. path (str): memory character device path. Returns: MMIO: MMIO object. Raises: MMIOError: if an I/O or OS error occurs. TypeError: if `physaddr` or `size` types are invalid. """ self.mapping = None self._open(physaddr, size, path) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, physaddr, size, path): if not isinstance(physaddr, (int, long)): raise TypeError("Invalid physaddr type, should be integer.") if not isinstance(size, (int, long)): raise TypeError("Invalid size type, should be integer.") pagesize = os.sysconf(os.sysconf_names['SC_PAGESIZE']) self._physaddr = physaddr self._size = size self._aligned_physaddr = physaddr - (physaddr % pagesize) self._aligned_size = size + (physaddr - self._aligned_physaddr) try: fd = os.open(path, os.O_RDWR | os.O_SYNC) except OSError as e: raise MMIOError(e.errno, "Opening {:s}: {:s}".format(path, e.strerror)) try: self.mapping = mmap.mmap(fd, self._aligned_size, flags=mmap.MAP_SHARED, prot=(mmap.PROT_READ | mmap.PROT_WRITE), offset=self._aligned_physaddr) except OSError as e: raise MMIOError(e.errno, "Mapping {:s}: {:s}".format(path, e.strerror)) try: os.close(fd) except OSError as e: raise MMIOError(e.errno, "Closing {:s}: {:s}".format(path, e.strerror)) # Methods def _adjust_offset(self, offset): return offset + (self._physaddr - self._aligned_physaddr) def _validate_offset(self, offset, length): if (offset + length) > self._aligned_size: raise ValueError("Offset out of bounds.") def read32(self, offset): """Read 32-bits from the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. Returns: int: 32-bit value read. Raises: TypeError: if `offset` type is invalid. ValueError: if `offset` is out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") offset = self._adjust_offset(offset) self._validate_offset(offset, 4) return ctypes.c_uint32.from_buffer(self.mapping, offset).value def read16(self, offset): """Read 16-bits from the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. Returns: int: 16-bit value read. Raises: TypeError: if `offset` type is invalid. ValueError: if `offset` is out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") offset = self._adjust_offset(offset) self._validate_offset(offset, 2) return ctypes.c_uint16.from_buffer(self.mapping, offset).value def read8(self, offset): """Read 8-bits from the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. Returns: int: 8-bit value read. Raises: TypeError: if `offset` type is invalid. ValueError: if `offset` is out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") offset = self._adjust_offset(offset) self._validate_offset(offset, 1) return ctypes.c_uint8.from_buffer(self.mapping, offset).value def read(self, offset, length): """Read a string of bytes from the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. length (int): number of bytes to read. Returns: bytes: bytes read. Raises: TypeError: if `offset` type is invalid. ValueError: if `offset` is out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") offset = self._adjust_offset(offset) self._validate_offset(offset, length) return bytes(self.mapping[offset:offset + length]) def write32(self, offset, value): """Write 32-bits to the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. value (int, long): 32-bit value to write. Raises: TypeError: if `offset` or `value` type are invalid. ValueError: if `offset` or `value` are out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") if not isinstance(value, (int, long)): raise TypeError("Invalid value type, should be integer.") if value < 0 or value > 0xffffffff: raise ValueError("Value out of bounds.") offset = self._adjust_offset(offset) self._validate_offset(offset, 4) ctypes.c_uint32.from_buffer(self.mapping, offset).value = value def write16(self, offset, value): """Write 16-bits to the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. value (int, long): 16-bit value to write. Raises: TypeError: if `offset` or `value` type are invalid. ValueError: if `offset` or `value` are out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") if not isinstance(value, (int, long)): raise TypeError("Invalid value type, should be integer.") if value < 0 or value > 0xffff: raise ValueError("Value out of bounds.") offset = self._adjust_offset(offset) self._validate_offset(offset, 2) ctypes.c_uint16.from_buffer(self.mapping, offset).value = value def write8(self, offset, value): """Write 8-bits to the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. value (int, long): 8-bit value to write. Raises: TypeError: if `offset` or `value` type are invalid. ValueError: if `offset` or `value` are out of bounds. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") if not isinstance(value, (int, long)): raise TypeError("Invalid value type, should be integer.") if value < 0 or value > 0xff: raise ValueError("Value out of bounds.") offset = self._adjust_offset(offset) self._validate_offset(offset, 1) ctypes.c_uint8.from_buffer(self.mapping, offset).value = value def write(self, offset, data): """Write a string of bytes to the specified `offset` in bytes, relative to the base physical address of the MMIO region. Args: offset (int, long): offset from base physical address, in bytes. data (bytes, bytearray, list): a byte array or list of 8-bit integers to write. Raises: TypeError: if `offset` or `data` type are invalid. ValueError: if `offset` is out of bounds, or if data is not valid bytes. """ if not isinstance(offset, (int, long)): raise TypeError("Invalid offset type, should be integer.") if not isinstance(data, (bytes, bytearray, list)): raise TypeError("Invalid data type, expected bytes, bytearray, or list.") offset = self._adjust_offset(offset) self._validate_offset(offset, len(data)) data = bytes(bytearray(data)) self.mapping[offset:offset + len(data)] = data def close(self): """Unmap the MMIO object's mapped physical memory.""" if self.mapping is None: return self.mapping.close() self.mapping = None self._fd = None # Immutable properties @property def base(self): """Get the base physical address of the MMIO region. :type: int """ return self._physaddr @property def size(self): """Get the mapping size of the MMIO region. :type: int """ return self._size @property def pointer(self): """Get a ctypes void pointer to the memory mapped region. :type: ctypes.c_void_p """ return ctypes.cast(ctypes.pointer(ctypes.c_uint8.from_buffer(self.mapping, self._adjust_offset(0))), ctypes.c_void_p) # String representation def __str__(self): return "MMIO 0x{:08x} (size={:d})".format(self.base, self.size) python-periphery-2.3.0/periphery/pwm.py000066400000000000000000000237451401237525600202460ustar00rootroot00000000000000import errno import os import time class PWMError(IOError): """Base class for PWM errors.""" pass class PWM(object): # Number of retries to check for successful PWM export on open PWM_STAT_RETRIES = 10 # Delay between check for scucessful PWM export on open (100ms) PWM_STAT_DELAY = 0.1 def __init__(self, chip, channel): """Instantiate a PWM object and open the sysfs PWM corresponding to the specified chip and channel. Args: chip (int): PWM chip number. channel (int): PWM channel number. Returns: PWM: PWM object. Raises: PWMError: if an I/O or OS error occurs. TypeError: if `chip` or `channel` types are invalid. LookupError: if PWM chip does not exist. TimeoutError: if waiting for PWM export times out. """ self._chip = None self._channel = None self._path = None self._period_ns = None self._open(chip, channel) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, chip, channel): if not isinstance(chip, int): raise TypeError("Invalid chip type, should be integer.") if not isinstance(channel, int): raise TypeError("Invalid channel type, should be integer.") chip_path = "/sys/class/pwm/pwmchip{}".format(chip) channel_path = "/sys/class/pwm/pwmchip{}/pwm{}".format(chip, channel) if not os.path.isdir(chip_path): raise LookupError("Opening PWM: PWM chip {} not found.".format(chip)) if not os.path.isdir(channel_path): # Export the PWM try: with open(os.path.join(chip_path, "export"), "w") as f_export: f_export.write("{:d}\n".format(channel)) except IOError as e: raise PWMError(e.errno, "Exporting PWM channel: " + e.strerror) # Loop until PWM is exported exported = False for i in range(PWM.PWM_STAT_RETRIES): if os.path.isdir(channel_path): exported = True break 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): 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): raise PWMError(e.errno, "Opening PWM period: " + e.strerror) time.sleep(PWM.PWM_STAT_DELAY) self._chip = chip self._channel = channel self._path = channel_path # Cache the period for fast duty cycle updates self._period_ns = self._get_period_ns() def close(self): """Close the PWM.""" if self._channel is not None: # Unexport the PWM channel try: unexport_fd = os.open("/sys/class/pwm/pwmchip{}/unexport".format(self._chip), os.O_WRONLY) os.write(unexport_fd, "{:d}\n".format(self._channel).encode()) os.close(unexport_fd) except OSError as e: raise PWMError(e.errno, "Unexporting PWM: " + e.strerror) self._chip = None self._channel = None def _write_channel_attr(self, attr, value): with open(os.path.join(self._path, attr), 'w') as f_attr: f_attr.write(value + "\n") def _read_channel_attr(self, attr): with open(os.path.join(self._path, attr), 'r') as f_attr: return f_attr.read().strip() # Methods def enable(self): """Enable the PWM output.""" self.enabled = True def disable(self): """Disable the PWM output.""" self.enabled = False # Immutable properties @property def devpath(self): """Get the device path of the underlying sysfs PWM device. :type: str """ return self._path @property def chip(self): """Get the PWM chip number. :type: int """ return self._chip @property def channel(self): """Get the PWM channel number. :type: int """ return self._channel # Mutable properties def _get_period_ns(self): period_ns_str = self._read_channel_attr("period") try: period_ns = int(period_ns_str) except ValueError: raise PWMError(None, "Unknown period value: \"{:s}\"".format(period_ns_str)) # Update our cached period self._period_ns = period_ns return period_ns def _set_period_ns(self, period_ns): if not isinstance(period_ns, int): raise TypeError("Invalid period type, should be int.") self._write_channel_attr("period", str(period_ns)) # Update our cached period self._period_ns = period_ns period_ns = property(_get_period_ns, _set_period_ns) """Get or set the PWM's output period in nanoseconds. Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not int. :type: int """ def _get_duty_cycle_ns(self): duty_cycle_ns_str = self._read_channel_attr("duty_cycle") try: duty_cycle_ns = int(duty_cycle_ns_str) except ValueError: raise PWMError(None, "Unknown duty cycle value: \"{:s}\"".format(duty_cycle_ns_str)) return duty_cycle_ns def _set_duty_cycle_ns(self, duty_cycle_ns): if not isinstance(duty_cycle_ns, int): raise TypeError("Invalid duty cycle type, should be int.") self._write_channel_attr("duty_cycle", str(duty_cycle_ns)) duty_cycle_ns = property(_get_duty_cycle_ns, _set_duty_cycle_ns) """Get or set the PWM's output duty cycle in nanoseconds. Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not int. :type: int """ def _get_period(self): return float(self.period_ns) / 1e9 def _set_period(self, period): if not isinstance(period, (int, float)): raise TypeError("Invalid period type, should be int or float.") # Convert period from seconds to integer nanoseconds self.period_ns = int(period * 1e9) period = property(_get_period, _set_period) """Get or set the PWM's output period in seconds. Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not int or float. :type: int, float """ def _get_duty_cycle(self): return float(self.duty_cycle_ns) / self._period_ns def _set_duty_cycle(self, duty_cycle): if not isinstance(duty_cycle, (int, float)): raise TypeError("Invalid duty cycle type, should be int or float.") elif not 0.0 <= duty_cycle <= 1.0: raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.") # Convert duty cycle from ratio to nanoseconds self.duty_cycle_ns = int(duty_cycle * self._period_ns) duty_cycle = property(_get_duty_cycle, _set_duty_cycle) """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0. Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not int or float. ValueError: if value is out of bounds of 0.0 to 1.0. :type: int, float """ def _get_frequency(self): return 1.0 / self.period def _set_frequency(self, frequency): if not isinstance(frequency, (int, float)): raise TypeError("Invalid frequency type, should be int or float.") self.period = 1.0 / frequency frequency = property(_get_frequency, _set_frequency) """Get or set the PWM's output frequency in Hertz. Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not int or float. :type: int, float """ def _get_polarity(self): return self._read_channel_attr("polarity") def _set_polarity(self, polarity): if not isinstance(polarity, str): raise TypeError("Invalid polarity type, should be str.") elif polarity.lower() not in ["normal", "inversed"]: raise ValueError("Invalid polarity, can be: \"normal\" or \"inversed\".") self._write_channel_attr("polarity", polarity.lower()) polarity = property(_get_polarity, _set_polarity) """Get or set the PWM's output polarity. Can be "normal" or "inversed". Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not str. ValueError: if value is invalid. :type: str """ def _get_enabled(self): enabled = self._read_channel_attr("enable") if enabled == "1": return True elif enabled == "0": return False raise PWMError(None, "Unknown enabled value: \"{:s}\"".format(enabled)) def _set_enabled(self, value): if not isinstance(value, bool): raise TypeError("Invalid enabled type, should be bool.") self._write_channel_attr("enable", "1" if value else "0") enabled = property(_get_enabled, _set_enabled) """Get or set the PWM's output enabled state. Raises: PWMError: if an I/O or OS error occurs. TypeError: if value type is not bool. :type: bool """ # String representation def __str__(self): return "PWM {:d}, chip {:d} (period={:f} sec, duty_cycle={:f}%, polarity={:s}, enabled={:s})" \ .format(self._channel, self._chip, self.period, self.duty_cycle * 100, self.polarity, str(self.enabled)) python-periphery-2.3.0/periphery/serial.py000066400000000000000000000612731401237525600207200ustar00rootroot00000000000000import os import fcntl import array import termios import select class SerialError(IOError): """Base class for Serial errors.""" pass class Serial(object): _DATABITS_TO_CFLAG = { 5: termios.CS5, 6: termios.CS6, 7: termios.CS7, 8: termios.CS8 } _CFLAG_TO_DATABITS = {v: k for k, v in _DATABITS_TO_CFLAG.items()} _BAUDRATE_TO_OSPEED = { 50: termios.B50, 75: termios.B75, 110: termios.B110, 134: termios.B134, 150: termios.B150, 200: termios.B200, 300: termios.B300, 600: termios.B600, 1200: termios.B1200, 1800: termios.B1800, 2400: termios.B2400, 4800: termios.B4800, 9600: termios.B9600, 19200: termios.B19200, 38400: termios.B38400, 57600: termios.B57600, 115200: termios.B115200, 230400: termios.B230400, # Linux baudrates bits missing in termios module included below 460800: 0x1004, 500000: 0x1005, 576000: 0x1006, 921600: 0x1007, 1000000: 0x1008, 1152000: 0x1009, 1500000: 0x100A, 2000000: 0x100B, 2500000: 0x100C, 3000000: 0x100D, 3500000: 0x100E, 4000000: 0x100F, } _OSPEED_TO_BAUDRATE = {v: k for k, v in _BAUDRATE_TO_OSPEED.items()} def __init__(self, devpath, baudrate, databits=8, parity="none", stopbits=1, xonxoff=False, rtscts=False): """Instantiate a Serial object and open the tty device at the specified path with the specified baudrate, and the defaults of 8 data bits, no parity, 1 stop bit, no software flow control (xonxoff), and no hardware flow control (rtscts). Args: devpath (str): tty device path. baudrate (int): baudrate. databits (int): data bits, can be 5, 6, 7, 8. parity (str): parity, can be "none", "even", "odd". stopbits (int): stop bits, can be 1 or 2. xonxoff (bool): software flow control. rtscts (bool): hardware flow control. Returns: Serial: Serial object. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `devpath`, `baudrate`, `databits`, `parity`, `stopbits`, `xonxoff`, or `rtscts` types are invalid. ValueError: if `baudrate`, `databits`, `parity`, or `stopbits` values are invalid. """ self._fd = None self._devpath = None self._use_termios_timeout = False self._open(devpath, baudrate, databits, parity, stopbits, xonxoff, rtscts) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, devpath, baudrate, databits, parity, stopbits, xonxoff, rtscts): if not isinstance(devpath, str): raise TypeError("Invalid devpath type, should be string.") elif not isinstance(baudrate, int): raise TypeError("Invalid baud rate type, should be integer.") elif not isinstance(databits, int): raise TypeError("Invalid data bits type, should be integer.") elif not isinstance(parity, str): raise TypeError("Invalid parity type, should be string.") elif not isinstance(stopbits, int): raise TypeError("Invalid stop bits type, should be integer.") elif not isinstance(xonxoff, bool): raise TypeError("Invalid xonxoff type, should be boolean.") elif not isinstance(rtscts, bool): raise TypeError("Invalid rtscts type, should be boolean.") if baudrate not in Serial._BAUDRATE_TO_OSPEED: raise ValueError("Unknown baud rate: {:d}".format(baudrate)) elif databits not in [5, 6, 7, 8]: raise ValueError("Invalid data bits, can be 5, 6, 7, 8.") elif parity.lower() not in ["none", "even", "odd"]: raise ValueError("Invalid parity, can be: \"none\", \"even\", \"odd\".") elif stopbits not in [1, 2]: raise ValueError("Invalid stop bits, can be 1, 2.") # Open tty try: self._fd = os.open(devpath, os.O_RDWR | os.O_NOCTTY) except OSError as e: raise SerialError(e.errno, "Opening serial port: " + e.strerror) self._devpath = devpath parity = parity.lower() (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = (0, 0, 0, 0, 0, 0, [0] * 32) ### # iflag # Ignore break characters iflag = termios.IGNBRK # Setup parity if parity != "none": iflag |= (termios.INPCK | termios.ISTRIP) # Setup xonxoff if xonxoff: iflag |= (termios.IXON | termios.IXOFF) ####### # oflag oflag = 0 ####### # lflag lflag = 0 ####### # cflag # Enable receiver, ignore modem control lines cflag = (termios.CREAD | termios.CLOCAL) # Setup data bits cflag |= Serial._DATABITS_TO_CFLAG[databits] # Setup parity if parity == "even": cflag |= termios.PARENB elif parity == "odd": cflag |= (termios.PARENB | termios.PARODD) # Setup stop bits if stopbits == 2: cflag |= termios.CSTOPB # Setup rtscts if rtscts: cflag |= termios.CRTSCTS # Setup baud rate cflag |= Serial._BAUDRATE_TO_OSPEED[baudrate] ######## # ispeed ispeed = Serial._BAUDRATE_TO_OSPEED[baudrate] ######## # ospeed ospeed = Serial._BAUDRATE_TO_OSPEED[baudrate] # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) self._use_termios_timeout = False # Methods def read(self, length, timeout=None): """Read up to `length` number of bytes from the serial port with an optional timeout. `timeout` can be positive for a blocking read with a timeout in seconds, zero for a non-blocking read, or negative or None for a blocking read that will block until `length` number of bytes are read. Default is a blocking read. For a non-blocking or timeout-bound read, `read()` may return less than the requested number of bytes. For a blocking read with the VMIN setting configured, `read()` will block until at least VMIN bytes are read. For a blocking read with both VMIN and VTIME settings configured, `read()` will block until at least VMIN bytes are read or the VTIME interbyte timeout expires after the last byte read. In either case, `read()` may return less than the requested number of bytes. Args: length (int): length in bytes. timeout (int, float, None): timeout duration in seconds. Returns: bytes: data read. Raises: SerialError: if an I/O or OS error occurs. """ data = b"" while len(data) < length: (rlist, _, _) = select.select([self._fd], [], [], timeout) if self._fd not in rlist: break try: chunk = os.read(self._fd, length - len(data)) except OSError as e: raise SerialError(e.errno, "Reading serial port: " + e.strerror) if self._use_termios_timeout: return chunk if not chunk: raise SerialError(0, "Reading serial port: unexpected empty read") data += chunk return data def write(self, data): """Write `data` to the serial port and return the number of bytes written. Args: data (bytes, bytearray, list): a byte array or list of 8-bit integers to write. Returns: int: number of bytes written. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `data` type is invalid. ValueError: if data is not valid bytes. """ if not isinstance(data, (bytes, bytearray, list)): raise TypeError("Invalid data type, should be bytes, bytearray, or list.") if isinstance(data, list): data = bytearray(data) try: return os.write(self._fd, data) except OSError as e: raise SerialError(e.errno, "Writing serial port: " + e.strerror) def poll(self, timeout=None): """Poll for data available for reading from the serial port with an optional timeout. `timeout` can be positive for a timeout in seconds, zero for a non-blocking poll, or negative or None for a blocking poll. Default is a blocking poll. Args: timeout (int, float, None): timeout duration in seconds. Returns: bool: ``True`` if data is available for reading from the serial port, ``False`` if not. """ p = select.poll() p.register(self._fd, select.POLLIN | select.POLLPRI) events = p.poll(int(timeout * 1000)) if len(events) > 0: return True return False def flush(self): """Flush the write buffer of the serial port, blocking until all bytes are written. Raises: SerialError: if an I/O or OS error occurs. """ try: termios.tcdrain(self._fd) except termios.error as e: raise SerialError(e.errno, "Flushing serial port: " + e.strerror) def input_waiting(self): """Query the number of bytes waiting to be read from the serial port. Returns: int: number of bytes waiting to be read. Raises: SerialError: if an I/O or OS error occurs. """ # Get input waiting buf = array.array('I', [0]) try: fcntl.ioctl(self._fd, termios.TIOCINQ, buf, True) except (OSError, IOError) as e: raise SerialError(e.errno, "Querying input waiting: " + e.strerror) return buf[0] def output_waiting(self): """Query the number of bytes waiting to be written to the serial port. Returns: int: number of bytes waiting to be written. Raises: SerialError: if an I/O or OS error occurs. """ # Get input waiting buf = array.array('I', [0]) try: fcntl.ioctl(self._fd, termios.TIOCOUTQ, buf, True) except (OSError, IOError) as e: raise SerialError(e.errno, "Querying output waiting: " + e.strerror) return buf[0] def close(self): """Close the tty device. Raises: SerialError: if an I/O or OS error occurs. """ if self._fd is None: return try: os.close(self._fd) except OSError as e: raise SerialError(e.errno, "Closing serial port: " + e.strerror) self._fd = None # Immutable properties @property def fd(self): """Get the file descriptor of the underlying tty device. :type: int """ return self._fd @property def devpath(self): """Get the device path of the underlying tty device. :type: str """ return self._devpath # Mutable properties def _get_baudrate(self): # Get tty attributes try: (_, _, _, _, _, ospeed, _) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) if ospeed not in Serial._OSPEED_TO_BAUDRATE: raise SerialError(None, "Unknown baud rate: ospeed 0x{:x}".format(ospeed)) return Serial._OSPEED_TO_BAUDRATE[ospeed] def _set_baudrate(self, baudrate): if not isinstance(baudrate, int): raise TypeError("Invalid baud rate type, should be integer.") if baudrate not in Serial._BAUDRATE_TO_OSPEED: raise ValueError("Unknown baud rate: {:d}".format(baudrate)) # Get tty attributes try: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) # Modify tty attributes cflag &= ~(termios.CBAUD | termios.CBAUDEX) cflag |= Serial._BAUDRATE_TO_OSPEED[baudrate] ispeed = Serial._BAUDRATE_TO_OSPEED[baudrate] ospeed = Serial._BAUDRATE_TO_OSPEED[baudrate] # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) baudrate = property(_get_baudrate, _set_baudrate) """Get or set the baudrate. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `baudrate` type is not int. ValueError: if `baudrate` value is not supported. :type: int """ def _get_databits(self): # Get tty attributes try: (_, _, cflag, _, _, _, _) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) cs = cflag & termios.CSIZE if cs not in Serial._CFLAG_TO_DATABITS: raise SerialError(None, "Unknown data bits setting: csize 0x{:x}".format(cs)) return Serial._CFLAG_TO_DATABITS[cs] def _set_databits(self, databits): if not isinstance(databits, int): raise TypeError("Invalid data bits type, should be integer.") elif databits not in [5, 6, 7, 8]: raise ValueError("Invalid data bits, can be 5, 6, 7, 8.") # Get tty attributes try: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) # Modify tty attributes cflag &= ~termios.CSIZE cflag |= Serial._DATABITS_TO_CFLAG[databits] # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) databits = property(_get_databits, _set_databits) """Get or set the data bits. Can be 5, 6, 7, 8. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `databits` type is not int. ValueError: if `databits` value is invalid. :type: int """ def _get_parity(self): # Get tty attributes try: (_, _, cflag, _, _, _, _) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) if (cflag & termios.PARENB) == 0: return "none" elif (cflag & termios.PARODD) == 0: return "even" else: return "odd" def _set_parity(self, parity): if not isinstance(parity, str): raise TypeError("Invalid parity type, should be string.") elif parity.lower() not in ["none", "even", "odd"]: raise ValueError("Invalid parity, can be: \"none\", \"even\", \"odd\".") parity = parity.lower() # Get tty attributes try: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) # Modify tty attributes iflag &= ~(termios.INPCK | termios.ISTRIP) cflag &= ~(termios.PARENB | termios.PARODD) if parity != "none": iflag |= (termios.INPCK | termios.ISTRIP) cflag |= termios.PARENB if parity == "odd": cflag |= termios.PARODD # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) parity = property(_get_parity, _set_parity) """Get or set the parity. Can be "none", "even", "odd". Raises: SerialError: if an I/O or OS error occurs. TypeError: if `parity` type is not str. ValueError: if `parity` value is invalid. :type: str """ def _get_stopbits(self): # Get tty attributes try: (_, _, cflag, _, _, _, _) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) if (cflag & termios.CSTOPB) != 0: return 2 else: return 1 def _set_stopbits(self, stopbits): if not isinstance(stopbits, int): raise TypeError("Invalid stop bits type, should be integer.") elif stopbits not in [1, 2]: raise ValueError("Invalid stop bits, can be 1, 2.") # Get tty attributes try: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) # Modify tty attributes cflag &= ~termios.CSTOPB if stopbits == 2: cflag |= termios.CSTOPB # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) stopbits = property(_get_stopbits, _set_stopbits) """Get or set the stop bits. Can be 1 or 2. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `stopbits` type is not int. ValueError: if `stopbits` value is invalid. :type: int """ def _get_xonxoff(self): # Get tty attributes try: (iflag, _, _, _, _, _, _) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) if (iflag & (termios.IXON | termios.IXOFF)) != 0: return True else: return False def _set_xonxoff(self, enabled): if not isinstance(enabled, bool): raise TypeError("Invalid enabled type, should be boolean.") # Get tty attributes try: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) # Modify tty attributes iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY) if enabled: iflag |= (termios.IXON | termios.IXOFF) # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) xonxoff = property(_get_xonxoff, _set_xonxoff) """Get or set software flow control. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `xonxoff` type is not bool. :type: bool """ def _get_rtscts(self): # Get tty attributes try: (_, _, cflag, _, _, _, _) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) if (cflag & termios.CRTSCTS) != 0: return True else: return False def _set_rtscts(self, enabled): if not isinstance(enabled, bool): raise TypeError("Invalid enabled type, should be boolean.") # Get tty attributes try: (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) # Modify tty attributes cflag = ~termios.CRTSCTS if enabled: cflag |= termios.CRTSCTS # Set tty attributes try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) rtscts = property(_get_rtscts, _set_rtscts) """Get or set hardware flow control. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `rtscts` type is not bool. :type: bool """ def _get_vmin(self): try: iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) return cc[termios.VMIN] def _set_vmin(self, vmin): if not isinstance(vmin, int): raise TypeError("Invalid vmin type, should be integer.") elif not (0 <= vmin <= 255): raise ValueError("Invalid vmin, can be 0 to 255.") try: iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) cc[termios.VMIN] = vmin try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) self._use_termios_timeout = vmin > 0 vmin = property(_get_vmin, _set_vmin) """Get or set the VMIN termios setting for minimum number of bytes returned from a blocking read. Can be between 0 and 255. When configured in conjunction with VTIME, VTIME acts as an interbyte timeout that restarts on every byte received, and a blocking read will block until at least VMIN bytes are read or the VTIME timeout expires after the last byte read. See the `termios` man page for more information. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `vmin` type is not int. ValueError: if `vmin` value is invalid. :type: int """ def _get_vtime(self): try: iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Getting serial port attributes: " + e.strerror) return float(cc[termios.VTIME]) / 10.0 def _set_vtime(self, vtime): if not isinstance(vtime, (float, int)): raise TypeError("Invalid vtime type, should be float or integer.") elif not (0 <= vtime <= 25.5): raise ValueError("Invalid vtime, can be 0 to 25.5 seconds.") try: iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self._fd) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) cc[termios.VTIME] = int(float(vtime) * 10.0) try: termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) except termios.error as e: raise SerialError(e.errno, "Setting serial port attributes: " + e.strerror) vtime = property(_get_vtime, _set_vtime) """Get or set the VTIME termios setting for timeout in seconds of a blocking read. Can be between 0 to 25.5 seconds, with a resolution of 0.1 seconds. When configured in conjunction with VMIN, VTIME acts as an interbyte timeout that restarts on every byte received, and a blocking read will block until at least VMIN bytes are read or the VTIME timeout expires after the last byte read. See the `termios` man page for more information. Raises: SerialError: if an I/O or OS error occurs. TypeError: if `vtime` type is not float or int. ValueError: if `vtime` value is invalid. :type: float """ # String representation def __str__(self): return "Serial (device={:s}, fd={:d}, baudrate={:d}, databits={:d}, parity={:s}, stopbits={:d}, xonxoff={:s}, rtscts={:s}, vmin={:d}, vtime={:.1f})" \ .format(self.devpath, self.fd, self.baudrate, self.databits, self.parity, self.stopbits, str(self.xonxoff), str(self.rtscts), self.vmin, self.vtime) python-periphery-2.3.0/periphery/spi.py000066400000000000000000000363201401237525600202270ustar00rootroot00000000000000import os import fcntl import array import ctypes import platform try: KERNEL_VERSION = tuple([int(s) for s in platform.release().split(".")[:2]]) except ValueError: KERNEL_VERSION = (0, 0) class SPIError(IOError): """Base class for SPI errors.""" pass class _CSpiIocTransfer(ctypes.Structure): _fields_ = [ ('tx_buf', ctypes.c_ulonglong), ('rx_buf', ctypes.c_ulonglong), ('len', ctypes.c_uint), ('speed_hz', ctypes.c_uint), ('delay_usecs', ctypes.c_ushort), ('bits_per_word', ctypes.c_ubyte), ('cs_change', ctypes.c_ubyte), ('tx_nbits', ctypes.c_ubyte), ('rx_nbits', ctypes.c_ubyte), ('pad', ctypes.c_ushort), ] class SPI(object): # Constants scraped from _SPI_CPHA = 0x1 _SPI_CPOL = 0x2 _SPI_LSB_FIRST = 0x8 _SPI_IOC_WR_MODE = 0x40016b01 _SPI_IOC_RD_MODE = 0x80016b01 _SPI_IOC_WR_MODE32 = 0x40046b05 _SPI_IOC_RD_MODE32 = 0x80046b05 _SPI_IOC_WR_MAX_SPEED_HZ = 0x40046b04 _SPI_IOC_RD_MAX_SPEED_HZ = 0x80046b04 _SPI_IOC_WR_BITS_PER_WORD = 0x40016b03 _SPI_IOC_RD_BITS_PER_WORD = 0x80016b03 _SPI_IOC_MESSAGE_1 = 0x40206b00 _SUPPORTS_MODE32 = KERNEL_VERSION >= (3, 15) def __init__(self, devpath, mode, max_speed, bit_order="msb", bits_per_word=8, extra_flags=0): """Instantiate a SPI object and open the spidev device at the specified path with the specified SPI mode, max speed in hertz, and the defaults of "msb" bit order and 8 bits per word. Args: devpath (str): spidev device path. mode (int): SPI mode, can be 0, 1, 2, 3. max_speed (int, float): maximum speed in Hertz. bit_order (str): bit order, can be "msb" or "lsb". bits_per_word (int): bits per word. extra_flags (int): extra spidev flags to be bitwise-ORed with the SPI mode. Returns: SPI: SPI object. Raises: SPIError: if an I/O or OS error occurs. TypeError: if `devpath`, `mode`, `max_speed`, `bit_order`, `bits_per_word`, or `extra_flags` types are invalid. ValueError: if `mode`, `bit_order`, `bits_per_word`, or `extra_flags` values are invalid. """ self._fd = None self._devpath = None self._open(devpath, mode, max_speed, bit_order, bits_per_word, extra_flags) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, devpath, mode, max_speed, bit_order, bits_per_word, extra_flags): if not isinstance(devpath, str): raise TypeError("Invalid devpath type, should be string.") elif not isinstance(mode, int): raise TypeError("Invalid mode type, should be integer.") elif not isinstance(max_speed, (int, float)): raise TypeError("Invalid max_speed type, should be integer or float.") elif not isinstance(bit_order, str): raise TypeError("Invalid bit_order type, should be string.") elif not isinstance(bits_per_word, int): raise TypeError("Invalid bits_per_word type, should be integer.") elif not isinstance(extra_flags, int): raise TypeError("Invalid extra_flags type, should be integer.") if mode not in [0, 1, 2, 3]: raise ValueError("Invalid mode, can be 0, 1, 2, 3.") elif bit_order.lower() not in ["msb", "lsb"]: raise ValueError("Invalid bit_order, can be \"msb\" or \"lsb\".") elif bits_per_word < 0 or bits_per_word > 255: raise ValueError("Invalid bits_per_word, must be 0-255.") # Open spidev try: self._fd = os.open(devpath, os.O_RDWR) except OSError as e: raise SPIError(e.errno, "Opening SPI device: " + e.strerror) self._devpath = devpath bit_order = bit_order.lower() # Set mode, bit order, extra flags if extra_flags > 0xff: if not SPI._SUPPORTS_MODE32: raise SPIError(None, "32-bit mode configuration not supported by kernel version {}.{}.".format(*KERNEL_VERSION)) # Use 32-bit mode if extra flags is wider than 8-bits buf = array.array("I", [mode | (SPI._SPI_LSB_FIRST if bit_order == "lsb" else 0) | extra_flags]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_MODE32, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI mode: " + e.strerror) else: # Prefer 8-bit mode for compatibility with older kernels buf = array.array("B", [mode | (SPI._SPI_LSB_FIRST if bit_order == "lsb" else 0) | extra_flags]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_MODE, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI mode: " + e.strerror) # Set max speed buf = array.array("I", [int(max_speed)]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_MAX_SPEED_HZ, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI max speed: " + e.strerror) # Set bits per word buf = array.array("B", [bits_per_word]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_BITS_PER_WORD, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI bits per word: " + e.strerror) # Methods def transfer(self, data): """Shift out `data` and return shifted in data. Args: data (bytes, bytearray, list): a byte array or list of 8-bit integers to shift out. Returns: bytes, bytearray, list: data shifted in. Raises: SPIError: if an I/O or OS error occurs. TypeError: if `data` type is invalid. ValueError: if data is not valid bytes. """ if not isinstance(data, (bytes, bytearray, list)): raise TypeError("Invalid data type, should be bytes, bytearray, or list.") # Create mutable array try: buf = array.array('B', data) except OverflowError: raise ValueError("Invalid data bytes.") buf_addr, buf_len = buf.buffer_info() # Prepare transfer structure spi_xfer = _CSpiIocTransfer() spi_xfer.tx_buf = buf_addr spi_xfer.rx_buf = buf_addr spi_xfer.len = buf_len # Transfer try: fcntl.ioctl(self._fd, SPI._SPI_IOC_MESSAGE_1, spi_xfer) except (OSError, IOError) as e: raise SPIError(e.errno, "SPI transfer: " + e.strerror) # Return shifted out data with the same type as shifted in data if isinstance(data, bytes): return bytes(bytearray(buf)) elif isinstance(data, bytearray): return bytearray(buf) elif isinstance(data, list): return buf.tolist() def close(self): """Close the spidev SPI device. Raises: SPIError: if an I/O or OS error occurs. """ if self._fd is None: return try: os.close(self._fd) except OSError as e: raise SPIError(e.errno, "Closing SPI device: " + e.strerror) self._fd = None # Immutable properties @property def fd(self): """Get the file descriptor of the underlying spidev device. :type: int """ return self._fd @property def devpath(self): """Get the device path of the underlying spidev device. :type: str """ return self._devpath # Mutable properties def _get_mode(self): buf = array.array('B', [0]) # Get mode try: fcntl.ioctl(self._fd, SPI._SPI_IOC_RD_MODE, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI mode: " + e.strerror) return buf[0] & 0x3 def _set_mode(self, mode): if not isinstance(mode, int): raise TypeError("Invalid mode type, should be integer.") if mode not in [0, 1, 2, 3]: raise ValueError("Invalid mode, can be 0, 1, 2, 3.") # Read-modify-write mode, because the mode contains bits for other settings # Get mode buf = array.array('B', [0]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_RD_MODE, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI mode: " + e.strerror) buf[0] = (buf[0] & ~(SPI._SPI_CPOL | SPI._SPI_CPHA)) | mode # Set mode try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_MODE, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI mode: " + e.strerror) mode = property(_get_mode, _set_mode) """Get or set the SPI mode. Can be 0, 1, 2, 3. Raises: SPIError: if an I/O or OS error occurs. TypeError: if `mode` type is not int. ValueError: if `mode` value is invalid. :type: int """ def _get_max_speed(self): # Get max speed buf = array.array('I', [0]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_RD_MAX_SPEED_HZ, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI max speed: " + e.strerror) return buf[0] def _set_max_speed(self, max_speed): if not isinstance(max_speed, (int, float)): raise TypeError("Invalid max_speed type, should be integer or float.") # Set max speed buf = array.array('I', [int(max_speed)]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_MAX_SPEED_HZ, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI max speed: " + e.strerror) max_speed = property(_get_max_speed, _set_max_speed) """Get or set the maximum speed in Hertz. Raises: SPIError: if an I/O or OS error occurs. TypeError: if `max_speed` type is not int or float. :type: int, float """ def _get_bit_order(self): # Get mode buf = array.array('B', [0]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_RD_MODE, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI mode: " + e.strerror) if (buf[0] & SPI._SPI_LSB_FIRST) > 0: return "lsb" return "msb" def _set_bit_order(self, bit_order): if not isinstance(bit_order, str): raise TypeError("Invalid bit_order type, should be string.") elif bit_order.lower() not in ["msb", "lsb"]: raise ValueError("Invalid bit_order, can be \"msb\" or \"lsb\".") # Read-modify-write mode, because the mode contains bits for other settings # Get mode buf = array.array('B', [0]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_RD_MODE, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI mode: " + e.strerror) bit_order = bit_order.lower() buf[0] = (buf[0] & ~SPI._SPI_LSB_FIRST) | (SPI._SPI_LSB_FIRST if bit_order == "lsb" else 0) # Set mode try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_MODE, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI mode: " + e.strerror) bit_order = property(_get_bit_order, _set_bit_order) """Get or set the SPI bit order. Can be "msb" or "lsb". Raises: SPIError: if an I/O or OS error occurs. TypeError: if `bit_order` type is not str. ValueError: if `bit_order` value is invalid. :type: str """ def _get_bits_per_word(self): # Get bits per word buf = array.array('B', [0]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_RD_BITS_PER_WORD, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI bits per word: " + e.strerror) return buf[0] def _set_bits_per_word(self, bits_per_word): if not isinstance(bits_per_word, int): raise TypeError("Invalid bits_per_word type, should be integer.") if bits_per_word < 0 or bits_per_word > 255: raise ValueError("Invalid bits_per_word, must be 0-255.") # Set bits per word buf = array.array('B', [bits_per_word]) try: fcntl.ioctl(self._fd, SPI._SPI_IOC_WR_BITS_PER_WORD, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI bits per word: " + e.strerror) bits_per_word = property(_get_bits_per_word, _set_bits_per_word) """Get or set the SPI bits per word. Raises: SPIError: if an I/O or OS error occurs. TypeError: if `bits_per_word` type is not int. ValueError: if `bits_per_word` value is invalid. :type: int """ def _get_extra_flags(self): if SPI._SUPPORTS_MODE32: buf = array.array('I', [0]) rd_cmd = SPI._SPI_IOC_RD_MODE32 else: buf = array.array('B', [0]) rd_cmd = SPI._SPI_IOC_RD_MODE try: fcntl.ioctl(self._fd, rd_cmd, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI mode: " + e.strerror) return buf[0] & ~(SPI._SPI_LSB_FIRST | SPI._SPI_CPHA | SPI._SPI_CPOL) def _set_extra_flags(self, extra_flags): if not isinstance(extra_flags, int): raise TypeError("Invalid extra_flags type, should be integer.") # Read-modify-write mode, because the mode contains bits for other settings if extra_flags > 0xff: if not SPI._SUPPORTS_MODE32: raise SPIError(None, "32-bit mode configuration not supported by kernel version {}.{}.".format(*KERNEL_VERSION)) buf = array.array('I', [0]) rd_cmd = SPI._SPI_IOC_RD_MODE32 wr_cmd = SPI._SPI_IOC_WR_MODE32 else: buf = array.array('B', [0]) rd_cmd = SPI._SPI_IOC_RD_MODE wr_cmd = SPI._SPI_IOC_WR_MODE # Get mode try: fcntl.ioctl(self._fd, rd_cmd, buf, True) except (OSError, IOError) as e: raise SPIError(e.errno, "Getting SPI mode: " + e.strerror) buf[0] = (buf[0] & (SPI._SPI_LSB_FIRST | SPI._SPI_CPHA | SPI._SPI_CPOL)) | extra_flags # Set mode try: fcntl.ioctl(self._fd, wr_cmd, buf, False) except (OSError, IOError) as e: raise SPIError(e.errno, "Setting SPI mode: " + e.strerror) extra_flags = property(_get_extra_flags, _set_extra_flags) """Get or set the spidev extra flags. Extra flags are bitwise-ORed with the SPI mode. Raises: SPIError: if an I/O or OS error occurs. TypeError: if `extra_flags` type is not int. ValueError: if `extra_flags` value is invalid. :type: int """ # String representation def __str__(self): return "SPI (device={:s}, fd={:d}, mode={:d}, max_speed={:d}, bit_order={:s}, bits_per_word={:d}, extra_flags=0x{:08x})" \ .format(self.devpath, self.fd, self.mode, self.max_speed, self.bit_order, self.bits_per_word, self.extra_flags) python-periphery-2.3.0/setup.cfg000066400000000000000000000001341401237525600166660ustar00rootroot00000000000000[metadata] description-file = README.md license_file = LICENSE [bdist_wheel] universal = 1 python-periphery-2.3.0/setup.py000066400000000000000000000031411401237525600165600ustar00rootroot00000000000000try: from setuptools import setup except ImportError: from distutils.core import setup setup( name='python-periphery', version='2.3.0', 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'], 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', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Embedded Systems', 'Topic :: System :: Hardware', 'Topic :: System :: Hardware :: Hardware Drivers', ], license='MIT', keywords='gpio spi led pwm i2c mmio serial uart embedded linux beaglebone raspberrypi rpi odroid', ) python-periphery-2.3.0/tests/000077500000000000000000000000001401237525600162115ustar00rootroot00000000000000python-periphery-2.3.0/tests/__init__.py000066400000000000000000000000001401237525600203100ustar00rootroot00000000000000python-periphery-2.3.0/tests/test.py000066400000000000000000000023521401237525600175440ustar00rootroot00000000000000import inspect STR_OKAY = " [\x1b[1;32m OK \x1b[0m]" STR_FAIL = " [\x1b[1;31mFAIL\x1b[0m]" def ptest(): frame = inspect.stack()[1] function, lineno = frame[3], frame[2] print("\n\nStarting test {:s}():{:d}".format(function, lineno)) def pokay(msg): print("{:s} {:s}".format(STR_OKAY, msg)) def passert(name, condition): frame = inspect.stack()[1] filename, function, lineno = frame[1].split('/')[-1], frame[3], frame[2] print("{:s} {:s} {:s}():{:d} {:s}".format(STR_OKAY if condition else STR_FAIL, filename, function, lineno, name)) assert condition class AssertRaises(object): def __init__(self, name, exception_type): self.name = name self.exception_type = exception_type def __enter__(self): return self def __exit__(self, t, value, traceback): frame = inspect.stack()[1] filename, function, lineno = frame[1].split('/')[-1], frame[3], frame[2] if not isinstance(value, self.exception_type): print("{:s} {:s} {:s}():{:d} {:s}".format(STR_FAIL, filename, function, lineno, self.name)) return False print("{:s} {:s} {:s}():{:d} {:s}".format(STR_OKAY, filename, function, lineno, self.name)) return True python-periphery-2.3.0/tests/test_gpio.py000066400000000000000000000272641401237525600205730ustar00rootroot00000000000000import os import sys import threading import time import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input import queue else: import Queue as queue path = None line_input = None line_output = None def test_arguments(): ptest() # Invalid open types with AssertRaises("invalid open types", TypeError): periphery.GPIO(1, 1, "in") with AssertRaises("invalid open types", TypeError): periphery.GPIO("abc", 2.3, "in") with AssertRaises("invalid open types", TypeError): periphery.GPIO("abc", 1, 1) # Invalid direction with AssertRaises("invalid direction", ValueError): periphery.GPIO("abc", 1, "blah") def test_open_close(): ptest() # Open non-existent GPIO (export should fail with EINVAL) with AssertRaises("non-existent GPIO", periphery.GPIOError): periphery.GPIO(path, 9999, "in") # Open legitimate GPIO gpio = periphery.GPIO(path, line_output, "in") passert("property line", gpio.line == line_output) passert("direction is in", gpio.direction == "in") passert("fd >= 0", gpio.fd >= 0) passert("chip_fd >= 0", gpio.chip_fd >= 0) # Check default label passert("property label", gpio.label == "periphery") # Set invalid direction with AssertRaises("set invalid direction", ValueError): gpio.direction = "blah" # Set invalid edge with AssertRaises("set invalid edge", ValueError): gpio.edge = "blah" # Set invalid bias with AssertRaises("set invalid bias", ValueError): gpio.bias = "blah" # Set invalid drive with AssertRaises("set invalid drive", ValueError): gpio.drive = "blah" # Set direction out, check direction out, check value low gpio.direction = "out" passert("direction is out", gpio.direction == "out") passert("value is low", gpio.read() == False) # Set direction low, check direction out, check value low gpio.direction = "low" passert("direction is out", gpio.direction == "out") passert("value is low", gpio.read() == False) # Set direction high, check direction out, check value high gpio.direction = "high" passert("direction is out", gpio.direction == "out") passert("value is high", gpio.read() == True) # Set drive open drain, check drive open drain gpio.drive = "open_drain" passert("drive is open drain", gpio.drive == "open_drain") # Set drive open source, check drive open source gpio.drive = "open_source" passert("drive is open drain", gpio.drive == "open_source") # Set drive default, check drive default gpio.drive = "default" passert("drive is default", gpio.drive == "default") # Set inverted true, check inverted true gpio.inverted = True passert("inverted is True", gpio.inverted == True) # Set inverted false, check inverted false gpio.inverted = False passert("inverted is False", gpio.inverted == False) # Attempt to set interrupt edge on output GPIO with AssertRaises("set interrupt edge on output GPIO", periphery.GPIOError): gpio.edge = "rising" # Attempt to read event on output GPIO with AssertRaises("read event on output GPIO", periphery.GPIOError): gpio.read_event() # Set direction in, check direction in gpio.direction = "in" passert("direction is in", gpio.direction == "in") # Set edge none, check edge none gpio.edge = "none" passert("edge is none", gpio.edge == "none") # Set edge rising, check edge rising gpio.edge = "rising" passert("edge is rising", gpio.edge == "rising") # Set edge falling, check edge falling gpio.edge = "falling" passert("edge is falling", gpio.edge == "falling") # Set edge both, check edge both gpio.edge = "both" passert("edge is both", gpio.edge == "both") # Set edge none, check edge none gpio.edge = "none" passert("edge is none", gpio.edge == "none") # Set bias pull up, check bias pull up gpio.bias = "pull_up" passert("bias is pull up", gpio.bias == "pull_up") # Set bias pull down, check bias pull down gpio.bias = "pull_down" passert("bias is pull down", gpio.bias == "pull_down") # Set bias disable, check bias disable gpio.bias = "disable" passert("bias is disable", gpio.bias == "disable") # Set bias default, check bias default gpio.bias = "default" passert("bias is default", gpio.bias == "default") # Attempt to set drive on input GPIO with AssertRaises("set drive on input GPIO", periphery.GPIOError): gpio.drive = "open_drain" gpio.close() # Open with keyword arguments gpio = periphery.GPIO(path, line_input, "in", edge="rising", bias="default", drive="default", inverted=False, label="test123") passert("property line", gpio.line == line_input) passert("direction is in", gpio.direction == "in") passert("fd >= 0", gpio.fd >= 0) passert("chip_fd >= 0", gpio.chip_fd >= 0) passert("edge is rising", gpio.edge == "rising") passert("bias is default", gpio.bias == "default") passert("drive is default", gpio.drive == "default") passert("inverted is False", gpio.inverted == False) passert("label is test123", gpio.label == "test123") gpio.close() def test_loopback(): ptest() # Open in and out lines gpio_in = periphery.GPIO(path, line_input, "in") gpio_out = periphery.GPIO(path, line_output, "out") # Drive out low, check in low print("Drive out low, check in low") gpio_out.write(False) passert("value is False", gpio_in.read() == False) # Drive out high, check in high print("Drive out high, check in high") gpio_out.write(True) passert("value is True", gpio_in.read() == True) # Wrapper for running poll() in a thread def threaded_poll(gpio, timeout): ret = queue.Queue() def f(): ret.put(gpio.poll(timeout)) thread = threading.Thread(target=f) thread.start() return ret # Check poll falling 1 -> 0 interrupt print("Check poll falling 1 -> 0 interrupt") gpio_in.edge = "falling" poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(False) passert("gpio_in polled True", poll_ret.get() == True) passert("value is low", gpio_in.read() == False) event = gpio_in.read_event() passert("event edge is falling", event.edge == "falling") passert("event timestamp is non-zero", event.timestamp != 0) # Check poll rising 0 -> 1 interrupt print("Check poll rising 0 -> 1 interrupt") gpio_in.edge = "rising" poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(True) passert("gpin_in polled True", poll_ret.get() == True) passert("value is high", gpio_in.read() == True) event = gpio_in.read_event() passert("event edge is rising", event.edge == "rising") passert("event timestamp is non-zero", event.timestamp != 0) # Set edge to both gpio_in.edge = "both" # Check poll falling 1 -> 0 interrupt print("Check poll falling 1 -> 0 interrupt") poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(False) passert("gpio_in polled True", poll_ret.get() == True) passert("value is low", gpio_in.read() == False) event = gpio_in.read_event() passert("event edge is falling", event.edge == "falling") passert("event timestamp is non-zero", event.timestamp != 0) # Check poll rising 0 -> 1 interrupt print("Check poll rising 0 -> 1 interrupt") poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(True) passert("gpio_in polled True", poll_ret.get() == True) passert("value is high", gpio_in.read() == True) event = gpio_in.read_event() passert("event edge is rising", event.edge == "rising") passert("event timestamp is non-zero", event.timestamp != 0) # Check poll timeout print("Check poll timeout") passert("gpio_in polled False", gpio_in.poll(1) == False) # Check poll falling 1 -> 0 interrupt with the poll_multiple() API print("Check poll falling 1 -> 0 interrupt with poll_multiple()") gpio_out.write(False) gpios_ready = periphery.GPIO.poll_multiple([gpio_in], 1) passert("gpios ready is gpio_in", gpios_ready == [gpio_in]) passert("value is low", gpio_in.read() == False) event = gpio_in.read_event() passert("event edge is falling", event.edge == "falling") passert("event timestamp is non-zero", event.timestamp != 0) # Check poll rising 0 -> 1 interrupt with the poll_multiple() API print("Check poll rising 0 -> 1 interrupt with poll_multiple()") gpio_out.write(True) gpios_ready = periphery.GPIO.poll_multiple([gpio_in], 1) passert("gpios ready is gpio_in", gpios_ready == [gpio_in]) passert("value is high", gpio_in.read() == True) event = gpio_in.read_event() passert("event edge is rising", event.edge == "rising") passert("event timestamp is non-zero", event.timestamp != 0) # Check poll timeout print("Check poll timeout with poll_multiple()") gpios_ready = periphery.GPIO.poll_multiple([gpio_in], 1) passert("gpios ready is empty", gpios_ready == []) gpio_in.close() gpio_out.close() # Open both GPIOs as inputs gpio_in = periphery.GPIO(path, line_input, "in") gpio_out = periphery.GPIO(path, line_output, "in") # Set bias pull-up, check value is high print("Check input GPIO reads high with pull-up bias") gpio_in.bias = "pull_up" time.sleep(0.1) passert("value is high", gpio_in.read() == True) # Set bias pull-down, check value is low print("Check input GPIO reads low with pull-down bias") gpio_in.bias = "pull_down" time.sleep(0.1) passert("value is low", gpio_in.read() == False) gpio_in.close() gpio_out.close() def test_interactive(): print("Starting interactive test...") gpio = periphery.GPIO(path, line_output, "out") print("Starting interactive test. Get out your multimeter, buddy!") raw_input("Press enter to continue...") # Check tostring print("GPIO description: {}".format(str(gpio))) passert("interactive success", raw_input("GPIO description looks ok? y/n ") == "y") # Drive GPIO out low gpio.write(False) passert("interactive success", raw_input("GPIO out is low? y/n ") == "y") # Drive GPIO out high gpio.write(True) passert("interactive success", raw_input("GPIO out is high? y/n ") == "y") # Drive GPIO out low gpio.write(False) passert("interactive success", raw_input("GPIO out is low? y/n ") == "y") gpio.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 3: print("Usage: python -m tests.test_gpio ") print("") print("[1/4] Argument test: No requirements.") print("[2/4] Open/close test: GPIO #2 should be real.") print("[3/4] Loopback test: GPIOs #1 and #2 should be connected with a wire.") print("[4/4] Interactive test: GPIO #2 should be observed with a multimeter.") print("") print("Hint: for Raspberry Pi 3,") print("Use GPIO 17 (header pin 11) and GPIO 27 (header pin 13),") print("connect a loopback between them, and run this test with:") print(" python -m tests.test_gpio /dev/gpiochip0 17 27") print("") sys.exit(1) path = sys.argv[1] line_input = int(sys.argv[2]) line_output = int(sys.argv[3]) test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_gpio_sysfs.py000066400000000000000000000204631401237525600220140ustar00rootroot00000000000000import os import sys import threading import time import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input import queue else: import Queue as queue line_input = None line_output = None def test_arguments(): ptest() # Invalid open types with AssertRaises("invalid open types", TypeError): periphery.GPIO("abc", "out") with AssertRaises("invalid open types", TypeError): periphery.GPIO(100, 100) # Invalid direction with AssertRaises("invalid direction", ValueError): periphery.GPIO(100, "blah") def test_open_close(): ptest() # Open non-existent GPIO (export should fail with EINVAL) with AssertRaises("non-existent GPIO", periphery.GPIOError): periphery.GPIO(9999, "in") # Open legitimate GPIO gpio = periphery.GPIO(line_output, "in") passert("property line", gpio.line == line_output) passert("direction is in", gpio.direction == "in") passert("fd >= 0", gpio.fd >= 0) # Set invalid direction with AssertRaises("set invalid direction", ValueError): gpio.direction = "blah" # Set invalid edge with AssertRaises("set invalid edge", ValueError): gpio.edge = "blah" # Unsupported property bias with AssertRaises("unsupported property bias", NotImplementedError): _ = gpio.bias with AssertRaises("unsupported property bias", NotImplementedError): gpio.bias = "pull_up" # Unsupported property drive with AssertRaises("unsupported property drive", NotImplementedError): _ = gpio.drive with AssertRaises("unsupported property drive", NotImplementedError): gpio.drive = "open_drain" # Unsupported proprety with AssertRaises("unsupported property chip_fd", NotImplementedError): _ = gpio.chip_fd # Unsupported method with AssertRaises("unsupported method", NotImplementedError): gpio.read_event() # Set direction out, check direction out, check value low gpio.direction = "out" passert("direction is out", gpio.direction == "out") passert("value is low", gpio.read() == False) # Set direction low, check direction out, check value low gpio.direction = "low" passert("direction is out", gpio.direction == "out") passert("value is low", gpio.read() == False) # Set direction high, check direction out, check value high gpio.direction = "high" passert("direction is out", gpio.direction == "out") passert("ivalue is high", gpio.read() == True) # Set inverted true, check inverted gpio.inverted = True passert("inverted is True", gpio.inverted == True) # Set inverted false, check inverted gpio.inverted = False passert("inverted is False", gpio.inverted == False) # Set direction in, check direction in gpio.direction = "in" passert("direction is in", gpio.direction == "in") # Set edge none, check edge none gpio.edge = "none" passert("edge is none", gpio.edge == "none") # Set edge rising, check edge rising gpio.edge = "rising" passert("edge is rising", gpio.edge == "rising") # Set edge falling, check edge falling gpio.edge = "falling" passert("edge is falling", gpio.edge == "falling") # Set edge both, check edge both gpio.edge = "both" passert("edge is both", gpio.edge == "both") # Set edge none, check edge none gpio.edge = "none" passert("edge is none", gpio.edge == "none") gpio.close() def test_loopback(): ptest() # Open in and out lines gpio_in = periphery.GPIO(line_input, "in") gpio_out = periphery.GPIO(line_output, "out") # Drive out low, check in low print("Drive out low, check in low") gpio_out.write(False) passert("value is low", gpio_in.read() == False) # Drive out high, check in high print("Drive out high, check in high") gpio_out.write(True) passert("value is high", gpio_in.read() == True) # Wrapper for running poll() in a thread def threaded_poll(gpio, timeout): ret = queue.Queue() def f(): ret.put(gpio.poll(timeout)) thread = threading.Thread(target=f) thread.start() return ret # Check poll falling 1 -> 0 interrupt print("Check poll falling 1 -> 0 interrupt") gpio_in.edge = "falling" poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(False) passert("gpio_in polled True", poll_ret.get() == True) passert("value is low", gpio_in.read() == False) # Check poll rising 0 -> 1 interrupt print("Check poll rising 0 -> 1 interrupt") gpio_in.edge = "rising" poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(True) passert("gpio_in polled True", poll_ret.get() == True) passert("value is high", gpio_in.read() == True) # Set edge to both gpio_in.edge = "both" # Check poll falling 1 -> 0 interrupt print("Check poll falling 1 -> 0 interrupt") poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(False) passert("gpio_in polled True", poll_ret.get() == True) passert("value is low", gpio_in.read() == False) # Check poll rising 0 -> 1 interrupt print("Check poll rising 0 -> 1 interrupt") poll_ret = threaded_poll(gpio_in, 5) time.sleep(0.5) gpio_out.write(True) passert("gpio_in polled True", poll_ret.get() == True) passert("value is high", gpio_in.read() == True) # Check poll timeout print("Check poll timeout") passert("gpio_in polled False", gpio_in.poll(1) == False) # Check poll falling 1 -> 0 interrupt with the poll_multiple() API print("Check poll falling 1 -> 0 interrupt with poll_multiple()") gpio_out.write(False) gpios_ready = periphery.GPIO.poll_multiple([gpio_in], 1) passert("gpios ready is gpio_in", gpios_ready == [gpio_in]) passert("value is low", gpio_in.read() == False) # Check poll rising 0 -> 1 interrupt with the poll_multiple() API print("Check poll rising 0 -> 1 interrupt with poll_multiple()") gpio_out.write(True) gpios_ready = periphery.GPIO.poll_multiple([gpio_in], 1) passert("gpios ready is gpio_in", gpios_ready == [gpio_in]) passert("value is high", gpio_in.read() == True) # Check poll timeout print("Check poll timeout with poll_multiple()") gpios_ready = periphery.GPIO.poll_multiple([gpio_in], 1) passert("gpios ready is empty", gpios_ready == []) gpio_in.close() gpio_out.close() def test_interactive(): ptest() gpio = periphery.GPIO(line_output, "out") print("Starting interactive test. Get out your multimeter, buddy!") raw_input("Press enter to continue...") # Check tostring print("GPIO description: {}".format(str(gpio))) passert("interactive success", raw_input("GPIO description looks ok? y/n ") == "y") # Drive GPIO out low gpio.write(False) passert("interactive success", raw_input("GPIO out is low? y/n ") == "y") # Drive GPIO out high gpio.write(True) passert("interactive success", raw_input("GPIO out is high? y/n ") == "y") # Drive GPIO out low gpio.write(False) passert("interactive success", raw_input("GPIO out is low? y/n ") == "y") gpio.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 3: print("Usage: python -m tests.test_gpio ") print("") print("[1/4] Argument test: No requirements.") print("[2/4] Open/close test: GPIO #2 should be real.") print("[3/4] Loopback test: GPIOs #1 and #2 should be connected with a wire.") print("[4/4] Interactive test: GPIO #2 should be observed with a multimeter.") print("") print("Hint: for Raspberry Pi 3,") print("Use GPIO 17 (header pin 11) and GPIO 27 (header pin 13),") print("connect a loopback between them, and run this test with:") print(" python -m tests.test_gpio_sysfs 17 27") print("") sys.exit(1) line_input = int(sys.argv[1]) line_output = int(sys.argv[2]) test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_i2c.py000066400000000000000000000056711401237525600203100ustar00rootroot00000000000000import os import sys import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input i2c_devpath = None def test_arguments(): ptest() # Open with invalid type with AssertRaises("open invalid type", TypeError): periphery.I2C(123) def test_open_close(): ptest() # Open non-existent device with AssertRaises("non-existent device", periphery.I2CError): periphery.I2C("/foo/bar") # Open legitimate device i2c = periphery.I2C(i2c_devpath) passert("fd >= 0", i2c.fd >= 0) # Close I2C i2c.close() def test_loopback(): ptest() # No general way to do a loopback test for I2C without a real component, skipping... def test_interactive(): ptest() # Open device 2 i2c = periphery.I2C(i2c_devpath) print("") print("Starting interactive test. Get out your logic analyzer, buddy!") raw_input("Press enter to continue...") # Check tostring print("I2C description: {}".format(str(i2c))) passert("interactive success", raw_input("I2C description looks ok? y/n ") == "y") # There isn't much we can do without assuming a device on the other end, # because I2C needs an acknowledgement bit on each transferred byte. # # But we can send a transaction and expect it to time out. # S [ 0x7a W ] [0xaa] [0xbb] [0xcc] [0xdd] NA messages = [periphery.I2C.Message([0xaa, 0xbb, 0xcc, 0xdd])] raw_input("Press enter to start transfer...") # Transfer to non-existent device with AssertRaises("transfer to non-existent device", periphery.I2CError): i2c.transfer(0x7a, messages) i2c.close() success = raw_input("I2C transfer occurred? y/n ") passert("interactive success", success == "y") if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 2: print("Usage: python -m tests.test_i2c ") print("") print("[1/4] Arguments test: No requirements.") print("[2/4] Open/close test: I2C device should be real.") print("[3/4] Loopback test: No test.") print("[4/4] Interactive test: I2C bus should be observed with an oscilloscope or logic analyzer.") print("") print("Hint: for Raspberry Pi 3, enable I2C1 with:") print(" $ echo \"dtparam=i2c_arm=on\" | sudo tee -a /boot/config.txt") print(" $ sudo reboot") print("Use pins I2C1 SDA (header pin 2) and I2C1 SCL (header pin 3),") print("and run this test with:") print(" python -m tests.test_i2c /dev/i2c-1") print("") sys.exit(1) i2c_devpath = sys.argv[1] test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_led.py000066400000000000000000000070101401237525600203640ustar00rootroot00000000000000import os import sys import time import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input led_name = None def test_arguments(): ptest() # Invalid open types with AssertRaises("invalid open types", TypeError): periphery.LED("abc", "out") with AssertRaises("invalid open types", TypeError): periphery.LED(100, 100) def test_open_close(): ptest() # Open non-existent LED with AssertRaises("non-existent led", LookupError): periphery.LED("invalid_led_XXX", 0) # Open legitimate LED led = periphery.LED(led_name, 0) passert("property name", led.name == led_name) passert("fd >= 0", led.fd >= 0) passert("max_brightness > 0", led.max_brightness > 0) # Set brightness to True, check brightness led.write(True) time.sleep(0.01) passert("brightness is max", led.read() == led.max_brightness) # Set brightness to False, check brightness led.write(False) time.sleep(0.01) passert("brightness is zero", led.read() == 0) # Set brightness to 1, check brightness led.write(1) time.sleep(0.01) passert("brightness is non-zero", led.read() >= 1) # Set brightness to 0, check brightness led.write(0) time.sleep(0.01) passert("brightness is zero", led.read() == 0) # Set brightness to 1, check brightness led.brightness = 1 time.sleep(0.01) passert("brightness is non-zero", led.brightness >= 1) # Set brightness to 0, check brightness led.brightness = 0 time.sleep(0.01) passert("brightness is zero", led.brightness == 0) led.close() def test_loopback(): ptest() # No general way to do a loopback test for I2C without a real component, skipping... def test_interactive(): ptest() led = periphery.LED(led_name, False) raw_input("Press enter to continue...") # Check tostring print("LED description: {}".format(str(led))) passert("interactive success", raw_input("LED description looks ok? y/n ") == "y") # Turn LED off led.write(False) passert("interactive success", raw_input("LED is off? y/n ") == "y") # Turn LED on led.write(True) passert("interactive success", raw_input("LED is on? y/n ") == "y") # Turn LED off led.write(False) passert("interactive success", raw_input("LED is off? y/n ") == "y") # Turn LED on led.write(True) passert("interactive success", raw_input("LED is on? y/n ") == "y") led.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 2: print("Usage: python -m tests.test_led ") print("") print("[1/4] Arguments test: No requirements.") print("[2/4] Open/close test: LED should be real.") print("[3/4] Loopback test: No test.") print("[4/4] Interactive test: LED should be observed.") print("") print("Hint: for Raspberry Pi 3, disable triggers for led1:") print(" $ echo none > /sys/class/leds/led1/trigger") print("Observe led1 (red power LED), and run this test:") print(" python -m tests.test_led led1") print("") sys.exit(1) led_name = sys.argv[1] test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_mmio.py000066400000000000000000000134101401237525600205620ustar00rootroot00000000000000import os import sys import time import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input PAGE_SIZE = 4096 CONTROL_MODULE_BASE = 0x44e10000 USB_VID_PID_OFFSET = 0x7f4 USB_VID_PID = 0x04516141 RTCSS_BASE = 0x44e3e000 RTC_SCRATCH2_REG_OFFSET = 0x68 RTC_KICK0R_REG_OFFSET = 0x6C RTC_KICK1R_REG_OFFSET = 0x70 def test_arguments(): ptest() def test_open_close(): ptest() # Open aligned base mmio = periphery.MMIO(CONTROL_MODULE_BASE, PAGE_SIZE) # Check properties passert("property base", mmio.base == CONTROL_MODULE_BASE) passert("property size", mmio.size == PAGE_SIZE) # Try to write immutable properties with AssertRaises("write immutable", AttributeError): mmio.base = 1000 with AssertRaises("write immutable", AttributeError): mmio.size = 1000 mmio.close() # Open unaligned base mmio = periphery.MMIO(CONTROL_MODULE_BASE + 123, PAGE_SIZE) # Check properties passert("property base", mmio.base == CONTROL_MODULE_BASE + 123) passert("property size", mmio.size == PAGE_SIZE) # Read out of bounds with AssertRaises("read 1 byte over", ValueError): mmio.read32(PAGE_SIZE - 3) with AssertRaises("read 2 bytes over", ValueError): mmio.read32(PAGE_SIZE - 2) with AssertRaises("read 3 bytes over", ValueError): mmio.read32(PAGE_SIZE - 1) with AssertRaises("read 4 bytes over", ValueError): mmio.read32(PAGE_SIZE) mmio.close() def test_loopback(): ptest() # Open control module mmio = periphery.MMIO(CONTROL_MODULE_BASE, PAGE_SIZE) # Read and compare USB VID/PID with read32() passert("compare USB VID/PID", mmio.read32(USB_VID_PID_OFFSET) == USB_VID_PID) # Read and compare USB VID/PID with bytes read data = mmio.read(USB_VID_PID_OFFSET, 4) data = bytearray(data) passert("compare byte 1", data[0] == USB_VID_PID & 0xff) passert("compare byte 2", data[1] == (USB_VID_PID >> 8) & 0xff) passert("compare byte 3", data[2] == (USB_VID_PID >> 16) & 0xff) passert("compare byte 4", data[3] == (USB_VID_PID >> 24) & 0xff) mmio.close() # Open RTC subsystem mmio = periphery.MMIO(RTCSS_BASE, PAGE_SIZE) # Disable write protection mmio.write32(RTC_KICK0R_REG_OFFSET, 0x83E70B13) mmio.write32(RTC_KICK1R_REG_OFFSET, 0x95A4F1E0) # Write/Read RTC Scratch2 Register mmio.write32(RTC_SCRATCH2_REG_OFFSET, 0xdeadbeef) passert("compare write 32-bit uint and readback", mmio.read32(RTC_SCRATCH2_REG_OFFSET) == 0xdeadbeef) # Write/Read RTC Scratch2 Register with bytes write mmio.write(RTC_SCRATCH2_REG_OFFSET, b"\xaa\xbb\xcc\xdd") data = mmio.read(RTC_SCRATCH2_REG_OFFSET, 4) passert("compare write 4-byte bytes and readback", data == b"\xaa\xbb\xcc\xdd") # Write/Read RTC Scratch2 Register with bytearray write mmio.write(RTC_SCRATCH2_REG_OFFSET, bytearray(b"\xbb\xcc\xdd\xee")) data = mmio.read(RTC_SCRATCH2_REG_OFFSET, 4) passert("compare write 4-byte bytearray and readback", data == b"\xbb\xcc\xdd\xee") # Write/Read RTC Scratch2 Register with list write mmio.write(RTC_SCRATCH2_REG_OFFSET, [0xcc, 0xdd, 0xee, 0xff]) data = mmio.read(RTC_SCRATCH2_REG_OFFSET, 4) passert("compare write 4-byte list and readback", data == b"\xcc\xdd\xee\xff") # Write/Read RTC Scratch2 Register with 16-bit write mmio.write16(RTC_SCRATCH2_REG_OFFSET, 0xaabb) passert("compare write 16-bit uint and readback", mmio.read16(RTC_SCRATCH2_REG_OFFSET) == 0xaabb) # Write/Read RTC Scratch2 Register with 8-bit write mmio.write8(RTC_SCRATCH2_REG_OFFSET, 0xab) passert("compare write 8-bit uint and readback", mmio.read8(RTC_SCRATCH2_REG_OFFSET) == 0xab) mmio.close() def test_interactive(): ptest() mmio = periphery.MMIO(RTCSS_BASE, PAGE_SIZE) # Check tostring print("MMIO description: {}".format(str(mmio))) passert("interactive success", raw_input("MMIO description looks ok? y/n ") == "y") print("Waiting for seconds ones digit to reset to 0...\n") # Wait until seconds low go to 0, so we don't have to deal with # overflows in comparing times tic = time.time() while mmio.read32(0x00) & 0xf != 0: periphery.sleep(1) passert("less than 12 seconds elapsed", (time.time() - tic) < 12) # Compare passage of OS time with RTC time tic = time.time() rtc_tic = mmio.read32(0x00) & 0xf bcd2dec = lambda x: 10 * ((x >> 4) & 0xf) + (x & 0xf) print("Date: {:04d}-{:02d}-{:02d}".format(2000 + bcd2dec(mmio.read32(0x14)), bcd2dec(mmio.read32(0x10)), bcd2dec(mmio.read32(0x0c)))) print("Time: {:02d}:{:02d}:{:02d}".format(bcd2dec(mmio.read32(0x08) & 0x7f), bcd2dec(mmio.read32(0x04)), bcd2dec(mmio.read32(0x00)))) periphery.sleep(3) print("Date: {:04d}-{:02d}-{:02d}".format(2000 + bcd2dec(mmio.read32(0x14)), bcd2dec(mmio.read32(0x10)), bcd2dec(mmio.read32(0x0c)))) print("Time: {:02d}:{:02d}:{:02d}".format(bcd2dec(mmio.read32(0x08) & 0x7f), bcd2dec(mmio.read32(0x04)), bcd2dec(mmio.read32(0x00)))) toc = time.time() rtc_toc = mmio.read32(0x00) & 0xf passert("real time elapsed", (toc - tic) > 2) passert("rtc time elapsed", (rtc_toc - rtc_tic) > 2) mmio.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) print("WARNING: This test suite assumes a BeagleBone Black (AM335x) host!") print("Other systems may experience unintended and dire consequences!") raw_input("Press enter to continue!") test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_pwm.py000066400000000000000000000162201401237525600204260ustar00rootroot00000000000000import os import sys import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input pwm_chip = None pwm_channel = None def test_arguments(): ptest() # Invalid open types with AssertRaises("invalid open types", TypeError): periphery.PWM("foo", 0) with AssertRaises("invalid open types", TypeError): periphery.PWM(0, "foo") def test_open_close(): ptest() # Open non-existent PWM chip with AssertRaises("non-existent PWM chip", LookupError): periphery.PWM(9999, pwm_channel) # Open non-existent PWM channel with AssertRaises("non-existent PWM channel", periphery.PWMError): periphery.PWM(pwm_chip, 9999) # Open legitimate PWM chip/channel pwm = periphery.PWM(pwm_chip, pwm_channel) passert("property chip", pwm.chip == pwm_chip) passert("property channel", pwm.channel == pwm_channel) # Initialize period and duty cycle pwm.period = 5e-3 pwm.duty_cycle = 0 # Set period, check period, check period_ns, check frequency pwm.period = 1e-3 passert("period is correct", abs(pwm.period - 1e-3) < 1e-4) passert("period_ns is correct", abs(pwm.period_ns - 1000000) < 1e5) passert("frequency is correct", abs(pwm.frequency - 1000) < 100) pwm.period = 5e-4 passert("period is correct", abs(pwm.period - 5e-4) < 1e-5) passert("period_ns is correct", abs(pwm.period_ns - 500000) < 1e4) passert("frequency is correct", abs(pwm.frequency - 2000) < 100) # Set frequency, check frequency, check period, check period_ns pwm.frequency = 1000 passert("frequency is correct", abs(pwm.frequency - 1000) < 100) passert("period is correct", abs(pwm.period - 1e-3) < 1e-4) passert("period_ns is correct", abs(pwm.period_ns - 1000000) < 1e5) pwm.frequency = 2000 passert("frequency is correct", abs(pwm.frequency - 2000) < 100) passert("period is correct", abs(pwm.period - 5e-4) < 1e-5) passert("period_ns is correct", abs(pwm.period_ns - 500000) < 1e4) # Set period_ns, check period_ns, check period, check frequency pwm.period_ns = 1000000 passert("period_ns is correct", abs(pwm.period_ns - 1000000) < 1e5) passert("period is correct", abs(pwm.period - 1e-3) < 1e-4) passert("frequency is correct", abs(pwm.frequency - 1000) < 100) pwm.period_ns = 500000 passert("period_ns is correct", abs(pwm.period_ns - 500000) < 1e4) passert("period is correct", abs(pwm.period - 5e-4) < 1e-5) passert("frequency is correct", abs(pwm.frequency - 2000) < 100) pwm.period_ns = 1000000 # Set duty cycle, check duty cycle, check duty_cycle_ns pwm.duty_cycle = 0.25 passert("duty_cycle is correct", abs(pwm.duty_cycle - 0.25) < 1e-3) passert("duty_cycle_ns is correct", abs(pwm.duty_cycle_ns - 250000) < 1e4) pwm.duty_cycle = 0.50 passert("duty_cycle is correct", abs(pwm.duty_cycle - 0.50) < 1e-3) passert("duty_cycle_ns is correct", abs(pwm.duty_cycle_ns - 500000) < 1e4) pwm.duty_cycle = 0.75 passert("duty_cycle is correct", abs(pwm.duty_cycle - 0.75) < 1e-3) passert("duty_cycle_ns is correct", abs(pwm.duty_cycle_ns - 750000) < 1e4) # Set duty_cycle_ns, check duty_cycle_ns, check duty_cycle pwm.duty_cycle_ns = 250000 passert("duty_cycle_ns is correct", abs(pwm.duty_cycle_ns - 250000) < 1e4) passert("duty_cycle is correct", abs(pwm.duty_cycle - 0.25) < 1e-3) pwm.duty_cycle_ns = 500000 passert("duty_cycle_ns is correct", abs(pwm.duty_cycle_ns - 500000) < 1e4) passert("duty_cycle is correct", abs(pwm.duty_cycle - 0.50) < 1e-3) pwm.duty_cycle_ns = 750000 passert("duty_cycle_ns is correct", abs(pwm.duty_cycle_ns - 750000) < 1e4) passert("duty_cycle is correct", abs(pwm.duty_cycle - 0.75) < 1e-3) # Set polarity, check polarity pwm.polarity = "normal" passert("polarity is normal", pwm.polarity == "normal") pwm.polarity = "inversed" passert("polarity is inversed", pwm.polarity == "inversed") # Set enabled, check enabled pwm.enabled = True passert("pwm is enabled", pwm.enabled == True) pwm.enabled = False passert("pwm is disabled", pwm.enabled == False) # Use enable()/disable(), check enabled pwm.enable() passert("pwm is enabled", pwm.enabled == True) pwm.disable() passert("pwm is disabled", pwm.enabled == False) # Set invalid polarity with AssertRaises("set invalid polarity", ValueError): pwm.polarity = "foo" pwm.close() def test_loopback(): ptest() def test_interactive(): ptest() pwm = periphery.PWM(pwm_chip, pwm_channel) print("Starting interactive test. Get out your oscilloscope, buddy!") raw_input("Press enter to continue...") # Set initial parameters and enable PWM pwm.duty_cycle = 0.0 pwm.frequency = 1e3 pwm.polarity = "normal" pwm.enabled = True # Check tostring print("PWM description: {}".format(str(pwm))) passert("interactive success", raw_input("PWM description looks ok? y/n ") == "y") # Set 1 kHz frequency, 0.25 duty cycle pwm.frequency = 1e3 pwm.duty_cycle = 0.25 passert("interactive success", raw_input("Frequency is 1 kHz, duty cycle is 25%? y/n ") == "y") # Set 1 kHz frequency, 0.50 duty cycle pwm.frequency = 1e3 pwm.duty_cycle = 0.50 passert("interactive success", raw_input("Frequency is 1 kHz, duty cycle is 50%? y/n ") == "y") # Set 2 kHz frequency, 0.25 duty cycle pwm.frequency = 2e3 pwm.duty_cycle = 0.25 passert("interactive success", raw_input("Frequency is 2 kHz, duty cycle is 25%? y/n ") == "y") # Set 2 kHz frequency, 0.50 duty cycle pwm.frequency = 2e3 pwm.duty_cycle = 0.50 passert("interactive success", raw_input("Frequency is 2 kHz, duty cycle is 50%? y/n ") == "y") pwm.duty_cycle = 0.0 pwm.enabled = False pwm.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 3: print("Usage: python -m tests.test_pwm ") print("") print("[1/4] Arguments test: No requirements.") print("[2/4] Open/close test: PWM channel should be real.") print("[3/4] Loopback test: No test.") print("[4/4] Interactive test: PWM channel should be observed with an oscilloscope or logic analyzer.") print("") print("Hint: for Raspberry Pi 3, enable PWM0 and PWM1 with:") print(" $ echo \"dtoverlay=pwm-2chan,pin=18,func=2,pin2=13,func2=4\" | sudo tee -a /boot/config.txt") print(" $ sudo reboot") print("Monitor GPIO 18 (header pin 12), and run this test with:") print(" python -m tests.test_pwm 0 0") print("or, monitor GPIO 13 (header pin 33), and run this test with:") print(" python -m tests.test_pwm 0 1") print("") sys.exit(1) pwm_chip = int(sys.argv[1]) pwm_channel = int(sys.argv[2]) test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_serial.py000066400000000000000000000212411401237525600211010ustar00rootroot00000000000000import os import sys import time import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input serial_device = None def test_arguments(): ptest() # Invalid data bits with AssertRaises("invalid databits", ValueError): periphery.Serial("/dev/ttyS0", 115200, databits=4) with AssertRaises("invalid databits", ValueError): periphery.Serial("/dev/ttyS0", 115200, databits=9) # Invalid parity with AssertRaises("invalid parity", ValueError): periphery.Serial("/dev/ttyS0", 115200, parity="blah") # Invalid stop bits with AssertRaises("invalid stop bits", ValueError): periphery.Serial("/dev/ttyS0", 115200, stopbits=0) with AssertRaises("invalid stop bits", ValueError): periphery.Serial("/dev/ttyS0", 115200, stopbits=3) # Everything else is fair game, although termios might not like it. def test_open_close(): ptest() serial = periphery.Serial(serial_device, 115200) # Confirm default settings passert("fd > 0", serial.fd > 0) passert("baudrate is 115200", serial.baudrate == 115200) passert("databits is 8", serial.databits == 8) passert("parity is none", serial.parity == "none") passert("stopbits is 1", serial.stopbits == 1) passert("xonxoff is False", serial.xonxoff == False) passert("rtscts is False", serial.rtscts == False) passert("vmin is 0", serial.vmin == 0) passert("vtime is 0", serial.vtime == 0) # Change some stuff and check that it changed serial.baudrate = 4800 passert("baudrate is 4800", serial.baudrate == 4800) serial.baudrate = 9600 passert("baudrate is 9600", serial.baudrate == 9600) serial.databits = 7 passert("databits is 7", serial.databits == 7) serial.parity = "odd" passert("parity is odd", serial.parity == "odd") serial.stopbits = 2 passert("stopbits is 2", serial.stopbits == 2) serial.xonxoff = True passert("xonxoff is True", serial.xonxoff == True) # Test serial port may not support rtscts serial.vmin = 50 passert("vmin is 50", serial.vmin == 50) serial.vtime = 15.3 passert("vtime is 15.3", abs(serial.vtime - 15.3) < 0.1) serial.close() def test_loopback(): ptest() lorem_ipsum = b"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." serial = periphery.Serial(serial_device, 115200) # Test write/flush/read with bytes write print("Write, flush, read lorem ipsum with bytes type") passert("wrote lorem ipsum bytes", serial.write(lorem_ipsum) == len(lorem_ipsum)) serial.flush() buf = serial.read(len(lorem_ipsum), timeout=3) passert("compare readback lorem ipsum", buf == lorem_ipsum) # Test write/flush/read with bytearray write print("Write, flush, read lorem ipsum with bytearray type") passert("wrote lorem ipsum bytearray", serial.write(bytearray(lorem_ipsum)) == len(lorem_ipsum)) serial.flush() buf = serial.read(len(lorem_ipsum), timeout=3) passert("compare readback lorem ipsum", buf == lorem_ipsum) # Test write/flush/read with list write print("Write, flush, read lorem ipsum with list type") passert("write lorem ipsum list", serial.write(list(bytearray(lorem_ipsum))) == len(lorem_ipsum)) serial.flush() buf = serial.read(len(lorem_ipsum), timeout=3) passert("compare readback lorem ipsum", buf == lorem_ipsum) # Test poll/write/flush/poll/input waiting/read print("Write, flush, poll, input waiting, read lorem ipsum") passert("poll timed out", serial.poll(0.5) == False) passert("write lorem ipsum", serial.write(lorem_ipsum) == len(lorem_ipsum)) serial.flush() passert("poll succeeded", serial.poll(0.5) == True) periphery.sleep_ms(500) passert("input waiting is lorem ipsum size", serial.input_waiting() == len(lorem_ipsum)) buf = serial.read(len(lorem_ipsum)) passert("compare readback lorem ipsum", buf == lorem_ipsum) # Test non-blocking poll print("Check non-blocking poll") passert("non-blocking poll is False", serial.poll(0) == False) # Test a very large read-write (likely to exceed internal buffer size (~4096)) print("Write, flush, read large buffer") lorem_hugesum = b"\xaa" * (4096 * 3) passert("wrote lorem hugesum", serial.write(lorem_hugesum) == len(lorem_hugesum)) serial.flush() buf = serial.read(len(lorem_hugesum), timeout=3) passert("compare readback lorem hugesum", buf == lorem_hugesum) # Test read timeout print("Check read timeout") tic = time.time() passert("read timed out", serial.read(4096 * 3, timeout=2) == b"") toc = time.time() passert("time elapsed", (toc - tic) > 1) # Test non-blocking read print("Check non-blocking read") tic = time.time() passert("read non-blocking is empty", serial.read(4096 * 3, timeout=0) == b"") toc = time.time() # Assuming we weren't context switched out for a second passert("almost no time elapsed", int(toc - tic) == 0) # Test blocking read with vmin=5 termios timeout print("Check blocking read with vmin termios timeout") serial.vmin = 5 passert("write 5 bytes of lorem ipsum", serial.write(lorem_ipsum[0:5]) == 5) serial.flush() buf = serial.read(len(lorem_ipsum)) passert("compare readback partial lorem ipsum", buf == lorem_ipsum[0:5]) # Test blocking read with vmin=5, vtime=2 termios timeout print("Check blocking read with vmin + vtime termios timeout") serial.vtime = 2 passert("write 3 bytes of lorem ipsum", serial.write(lorem_ipsum[0:3]) == 3) serial.flush() tic = time.time() buf = serial.read(len(lorem_ipsum)) toc = time.time() passert("compare readback partial lorem ipsum", buf == lorem_ipsum[0:3]) passert("time elapsed", (toc - tic) > 1) serial.close() def test_interactive(): ptest() buf = b"Hello World!" serial = periphery.Serial(serial_device, 4800) print("Starting interactive test. Get out your logic analyzer, buddy!") raw_input("Press enter to continue...") # Check tostring print("Serial description: {}".format(str(serial))) passert("interactive success", raw_input("Serial description looks ok? y/n ") == "y") serial.baudrate = 4800 raw_input("Press enter to start transfer...") passert("serial write", serial.write(buf) == len(buf)) passert("interactive success", raw_input("Serial transfer baudrate 4800, 8n1 occurred? y/n ") == "y") serial.baudrate = 9600 raw_input("Press enter to start transfer...") passert("serial write", serial.write(buf) == len(buf)) passert("interactive success", raw_input("Serial transfer baudrate 9600, 8n1 occurred? y/n ") == "y") serial.baudrate = 115200 raw_input("Press enter to start transfer...") passert("serial write", serial.write(buf) == len(buf)) passert("interactive success", raw_input("Serial transfer baudrate 115200, 8n1 occurred? y/n ") == "y") serial.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 2: print("Usage: python -m tests.test_serial ") print("") print("[1/4] Arguments test: No requirements.") print("[2/4] Open/close test: Serial port device should be real.") print("[3/4] Loopback test: Serial TX and RX should be connected with a wire.") print("[4/4] Interactive test: Serial TX should be observed with an oscilloscope or logic analyzer.") print("") print("Hint: for Raspberry Pi 3, enable UART0 with:") print(" $ echo \"dtoverlay=pi3-disable-bt\" | sudo tee -a /boot/config.txt") print(" $ sudo systemctl disable hciuart") print(" $ sudo reboot") print(" (Note that this will disable Bluetooth)") print("Use pins UART0 TXD (header pin 8) and UART0 RXD (header pin 10),") print("connect a loopback between TXD and RXD, and run this test with:") print(" python -m tests.test_serial /dev/ttyAMA0") print("") sys.exit(1) serial_device = sys.argv[1] test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!") python-periphery-2.3.0/tests/test_spi.py000066400000000000000000000130611401237525600204160ustar00rootroot00000000000000import os import sys import periphery from .test import ptest, pokay, passert, AssertRaises if sys.version_info[0] == 3: raw_input = input spi_device = None def test_arguments(): ptest() # Invalid mode with AssertRaises("invalid mode", ValueError): periphery.SPI("/dev/spidev0.0", 4, int(1e6)) # Invalid bit order with AssertRaises("invalid bit order", ValueError): periphery.SPI("/dev/spidev0.0", 4, int(1e6), bit_order="blah") def test_open_close(): ptest() # Normal open (mode=1, max_speed = 100000) spi = periphery.SPI(spi_device, 1, 100000) # Confirm fd and defaults passert("fd > 0", spi.fd > 0) passert("mode is 1", spi.mode == 1) passert("max speed is 100000", spi.max_speed == 100000) passert("default bit_order is msb", spi.bit_order == "msb") passert("default bits_per_word is 8", spi.bits_per_word == 8) # Not going to try different bit order or bits per word, because not # all SPI controllers support them # Try modes 0, 1, 2, 3 spi.mode = 0 passert("spi mode is 0", spi.mode == 0) spi.mode = 1 passert("spi mode is 1", spi.mode == 1) spi.mode = 2 passert("spi mode is 2", spi.mode == 2) spi.mode = 3 passert("spi mode is 3", spi.mode == 3) # Try max speeds 100Khz, 500KHz, 1MHz, 2MHz spi.max_speed = 100000 passert("max speed is 100KHz", spi.max_speed == 100000) spi.max_speed = 500000 passert("max speed is 500KHz", spi.max_speed == 500000) spi.max_speed = 1000000 passert("max speed is 1MHz", spi.max_speed == 1000000) spi.max_speed = 2e6 passert("max speed is 2MHz", spi.max_speed == 2000000) spi.close() def test_loopback(): ptest() spi = periphery.SPI(spi_device, 0, 100000) # Try list transfer buf_in = list(range(256)) * 4 buf_out = spi.transfer(buf_in) passert("compare readback", buf_out == buf_in) # Try bytearray transfer buf_in = bytearray(buf_in) buf_out = spi.transfer(buf_in) passert("compare readback", buf_out == buf_in) # Try bytes transfer buf_in = bytes(bytearray(buf_in)) buf_out = spi.transfer(buf_in) passert("compare readback", buf_out == buf_in) spi.close() def test_interactive(): ptest() spi = periphery.SPI(spi_device, 0, 100000) print("Starting interactive test. Get out your logic analyzer, buddy!") raw_input("Press enter to continue...") # Check tostring print("SPI description: {}".format(str(spi))) passert("interactive success", raw_input("SPI description looks ok? y/n ") == "y") # Mode 0 transfer raw_input("Press enter to start transfer...") spi.transfer([0x55, 0xaa, 0x0f, 0xf0]) print("SPI data 0x55, 0xaa, 0x0f, 0xf0") passert("interactive success", raw_input("SPI transfer speed <= 100KHz, mode 0 occurred? y/n ") == "y") # Mode 1 transfer spi.mode = 1 raw_input("Press enter to start transfer...") spi.transfer([0x55, 0xaa, 0x0f, 0xf0]) print("SPI data 0x55, 0xaa, 0x0f, 0xf0") passert("interactive success", raw_input("SPI transfer speed <= 100KHz, mode 1 occurred? y/n ") == "y") # Mode 2 transfer spi.mode = 2 raw_input("Press enter to start transfer...") spi.transfer([0x55, 0xaa, 0x0f, 0xf0]) print("SPI data 0x55, 0xaa, 0x0f, 0xf0") passert("interactive success", raw_input("SPI transfer speed <= 100KHz, mode 2 occurred? y/n ") == "y") # Mode 3 transfer spi.mode = 3 raw_input("Press enter to start transfer...") spi.transfer([0x55, 0xaa, 0x0f, 0xf0]) print("SPI data 0x55, 0xaa, 0x0f, 0xf0") passert("interactive success", raw_input("SPI transfer speed <= 100KHz, mode 3 occurred? y/n ") == "y") spi.mode = 0 # 500KHz transfer spi.max_speed = 500000 raw_input("Press enter to start transfer...") spi.transfer([0x55, 0xaa, 0x0f, 0xf0]) print("SPI data 0x55, 0xaa, 0x0f, 0xf0") passert("interactive success", raw_input("SPI transfer speed <= 500KHz, mode 0 occurred? y/n ") == "y") # 1MHz transfer spi.max_speed = 1000000 raw_input("Press enter to start transfer...") spi.transfer([0x55, 0xaa, 0x0f, 0xf0]) print("SPI data 0x55, 0xaa, 0x0f, 0xf0") passert("interactive success", raw_input("SPI transfer speed <= 1MHz, mode 0 occurred? y/n ") == "y") spi.close() if __name__ == "__main__": if os.environ.get("CI") == "true": test_arguments() sys.exit(0) if len(sys.argv) < 2: print("Usage: python -m tests.test_spi ") print("") print("[1/4] Arguments test: No requirements.") print("[2/4] Open/close test: SPI device should be real.") print("[3/4] Loopback test: SPI MISO and MOSI should be connected with a wire.") print("[4/4] Interactive test: SPI MOSI, CLK, CS should be observed with an oscilloscope or logic analyzer.") print("") print("Hint: for Raspberry Pi 3, enable SPI0 with:") print(" $ echo \"dtparam=spi=on\" | sudo tee -a /boot/config.txt") print(" $ sudo reboot") print("Use pins SPI0 MOSI (header pin 19), SPI0 MISO (header pin 21), SPI0 SCLK (header pin 23),") print("connect a loopback between MOSI and MISO, and run this test with:") print(" python -m tests.test_spi /dev/spidev0.0") print("") sys.exit(1) spi_device = sys.argv[1] test_arguments() pokay("Arguments test passed.") test_open_close() pokay("Open/close test passed.") test_loopback() pokay("Loopback test passed.") test_interactive() pokay("Interactive test passed.") pokay("All tests passed!")