# -*- coding: utf-8 -*-
# (c) Copyright 2019 Sensirion AG, Switzerland
from __future__ import absolute_import, division, print_function
from .device_base import ShdlcDeviceBase
from .commands.device_info import ShdlcCmdGetProductType, \
ShdlcCmdGetProductName, ShdlcCmdGetArticleCode, ShdlcCmdGetSerialNumber, \
ShdlcCmdGetProductSubType
from .commands.device_version import ShdlcCmdGetVersion
from .commands.error_state import ShdlcCmdGetErrorState
from .commands.device_reset import ShdlcCmdDeviceReset
from .commands.slave_address import ShdlcCmdGetSlaveAddress, \
ShdlcCmdSetSlaveAddress
from .commands.baudrate import ShdlcCmdGetBaudrate, ShdlcCmdSetBaudrate
from .commands.reply_delay import ShdlcCmdGetReplyDelay, ShdlcCmdSetReplyDelay
from .commands.system_up_time import ShdlcCmdGetSystemUpTime
from .commands.factory_reset import ShdlcCmdFactoryReset
import logging
log = logging.getLogger(__name__)
class ShdlcDevice(ShdlcDeviceBase):
"""
Generic SHDLC device, providing only common SHDLC commands. This class is
intended only to communicate with devices which do not provide a
corresponding device driver (yet). With this class you can for example
read the serial number of a device even if no device specific driver
exists. But if there exists a device specific driver, you should always
use it instead of this driver.
This is a low-level driver which just provides all SHDLC commands as Python
methods. Typically, calling a method sends one SHDLC request to the device
and interprets its response. There is no higher level functionality
available, please look for other drivers if you need a higher level
interface.
There is no (or very few) caching functionality in this driver. For example
if you call
:py:meth:`~sensirion_shdlc_driver.device.ShdlcDevice.get_serial_number()`
100 times, it will send the command 100 times over the SHDLC interface to
the device. This makes the driver (nearly) stateless.
"""
def __init__(self, connection, slave_address):
"""
Create an SHDLC device instance on an SHDLC connection.
.. note:: This constructor does not communicate with the device, so
it's possible to instantiate an object even if the device is
not connected or powered yet.
:param ~sensirion_shdlc_driver.connection.ShdlcConnection connection:
The connection used for the communication.
:param byte slave_address:
The address of the device.
"""
super(ShdlcDevice, self).__init__(connection, slave_address)
def get_product_type(self, as_int=False):
"""
Get the product type. The product type (sometimes also called "device
type") can be used to detect what kind of SHDLC product is connected.
:param bool as_int: If ``True``, the product type is returned as an
integer, otherwise as a string of hexadecimal
digits (default).
:return: The product type as an integer or string of hexadecimal
digits.
:rtype: string/int
"""
product_type = self.execute(ShdlcCmdGetProductType())
if as_int:
product_type = int(product_type, 16)
return product_type
def get_product_subtype(self):
"""
Get the product subtype. Some product types exist in multiple slightly
different variants, this command allows to determine the exact variant
of the connected device. Sometimes this is called "device subtype".
.. note:: This command is not supported by every product type.
:return: The product subtype as a byte (the interpretation depends on
the connected product type).
:rtype: byte
"""
return self.execute(ShdlcCmdGetProductSubType())
def get_product_name(self):
"""
Get the product name of the device.
.. note:: This command is not supported by every product type.
:return: The product name as an ASCII string.
:rtype: string
"""
return self.execute(ShdlcCmdGetProductName())
def get_article_code(self):
"""
Get the article code of the device.
.. note:: This command is not supported by every product type.
:return: The article code as an ASCII string.
:rtype: string
"""
return self.execute(ShdlcCmdGetArticleCode())
def get_serial_number(self):
"""
Get the serial number of the device.
:return: The serial number as an ASCII string.
:rtype: string
"""
return self.execute(ShdlcCmdGetSerialNumber())
def get_version(self):
"""
Get the version of the device firmware, hardware and SHDLC protocol.
:return: The device version as a Version object.
:rtype: Version
"""
return self.execute(ShdlcCmdGetVersion())
def get_error_state(self, clear=True, as_exception=False):
"""
Get and optionally clear the device error state and the last error. The
state and error code interpretation depends on the connected device
type.
:param bool clear:
If ``True``, the error state on the device gets cleared.
:param bool as_exception:
If ``True``, the error state is returned as an
:py:class:`~sensirion_shdlc_driver.errors.ShdlcDeviceError`
object instead of a byte.
:return: The device state as a 32-bit unsigned integer containing all
error flags, and the last error which occurred on the device.
If ``as_exception`` is ``True``, it's returned as an
:py:class:`~sensirion_shdlc_driver.errors.ShdlcDeviceError`
object or ``None``, otherwise as a byte.
:rtype: int, byte/ShdlcDeviceError/None
"""
state, error = self.execute(ShdlcCmdGetErrorState(clear=clear))
if as_exception:
error = self._get_device_error(error)
return state, error
def get_slave_address(self):
"""
Get the SHDLC slave address of the device.
.. note:: See also the property
:py:attr:`~sensirion_shdlc_driver.device.ShdlcDevice.slave_address`
which returns the device's slave address without sending a
command. This method really sends a command to the device,
even though the slave address is actually already known by
this object.
:return: The slave address of the device.
:rtype: byte
"""
return self.execute(ShdlcCmdGetSlaveAddress())
def set_slave_address(self, slave_address, update_driver=True):
"""
Set the SHDLC slave address of the device.
.. note:: The slave address is stored in non-volatile memory of the
device and thus persists after a device reset. So the next
time connecting to the device, you have to use the new
address.
.. warning:: When changing the address of a slave, make sure there
isn't already a slave with that address on the same bus!
In that case you would get communication issues which can
only be fixed by disconnecting one of the slaves.
:param byte slave_address:
The new slave address [0..254]. The address 255 is reserved for
broadcasts.
:param bool update_driver:
If ``True``, the property
:py:attr:`~sensirion_shdlc_driver.device.ShdlcDevice.slave_address`
of this object is also updated with the new address. This is
needed to allow further communication with the device, as its
address has changed.
"""
self.execute(ShdlcCmdSetSlaveAddress(slave_address))
if update_driver:
self._slave_address = slave_address
def get_baudrate(self):
"""
Get the SHDLC baudrate of the device.
.. note:: This method really sends a command to the device, even though
the baudrate is already known by the used
:py:class:`~sensirion_shdlc_driver.port.ShdlcPort` object.
:return: The baudrate of the device [bit/s].
:rtype: int
"""
return self.execute(ShdlcCmdGetBaudrate())
def set_baudrate(self, baudrate, update_driver=True):
"""
Set the SHDLC baudrate of the device.
.. note:: The baudrate is stored in non-volatile memory of the
device and thus persists after a device reset. So the next
time connecting to the device, you have to use the new
baudrate.
.. warning:: If you pass ``True`` to the argument ``update_driver``,
the baudrate of the underlaying
:py:class:`~sensirion_shdlc_driver.port.ShdlcPort` object
is changed. As the baudrate applies to the whole bus (with
all its slaves), you might no longer be able to
communicate with other slaves. Generally you should change
the baudrate of all slaves consecutively, and only set
``update_driver`` to ``True`` the last time.
:param int baudrate:
The new baudrate. See device documentation for a list of supported
baudrates. Many devices support the baudrates 9600, 19200 and
115200.
:param bool update_driver:
If true, the baudrate of the
:py:class:`~sensirion_shdlc_driver.port.ShdlcPort` object is also
updated with the baudrate. This is needed to allow further
communication with the device, as its baudrate has changed.
"""
self.execute(ShdlcCmdSetBaudrate(baudrate))
if update_driver:
self._connection.port.bitrate = baudrate
def get_reply_delay(self):
"""
Get the SHDLC reply delay of the device.
See
:py:meth:`~sensirion_shdlc_driver.device.ShdlcDevice.set_reply_delay()`
for details.
:return: The reply delay of the device [μs].
:rtype: byte
"""
return self.execute(ShdlcCmdGetReplyDelay())
def set_reply_delay(self, reply_delay):
"""
Set the SHDLC reply delay of the device.
The reply delay allows to increase the minimum response time of the
slave to a given value in Microseconds. This is needed for RS485
masters which require some time to switch from sending to receiving.
If the slave starts sending the response while the master is still
driving the bus lines, a conflict on the bus occurs and communication
fails. If you use such a slow RS485 master, you can increase the reply
delay of all slaves to avoid this issue.
:param byte reply_delay: The new reply delay [μs].
"""
self.execute(ShdlcCmdSetReplyDelay(reply_delay))
def get_system_up_time(self):
"""
Get the system up time of the device.
:return: The time since the last power-on or device reset [s].
:rtype: int
"""
return self.execute(ShdlcCmdGetSystemUpTime())
def device_reset(self):
"""
Execute a device reset (reboot firmware, similar to power cycle).
"""
self.execute(ShdlcCmdDeviceReset())
def factory_reset(self):
"""
Perform a factory reset (restore the off-the-shelf factory
configuration).
.. warning:: This resets any configuration done after leaving the
factory! Keep in mind that this command might also change
communication parameters (i.e. baudrate and slave address)
and thus you might have to adjust the driver's parameters
to allow further communication with the device.
"""
self.execute(ShdlcCmdFactoryReset())