841 lines
35 KiB
ReStructuredText
841 lines
35 KiB
ReStructuredText
.. role:: strike
|
|
|
|
Design Guide
|
|
============
|
|
|
|
This guide covers a variety of development practices for CircuitPython core and library APIs. These
|
|
APIs are both `built-into CircuitPython
|
|
<https://github.com/adafruit/circuitpython/tree/main/shared-bindings>`_ and those that are
|
|
`distributed on GitHub <https://github.com/search?utf8=%E2%9C%93&q=topic%3Acircuitpython&type=>`_
|
|
and in the `Adafruit <https://github.com/adafruit/Adafruit_CircuitPython_Bundle>`_ and `Community
|
|
<https://github.com/adafruit/CircuitPython_Community_Bundle/>`_ bundles. Consistency with these
|
|
practices ensures that beginners can learn a pattern once and apply it throughout the CircuitPython
|
|
ecosystem.
|
|
|
|
Start libraries with the cookiecutter
|
|
-------------------------------------
|
|
|
|
Cookiecutter is a tool that lets you bootstrap a new repo based on another repo.
|
|
We've made one `here <https://github.com/adafruit/cookiecutter-adafruit-circuitpython>`_
|
|
for CircuitPython libraries that include configs for Travis CI and ReadTheDocs
|
|
along with a setup.py, license, code of conduct, readme among other files.
|
|
|
|
.. code-block::sh
|
|
|
|
# The first time
|
|
pip install cookiecutter
|
|
|
|
cookiecutter gh:adafruit/cookiecutter-adafruit-circuitpython
|
|
|
|
Cookiecutter will provide a series of prompts relating to the library and then create a new
|
|
directory with all of the files. See `the CircuitPython cookiecutter README
|
|
<https://github.com/adafruit/cookiecutter-adafruit-circuitpython#introduction>`_ for more details.
|
|
|
|
Module Naming
|
|
-------------
|
|
|
|
Adafruit funded libraries should be under the
|
|
`adafruit organization <https://github.com/adafruit>`_ and have the format
|
|
``Adafruit_CircuitPython_<name>`` and have a corresponding ``adafruit_<name>``
|
|
directory (aka package) or ``adafruit_<name>.py`` file (aka module).
|
|
|
|
If the name would normally have a space, such as "Thermal Printer", use an underscore instead
|
|
("Thermal_Printer"). This underscore will be used everywhere even when the separation between
|
|
"adafruit" and "circuitpython" is done with a ``-``. Use the underscore in the cookiecutter prompts.
|
|
|
|
Community created libraries should have the repo format ``CircuitPython_<name>`` and
|
|
not have the ``adafruit_`` module or package prefix.
|
|
|
|
Both should have the CircuitPython repository topic on GitHub.
|
|
|
|
Terminology
|
|
-----------
|
|
|
|
As our Code of Conduct states, we strive to use "welcoming and inclusive
|
|
language." Whether it is in documentation or in code, the words we use matter.
|
|
This means we disfavor language that due to historical and social context can
|
|
make community members and potential community members feel unwelcome.
|
|
|
|
There are specific terms to avoid except where technical limitations require it.
|
|
While specific cases may call for other terms, consider using these suggested
|
|
terms first:
|
|
|
|
+--------------------+---------------------+
|
|
| Preferred | Deprecated |
|
|
+====================+=====================+
|
|
| Main (device) | :strike:`Master` |
|
|
+--------------------+---------------------+
|
|
| Peripheral | :strike:`Slave` |
|
|
+--------------------+ +
|
|
| Sensor | |
|
|
+--------------------+ +
|
|
| Secondary (device) | |
|
|
+--------------------+---------------------+
|
|
| Denylist | :strike:`Blacklist` |
|
|
+--------------------+---------------------+
|
|
| Allowlist | :strike:`Whitelist` |
|
|
+--------------------+---------------------+
|
|
|
|
Note that "technical limitations" refers e.g., to the situation where an
|
|
upstream library or URL has to contain those substrings in order to work.
|
|
However, when it comes to documentation and the names of parameters and
|
|
properties in CircuitPython, we will use alternate terms even if this breaks
|
|
tradition with past practice.
|
|
|
|
|
|
.. _lifetime-and-contextmanagers:
|
|
|
|
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``.
|
|
|
|
For example, a user can then use ``deinit()```::
|
|
|
|
import digitalio
|
|
import board
|
|
import time
|
|
|
|
led = digitalio.DigitalInOut(board.LED)
|
|
led.direction = digitalio.Direction.OUTPUT
|
|
|
|
for i in range(10):
|
|
led.value = True
|
|
time.sleep(0.5)
|
|
|
|
led.value = False
|
|
time.sleep(0.5)
|
|
led.deinit()
|
|
|
|
This will deinit the underlying hardware at the end of the program as long as no
|
|
exceptions occur.
|
|
|
|
Alternatively, using a ``with`` statement ensures that the hardware is deinitialized::
|
|
|
|
import digitalio
|
|
import board
|
|
import time
|
|
|
|
with digitalio.DigitalInOut(board.LED) as led:
|
|
led.direction = digitalio.Direction.OUTPUT
|
|
|
|
for i in range(10):
|
|
led.value = True
|
|
time.sleep(0.5)
|
|
|
|
led.value = False
|
|
time.sleep(0.5)
|
|
|
|
Python's ``with`` statement ensures that the deinit code is run regardless of
|
|
whether the code within the with statement executes without exceptions.
|
|
|
|
For small programs like the examples this isn't a major concern because all
|
|
user usable hardware is reset after programs are run or the REPL is run. However,
|
|
for more complex programs that may use hardware intermittently and may also
|
|
handle exceptions on their own, deinitializing the hardware using a with
|
|
statement will ensure hardware isn't enabled longer than needed.
|
|
|
|
Verify your device
|
|
--------------------------------------------------------------------------------
|
|
|
|
Whenever possible, make sure the device you are talking to is the device you expect.
|
|
If not, raise a RuntimeError. 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 sequences of abstract actions that the device performs. State is a
|
|
property of the device as a whole that exists regardless of what the code is
|
|
doing. This includes things like temperature, time, sound, light and the state
|
|
of a switch. For a more complete list see the sensor properties bullet below.
|
|
|
|
Another way to separate state from actions is that state is usually something
|
|
the user can sense themselves by sight or feel for example. Actions are
|
|
something the user can watch. The device does this and then this.
|
|
|
|
Making this separation clear to the user will help beginners understand when to
|
|
use what.
|
|
|
|
Here is more info on properties from
|
|
`Python <https://docs.python.org/3/library/functions.html#property>`_.
|
|
|
|
Exceptions and asserts
|
|
--------------------------------------------------------------------------------
|
|
|
|
Raise an appropriate `Exception <https://docs.python.org/3/library/exceptions.html#bltin-exceptions>`_,
|
|
along with a useful message, whenever a critical test or other condition fails.
|
|
|
|
Example::
|
|
|
|
if not 0 <= pin <= 7:
|
|
raise ValueError("Pin number must be 0-7.")
|
|
|
|
If memory is constrained and a more compact method is needed, use `assert`
|
|
instead.
|
|
|
|
Example::
|
|
|
|
assert 0 <= pin <= 7, "Pin number must be 0-7."
|
|
|
|
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.
|
|
|
|
When adding a new module for additional functionality related to a CPython
|
|
module do NOT simply prefix it with u. This is not a large enough differentiation
|
|
from CPython. This is the MicroPython convention and they use u* modules
|
|
interchangeably with the CPython name. This is confusing. Instead, think up a
|
|
new name that is related to the extra functionality you are adding.
|
|
|
|
For example, storage mounting and unmounting related functions were moved from
|
|
``uos`` into a new `storage` module. These names better match their
|
|
functionality and do not conflict with CPython names. Make sure to check that
|
|
you don't conflict with CPython libraries too. That way we can port the API to
|
|
CPython in the future.
|
|
|
|
Example
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
When adding extra functionality to CircuitPython to mimic what a normal
|
|
operating system would do, either copy an existing CPython API (for example file
|
|
writing) or create a separate module to achieve what you want. For example,
|
|
mounting and unmounting drives is not a part of CPython so it should be done in a
|
|
module, such as a new ``storage`` module, that is only available in CircuitPython.
|
|
That way when someone moves the code to CPython they know what parts need to be
|
|
adapted.
|
|
|
|
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 `Sphinx flavor rST <http://www.sphinx-doc.org/en/stable/rest.html>`_ for markup.
|
|
|
|
Lots of documentation is a good thing but it can take a lot of space. To
|
|
minimize the space used on disk and on load, distribute the library as both .py
|
|
and .mpy, MicroPython and CircuitPython's bytecode format that omits comments.
|
|
|
|
Module description
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
After the license comment::
|
|
|
|
"""
|
|
`<module name>`
|
|
=================================================
|
|
|
|
<Longer description>
|
|
|
|
* Author(s):
|
|
|
|
Implementation Notes
|
|
--------------------
|
|
|
|
|
|
**Hardware:**
|
|
|
|
* `Adafruit Device Description
|
|
<hyperlink>`_ (Product ID: <Product Number>)
|
|
|
|
**Software and Dependencies:**
|
|
|
|
* Adafruit CircuitPython firmware for the supported boards:
|
|
https://circuitpython.org/downloads
|
|
|
|
* Adafruit's Bus Device library:
|
|
https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
|
|
|
* Adafruit's Register library:
|
|
https://github.com/adafruit/Adafruit_CircuitPython_Register
|
|
|
|
"""
|
|
|
|
Version description
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
After the import statements::
|
|
|
|
__version__ = "0.0.0+auto.0"
|
|
__repo__ = "<repo github link>"
|
|
|
|
|
|
Class description
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
At the class level document what class does and how to initialize it::
|
|
|
|
class DS3231:
|
|
"""DS3231 real-time clock.
|
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus the DS3231 is connected to.
|
|
:param int address: The I2C address of the device. Defaults to :const:`0x40`
|
|
"""
|
|
|
|
def __init__(self, i2c_bus, address=0x40):
|
|
self._i2c = i2c_bus
|
|
|
|
|
|
Renders as:
|
|
|
|
.. py:class:: DS3231(i2c_bus, address=64)
|
|
:noindex:
|
|
|
|
DS3231 real-time clock.
|
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus the DS3231 is connected to.
|
|
:param int address: The I2C address of the device. Defaults to :const:`0x40`
|
|
|
|
|
|
Documenting Parameters
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
Although there are different ways to document class and functions definitions in Python,
|
|
the following is the prevalent method of documenting parameters
|
|
for CircuitPython libraries. When documenting class parameters you should use the
|
|
following structure:
|
|
|
|
.. code-block:: sh
|
|
|
|
:param param_type param_name: Parameter_description
|
|
|
|
|
|
param_type
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
The type of the parameter. This could be, among others, ``int``, ``float``, ``str``, ``bool``, etc.
|
|
To document an object in the CircuitPython domain, you need to include a ``~`` before the
|
|
definition as shown in the following example:
|
|
|
|
.. code-block:: sh
|
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus the DS3231 is connected to.
|
|
|
|
|
|
To include references to CircuitPython modules, cookiecutter creates an entry in the
|
|
intersphinx_mapping section in the ``conf.py`` file located within the ``docs`` directory.
|
|
To add different types outside CircuitPython you need to include them in the intersphinx_mapping::
|
|
|
|
|
|
intersphinx_mapping = {
|
|
"python": ("https://docs.python.org/3.4", None),
|
|
"BusDevice":("https://circuitpython.readthedocs.io/projects/busdevice/en/latest/", None,),
|
|
"CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None),
|
|
}
|
|
|
|
The intersphinx_mapping above includes references to Python, BusDevice and CircuitPython
|
|
Documentation
|
|
|
|
When the parameter have two different types, you should reference them as follows::
|
|
|
|
|
|
class Character_LCD:
|
|
"""Base class for character LCD
|
|
|
|
:param ~digitalio.DigitalInOut rs: The reset data line
|
|
:param ~pwmio.PWMOut,~digitalio.DigitalInOut blue: Blue RGB Anode
|
|
|
|
"""
|
|
|
|
def __init__(self, rs, blue):
|
|
self._rc = rs
|
|
self.blue = blue
|
|
|
|
|
|
Renders as:
|
|
|
|
.. py:class:: Character_LCD(rs, blue)
|
|
:noindex:
|
|
|
|
Base class for character LCD
|
|
|
|
:param ~digitalio.DigitalInOut rs: The reset data line
|
|
:param ~pwmio.PWMOut,~digitalio.DigitalInOut blue: Blue RGB Anode
|
|
|
|
|
|
param_name
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Parameter name used in the class or method definition
|
|
|
|
|
|
Parameter_description
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Parameter description. When the parameter defaults to a particular value, it is good
|
|
practice to include the default::
|
|
|
|
:param int pitch: Pitch value for the servo. Defaults to :const:`4500`
|
|
|
|
|
|
Attributes
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Attributes are state on objects. (See `Getters/Setters`_ above for more discussion
|
|
about when to use them.) They can be defined internally in a number of different
|
|
ways. Each approach is enumerated below with an explanation of where the comment
|
|
goes.
|
|
|
|
Regardless of how the attribute is implemented, it should have a short
|
|
description of what state it represents including the type, possible values and/or
|
|
units. It should be marked as ``(read-only)`` or ``(write-only)`` at the end of
|
|
the first line for attributes that are not both readable and writable.
|
|
|
|
Instance attributes
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Comment comes from after the assignment::
|
|
|
|
def __init__(self, drive_mode):
|
|
self.drive_mode = drive_mode
|
|
"""
|
|
The pin drive mode. One of:
|
|
|
|
- `digitalio.DriveMode.PUSH_PULL`
|
|
- `digitalio.DriveMode.OPEN_DRAIN`
|
|
"""
|
|
|
|
Renders as:
|
|
|
|
.. py:attribute:: drive_mode
|
|
:noindex:
|
|
|
|
The pin drive mode. One of:
|
|
|
|
- `digitalio.DriveMode.PUSH_PULL`
|
|
- `digitalio.DriveMode.OPEN_DRAIN`
|
|
|
|
Property description
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Comment comes from the getter::
|
|
|
|
@property
|
|
def datetime(self):
|
|
"""The current date and time as a `time.struct_time`."""
|
|
return self.datetime_register
|
|
|
|
@datetime.setter
|
|
def datetime(self, value):
|
|
pass
|
|
|
|
Renders as:
|
|
|
|
.. py:attribute:: datetime
|
|
:noindex:
|
|
|
|
The current date and time as a `time.struct_time`.
|
|
|
|
Read-only example::
|
|
|
|
@property
|
|
def temperature(self):
|
|
"""
|
|
The current temperature in degrees Celsius. (read-only)
|
|
|
|
The device may require calibration to get accurate readings.
|
|
"""
|
|
return self._read(TEMPERATURE)
|
|
|
|
|
|
Renders as:
|
|
|
|
.. py:attribute:: temperature
|
|
:noindex:
|
|
|
|
The current temperature in degrees Celsius. (read-only)
|
|
|
|
The device may require calibration to get accurate readings.
|
|
|
|
Data descriptor description
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Comment is after the definition::
|
|
|
|
lost_power = i2c_bit.RWBit(0x0f, 7)
|
|
"""True if the device has lost power since the time was set."""
|
|
|
|
Renders as:
|
|
|
|
.. py:attribute:: lost_power
|
|
:noindex:
|
|
|
|
True if the device has lost power since the time was set.
|
|
|
|
Method description
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
First line after the method definition::
|
|
|
|
def turn_right(self, degrees):
|
|
"""Turns the bot ``degrees`` right.
|
|
|
|
:param float degrees: Degrees to turn right
|
|
"""
|
|
|
|
Renders as:
|
|
|
|
.. py:method:: turn_right(degrees)
|
|
:noindex:
|
|
|
|
Turns the bot ``degrees`` right.
|
|
|
|
:param float degrees: Degrees to turn right
|
|
|
|
Documentation References to other Libraries
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
When you need to make references to documentation in other libraries you should refer the class using single
|
|
backticks ``:class:`~adafruit_motor.servo.Servo```. You must also add the reference in the ``conf.py`` file in the
|
|
``intersphinx_mapping section`` by adding a new entry::
|
|
|
|
"adafruit_motor": ("https://circuitpython.readthedocs.io/projects/motor/en/latest/", None,),
|
|
|
|
Use ``adafruit_register`` when possible
|
|
--------------------------------------------------------------------------------
|
|
`Register <https://github.com/adafruit/Adafruit_CircuitPython_Register>`_ is
|
|
a foundational library that manages packing and unpacking data from I2C device
|
|
registers. There is also `Register SPI <https://github.com/adafruit/Adafruit_CircuitPython_Register_SPI>`_
|
|
for SPI devices. When possible, use one of these libraries for unpacking and
|
|
packing registers. This ensures the packing code is shared amongst all
|
|
registers (even across drivers). Furthermore, it simplifies device definitions
|
|
by making them declarative (only data.)
|
|
|
|
Values with non-consecutive bits in a register or that represent FIFO endpoints
|
|
may not map well to existing register classes. In unique cases like these, it is
|
|
ok to read and write the register directly.
|
|
|
|
*Do not* add all registers from a datasheet upfront. Instead, only add the ones
|
|
necessary for the functionality the driver exposes. Adding them all will lead to
|
|
unnecessary file size and API clutter. See `this video about outside-in design
|
|
from @tannewt <https://www.youtube.com/watch?v=3QewiyfBQh8>`_.
|
|
|
|
I2C Example
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
.. code-block:: python
|
|
|
|
from adafruit_register import i2c_bit
|
|
from adafruit_bus_device import i2c_device
|
|
|
|
class HelloWorldDevice:
|
|
"""Device with two bits to control when the words 'hello' and 'world' are lit."""
|
|
|
|
hello = i2c_bit.RWBit(0x0, 0x0)
|
|
"""Bit to indicate if hello is lit."""
|
|
|
|
world = i2c_bit.RWBit(0x1, 0x0)
|
|
"""Bit to indicate if world is lit."""
|
|
|
|
def __init__(self, i2c, device_address=0x0):
|
|
self.i2c_device = i2c_device.I2CDevice(i2c, device_address)
|
|
|
|
Use BusDevice
|
|
--------------------------------------------------------------------------------
|
|
|
|
`BusDevice <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_ is an
|
|
awesome foundational library that manages talking on a shared I2C or SPI device
|
|
for you. The devices manage locking which ensures that a transfer is done as a
|
|
single unit despite CircuitPython internals and, in the future, other Python
|
|
threads. For I2C, the device also manages the device address. The SPI device,
|
|
manages baudrate settings, chip select line and extra post-transaction clock
|
|
cycles.
|
|
|
|
I2C Example
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
.. code-block:: python
|
|
|
|
from adafruit_bus_device import i2c_device
|
|
|
|
DEVICE_DEFAULT_I2C_ADDR = 0x42
|
|
|
|
class Widget:
|
|
"""A generic widget."""
|
|
|
|
def __init__(self, i2c, address=DEVICE_DEFAULT_I2C_ADDR):
|
|
self.i2c_device = i2c_device.I2CDevice(i2c, address)
|
|
self.buf = bytearray(1)
|
|
|
|
@property
|
|
def register(self):
|
|
"""Widget's one register."""
|
|
with self.i2c_device as i2c:
|
|
i2c.writeto(b'0x00')
|
|
i2c.readfrom_into(self.buf)
|
|
return self.buf[0]
|
|
|
|
|
|
SPI Example
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
.. code-block:: python
|
|
|
|
from adafruit_bus_device import spi_device
|
|
|
|
class SPIWidget:
|
|
"""A generic widget with a weird baudrate."""
|
|
|
|
def __init__(self, spi, chip_select):
|
|
# chip_select is a pin reference such as board.D10.
|
|
self.spi_device = spi_device.SPIDevice(spi, chip_select, baudrate=12345)
|
|
self.buf = bytearray(1)
|
|
|
|
@property
|
|
def register(self):
|
|
"""Widget's one register."""
|
|
with self.spi_device as spi:
|
|
spi.write(b'0x00')
|
|
spi.readinto(self.buf)
|
|
return self.buf[0]
|
|
|
|
|
|
|
|
Class documentation example template
|
|
--------------------------------------------------------------------------------
|
|
When documenting classes, you should use the following template to illustrate basic usage.
|
|
It is similar with the simpletest example, however this will display the information in the Read The Docs
|
|
documentation.
|
|
The advantage of using this template is it makes the documentation consistent across the libraries.
|
|
|
|
This is an example for a AHT20 temperature sensor. Include the following after the class parameter:
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
"""
|
|
|
|
**Quickstart: Importing and using the AHT10/AHT20 temperature sensor**
|
|
|
|
Here is an example of using the :class:`AHTx0` class.
|
|
First you will need to import the libraries to use the sensor
|
|
|
|
.. code-block:: python
|
|
|
|
import board
|
|
import adafruit_ahtx0
|
|
|
|
Once this is done you can define your `board.I2C` object and define your sensor object
|
|
|
|
.. code-block:: python
|
|
|
|
i2c = board.I2C() # uses board.SCL and board.SDA
|
|
aht = adafruit_ahtx0.AHTx0(i2c)
|
|
|
|
Now you have access to the temperature and humidity using
|
|
the :attr:`temperature` and :attr:`relative_humidity` attributes
|
|
|
|
.. code-block:: python
|
|
|
|
temperature = aht.temperature
|
|
relative_humidity = aht.relative_humidity
|
|
|
|
"""
|
|
|
|
|
|
Use composition
|
|
--------------------------------------------------------------------------------
|
|
|
|
When writing a driver, take in objects that provide the functionality you need
|
|
rather than taking their arguments and constructing them yourself or subclassing
|
|
a parent class with functionality. This technique is known as composition and
|
|
leads to code that is more flexible and testable than traditional inheritance.
|
|
|
|
.. seealso:: `Wikipedia <https://en.wikipedia.org/wiki/Dependency_inversion_principle>`_
|
|
has more information on "dependency inversion".
|
|
|
|
For example, if you are writing a driver for an I2C device, then take in an I2C
|
|
object instead of the pins themselves. This allows the calling code to provide
|
|
any object with the appropriate methods such as an I2C expansion board.
|
|
|
|
Another example is to expect a :py:class:`~digitalio.DigitalInOut` for a pin to
|
|
toggle instead of a :py:class:`~microcontroller.Pin` from :py:mod:`board`.
|
|
Taking in the :py:class:`~microcontroller.Pin` object alone would limit the
|
|
driver to pins on the actual microcontroller instead of pins provided by another
|
|
driver such as an IO expander.
|
|
|
|
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, it's 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. Unified hardware API classes such as `busio.SPI` are design to read and
|
|
write to subsections of buffers.
|
|
|
|
It's ok to allocate an object to return to the user. Just beware of causing more
|
|
than one allocation per call due to internal logic.
|
|
|
|
**However**, this is a memory tradeoff so do not do it for large or rarely used
|
|
buffers.
|
|
|
|
Examples
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
struct.pack
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Use `struct.pack_into` instead of `struct.pack`.
|
|
|
|
Use of MicroPython ``const()``
|
|
--------------------------------------------------------------------------------
|
|
The MicroPython ``const()`` feature, as discussed in `this forum post
|
|
<https://forum.micropython.org/viewtopic.php?t=450>`_, and in `this issue thread
|
|
<https://github.com/micropython/micropython/issues/573>`_, provides some
|
|
optimizations that can be useful on smaller, memory constrained devices. However,
|
|
when using ``const()``, keep in mind these general guide lines:
|
|
|
|
- Always use via an import, ex: ``from micropython import const``
|
|
- Limit use to global (module level) variables only.
|
|
- Only used when the user will not need access to variable and prefix name with
|
|
a leading underscore, ex: ``_SOME_CONST``.
|
|
|
|
Example
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
.. code-block:: python
|
|
|
|
from adafruit_bus_device import i2c_device
|
|
from micropython import const
|
|
|
|
_DEFAULT_I2C_ADDR = const(0x42)
|
|
|
|
class Widget:
|
|
"""A generic widget."""
|
|
|
|
def __init__(self, i2c, address=_DEFAULT_I2C_ADDR):
|
|
self.i2c_device = i2c_device.I2CDevice(i2c, address)
|
|
|
|
Libraries Examples
|
|
------------------
|
|
When adding examples, cookiecutter will add a ``<name>_simpletest.py`` file in the examples directory for you.
|
|
Be sure to include code with the library minimal functionalities to work on a device.
|
|
You could other examples if needed featuring different
|
|
functionalities of the library.
|
|
If you add additional examples, be sure to include them in the ``examples.rst``. Naming of the examples
|
|
files should use the name of the library followed by a description, using underscore to separate them.
|
|
|
|
|
|
Sensor properties and units
|
|
--------------------------------------------------------------------------------
|
|
|
|
The `Adafruit Unified Sensor Driver Arduino library <https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver/introduction>`_ has a
|
|
`great list <https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver?view=all#standardised-si-units-for-sensor-data>`_
|
|
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, float, float) | x, y, z micro-Tesla (uT) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``orientation`` | (float, float, float) | x, y, z degrees |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``gyro`` | (float, float, float) | x, y, z radians per second |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``temperature`` | float | degrees Celsius |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``CO2`` | float | measured CO2 in ppm |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``eCO2`` | float | equivalent/estimated CO2 in ppm (estimated from some other measurement) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``TVOC`` | float | Total Volatile Organic Compounds in ppb |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``distance`` | float | centimeters (cm) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``proximity`` | int | non-unit-specific proximity values (monotonic but not actual distance) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``light`` | float | non-unit-specific light levels (should be monotonic but is not lux) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``lux`` | 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 such as "hourly" |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``datetime`` | time.struct | date and time |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``duty_cycle`` | int | 16-bit PWM duty cycle (regardless of output resolution) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``frequency`` | int | Hertz (Hz) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``value`` | bool | Digital logic |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``value`` | int | 16-bit Analog value, unit-less |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``weight`` | float | grams (g) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
| ``sound_level`` | float | non-unit-specific sound level (monotonic but not actual decibels) |
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
Driver constant naming
|
|
--------------------------------------------------------------------------------
|
|
|
|
When adding variables for constant values for a driver. Do not include the
|
|
device's name in the variable name. For example, in ``adafruit_fancy123.py``,
|
|
variables should not start with ``FANCY123_``. Adding this prefix increases RAM
|
|
usage and .mpy file size because variable names are preserved. User code should
|
|
refer to these constants as ``adafruit_fancy123.HELLO_WORLD`` for clarity.
|
|
``adafruit_fancy123.FANCY123_HELLO_WORLD`` would be overly verbose.
|
|
|
|
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``.
|
|
|
|
To test your native modules or core enhancements, follow these Adafruit Learning Guides
|
|
for building local firmware to flash onto your device(s):
|
|
|
|
`Build CircuitPython <https://learn.adafruit.com/building-circuitpython>`_
|
|
|
|
|
|
MicroPython compatibility
|
|
--------------------------------------------------------------------------------
|
|
|
|
Keeping compatibility with MicroPython isn't a high priority. It should be done
|
|
when it's not in conflict with any of the above goals.
|