This library works fine now but it only supports a few chips. So here is a tutorial for adding support for a chip or other electronic component.

Creating a module with a class

Every chips gets its own module. Create a new file under electronics/devices with the most generic name possible for the chip. For example for the NXP LM75A. If a chip supports multiple communication modes then create two classes in the same file (for example the MPU-6050, although it only supports I2C now it has the I2C suffix).

The class for the device should subclass the communication bus. It should also at least implement the same arguments for the __init__ call:

from electronics.device import I2CDevice

class LM75(I2CDevice):
    def __init__(self, bus, address=0x49):
        super().__init__(bus, address)

The superclass contains the logic for communicating with the bus. To communicate with the outside world you need to call the communication methods on the superclass. In the case of I2c you would use i2c_read_register and i2c_write_register and if some device is doing wierd stuff you can use i2c_read and i2c_write

If something invokes an action on the communication bus it should be in a method, not in a attribute getter/setter. It should be clear that calling it might be an expensive operation. If you need to set a lot of properties (like device configuration) then you can put the settings in class attribute and use a method to sync those values with the device.

Writing tests

Writing tests for the hardware is challenging since they don’t behave very predictable (They’re sensors. you use them because you don’t know something). To add tests for the calculations you add an example in the inline documentation that uses the MockGateway. The MockGateway will return the exact same values everytime make doctest is ran:

.. testsetup::

    # This is hidden in the documentation but creates the variables we need
    from electronics.gateways import MockGateway
    from electronics.devices import MPU6050I2C
    gw = MockGateway()


>>> sensor = MPU6050I2C(gw)
>>> sensor.set_range(accel=MPU6050I2C.RANGE_ACCEL_2G, gyro=MPU6050I2C.RANGE_GYRO_250DEG)
>>> # Read a value
>>> sensor.wakeup()
>>> sensor.temperature()
>>> sensor.sleep()
>>> # Read a value using a context manager instead of wakeup() and sleep()
>>> with sensor:
...     sensor.temperature()

Registering the class in the library

To add the device to the library properly:

  • Create a device
  • Write documentation in the class of the device. Examples are great, write examples.
  • Add the class to electronics/devices/
  • Create a file in docs/devices that renders the inline documentation for the class
  • Add that file to docs/devices.rst
  • Create a pull request