7.1 KiB

Design Guide

MicroPython has created a great foundation to build upon and to make it even better for beginners we've created CircuitPython. This guide covers a number of ways the core and libraries are geared towards beginners.

Start libraries with the cookiecutter

Cookiecutter is a cool tool that lets you bootstrap a new repo based on another repo. We've made one here for CircuitPython libraries that include configs for Travis CI and ReadTheDocs along with a setup.py, license, code of conduct and readme.

# The first time
pip install cookiecutter

cookiecutter gh:adafruit/cookiecutter-adafruit-circuitpython

Module Naming

Adafruit funded libraries should be under the adafruit organization and have the format Adafruit_CircuitPython_ and have a corresponding adafruit_<name> directory (aka package) or adafruit_<name>.py file (aka module).

Community created libraries should have the format CircuitPython_ and not have the adafruit_ module or package prefix.

Both should have the CircuitPython repository topic on GitHub.

Lifetime and ContextManagers

A driver should be initialized and ready to use after construction. If the device requires deinitialization, then provide it through deinit() and also provide __enter__ and __exit__ to create a context manager usable with with.

Verify your device

Make sure device you are talking to is the device you expect. If not, raise a ValueError. Beware that I2C addresses can be identical on different devices so read registers you know to make sure they match your expectation. Validating this upfront will help catch mistakes.

Getters/Setters

When designing a driver for a device, use properties for device state and use methods for actions that the device performs. Doing this well helps beginners understand when to use what. It is also consistent with Python.

Design for compatibility with CPython

CircuitPython is aimed to be one's first experience with code. It will be the first step into the world of hardware and software. To ease one's exploration out from this first step, make sure that functionality shared with CPython shares the same API. It doesn't need to be the full API it can be a subset. However, do not add non-CPython APIs to the same modules. Instead, use separate non-CPython modules to add extra functionality. By distinguishing API boundaries at modules you increase the likelihood that incorrect expectations are found on import and not randomly during runtime.

Document inline

Whenever possible, document your code right next to the code that implements it. This makes it more likely to stay up to date with the implementation itself. Use Sphinx's automodule to format these all nicely in ReadTheDocs. The cookiecutter helps set these up.

Use rST for markup.

Module description

After the license comment:

"""
`<module name>` - <Short description>
=================================================
<Longer description.>
"""

Class description

class DS3231:
    """Interface to the DS3231 RTC."""

Data descriptor description

Comment is after even though its weird.

lost_power = i2c_bit.RWBit(0x0f, 7)
"""True if the device has lost power since the time was set."""

Property description

@property
def datetime(self):
    """Gets the current date and time or sets the current date and time then starts the clock."""
    return self.datetime_register

@datetime.setter
def datetime(self, value):
    pass

Method description

def datetime(self):
    """Gets the current date and time or sets the current date and time then starts the clock."""
    return self.datetime_register

Use BusDevice

BusDevice is an awesome foundational library that manages talking on a shared I2C or SPI device for you. Use it to ensure the library can be used in threads later.

Lots of small modules

CircuitPython boards tend to have a small amount of internal flash and a small amount of ram but large amounts of external flash for the file system. So, create many small libraries that can be loaded as needed instead of one large file that does everything.

Speed second

Speed isn't as important as API clarity and code size. So, prefer simple APIs like properties for state even if it sacrifices a bit of speed.

Avoid allocations in drivers

Although Python doesn't require managing memory, its still a good practice for library writers to think about memory allocations. Avoid them in drivers if you can because you never know how much something will be called. Fewer allocations means less time spent cleaning up. So, where you can, prefer bytearray buffers that are created in __init__ and used throughout the object with methods that read or write into the buffer instead of creating new objects. nativeio classes are design to read and write to subsections of buffers.

However, this is a memory tradeoff so do not do it for large or rarely used buffers.

Examples

ustruct.pack

Use ustruct.pack_into instead of ustruct.pack.

Sensor properties and units

The Adafruit Unified Sensor Driver Arduino library has a great list of measurements and their units. Use the same ones including the property name itself so that drivers can be used interchangeably when they have the same properties.

Property name Python type Units
acceleration (float, float, float) x, y, z meter per second per second
magnetic float micro-Tesla (uT)
orientation (float, float, float) x, y, z degrees
gyro (float, float, float) x, y, z radians per second
temperature float degrees centigrade
distance float centimeters
light float SI lux
pressure float hectopascal (hPa)
relative_humidity float percent
current float milliamps (mA)
voltage float volts (V)
color int RGB, eight bits per channel (0xff0000 is red)
alarm (time.struct, str) Sample alarm time and string to characterize frequency ("secondly", "minutely", "hourly", "daily", "weekly", "monthly")
datetime time.struct date and time

Adding native modules

The Python API for a new module should be defined and documented in shared-bindings and define an underlying C API. If the implementation is port-agnostic or relies on underlying APIs of another module, the code should live in shared-module. If it is port specific then it should live in common-hal within the port's folder. In either case, the file and folder structure should mimic the structure in shared-bindings.

MicroPython compatibility

Keeping compatibility with MicroPython isn't a high priority. It should be done when its not in conflict with any of the above goals.