From b5075840d123b6e539ecc3ce90528f8b9574d255 Mon Sep 17 00:00:00 2001 From: Demo User Date: Sun, 22 Dec 2019 11:35:36 -0600 Subject: [PATCH] adding smbus2.py and the __init__.py files... --- __init__.py | 25 ++ smbus2.py | 667 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 692 insertions(+) create mode 100644 __init__.py create mode 100644 smbus2.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..591a296 --- /dev/null +++ b/__init__.py @@ -0,0 +1,25 @@ +# smbus2 - A drop-in replacement for smbus-cffi/smbus-python +# The MIT License (MIT) +# Copyright (c) 2017 Karl-Petter Lindegaard +# +# 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. + +from .smbus2 import SMBus, SMBusWrapper, i2c_msg, I2cFunc # noqa: F401 + +__version__ = "0.3.0" diff --git a/smbus2.py b/smbus2.py new file mode 100644 index 0000000..5b7983c --- /dev/null +++ b/smbus2.py @@ -0,0 +1,667 @@ +# smbus2 - A drop-in replacement for smbus-cffi/smbus-python +# The MIT License (MIT) +# Copyright (c) 2017 Karl-Petter Lindegaard +# +# 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. + +import os +import sys +from fcntl import ioctl +from ctypes import c_uint32, c_uint8, c_uint16, c_char, POINTER, Structure, Array, Union, create_string_buffer, string_at + + +# Commands from uapi/linux/i2c-dev.h +I2C_SLAVE = 0x0703 # Use this slave address +I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if it is already in use by a driver! +I2C_FUNCS = 0x0705 # Get the adapter functionality mask +I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only) +I2C_SMBUS = 0x0720 # SMBus transfer. Takes pointer to i2c_smbus_ioctl_data + +# SMBus transfer read or write markers from uapi/linux/i2c.h +I2C_SMBUS_WRITE = 0 +I2C_SMBUS_READ = 1 + +# Size identifiers uapi/linux/i2c.h +I2C_SMBUS_QUICK = 0 +I2C_SMBUS_BYTE = 1 +I2C_SMBUS_BYTE_DATA = 2 +I2C_SMBUS_WORD_DATA = 3 +I2C_SMBUS_PROC_CALL = 4 +I2C_SMBUS_BLOCK_DATA = 5 # This isn't supported by Pure-I2C drivers with SMBUS emulation, like those in RaspberryPi, OrangePi, etc :( +I2C_SMBUS_BLOCK_PROC_CALL = 7 # Like I2C_SMBUS_BLOCK_DATA, it isn't supported by Pure-I2C drivers either. +I2C_SMBUS_I2C_BLOCK_DATA = 8 +I2C_SMBUS_BLOCK_MAX = 32 + +# To determine what functionality is present (uapi/linux/i2c.h) +try: + from enum import IntFlag +except: + IntFlag = int + + +class I2cFunc(IntFlag): + + #These flags identify the operations supported by an I2C/SMBus device. + + #You can test these flags on your `smbus.funcs` + + #On newer python versions, I2cFunc is an IntFlag enum, but it + #falls back to class with a bunch of int constants on older releases. + + I2C = 0x00000001 + ADDR_10BIT = 0x00000002 + PROTOCOL_MANGLING = 0x00000004 # I2C_M_IGNORE_NAK etc. + SMBUS_PEC = 0x00000008 + NOSTART = 0x00000010 # I2C_M_NOSTART + SLAVE = 0x00000020 + SMBUS_BLOCK_PROC_CALL = 0x00008000 # SMBus 2.0 + SMBUS_QUICK = 0x00010000 + SMBUS_READ_BYTE = 0x00020000 + SMBUS_WRITE_BYTE = 0x00040000 + SMBUS_READ_BYTE_DATA = 0x00080000 + SMBUS_WRITE_BYTE_DATA = 0x00100000 + SMBUS_READ_WORD_DATA = 0x00200000 + SMBUS_WRITE_WORD_DATA = 0x00400000 + SMBUS_PROC_CALL = 0x00800000 + SMBUS_READ_BLOCK_DATA = 0x01000000 + SMBUS_WRITE_BLOCK_DATA = 0x02000000 + SMBUS_READ_I2C_BLOCK = 0x04000000 # I2C-like block xfer + SMBUS_WRITE_I2C_BLOCK = 0x08000000 # w/ 1-byte reg. addr. + SMBUS_HOST_NOTIFY = 0x10000000 + + SMBUS_BYTE = 0x00060000 + SMBUS_BYTE_DATA = 0x00180000 + SMBUS_WORD_DATA = 0x00600000 + SMBUS_BLOCK_DATA = 0x03000000 + SMBUS_I2C_BLOCK = 0x0c000000 + SMBUS_EMUL = 0x0eff0008 + + +# i2c_msg flags from uapi/linux/i2c.h +I2C_M_RD = 0x0001 + +# Pointer definitions +LP_c_uint8 = POINTER(c_uint8) +LP_c_uint16 = POINTER(c_uint16) +LP_c_uint32 = POINTER(c_uint32) + + +############################################################# +# Type definitions as in i2c.h + + +class i2c_smbus_data(Array): + + #Adaptation of the i2c_smbus_data union in ``i2c.h``. + + #Data for SMBus messages. + + _length_ = I2C_SMBUS_BLOCK_MAX + 2 + _type_ = c_uint8 + + +class union_i2c_smbus_data(Union): + _fields_ = [ + ("byte", c_uint8), + ("word", c_uint16), + ("block", i2c_smbus_data) + ] + + +union_pointer_type = POINTER(union_i2c_smbus_data) + + +class i2c_smbus_ioctl_data(Structure): + + #As defined in ``i2c-dev.h``. + + _fields_ = [ + ('read_write', c_uint8), + ('command', c_uint8), + ('size', c_uint32), + ('data', union_pointer_type)] + __slots__ = [name for name, type in _fields_] + + @staticmethod + def create(read_write=I2C_SMBUS_READ, command=0, size=I2C_SMBUS_BYTE_DATA): + u = union_i2c_smbus_data() + return i2c_smbus_ioctl_data( + read_write=read_write, command=command, size=size, + data=union_pointer_type(u)) + + +############################################################# +# Type definitions for i2c_rdwr combined transactions + + +class i2c_msg(Structure): + + #As defined in ``i2c.h``. + + _fields_ = [ + ('addr', c_uint16), + ('flags', c_uint16), + ('len', c_uint16), + ('buf', POINTER(c_char))] + + def __iter__(self): + #Iterator / Generator + + #:return: iterates over :py:attr:`buf` + #:rtype: :py:class:`generator` which returns int values + + idx = 0 + while idx < self.len: + yield ord(self.buf[idx]) + idx += 1 + + def __len__(self): + return self.len + + def __bytes__(self): + return string_at(self.buf, self.len) + + def __repr__(self): + return 'i2c_msg(%d,%d,%r)' % (self.addr, self.flags, self.__bytes__()) + + def __str__(self): + s = self.__bytes__() + if sys.version_info.major >= 3: + s = ''.join(map(chr, s)) + return s + + @staticmethod + def read(address, length): + + #Prepares an i2c read transaction. + + #:param address: Slave address. + #:type: address: int + #:param length: Number of bytes to read. + #:type: length: int + #:return: New :py:class:`i2c_msg` instance for read operation. + #:rtype: :py:class:`i2c_msg` + + arr = create_string_buffer(length) + return i2c_msg( + addr=address, flags=I2C_M_RD, len=length, + buf=arr) + + @staticmethod + def write(address, buf): + + #Prepares an i2c write transaction. + + #:param address: Slave address. + #:type address: int + #:param buf: Bytes to write. Either list of values or str. + #:type buf: list + #:return: New :py:class:`i2c_msg` instance for write operation. + #:rtype: :py:class:`i2c_msg` + + if sys.version_info.major >= 3: + if type(buf) is str: + buf = bytes(map(ord, buf)) + else: + buf = bytes(buf) + else: + if type(buf) is not str: + buf = ''.join([chr(x) for x in buf]) + arr = create_string_buffer(buf, len(buf)) + return i2c_msg( + addr=address, flags=0, len=len(arr), + buf=arr) + + +class i2c_rdwr_ioctl_data(Structure): + + #As defined in ``i2c-dev.h``. + + _fields_ = [ + ('msgs', POINTER(i2c_msg)), + ('nmsgs', c_uint32) + ] + __slots__ = [name for name, type in _fields_] + + @staticmethod + def create(*i2c_msg_instances): + + #Factory method for creating a i2c_rdwr_ioctl_data struct that can + #e called with ``ioctl(fd, I2C_RDWR, data)``. + + #:param i2c_msg_instances: Up to 42 i2c_msg instances + #:rtype: i2c_rdwr_ioctl_data + #""" + n_msg = len(i2c_msg_instances) + msg_array = (i2c_msg * n_msg)(*i2c_msg_instances) + return i2c_rdwr_ioctl_data( + msgs=msg_array, + nmsgs=n_msg + ) + + +############################################################# + + +class SMBus(object): + + def __init__(self, bus=None, force=False): + + #Initialize and (optionally) open an i2c bus connection. + + #:param bus: i2c bus number (e.g. 0 or 1) + #or an absolute file path (e.g. `/dev/i2c-42`). + #If not given, a subsequent call to ``open()`` is required. + #:type bus: int or str + #:param force: force using the slave address even when driver is + #already using it. + #:type force: boolean + + self.fd = None + self.funcs = I2cFunc(0) + if bus is not None: + self.open(bus) + self.address = None + self.force = force + self._force_last = None + + def __enter__(self): + #Enter handler + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + #Exit handler + self.close() + + def open(self, bus): + + #Open a given i2c bus. + + #:param bus: i2c bus number (e.g. 0 or 1) + #or an absolute file path (e.g. '/dev/i2c-42'). + #:type bus: int or str + #:raise TypeError: if type(bus) is not in (int, str) + + if isinstance(bus, int): + filepath = "/dev/i2c-2".format(bus) + elif isinstance(bus, str): + filepath = bus + else: + raise TypeError("Unexpected type(bus)={}".format(type(bus))) + + self.fd = os.open(filepath, os.O_RDWR) + self.funcs = self._get_funcs() + + def close(self): + + #Close the i2c connection. + + if self.fd: + os.close(self.fd) + self.fd = None + + def _set_address(self, address, force=None): + + #Set i2c slave address to use for subsequent calls. + + #:param address: + #:type address: int + #:param force: + #:type force: Boolean + + force = force if force is not None else self.force + if self.address != address or self._force_last != force: + if force is True: + ioctl(self.fd, I2C_SLAVE_FORCE, address) + else: + ioctl(self.fd, I2C_SLAVE, address) + self.address = address + self._force_last = force + + def _get_funcs(self): + + #Returns a 32-bit value stating supported I2C functions. + + #:rtype: int + + f = c_uint32() + ioctl(self.fd, I2C_FUNCS, f) + return f.value + + def write_quick(self, i2c_addr, force=None): + + #Perform quick transaction. Throws IOError if unsuccessful. + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param force: + #:type force: Boolean + + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=0, size=I2C_SMBUS_QUICK) + ioctl(self.fd, I2C_SMBUS, msg) + + def read_byte(self, i2c_addr, force=None): + + #Read a single byte from a device. + + #:rtype: int + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param force: + #:type force: Boolean + #:return: Read byte value + + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_READ, command=0, size=I2C_SMBUS_BYTE + ) + ioctl(self.fd, I2C_SMBUS, msg) + return msg.data.contents.byte + + def write_byte(self, i2c_addr, value, force=None): + + #Write a single byte to a device. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param value: value to write + #:type value: int + #:param force: + #:type force: Boolean + + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=value, size=I2C_SMBUS_BYTE + ) + ioctl(self.fd, I2C_SMBUS, msg) + + def read_byte_data(self, i2c_addr, register, force=None): + + #Read a single byte from a designated register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Register to read + #:type register: int + #:param force: + #:type force: Boolean + #:return: Read byte value + #:rtype: int + + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_BYTE_DATA + ) + ioctl(self.fd, I2C_SMBUS, msg) + return msg.data.contents.byte + + def write_byte_data(self, i2c_addr, register, value, force=None): + + #Write a byte to a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Register to write to + #:type register: int + #:param value: Byte value to transmit + #:type value: int + #:param force: + #:type force: Boolean + #:rtype: None + + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_BYTE_DATA + ) + msg.data.contents.byte = value + ioctl(self.fd, I2C_SMBUS, msg) + + def read_word_data(self, i2c_addr, register, force=None): + + #Read a single word (2 bytes) from a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Register to read + #:type register: int + #:param force: + #:type force: Boolean + #:return: 2-byte word + #:rtype: int + + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_WORD_DATA + ) + ioctl(self.fd, I2C_SMBUS, msg) + return msg.data.contents.word + + def write_word_data(self, i2c_addr, register, value, force=None): + #""" + #Write a byte to a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Register to write to + #:type register: int + #:param value: Word value to transmit + #:type value: int + #:param force: + #:type force: Boolean + #:rtype: None + #""" + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_WORD_DATA + ) + msg.data.contents.word = value + ioctl(self.fd, I2C_SMBUS, msg) + + def process_call(self, i2c_addr, register, value, force=None): + #""" + #Executes a SMBus Process Call, sending a 16-bit value and receiving a 16-bit response + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Register to read/write to + #:type register: int + #:param value: Word value to transmit + #:type value: int + #:param force: + #:type force: Boolean + #:rtype: int + #""" + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_PROC_CALL + ) + msg.data.contents.word = value + ioctl(self.fd, I2C_SMBUS, msg) + return msg.data.contents.word + + def read_block_data(self, i2c_addr, register, force=None): + #""" + #Read a block of up to 32-bytes from a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Start register + #:type register: int + #:param force: + #:type force: Boolean + #:return: List of bytes + #:rtype: list + #""" + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_BLOCK_DATA + ) + ioctl(self.fd, I2C_SMBUS, msg) + length = msg.data.contents.block[0] + return msg.data.contents.block[1:length + 1] + + def write_block_data(self, i2c_addr, register, data, force=None): + #""" + #Write a block of byte data to a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Start register + #:type register: int + #:param data: List of bytes + #:type data: list + #:param force: + #:type force: Boolean + #:rtype: None + #""" + length = len(data) + if length > I2C_SMBUS_BLOCK_MAX: + raise ValueError("Data length cannot exceed %d bytes" % I2C_SMBUS_BLOCK_MAX) + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_BLOCK_DATA + ) + msg.data.contents.block[0] = length + msg.data.contents.block[1:length + 1] = data + ioctl(self.fd, I2C_SMBUS, msg) + + def block_process_call(self, i2c_addr, register, data, force=None): + #""" + #Executes a SMBus Block Process Call, sending a variable-size data + #block and receiving another variable-size response + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Register to read/write to + #:type register: int + #:param data: List of bytes + #:type data: list + #:param force: + #:type force: Boolean + #:return: List of bytes + #:rtype: list + #""" + length = len(data) + if length > I2C_SMBUS_BLOCK_MAX: + raise ValueError("Data length cannot exceed %d bytes" % I2C_SMBUS_BLOCK_MAX) + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_BLOCK_PROC_CALL + ) + msg.data.contents.block[0] = length + msg.data.contents.block[1:length + 1] = data + ioctl(self.fd, I2C_SMBUS, msg) + length = msg.data.contents.block[0] + return msg.data.contents.block[1:length + 1] + + def read_i2c_block_data(self, i2c_addr, register, length, force=None): + #""" + #Read a block of byte data from a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Start register + #:type register: int + #:param length: Desired block length + #:type length: int + #:param force: + #:type force: Boolean + #:return: List of bytes + #:rtype: list + #""" + if length > I2C_SMBUS_BLOCK_MAX: + raise ValueError("Desired block length over %d bytes" % I2C_SMBUS_BLOCK_MAX) + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_READ, command=register, size=I2C_SMBUS_I2C_BLOCK_DATA + ) + msg.data.contents.byte = length + ioctl(self.fd, I2C_SMBUS, msg) + return msg.data.contents.block[1:length + 1] + + def write_i2c_block_data(self, i2c_addr, register, data, force=None): + #""" + #Write a block of byte data to a given register. + + #:param i2c_addr: i2c address + #:type i2c_addr: int + #:param register: Start register + #:type register: int + #:param data: List of bytes + #:type data: list + #:param force: + #:type force: Boolean + #:rtype: None + #""" + length = len(data) + if length > I2C_SMBUS_BLOCK_MAX: + raise ValueError("Data length cannot exceed %d bytes" % I2C_SMBUS_BLOCK_MAX) + self._set_address(i2c_addr, force=force) + msg = i2c_smbus_ioctl_data.create( + read_write=I2C_SMBUS_WRITE, command=register, size=I2C_SMBUS_I2C_BLOCK_DATA + ) + msg.data.contents.block[0] = length + msg.data.contents.block[1:length + 1] = data + ioctl(self.fd, I2C_SMBUS, msg) + + def i2c_rdwr(self, *i2c_msgs): + #""" + #Combine a series of i2c read and write operations in a single + #transaction (with repeated start bits but no stop bits in between). + + #This method takes i2c_msg instances as input, which must be created + #first with :py:meth:`i2c_msg.read` or :py:meth:`i2c_msg.write`. + + #:param i2c_msgs: One or more i2c_msg class instances. + #:type i2c_msgs: i2c_msg + #:rtype: None + #""" + ioctl_data = i2c_rdwr_ioctl_data.create(*i2c_msgs) + ioctl(self.fd, I2C_RDWR, ioctl_data) + + +class SMBusWrapper: + #""" + #Wrapper class around the SMBus. + #Deprecated as of version 0.3.0. Please replace with :py:class:`SMBus`. + + #Enables the user to wrap access to the :py:class:`SMBus` class in a + #"with" statement. If auto_cleanup is True (default), the + #:py:class:`SMBus` handle will be automatically closed + #upon exit of the ``with`` block. + #""" + def __init__(self, bus_number=0, auto_cleanup=True, force=False): + #""" + #:param auto_cleanup: Close bus when leaving scope. + #:type auto_cleanup: Boolean + #:param force: Force using the slave address even when driver is already using it. + #:type force: Boolean + #""" + self.bus_number = bus_number + self.auto_cleanup = auto_cleanup + self.force = force + + def __enter__(self): + self.bus = SMBus(bus=self.bus_number, force=self.force) + return self.bus + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.auto_cleanup: + self.bus.close()