Source code for philander.serialbus

"""Serial bus convergence layer for smbus, smbus2, periphery or simulative implementation.

Provide a common API for serial bus communication (I2C / SPI).
This interface is to to abstract from details of the implementation.

Basically, there are two main classes: ``SerialBus`` and ``SerialBusDevice``.
The ``SerialBus`` class unifies the implementations like smbus or periphery
by providing similar communication functions, such as read/write byte,
word and buffer data.

The ``SerialBusDevice`` carries specific information for a specific bus
participant, such as its address.
For that reason, every read or write function of the ``SerialBus`` class needs
an ``SerialBusDevice`` instance as a parameter. For convenience, read and
write functions are also available at the ``SerialBusDevice`` class,
delegating their calls to the matching functions in ``SerialBus`` along
with their self-reference.

For the sake of consistency, each ``SerialBusDevice`` must be mated with
a certain ``SerialBus`` in order to work, properly. This process is called
*attaching a device to a bus*. Several devices may be attached to the
same bus. However, a single device may only attached to at most one bus.
After attaching, the bus and device are double-linked to each other:
The bus has a list of attached devices, while a device has a reference
to the bus it is attached to.  
"""
__author__ = "Oliver Maye"
__version__ = "0.1"
__all__ = ["SerialBus", "SerialBusDevice", "SerialBusType"]

from philander.penum import Enum, unique, auto, idiotypic

from philander.gpio import GPIO
from philander.module import Module
from philander.sysfactory import SysProvider, SysFactory
from philander.systypes import ErrorCode


[docs] class SerialBusDevice( Module ): """Reflect a specific device communicating over a serial bus. As its main information, an instance of ``SerialBusDevice`` is to hold specific information of that single device, such as its unique bus address. This class is meant to be sub-classed by implementations for real devices. Before using a device for communication, it must be attached to a bus by calling :meth:`SerialBus.attach`. However, a device's :meth:`isAttached` function may be used to check, whether it has been attached to a bus, already. """ DEFAULT_ADDRESS = 0x21 def __init__(self): self.provider = SysProvider.NONE self.serialBus = None self.address = SerialBusDevice.DEFAULT_ADDRESS self.pinCS = None
[docs] @classmethod def Params_init( cls, paramDict ): """Initialize the set of configuration parameters with supported options. Supported configuration key names and their meanings are: ========================= =================================================================== Key Range, meaning and default ========================= =================================================================== SerialBusDevice.address int; I2C address of the device; :attr:`DEFAULT_ADDRESS` SerialBusDevice.CS.gpio.* SPI chip select pin configuration; See :meth:`.GPIO.Params_init`. ========================= =================================================================== Also see :meth:`.module.Module.Params_init`. :param dict(str, object) paramDict: Dictionary mapping option\ names to their respective values. :returns: none :rtype: None """ # Add bus-related defaults bus = paramDict.get("SerialBusDevice.bus", None) if bus: bus.Params_init(paramDict) else: SerialBus.Params_init(paramDict) # Add device-related defaults; may depend on bus type. btype = paramDict.get("SerialBus.type", None) defaults = {} if btype == SerialBusType.I2C: defaults = { "SerialBusDevice.address": SerialBusDevice.DEFAULT_ADDRESS, } elif btype == SerialBusType.SPI: defaults = { "SerialBusDevice.CS.gpio.direction": GPIO.DIRECTION_OUT, } for key, value in defaults.items(): if not key in paramDict: paramDict[key] = value return None
[docs] def open(self, paramDict): """Opens this serial device and puts it into a usable state. If this device has been attached to some bus, already, this method returns an error code. Otherwise, it tries to do this attachment as follows: * If the ``paramDict`` configuration parameters contain the\ ``SerialBusDevice.bus`` key, the associated value object is checked\ to be an instance of ``SerialBus``. If successful, this device\ is attached to that bus. Otherwise, an error code is returned. * If no bus instance is passed in, one is created and opened\ using the same ``paramDict`` dictionary of options. If successful,\ this device gets attached to that new bus. Upon return, the caller\ might retrieve a reference to the new bus from the parameter\ dictionary entry with key ``SerialBusDevice.bus``, or by\ reading the :attr:`SerialBusDevice.serialBus` attribute. Also see: :meth:`.module.Module.open`. :param dict(str, object) paramDict: Configuration parameters as\ obtained from :meth:`Params_init`, possibly. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ result = ErrorCode.errOk if (self.serialBus is None ): if ("SerialBusDevice.bus" in paramDict): sb = paramDict["SerialBusDevice.bus"] if not( isinstance(sb, SerialBus)): result = ErrorCode.errInvalidParameter elif not( sb.isOpen().isOk() ): result = sb.open(paramDict) else: sb = SerialBus.getSerialBus() if (sb is None): result = ErrorCode.errExhausted else: result = sb.open(paramDict) if (result.isOk()): paramDict["SerialBusDevice.bus"] = sb if (result.isOk()): if sb.type == SerialBusType.I2C: # Determine I2C address self.address = paramDict.get("SerialBusDevice.address", SerialBusDevice.DEFAULT_ADDRESS) if not isinstance(self.address, int): try: self.address = int( self.address, 0 ) except ValueError: self.address = SerialBusDevice.DEFAULT_ADDRESS elif sb.type == SerialBusType.SPI: # Setup GPIO pin for CS line if ("SerialBusDevice.CS.gpio.pinDesignator" in paramDict): paramDict["SerialBusDevice.CS.gpio.direction"] = GPIO.DIRECTION_OUT paramDict["SerialBusDevice.CS.gpio.level"] = GPIO.LEVEL_HIGH prefix = "SerialBusDevice.CS." gpioParams = dict( [(k.replace(prefix, ""),v) for k,v in paramDict.items() if k.startswith(prefix)] ) self.pinCS = SysFactory.getGPIO() # open GPIO pin result = self.pinCS.open(gpioParams) if (result.isOk()): result = sb.attach( self ) else: result = ErrorCode.errResourceConflict return result
[docs] def close(self): """Shut down this instance and release associated hardware resources. If this instance is attached to some bus, it gets detached, before the method returns. Also see: :meth:`.module.Module.close`. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ result = ErrorCode.errOk if self.serialBus is not None: result = self.serialBus.detach(self) if self.pinCS is not None: err = self.pinCS.close() if result.isOk(): result = err return result
[docs] def isAttached(self): """Determines, if this instance is attached to some bus. Also see: :meth:`SerialBus.isAttached`. :return: An error code. :attr:`ErrorCode.errOk`, if the device\ is already attached to some bus; :attr:`ErrorCode.errUnavailable`,\ if it has not been attached before; Any other value to indicate\ the failure or reason, why this information could not be retrieved. :rtype: ErrorCode """ err = ErrorCode.errOk if (self.serialBus is None): err = ErrorCode.errUnavailable else: err = ErrorCode.errOk return err
[docs] def readByteRegister( self, reg ): """This method provides 8 bit register read access to a device. The call is delegated to the corresponding method at the bus that this device is attached to. Also see: :meth:`SerialBus.readByteRegister`. :param int reg: The data to write to this device. This may be a\ register identification or some sort of command. :return: A one-byte integer representing the response of the device\ and an error code indicating success or the reason of failure. :rtype: int, ErrorCode """ return self.serialBus.readByteRegister( self, reg )
[docs] def writeByteRegister( self, reg, data8 ): """Assuming a register-type access, this function writes a byte register. The call is delegated to the corresponding method at the bus that this device is attached to. The register value is written first, followed by the given data parameter. Also see: :meth:`SerialBus.writeByteRegister`. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int data8: The data to write to the addressed register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ return self.serialBus.writeByteRegister( self, reg, data8)
[docs] def readWordRegister( self, reg ): """Provide register read access for 16 bit data words. The call is delegated to the corresponding method at the bus that this device is attached to. After a byte is sent, two bytes are read from the device in little endian order. Also see: :meth:`SerialBus.readWordRegister`. :param int reg: The register identification or command to write to this device. :return: A 16-bit integer representing the response of the device\ and an error code indicating success or the reason of failure. :rtype: int, ErrorCode """ return self.serialBus.readWordRegister( self, reg )
[docs] def writeWordRegister( self, reg, data16 ): """Assuming a register-type access, this function writes a word register. The call is delegated to the corresponding method at the bus that this device is attached to. The register ``reg`` value is written first, followed by the given ``data16`` parameter in little-endian order. Also see: :meth:`SerialBus.writeWordRegister`. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int data16: The word to store to the given register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ return self.serialBus.writeWordRegister( self, reg, data16 )
[docs] def readDWordRegister( self, reg ): """Provide register read access for 32 bit data words. The call is delegated to the corresponding method at the bus that this device is attached to. After a byte is sent, four bytes are read from the device in little endian order. Also see: :meth:`SerialBus.readDWordRegister`. :param int reg: The register identification or command to write to this device. :return: A 32-bit integer representing the response of the device\ and an error code indicating success or the reason of failure. :rtype: int, ErrorCode """ return self.serialBus.readDWordRegister( self, reg )
[docs] def writeDWordRegister( self, reg, data32 ): """Assuming a register-type access, this function writes a dword register. The call is delegated to the corresponding method at the bus that this device is attached to. The register ``reg`` value is written first, followed by the given ``data32`` parameter in little-endian order. Also see: :meth:`SerialBus.writeDWordRegister`. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int data32: The double-word to store to the given register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ return self.serialBus.writeDWordRegister( self, reg, data32 )
[docs] def readBufferRegister( self, reg, length ): """Multi-byte read access to a register-type serial bus device. The call is delegated to the corresponding method at the bus that this device is attached to. After sending one byte of command or register address, a number of bytes is read back and returned. For SPI, the byte received during transmission of the ``reg`` byte is discarded. It does not appear in the response buffer. Then, enough dummy traffic is generated to receive ``length`` number of bytes. Also see: :meth:`SerialBus.readBufferRegister`. :param int reg: The byte to send. May be a command or register\ address, depending on the protocol of the addressed device. :param int length: The number of bytes to read from the device.\ Should be greater than zero. :return: A buffer of the indicated length holding the response\ and an error code indicating success or the reason of failure. :rtype: int[], ErrorCode """ return self.serialBus.readBufferRegister( self, reg, length )
[docs] def writeBufferRegister( self, reg, buffer ): """Assuming a register-type access, this function writes a buffer to a register. The call is delegated to the corresponding method at the bus that this device is attached to. The register ``reg`` value is written first, followed by the given ``buffer`` content. Also see: :meth:`SerialBus.writeBufferRegister`. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int[] buffer: The data to store to the given register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ return self.serialBus.writeBufferRegister( self, reg, buffer )
[docs] def readBuffer( self, length ): """Directly reads multiple bytes from the given device. The call is delegated to the corresponding method at the bus that this device is attached to. Differently from :meth:`readBufferRegister`, this method does not write any register information beforehand, but just starts reading. Also see: :meth:`SerialBus.readBuffer`, :meth:`readBufferRegister`. :param int length: The number of bytes to read from the device.\ Should be greater than zero. :return: A buffer of the indicated length holding the response\ and an error code indicating success or the reason of failure. :rtype: int[], ErrorCode """ return self.serialBus.readBuffer( self, length)
[docs] def writeBuffer( self, buffer ): """Writes the given data to the device specified. The call is delegated to the corresponding method at the bus that this device is attached to. The buffer is not interpreted any further but is written as such, no matter of a register information being present, or not. In SPI mode, the data received during transmission, is discarded. Also see: :meth:`SerialBus.writeBuffer`, :meth:`writeBufferRegister`. :param int[] buffer: The data to store. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ return self.serialBus.writeBuffer( self, buffer )
[docs] def writeReadBuffer( self, outBuffer, inLength ): """Writes and reads a number of bytes. The call is delegated to the corresponding method at the bus that this device is attached to. Also see: :meth:`SerialBus.writeReadBuffer`. :param int[] outBuffer: The data to write to the device. :param int inLength: The number of bytes to read from the device.\ Should be greater than zero. :return: A buffer of the indicated length holding the response\ and an error code indicating success or the reason of failure. :rtype: int[], ErrorCode """ return self.serialBus.writeReadBuffer( self, outBuffer, inLength )
[docs] @unique @idiotypic class SerialBusType(Enum): I2C = 10 SPI = 20 UART= 30
@unique @idiotypic class SPIMode(Enum): """SPI mode specification. The basic SPI specification leaves some degree of freedom with respect to the clock idle state, often referred to as clock polarity (CPOL). It may be chosen either low or high. Also the phase (edge), at which to read data can either be first or second. This is referred to as the clock phase (CPHA). Varying CPOL and CPHA offers in total 4 modes that SPI can be operated in. For more information on SPI modes refer to: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#Clock_polarity_and_phase """ CPOL0_CPHA0 = 0 # idle low, read on first edge (rising) CPOL0_CPHA1 = 1 # idle low, read on second edge (falling) CPOL1_CPHA0 = 2 # idle high, read on first edge (falling) CPOL1_CPHA1 = 3 # idle high, read on second edge (rising)
[docs] class SerialBus( Module ): """Convergence layer to abstract from multiple implementations of\ serial communication (I2C, SPI), such as smbus or periphery. This class represents the serial bus as such, without any participating device. For communicating with a specific device, a corresponding instance of ``SerialBusDevice`` must be provided to the read/write method of interest. A sub class must overwrite at least the methods for reading and writing a single byte and buffer. """ _STATUS_FREE = 1 _STATUS_OPEN = 2 DEFAULT_TYPE = SerialBusType.I2C DEFAULT_DESGINATOR = "/dev/i2c-1" DEFAULT_SPI_SPEED = 1000000 DEFAULT_I2C_SPEED = 400000 DEFAULT_SPEED = DEFAULT_I2C_SPEED DEFAULT_SPI_MODE = SPIMode.CPOL1_CPHA1 DEFAULT_SPI_BIT_ORDER= "MSB" DEFAULT_SPI_BITS_PER_WORD = 8
[docs] @staticmethod def getSerialBus( provider=SysProvider.AUTO ): """Generates a serial bus implementation according to the requested provider. :param SysProvider provider: The low-level lib to rely on, or AUTO\ for automatic detection. :return: A serial bus implementation object, or None in case of an error. :rtype: SerialBus """ deps = [(SysProvider.PERIPHERY, "periphery", "I2C"), (SysProvider.MICROPYTHON, "machine", "I2C"), (SysProvider.SMBUS2, "smbus2", "SMBus"), ] impls = { SysProvider.MICROPYTHON: ("philander.serialbus_micropython", "_SerialBus_Micropython"), SysProvider.PERIPHERY: ("philander.serialbus_periphery", "_SerialBus_Periphery"), SysProvider.SIM: ("philander.serialbus_sim", "_SerialBus_Sim"), SysProvider.SMBUS2: ("philander.serialbus_smbus2", "_SerialBus_SMBus2"), } if provider == SysProvider.AUTO: provider = SysFactory.autoDetectProvider( deps, SysProvider.SIM ) ret = SysFactory.createInstance( provider, impls ) return ret
# # Internal helpers # def __init__(self): self.designator = "" self.provider = SysProvider.NONE self.type = SerialBusType.I2C self._attachedDevices = list() self._status = SerialBus._STATUS_FREE # # Module API #
[docs] @classmethod def Params_init( cls, paramDict ): """Initialize parameters with default values. Supported key names and their meanings are: ====================== ================================================= ============================================== Key Range Default ====================== ================================================= ============================================== SerialBus.type :class:`SerialBusType` to indicate the protocol. :attr:`SerialBus.DEFAULT_TYPE`. SerialBus.designator [string | number]: bus port, "/dev/i2c-3" or 1. "/dev/i2c-1". SerialBus.speed [int|float] maximum bus clock frequency in Hz. :attr:`SerialBus.DEFAULT_SPEED`. SerialBus.SPI.mode :class:`SPIMode` mode; only for SPI. :attr:`SerialBus.DEFAULT_SPI_MODE`. SerialBus.SPI.bitorder ["msb"|"lsb"] bit transmission order. :attr:`SerialBus.DEFAULT_SPI_BIT_ORDER`. SerialBus.SPI.bpw int; bits per word :attr:`SerialBus.DEFAULT_SPI_BITS_PER_WORD`. ====================== ================================================= ============================================== :param dict(str, object) paramDict: Configuration parameters as obtained from :meth:`Params_init`, possibly. :return: none :rtype: None """ defaults = { "SerialBus.type": SerialBus.DEFAULT_TYPE, "SerialBus.designator": SerialBus.DEFAULT_DESGINATOR, } for key, value in defaults.items(): if not key in paramDict: paramDict[key] = value if paramDict.get( "SerialBus.type", None ) == SerialBusType.I2C: I2Cdefaults = { "SerialBus.speed": SerialBus.DEFAULT_I2C_SPEED, } for key, value in I2Cdefaults.items(): if not key in paramDict: paramDict[key] = value elif paramDict.get( "SerialBus.type", None ) == SerialBusType.SPI: SPIdefaults = { "SerialBus.speed": SerialBus.DEFAULT_SPI_SPEED, "SerialBus.SPI.mode": SerialBus.DEFAULT_SPI_MODE, "SerialBus.SPI.bitorder": SerialBus.DEFAULT_SPI_BIT_ORDER, "SerialBus.SPI.bpw": SerialBus.DEFAULT_SPI_BITS_PER_WORD, } for key, value in SPIdefaults.items(): if not key in paramDict: paramDict[key] = value else: GENdefaults = { "SerialBus.speed": SerialBus.DEFAULT_SPEED, } for key, value in GENdefaults.items(): if not key in paramDict: paramDict[key] = value return None
[docs] def open(self, paramDict): """Open a new serial bus and apply the given configuration. If this instance was opened before, already, this method returns an error code. The same is true, when the same physical bus was opened before, possible using another instance. Also see: :meth:`Params_init`, :meth:`.module.Module.open`. :param dict(str, object) paramDict: Configuration parameters as\ obtained from :meth:`Params_init`, possibly. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk if (self._status == SerialBus._STATUS_OPEN): ret = ErrorCode.errResourceConflict else: # Retrieve defaults self.Params_init(paramDict) # Scan parameters self.type = paramDict["SerialBus.type"] self.designator = paramDict["SerialBus.designator"] if( ret.isOk() ): self._status = SerialBus._STATUS_OPEN return ret
[docs] def close(self): """Shut down this bus and release associated hardware resources. If this bus has some devices attached, they get detached, before the method returns. Also see: :meth:`.module.Module.close`. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk # Actually close the bus if (self._status != SerialBus._STATUS_FREE): # Detach all devices. self.detachAll() self._status = SerialBus._STATUS_FREE return ret
[docs] def setRunLevel(self, level): """Switch the bus into some operating or power-saving mode. Also see: :meth:`.module.Module.setRunLevel`. :param RunLevel level: The level to switch to. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del level err = ErrorCode.errOk if (self._status != SerialBus._STATUS_OPEN): err = ErrorCode.errResourceConflict return err
# # SerialBus API #
[docs] def isOpen( self ): """Determine, if the given bus is already open. :return: :attr:`ErrorCode.errOk`, if the bus is already open;\ :attr:`ErrorCode.errUnavailable`, if it has not been opened before;\ Any other value to indicate the failure or reason, why this\ information could not be retrieved. :rtype: ErrorCode """ result = ErrorCode.errOk if (self._status == SerialBus._STATUS_OPEN): result = ErrorCode.errOk else: result = ErrorCode.errUnavailable return result
[docs] def attach( self, device ): """Attaches a device to this serial bus. If this bus is not open, yet, then it will get opened, now. If the same device has been attached before, the method will just return successfully. :param: SerialBusDevice device: The device to be attached. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ result = ErrorCode.errOk if (device.serialBus == self): result = ErrorCode.errOk elif (device.serialBus != None): result = ErrorCode.errResourceConflict else: # Check if bus is open, already result = self.isOpen() if (result == ErrorCode.errUnavailable): params = {} self.Params_init(params) result = self.open(params) # Attach it to the implementation if (result.isOk()): if not (device in self._attachedDevices): self._attachedDevices.append( device ) # Mark the device as being attached device.serialBus = self return result
[docs] def detach( self, device ): """Detach a device from this serial bus. If this is the last device on the bus, the bus is closed, automatically. :param: SerialBusDevice device: The device to be detached. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ result = ErrorCode.errOk if (device.serialBus == self): device.serialBus = None if (device in self._attachedDevices): self._attachedDevices.remove( device ) if (self._status == SerialBus._STATUS_OPEN) and \ (self.isAnyAttached() == ErrorCode.errUnavailable ): result = self.close() else: result = ErrorCode.errResourceConflict return result
[docs] def detachAll(self): """Detaches all devices from this serial bus. Note that this will *not* close the bus automatically. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ result = ErrorCode.errOk for device in self._attachedDevices: device.serialBus = None self._attachedDevices.clear() return result
[docs] def isAttached( self, device ): """ Determines, if the given device is already attached to this bus. Also see: :meth:`SerialBusDevice.isAttached`. :return: An error code. :attr:`ErrorCode.errOk`, if the device\ is already attached to some bus; :attr:`ErrorCode.errUnavailable`,\ if it has not been attached before; Any other value to indicate\ the failure or reason, why this information could not be retrieved. :rtype: ErrorCode """ ret = ErrorCode.errOk if (self._status == SerialBus._STATUS_OPEN): if (device in self._attachedDevices): ret = ErrorCode.errOk else: ret = ErrorCode.errUnavailable else: ret = ErrorCode.errResourceConflict return ret
[docs] def isAnyAttached( self ): """ Determines, if there is any device attached to this bus implementation. :return: An error code. :attr:`ErrorCode.errOk`, if there is at\ least one device attached to this bus;\ :attr:`ErrorCode.errUnavailable`,\ if no device has been attached before;\ Any other value to indicate the failure or reason, why this\ information could not be retrieved. :rtype: ErrorCode """ result = ErrorCode.errOk if (self._status == SerialBus._STATUS_OPEN): if ( self._attachedDevices ): result = ErrorCode.errOk else: result = ErrorCode.errUnavailable else: result = ErrorCode.errResourceConflict return result
[docs] def readByteRegister( self, device, reg ): """This method provides 8 bit register read access to a device. First, the ``reg`` byte is sent to the device. This may address the register to be read out or be some sort of command. Then, one byte is read back from the device. Depending on the device protocol semantics, this may be the register content or the command response. Also see: :meth:`SerialBusDevice.readByteRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The data to write to this device. This may be a\ register identification or some sort of command. :return: A one-byte integer representing the response of the device\ and an error code indicating success or the reason of failure. :rtype: int, ErrorCode """ # A sub-class implementation must overwrite this method. del device, reg return 0, ErrorCode.errNotImplemented
[docs] def writeByteRegister( self, device, reg, data8 ): """Assuming a register-type access, this function writes a byte register. The register value is written first, followed by the given data parameter. Also see: :meth:`SerialBusDevice.writeByteRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int data8: The data to write to the addressed register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ # A sub-class implementation must overwrite this method. del device, reg, data8 return ErrorCode.errNotImplemented
[docs] def readWordRegister( self, device, reg ): """Provide register read access for 16 bit data words. After a byte is sent, two bytes are read from the device. The word is always read in little endian order, i.e. the least significant low-byte first, the highes-significant high-byte second. Also see: :meth:`SerialBusDevice.readByteRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The register identification or command to write\ to this device. :return: A 16-bit integer representing the response of the device\ and an error code indicating success or the reason of failure. :rtype: int, ErrorCode """ lo, _ = self.readByteRegister(device, reg) hi, err = self.readByteRegister(device, reg+1) return ((hi << 8) | lo), err
[docs] def writeWordRegister( self, device, reg, data16 ): """Assuming a register-type access, this function writes a word register. The register ``reg`` value is written first, followed by the given ``data16`` parameter in little-endian order. Also see: :meth:`SerialBusDevice.writeWordRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int data16: The word to store to the given register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ bVal = data16 & 0xFF self.writeByteRegister(device, reg, bVal) bVal = (data16 >> 8) & 0xFF err = self.writeByteRegister(device, reg+1, bVal) return err
[docs] def readDWordRegister( self, device, reg ): """Read a 32-bit word from the given register. After the ``reg`` byte is sent, four bytes are read from the device. The 32 bit double-word is always read in little endian order, i.e. the least significant low-byte first, the highes-significant high-byte last. Also see: :meth:`SerialBusDevice.readDWordRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The register identification or command to write\ to this device. :return: A 32-bit integer representing the response of the device\ and an error code indicating success or the reason of failure. :rtype: int, ErrorCode """ L, _ = self.readWordRegister( device, reg ) H, err = self.readWordRegister( device, reg+2 ) ret = (H << 16) + L return ret, err
[docs] def writeDWordRegister( self, device, reg, data32 ): """Write a 32 bit double-word to the given register. The register ``reg`` value is written first, followed by the given ``data32`` parameter in little-endian order. Also see: :meth:`SerialBusDevice.writeDWordRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int data32: The dword to store to the given register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ L = data32 & 0xFFFF H = (data32 & 0xFFFF0000) >> 16 self.writeWordRegister( device, reg, L ) err = self.writeWordRegister( device, reg+2, H ) return err
[docs] def readBufferRegister( self, device, reg, length ): """Multi-byte read access to a register-type serial bus device. After sending one byte of command or register address, a number of bytes is read back and returned. For SPI, the byte received during transmission of the ``reg`` byte is discarded. It does not appear in the response buffer. Then, enough dummy traffic is generated to receive ``length`` number of bytes. Also see: :meth:`SerialBusDevice.readBufferRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The byte to send. May be a command or register\ address, depending on the protocol of the addressed device. :param int length: The number of bytes to read from the device.\ Should be greater than zero. :return: A buffer of the indicated length holding the response\ and an error code indicating success or the reason of failure. :rtype: int[], ErrorCode """ data = [0] * length err = ErrorCode.errOk for idx in range(length): data[idx], err = self.readByteRegister(device, reg+idx) return data, err
[docs] def writeBufferRegister( self, device, reg, buffer ): """Assuming a register-type access, this function writes a buffer\ to a register. The register ``reg`` value is written first, followed by the given ``buffer`` content. Also see: :meth:`SerialBusDevice.writeBufferRegister`. :param SerialBusDevice device: The device to communicate with. :param int reg: The register number. This addresses the place\ where to put the content. Depending on the device, this could\ also be some kind of command. :param int buffer: The data to store to the given register. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ err = ErrorCode.errOk for idx in range( len(buffer) ): err = self.writeByteRegister(device, reg+idx, buffer[idx]) return err
[docs] def readBuffer( self, device, length ): """Directly reads multiple bytes from the given device. Also see: :meth:`SerialBusDevice.readBuffer`. :param SerialBusDevice device: The device to communicate with. :param int length: The number of bytes to read from the device.\ Should be greater than zero. :return: A buffer of the indicated length holding the response\ and an error code indicating success or the reason of failure. :rtype: int[], ErrorCode """ # A sub-class implementation must overwrite this method. del device, length return [], ErrorCode.errNotImplemented
[docs] def writeBuffer( self, device, buffer ): """Writes the given data to the device specified. The buffer is not interpreted any further but is written as such, no matter of a register information being present, or not. Also see: :meth:`SerialBusDevice.writeBuffer`. :param SerialBusDevice device: The device to communicate with. :param int[] buffer: The data to store. :return: An error code indicating success or the reason of failure. :rtype: ErrorCode """ # A sub-class implementation must overwrite this method. del device, buffer return ErrorCode.errNotImplemented
[docs] def writeReadBuffer( self, device, outBuffer, inLength ): """Writes and reads a number of bytes simultaneously, if possible. The output buffer is written. The last ``inLength`` number of bytes of the resulting input buffer are returned. E.g. ``inLength=2`` will the method make return the last two bytes (one word) of the buffer retrieved while/after writing the given ``outBuffer``. If data can be read simultaneously while writing (SPI), that input is considered. Again, ``inLength`` is counted from the end of the buffer. If that number is larger the given ``outBuffer``´s length, dummy bytes are written, accordingly. So, the total traffic caused in this case is ``max( len(outBuffer), inLength )`` bytes. If reading simultaneously is impossible (I2C), the given number of bytes are read by a separate message. The total traffic caused in this case is ``len(outBuffer) + inLength`` bytes. Also see: :meth:`SerialBusDevice.writeReadBuffer`. :param SerialBusDevice device: The device to communicate with. :param int inLength: The number of bytes to read from the device.\ Should be greater than zero. :param int[] outBuffer: The data to write to the device. :return: A buffer of the indicated length holding the response\ and an error code indicating success or the reason of failure. :rtype: int[], ErrorCode """ # A sub-class implementation must overwrite this method. del device, outBuffer, inLength return [], ErrorCode.errNotImplemented