Error Handling

This page gives you an overview how and which errors are handled by the driver, and how the user has to implement error handling. Since it is heavily related to the Single/Multi-Channel Mode, you should read that documentation first.

The following errors can occur when executing I²C commands:

  • Slave didn’t acknowledge the data from the master (NACK): In this case, you’ll get an I2cTransceiveError, or its derived (i.e. more specific) error I2cNackError. It depends on the capabilities of the underlying I²C transceiver which error you’ll get.
  • Slave stretched the clock line longer than expected (clock stretching timeout): Similar to the NACK error, you’ll get an I2cTransceiveError, or its derived error I2cTimeoutError.
  • On multi channel I²C transceivers, it’s possible that some specific I²C channels are simply disabled, i.e. no I²C commands will be executed on these channels. Such channels will return the error I2cChannelDisabledError.
  • If the response of the executed I²C command contains CRCs (like SensirionI2cCommand), you’ll get an I2cChecksumError if at least one received CRC was wrong.
  • The interpret_response() method of executed commands might even raise any other exception which is then forwarded to you like any other error described above.
  • If the execution of I²C commands by the underlying transceiver completely failes, the transceiver might raise a transceiver specific exception which will not be handled by this driver at all. So it will propagate directly up into the user code.

Single Channel Error Handling

In Single Channel mode, you can handle errors just the usual way with try/except:

from circuitpython_sensirion_i2c_driver import LinuxI2cTransceiver, I2cConnection, \
    I2cDevice, I2cCommand
from circuitpython_sensirion_i2c_driver.errors import I2cNackError, I2cChecksumError


class MyI2cDevice(I2cDevice):
    def __init__(self, connection):
        super(MyI2cDevice, self).__init__(connection, slave_address=0x69)

    def do_something(self):
        return self.execute(I2cCommand(tx_data=b"\x80\x04", rx_length=6,
                                       read_delay=5e-3, timeout=0))


with LinuxI2cTransceiver('/dev/i2c-1') as transceiver:
    connection = I2cConnection(transceiver)
    device = MyI2cDevice(connection)

    # Handle any error the same way
    try:
        response = device.do_something()
        print("Response: {}".format(response))
    except Exception as e:
        print("Error: {}".format(str(e)))

    # Handle NACK and CRC errors separately
    try:
        response = device.do_something()
        print("Response: {}".format(response))
    except I2cNackError as e:
        print("Error: Slave did not acknowledge!")
    except I2cChecksumError as e:
        print("Error: Received wrong CRC!")
    except Exception as e:
        print("Error: {}".format(str(e)))

Multi Channel Error Handling

In Multi Channel mode, error handling is slighly more complicated since exceptions are not raised, but returned as items within the list of responses.

from circuitpython_sensirion_i2c_driver import LinuxI2cTransceiver, I2cConnection, \
    I2cDevice, I2cCommand
from circuitpython_sensirion_i2c_driver.errors import I2cNackError, I2cChecksumError


class MyI2cDevice(I2cDevice):
    def __init__(self, connection):
        super(MyI2cDevice, self).__init__(connection, slave_address=0x69)

    def do_something(self):
        return self.execute(I2cCommand(tx_data=b"\x80\x04", rx_length=6,
                                       read_delay=5e-3, timeout=0))


with LinuxI2cTransceiver('/dev/i2c-1') as transceiver:
    connection = I2cConnection(transceiver)
    connection.always_multi_channel_response = True  # Make it multi channel
    device = MyI2cDevice(connection)

    # do_something() will always return a list, even if I²C errors occurred
    responses = device.do_something()

    # Just check if none of the responses is an error
    if any(isinstance(response, Exception) for response in responses):
        print("The command failed on at least one channel!")

    # Print all responses
    for response in responses:
        if isinstance(response, Exception):
            print("Error: {}".format(str(response)))
        else:
            print("Response: {}".format(str(response)))

    # Convert all response values (example: multiply by 2), except errors
    responses = map(lambda x: x if isinstance(x, Exception) else x * 2, responses)