pax_global_header00006660000000000000000000000064136052746670014532gustar00rootroot0000000000000052 comment=657fab3f8d4909eebadb914f0381aac88cad761b python-periphery-2.0.1/000077500000000000000000000000001360527466700150605ustar00rootroot00000000000000python-periphery-2.0.1/.gitignore000066400000000000000000000001011360527466700170400ustar00rootroot00000000000000*.swp *.pyc docs/_build/ build/ dist/ python_periphery.egg-info/ python-periphery-2.0.1/.travis.yml000066400000000000000000000005371360527466700171760ustar00rootroot00000000000000language: 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.0.1/CHANGELOG.md000066400000000000000000000042561360527466700167000ustar00rootroot00000000000000* 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.0.1/LICENSE000066400000000000000000000021131360527466700160620ustar00rootroot00000000000000 Copyright (c) 2015-2019 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.0.1/README.md000066400000000000000000000125501360527466700163420ustar00rootroot00000000000000# 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. ## 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.](http://python-periphery.readthedocs.org/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.](http://python-periphery.readthedocs.org/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.](http://python-periphery.readthedocs.org/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.](http://python-periphery.readthedocs.org/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.](http://python-periphery.readthedocs.org/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.](http://python-periphery.readthedocs.org/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.](http://python-periphery.readthedocs.org/en/latest/serial.html) ## Documentation Documentation is hosted at [http://python-periphery.readthedocs.org/](http://python-periphery.readthedocs.org/). 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.0.1/docs/000077500000000000000000000000001360527466700160105ustar00rootroot00000000000000python-periphery-2.0.1/docs/Makefile000066400000000000000000000011721360527466700174510ustar00rootroot00000000000000# 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.0.1/docs/conf.py000066400000000000000000000045511360527466700173140ustar00rootroot00000000000000# 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-2019, vsergeev / Ivan (Vanya) A. Sergeev' author = u'Vanya A. Sergeev' # The short X.Y version. version = '2.0.1' # 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.0.1/docs/gpio.rst000066400000000000000000000015601360527466700175020ustar00rootroot00000000000000GPIO ==== 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.0.1/docs/i2c.rst000066400000000000000000000011401360527466700172130ustar00rootroot00000000000000I2C === 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.0.1/docs/index.rst000066400000000000000000000014601360527466700176520ustar00rootroot00000000000000.. 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.0.1/docs/led.rst000066400000000000000000000011321360527466700173030ustar00rootroot00000000000000LED ==== 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.0.1/docs/mmio.rst000066400000000000000000000016531360527466700175100ustar00rootroot00000000000000MMIO ==== 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.0.1/docs/pwm.rst000066400000000000000000000010501360527466700173410ustar00rootroot00000000000000PWM === 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.0.1/docs/serial.rst000066400000000000000000000011671360527466700200260ustar00rootroot00000000000000Serial ====== 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.0.1/docs/spi.rst000066400000000000000000000012171360527466700173360ustar00rootroot00000000000000SPI === 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.0.1/docs/version.rst000066400000000000000000000002771360527466700202350ustar00rootroot00000000000000Version and Helper Functions ---------------------------- .. automodule:: periphery :members: __version__, version, sleep, sleep_ms, sleep_us :undoc-members: :show-inheritance: python-periphery-2.0.1/periphery/000077500000000000000000000000001360527466700170675ustar00rootroot00000000000000python-periphery-2.0.1/periphery/__init__.py000066400000000000000000000020061360527466700211760ustar00rootroot00000000000000import time __version__ = "2.0.1" "Module version string." version = (2, 0, 1) "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.0.1/periphery/gpio.py000066400000000000000000000720221360527466700204020ustar00rootroot00000000000000import collections import ctypes import errno import fcntl import os import os.path import select import time 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): if len(args) > 2: return CdevGPIO.__new__(cls, *args) else: return SysfsGPIO.__new__(cls, *args) 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. 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, 0 for a non-blocking poll, or negative or None for a blocking poll. Defaults to 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() def close(self): """Close the sysfs 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. his 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 """ # 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 _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 def __init__(self, path, line, direction): """**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"). `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: 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". Returns: CdevGPIO: GPIO object. Raises: GPIOError: if an I/O or OS error occurs. TypeError: if `path`, `line`, or `direction` types are invalid. ValueError: if `direction` value is invalid. LookupError: if the GPIO line was not found by the provided name. """ self._devpath = None self._line_fd = None self._chip_fd = None self._edge = "none" self._direction = "in" self._line = None self._open(path, line, direction) def __new__(self, path, line, direction): return object.__new__(CdevGPIO) def _open(self, path, line, direction): 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.") if direction.lower() not in ["in", "out", "high", "low"]: raise ValueError("Invalid direction, can be: \"in\", \"out\", \"high\", \"low\".") # 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 if isinstance(line, int): self._line = line self._reopen(direction, "none") else: self._line = self._find_line_by_name(line) self._reopen(direction, "none") def _reopen(self, direction, edge): # 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) if direction == "in": if edge == "none": request = _CGpiohandleRequest() request.lineoffsets[0] = self._line request.flags = CdevGPIO._GPIOHANDLE_REQUEST_INPUT request.consumer_label = b"periphery" 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 self._direction = "in" self._edge = "none" else: request = _CGpioeventRequest() request.lineoffset = self._line request.handleflags = 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 = b"periphery" 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 self._direction = "in" self._edge = edge else: request = _CGpiohandleRequest() initial_value = True if direction == "high" else False request.lineoffsets[0] = self._line request.flags = CdevGPIO._GPIOHANDLE_REQUEST_OUTPUT request.default_values[0] = initial_value request.consumer_label = b"periphery" 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 = "out" self._edge = "none" def _find_line_by_name(self, line): # Get chip info for number of lines 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) # Get each line info line_info = _CGpiolineInfo() for i in range(chip_info.lines): line_info.line_offset = i 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) if line_info.name.decode() == line: 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.") 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.") # 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._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 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.lower() 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") 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.lower() 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("in", edge) edge = property(_get_edge, _set_edge) # String representation def __str__(self): try: str_name = self.name except GPIOError: str_name = "" 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 = "" return "GPIO {:d} (name=\"{:s}\", device={:s}, line_fd={:d}, chip_fd={:d}, direction={:s}, edge={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=cdev)" \ .format(self._line, str_name, self._devpath, self._line_fd, self._chip_fd, str_direction, str_edge, 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._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 exported = False for i in range(SysfsGPIO.GPIO_OPEN_RETRIES): if os.path.isdir(gpio_path): exported = True break time.sleep(SysfsGPIO.GPIO_OPEN_DELAY) if not 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) else: # Write direction try: with open(os.path.join(gpio_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) # 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 # 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 # 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 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) # 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 = "" return "GPIO {:d} (device={:s}, fd={:d}, direction={:s}, edge={:s}, chip_name=\"{:s}\", chip_label=\"{:s}\", type=sysfs)" \ .format(self._line, self._path, self._fd, str_direction, str_edge, str_chip_name, str_chip_label) python-periphery-2.0.1/periphery/i2c.py000066400000000000000000000151611360527466700201220ustar00rootroot00000000000000import 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.0.1/periphery/led.py000066400000000000000000000135551360527466700202160ustar00rootroot00000000000000import 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.0.1/periphery/mmio.py000066400000000000000000000236241360527466700204110ustar00rootroot00000000000000import 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): """Instantiate an MMIO object and map the region of physical memory specified by the address base `physaddr` and size `size` in bytes. Args: physaddr (int, long): base physical address of memory region. size (int, long): size of memory region. 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) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, t, value, traceback): self.close() def _open(self, physaddr, size): 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("/dev/mem", os.O_RDWR | os.O_SYNC) except OSError as e: raise MMIOError(e.errno, "Opening /dev/mem: " + 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 /dev/mem: " + e.strerror) try: os.close(fd) except OSError as e: raise MMIOError(e.errno, "Closing /dev/mem: " + 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 struct.unpack("=L", self.mapping[offset:offset + 4])[0] 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 struct.unpack("=H", self.mapping[offset:offset + 2])[0] 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 struct.unpack("B", self.mapping[offset:offset + 1])[0] 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) self.mapping[offset:offset + 4] = struct.pack("=L", 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) self.mapping[offset:offset + 2] = struct.pack("=H", 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) self.mapping[offset:offset + 1] = struct.pack("B", 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, 0)), ctypes.c_void_p) # String representation def __str__(self): return "MMIO 0x{:08x} (size={:d})".format(self.base, self.size) python-periphery-2.0.1/periphery/pwm.py000066400000000000000000000237451360527466700202570ustar00rootroot00000000000000import 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.0.1/periphery/serial.py000066400000000000000000000513071360527466700207260ustar00rootroot00000000000000import 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._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) # 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 timeout in seconds, 0 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 data whose length is less than or equal to the requested length. 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"" # Read length bytes if timeout is None # Read up to length bytes if timeout is not None while True: if timeout is not None: # Select (rlist, _, _) = select.select([self._fd], [], [], timeout) # If timeout if self._fd not in rlist: break try: data += os.read(self._fd, length - len(data)) except OSError as e: raise SerialError(e.errno, "Reading serial port: " + e.strerror) if len(data) == length: break 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. `timeout` can be positive for a timeout in seconds, 0 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 """ # String representation def __str__(self): return "Serial (device={:s}, fd={:d}, baudrate={:d}, databits={:d}, parity={:s}, stopbits={:d}, xonxoff={:s}, rtscts={:s})" \ .format(self.devpath, self.fd, self.baudrate, self.databits, self.parity, self.stopbits, str(self.xonxoff), str(self.rtscts)) python-periphery-2.0.1/periphery/spi.py000066400000000000000000000337241360527466700202450ustar00rootroot00000000000000import os import fcntl import array import ctypes 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_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 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.") elif extra_flags < 0 or extra_flags > 255: raise ValueError("Invalid extra_flags, 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 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): # 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) 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.") if extra_flags < 0 or extra_flags > 255: raise ValueError("Invalid extra_flags, must be 0-255.") # 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_LSB_FIRST | SPI._SPI_CPHA | SPI._SPI_CPOL)) | extra_flags # 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) 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{:02x})" \ .format(self.devpath, self.fd, self.mode, self.max_speed, self.bit_order, self.bits_per_word, self.extra_flags) python-periphery-2.0.1/setup.cfg000066400000000000000000000001341360527466700166770ustar00rootroot00000000000000[metadata] description-file = README.md license_file = LICENSE [bdist_wheel] universal = 1 python-periphery-2.0.1/setup.py000066400000000000000000000031411360527466700165710ustar00rootroot00000000000000try: from setuptools import setup except ImportError: from distutils.core import setup setup( name='python-periphery', version='2.0.1', description='A pure Python 2/3 library for peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) in Linux.', author='vsergeev', author_email='v@sergeev.io', url='https://github.com/vsergeev/python-periphery', packages=['periphery'], 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.0.1/tests/000077500000000000000000000000001360527466700162225ustar00rootroot00000000000000python-periphery-2.0.1/tests/__init__.py000066400000000000000000000000001360527466700203210ustar00rootroot00000000000000python-periphery-2.0.1/tests/asserts.py000066400000000000000000000004561360527466700202650ustar00rootroot00000000000000class AssertRaises(object): def __init__(self, exception_type): self.exception_type = exception_type def __enter__(self): return self def __exit__(self, t, value, traceback): if isinstance(value, self.exception_type): return True return False python-periphery-2.0.1/tests/test_gpio.py000066400000000000000000000153331360527466700205760ustar00rootroot00000000000000import os import sys import threading import time import periphery from .asserts import 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(): print("Starting arguments test...") # Invalid open types with AssertRaises(TypeError): periphery.GPIO(1, 1, "in") with AssertRaises(TypeError): periphery.GPIO("abc", 2.3, "in") with AssertRaises(TypeError): periphery.GPIO("abc", 1, 1) # Invalid direction with AssertRaises(ValueError): periphery.GPIO("abc", 1, "blah") print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Open non-existent GPIO (export should fail with EINVAL) with AssertRaises(periphery.GPIOError): periphery.GPIO(path, 9999, "in") # Open legitimate GPIO gpio = periphery.GPIO(path, line_output, "in") assert gpio.line == line_output assert gpio.direction == "in" assert gpio.fd >= 0 assert gpio.chip_fd >= 0 # Set invalid direction with AssertRaises(ValueError): gpio.direction = "blah" # Set invalid edge with AssertRaises(ValueError): gpio.edge = "blah" # Set direction out, check direction out, check value low gpio.direction = "out" assert gpio.direction == "out" assert gpio.read() == False # Set direction low, check direction out, check value low gpio.direction = "low" assert gpio.direction == "out" assert gpio.read() == False # Set direction high, check direction out, check value high gpio.direction = "high" assert gpio.direction == "out" assert gpio.read() == True # Attempt to set interrupt edge on output GPIO with AssertRaises(periphery.GPIOError): gpio.edge = "rising" # Attempt to read event on output GPIO with AssertRaises(periphery.GPIOError): gpio.read_event() # Set direction in, check direction in gpio.direction = "in" assert gpio.direction == "in" # Set edge none, check edge none gpio.edge = "none" assert gpio.edge == "none" # Set edge rising, check edge rising gpio.edge = "rising" assert gpio.edge == "rising" # Set edge falling, check edge falling gpio.edge = "falling" assert gpio.edge == "falling" # Set edge both, check edge both gpio.edge = "both" assert gpio.edge == "both" # Set edge none, check edge none gpio.edge = "none" assert gpio.edge == "none" gpio.close() print("Open/close test passed.") def test_loopback(): print("Starting loopback test...") # 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) assert gpio_in.read() == False # Drive out high, check in high print("Drive out high, check in high") gpio_out.write(True) assert 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) assert poll_ret.get() == True assert gpio_in.read() == False event = gpio_in.read_event() assert event.edge == "falling" assert 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) assert poll_ret.get() == True assert gpio_in.read() == True event = gpio_in.read_event() assert event.edge == "rising" assert 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) assert poll_ret.get() == True assert gpio_in.read() == False event = gpio_in.read_event() assert event.edge == "falling" assert 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) assert poll_ret.get() == True assert gpio_in.read() == True event = gpio_in.read_event() assert event.edge == "rising" assert event.timestamp != 0 # Check poll timeout print("Check poll timeout") assert gpio_in.poll(1) == False gpio_in.close() gpio_out.close() print("Loopback test passed.") 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))) assert raw_input("GPIO description looks ok? y/n ") == "y" # Drive GPIO out low gpio.write(False) assert raw_input("GPIO out is low? y/n ") == "y" # Drive GPIO out high gpio.write(True) assert raw_input("GPIO out is high? y/n ") == "y" # Drive GPIO out low gpio.write(False) assert raw_input("GPIO out is low? y/n ") == "y" gpio.close() print("Interactive test passed.") 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]) print("Starting GPIO tests...") test_arguments() test_open_close() test_loopback() test_interactive() print("All GPIO tests passed.") python-periphery-2.0.1/tests/test_gpio_sysfs.py000066400000000000000000000141231360527466700220210ustar00rootroot00000000000000import os import sys import threading import time import periphery from .asserts import 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(): print("Starting arguments test...") # Invalid open types with AssertRaises(TypeError): periphery.GPIO("abc", "out") with AssertRaises(TypeError): periphery.GPIO(100, 100) # Invalid direction with AssertRaises(ValueError): periphery.GPIO(100, "blah") print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Open non-existent GPIO (export should fail with EINVAL) with AssertRaises(periphery.GPIOError): periphery.GPIO(9999, "in") # Open legitimate GPIO gpio = periphery.GPIO(line_output, "in") assert gpio.line == line_output assert gpio.direction == "in" assert gpio.fd >= 0 # Set invalid direction with AssertRaises(ValueError): gpio.direction = "blah" # Set invalid edge with AssertRaises(ValueError): gpio.edge = "blah" # Unsupported proprety with AssertRaises(NotImplementedError): _ = gpio.chip_fd # Unsupported method with AssertRaises(NotImplementedError): gpio.read_event() # Set direction out, check direction out, check value low gpio.direction = "out" assert gpio.direction == "out" assert gpio.read() == False # Set direction low, check direction out, check value low gpio.direction = "low" assert gpio.direction == "out" assert gpio.read() == False # Set direction high, check direction out, check value high gpio.direction = "high" assert gpio.direction == "out" assert gpio.read() == True # Set direction in, check direction in gpio.direction = "in" assert gpio.direction == "in" # Set edge none, check edge none gpio.edge = "none" assert gpio.edge == "none" # Set edge rising, check edge rising gpio.edge = "rising" assert gpio.edge == "rising" # Set edge falling, check edge falling gpio.edge = "falling" assert gpio.edge == "falling" # Set edge both, check edge both gpio.edge = "both" assert gpio.edge == "both" # Set edge none, check edge none gpio.edge = "none" assert gpio.edge == "none" gpio.close() print("Open/close test passed.") def test_loopback(): print("Starting loopback test...") # 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) assert gpio_in.read() == False # Drive out high, check in high print("Drive out high, check in high") gpio_out.write(True) assert 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) assert poll_ret.get() == True assert 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) assert poll_ret.get() == True assert 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) assert poll_ret.get() == True assert 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) assert poll_ret.get() == True assert gpio_in.read() == True # Check poll timeout print("Check poll timeout") assert gpio_in.poll(1) == False gpio_in.close() gpio_out.close() print("Loopback test passed.") def test_interactive(): print("Starting interactive test...") 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))) assert raw_input("GPIO description looks ok? y/n ") == "y" # Drive GPIO out low gpio.write(False) assert raw_input("GPIO out is low? y/n ") == "y" # Drive GPIO out high gpio.write(True) assert raw_input("GPIO out is high? y/n ") == "y" # Drive GPIO out low gpio.write(False) assert raw_input("GPIO out is low? y/n ") == "y" gpio.close() print("Interactive test passed.") 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]) print("Starting GPIO tests...") test_arguments() test_open_close() test_loopback() test_interactive() print("All GPIO tests passed.") python-periphery-2.0.1/tests/test_i2c.py000066400000000000000000000056721360527466700203220ustar00rootroot00000000000000import os import sys import periphery from .asserts import AssertRaises if sys.version_info[0] == 3: raw_input = input i2c_devpath = None def test_arguments(): print("Starting arguments test...") # Open with invalid type with AssertRaises(TypeError): periphery.I2C(123) print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Open non-existent device with AssertRaises(periphery.I2CError): periphery.I2C("/foo/bar") # Open legitimate device i2c = periphery.I2C(i2c_devpath) assert i2c.fd > 0 # Close I2C i2c.close() print("Open/close test passed.") def test_loopback(): print("Starting loopback test...") print("No general way to do a loopback test for I2C without a real component, skipping...") print("Loopback test passed.") def test_interactive(): print("Starting interactive test...") # 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))) assert 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(periphery.I2CError): i2c.transfer(0x7a, messages) i2c.close() success = raw_input("I2C transfer occurred? y/n ") assert success == "y" print("Interactive test passed.") 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] print("Starting I2C tests...") test_arguments() test_open_close() test_loopback() test_interactive() print("All I2C tests passed.") python-periphery-2.0.1/tests/test_led.py000066400000000000000000000060641360527466700204050ustar00rootroot00000000000000import os import sys import time import periphery from .asserts import AssertRaises if sys.version_info[0] == 3: raw_input = input led_name = None def test_arguments(): print("Starting arguments test...") # Invalid open types with AssertRaises(TypeError): periphery.LED("abc", "out") with AssertRaises(TypeError): periphery.LED(100, 100) print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Open non-existent LED with AssertRaises(LookupError): periphery.LED("invalid_led_XXX", 0) # Open legitimate LED led = periphery.LED(led_name, 0) assert led.name == led_name assert led.fd > 0 assert led.max_brightness > 0 # Set brightness to True, check brightness led.write(True) time.sleep(0.01) assert led.read() == led.max_brightness # Set brightness to False, check brightness led.write(False) time.sleep(0.01) assert led.read() == 0 # Set brightness to 1, check brightness led.write(1) time.sleep(0.01) assert led.read() >= 1 # Set brightness to 0, check brightness led.write(0) time.sleep(0.01) assert led.read() == 0 # Set brightness to 1, check brightness led.brightness = 1 time.sleep(0.01) assert led.brightness >= 1 # Set brightness to 0, check brightness led.brightness = 0 time.sleep(0.01) assert led.brightness == 0 led.close() print("Open/close test passed.") def test_interactive(): print("Starting interactive test...") led = periphery.LED(led_name, False) raw_input("Press enter to continue...") # Check tostring print("LED description: {}".format(str(led))) assert raw_input("LED description looks ok? y/n ") == "y" # Turn LED off led.write(False) assert raw_input("LED is off? y/n ") == "y" # Turn LED on led.write(True) assert raw_input("LED is on? y/n ") == "y" # Turn LED off led.write(False) assert raw_input("LED is off? y/n ") == "y" # Turn LED on led.write(True) assert raw_input("LED is on? y/n ") == "y" led.close() print("Interactive test passed.") 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] print("Starting LED tests...") test_arguments() test_open_close() test_interactive() print("All LED tests passed.") python-periphery-2.0.1/tests/test_mmio.py000066400000000000000000000123571360527466700206040ustar00rootroot00000000000000import os import sys import time import periphery from .asserts import 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(): print("Starting arguments test...") print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Open aligned base mmio = periphery.MMIO(CONTROL_MODULE_BASE, PAGE_SIZE) # Check properties assert mmio.base == CONTROL_MODULE_BASE assert mmio.size == PAGE_SIZE # Try to write immutable properties with AssertRaises(AttributeError): mmio.base = 1000 with AssertRaises(AttributeError): mmio.size = 1000 mmio.close() # Open unaligned base mmio = periphery.MMIO(CONTROL_MODULE_BASE + 123, PAGE_SIZE) # Check properties assert mmio.base == CONTROL_MODULE_BASE + 123 assert mmio.size == PAGE_SIZE # Read out of bounds with AssertRaises(ValueError): mmio.read32(PAGE_SIZE - 3) with AssertRaises(ValueError): mmio.read32(PAGE_SIZE - 2) with AssertRaises(ValueError): mmio.read32(PAGE_SIZE - 1) with AssertRaises(ValueError): mmio.read32(PAGE_SIZE) mmio.close() print("Open/close test passed.") def test_loopback(): print("Starting loopback test...") # Open control module mmio = periphery.MMIO(CONTROL_MODULE_BASE, PAGE_SIZE) # Read and compare USB VID/PID with read32() assert 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) assert data[0] == USB_VID_PID & 0xff assert data[1] == (USB_VID_PID >> 8) & 0xff assert data[2] == (USB_VID_PID >> 16) & 0xff assert 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) assert 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) assert 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) assert 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) assert data == b"\xcc\xdd\xee\xff" # Write/Read RTC Scratch2 Register with 16-bit write mmio.write16(RTC_SCRATCH2_REG_OFFSET, 0xaabb) assert mmio.read16(RTC_SCRATCH2_REG_OFFSET) == 0xaabb # Write/Read RTC Scratch2 Register with 8-bit write mmio.write16(RTC_SCRATCH2_REG_OFFSET, 0xab) assert mmio.read8(RTC_SCRATCH2_REG_OFFSET) == 0xab mmio.close() print("Loopback test passed.") def test_interactive(): print("Starting interactive test...") mmio = periphery.MMIO(RTCSS_BASE, PAGE_SIZE) # Check tostring print("MMIO description: {}".format(str(mmio))) assert 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) assert (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 assert (toc - tic) > 2 assert (rtc_toc - rtc_tic) > 2 mmio.close() print("Interactive test passed.") 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!") print("Starting MMIO tests...") test_arguments() test_open_close() test_loopback() test_interactive() print("All MMIO tests passed.") python-periphery-2.0.1/tests/test_pwm.py000066400000000000000000000137461360527466700204510ustar00rootroot00000000000000import os import sys import periphery from .asserts import AssertRaises if sys.version_info[0] == 3: raw_input = input pwm_chip = None pwm_channel = None def test_arguments(): print("Starting arguments test...") # Invalid open types with AssertRaises(TypeError): periphery.PWM("foo", 0) with AssertRaises(TypeError): periphery.PWM(0, "foo") print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Open non-existent PWM chip with AssertRaises(LookupError): periphery.PWM(9999, pwm_channel) # Open non-existent PWM channel with AssertRaises(periphery.PWMError): periphery.PWM(pwm_chip, 9999) # Open legitimate PWM chip/channel pwm = periphery.PWM(pwm_chip, pwm_channel) assert pwm.chip == pwm_chip assert 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 assert abs(pwm.period - 1e-3) < 1e-4 assert abs(pwm.period_ns - 1000000) < 1e5 assert abs(pwm.frequency - 1000) < 100 pwm.period = 5e-4 assert abs(pwm.period - 5e-4) < 1e-5 assert abs(pwm.period_ns - 500000) < 1e4 assert abs(pwm.frequency - 2000) < 100 # Set frequency, check frequency, check period, check period_ns pwm.frequency = 1000 assert abs(pwm.frequency - 1000) < 100 assert abs(pwm.period - 1e-3) < 1e-4 assert abs(pwm.period_ns - 1000000) < 1e5 pwm.frequency = 2000 assert abs(pwm.frequency - 2000) < 100 assert abs(pwm.period - 5e-4) < 1e-5 assert abs(pwm.period_ns - 500000) < 1e4 # Set period_ns, check period_ns, check period, check frequency pwm.period_ns = 1000000 assert abs(pwm.period_ns - 1000000) < 1e5 assert abs(pwm.period - 1e-3) < 1e-4 assert abs(pwm.frequency - 1000) < 100 pwm.period_ns = 500000 assert abs(pwm.period_ns - 500000) < 1e4 assert abs(pwm.period - 5e-4) < 1e-5 assert abs(pwm.frequency - 2000) < 100 pwm.period_ns = 1000000 # Set duty cycle, check duty cycle, check duty_cycle_ns pwm.duty_cycle = 0.25 assert abs(pwm.duty_cycle - 0.25) < 1e-3 assert abs(pwm.duty_cycle_ns - 250000) < 1e4 pwm.duty_cycle = 0.50 assert abs(pwm.duty_cycle - 0.50) < 1e-3 assert abs(pwm.duty_cycle_ns - 500000) < 1e4 pwm.duty_cycle = 0.75 assert abs(pwm.duty_cycle - 0.75) < 1e-3 assert abs(pwm.duty_cycle_ns - 750000) < 1e4 # Set duty_cycle_ns, check duty_cycle_ns, check duty_cycle pwm.duty_cycle_ns = 250000 assert abs(pwm.duty_cycle_ns - 250000) < 1e4 assert abs(pwm.duty_cycle - 0.25) < 1e-3 pwm.duty_cycle_ns = 500000 assert abs(pwm.duty_cycle_ns - 500000) < 1e4 assert abs(pwm.duty_cycle - 0.50) < 1e-3 pwm.duty_cycle_ns = 750000 assert abs(pwm.duty_cycle_ns - 750000) < 1e4 assert abs(pwm.duty_cycle - 0.75) < 1e-3 # Set polarity, check polarity pwm.polarity = "normal" assert pwm.polarity == "normal" pwm.polarity = "inversed" assert pwm.polarity == "inversed" # Set enabled, check enabled pwm.enabled = True assert pwm.enabled == True pwm.enabled = False assert pwm.enabled == False # Use enable()/disable(), check enabled pwm.enable() assert pwm.enabled == True pwm.disable() assert pwm.enabled == False # Set invalid polarity with AssertRaises(ValueError): pwm.polarity = "foo" pwm.close() print("Open/close test passed.") def test_interactive(): print("Starting interactive test...") 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))) assert 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 assert 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 assert 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 assert 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 assert raw_input("Frequency is 2 kHz, duty cycle is 50%? y/n ") == "y" pwm.duty_cycle = 0.0 pwm.enabled = False pwm.close() print("Interactive test passed.") 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]) print("Starting PMW tests...") test_arguments() test_open_close() test_interactive() print("All PWM tests passed.") python-periphery-2.0.1/tests/test_serial.py000066400000000000000000000154211360527466700211150ustar00rootroot00000000000000import os import sys import time import periphery from .asserts import AssertRaises if sys.version_info[0] == 3: raw_input = input serial_device = None def test_arguments(): print("Starting arguments test...") # Invalid data bits with AssertRaises(ValueError): periphery.Serial("/dev/ttyS0", 115200, databits=4) with AssertRaises(ValueError): periphery.Serial("/dev/ttyS0", 115200, databits=9) # Invalid parity with AssertRaises(ValueError): periphery.Serial("/dev/ttyS0", 115200, parity="blah") # Invalid stop bits with AssertRaises(ValueError): periphery.Serial("/dev/ttyS0", 115200, stopbits=0) with AssertRaises(ValueError): periphery.Serial("/dev/ttyS0", 115200, stopbits=3) # Everything else is fair game, although termios might not like it. print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") serial = periphery.Serial(serial_device, 115200) # Confirm default settings assert serial.fd > 0 assert serial.baudrate == 115200 assert serial.databits == 8 assert serial.parity == "none" assert serial.stopbits == 1 assert serial.xonxoff == False assert serial.rtscts == False # Change some stuff and check that it changed serial.baudrate = 4800 assert serial.baudrate == 4800 serial.baudrate = 9600 assert serial.baudrate == 9600 serial.databits = 7 assert serial.databits == 7 serial.parity = "odd" assert serial.parity == "odd" serial.stopbits = 2 assert serial.stopbits == 2 serial.xonxoff = True assert serial.xonxoff == True # Test serial port may not support rtscts serial.close() print("Open/close test passed.") def test_loopback(): print("Starting loopback test...") 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") assert serial.write(lorem_ipsum) == len(lorem_ipsum) serial.flush() buf = serial.read(len(lorem_ipsum), timeout=3) assert buf == lorem_ipsum # Test write/flush/read with bytearray write print("Write, flush, read lorem ipsum with bytearray type") assert serial.write(bytearray(lorem_ipsum)) == len(lorem_ipsum) serial.flush() buf = serial.read(len(lorem_ipsum), timeout=3) assert buf == lorem_ipsum # Test write/flush/read with list write print("Write, flush, read lorem ipsum with list type") assert serial.write(list(bytearray(lorem_ipsum))) == len(lorem_ipsum) serial.flush() buf = serial.read(len(lorem_ipsum), timeout=3) assert buf == lorem_ipsum # Test poll/write/flush/poll/input waiting/read print("Write, flush, poll, input waiting, read lorem ipsum") assert serial.poll(0.5) == False assert serial.write(lorem_ipsum) == len(lorem_ipsum) serial.flush() assert serial.poll(0.5) == True periphery.sleep_ms(500) assert serial.input_waiting() == len(lorem_ipsum) buf = serial.read(len(lorem_ipsum)) assert buf == lorem_ipsum # Test non-blocking poll print("Check non-blocking poll") assert 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) assert serial.write(lorem_hugesum) == len(lorem_hugesum) serial.flush() buf = serial.read(len(lorem_hugesum), timeout=3) assert buf == lorem_hugesum # Test read timeout print("Check read timeout") tic = time.time() assert serial.read(4096 * 3, timeout=2) == b"" toc = time.time() assert (toc - tic) > 1 # Test non-blocking read print("Check non-blocking read") tic = time.time() assert serial.read(4096 * 3, timeout=0) == b"" toc = time.time() # Assuming we weren't context switched out for a second assert int(toc - tic) == 0 serial.close() print("Loopback test passed.") def test_interactive(): print("Starting interactive test...") 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))) assert raw_input("Serial description looks ok? y/n ") == "y" serial.baudrate = 4800 raw_input("Press enter to start transfer...") assert serial.write(buf) == len(buf) assert raw_input("Serial transfer baudrate 4800, 8n1 occurred? y/n ") == "y" serial.baudrate = 9600 raw_input("Press enter to start transfer...") assert serial.write(buf) == len(buf) assert raw_input("Serial transfer baudrate 9600, 8n1 occurred? y/n ") == "y" serial.baudrate = 115200 raw_input("Press enter to start transfer...") assert serial.write(buf) == len(buf) assert raw_input("Serial transfer baudrate 115200, 8n1 occurred? y/n ") == "y" serial.close() print("Interactive test passed.") 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] print("Starting Serial tests...") test_arguments() test_open_close() test_loopback() test_interactive() print("All Serial tests passed.") python-periphery-2.0.1/tests/test_spi.py000066400000000000000000000122451360527466700204320ustar00rootroot00000000000000import os import sys import periphery from .asserts import AssertRaises if sys.version_info[0] == 3: raw_input = input spi_device = None def test_arguments(): print("Starting arguments test...") # Invalid mode with AssertRaises(ValueError): periphery.SPI("/dev/spidev0.0", 4, int(1e6)) # Invalid bit order with AssertRaises(ValueError): periphery.SPI("/dev/spidev0.0", 4, int(1e6), bit_order="blah") print("Arguments test passed.") def test_open_close(): print("Starting open/close test...") # Normal open (mode=1, max_speed = 100000) spi = periphery.SPI(spi_device, 1, 100000) # Confirm fd and defaults assert spi.fd > 0 assert spi.mode == 1 assert spi.max_speed == 100000 assert spi.bit_order == "msb" assert spi.bits_per_word == 8 assert spi.extra_flags == 0 # 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 assert spi.mode == 0 spi.mode = 1 assert spi.mode == 1 spi.mode = 2 assert spi.mode == 2 spi.mode = 3 assert spi.mode == 3 # Try max speeds 100Khz, 500KHz, 1MHz, 2MHz spi.max_speed = 100000 assert spi.max_speed == 100000 spi.max_speed = 500000 assert spi.max_speed == 500000 spi.max_speed = 1000000 assert spi.max_speed == 1000000 spi.max_speed = 2e6 assert spi.max_speed == 2000000 spi.close() print("Open/close test passed.") def test_loopback(): print("Starting loopback test...") spi = periphery.SPI(spi_device, 0, 100000) # Try list transfer buf_in = list(range(256)) * 4 buf_out = spi.transfer(buf_in) assert buf_out == buf_in # Try bytearray transfer buf_in = bytearray(buf_in) buf_out = spi.transfer(buf_in) assert buf_out == buf_in # Try bytes transfer buf_in = bytes(bytearray(buf_in)) buf_out = spi.transfer(buf_in) assert buf_out == buf_in spi.close() print("Loopback test passed.") def test_interactive(): print("Starting interactive test...") 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))) assert 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") assert 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") assert 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") assert 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") assert 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") assert 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") assert raw_input("SPI transfer speed <= 1MHz, mode 0 occurred? y/n ") == "y" spi.close() print("Interactive test passed.") 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] print("Starting SPI tests...") test_arguments() test_open_close() test_loopback() test_interactive() print("All SPI tests passed.")