2020-06-15 17:48:16 -04:00
|
|
|
.. role:: strike
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
Design Guide
|
|
|
|
============
|
|
|
|
|
2018-02-08 15:48:59 -05:00
|
|
|
This guide covers a variety of development practices for CircuitPython core and library APIs. These
|
|
|
|
APIs are both `built-into CircuitPython
|
2020-06-14 15:25:58 -04:00
|
|
|
<https://github.com/adafruit/circuitpython/tree/main/shared-bindings>`_ and those that are
|
2018-02-08 15:48:59 -05:00
|
|
|
`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.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
Start libraries with the cookiecutter
|
|
|
|
-------------------------------------
|
|
|
|
|
2018-02-08 15:48:59 -05:00
|
|
|
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>`_
|
2017-02-03 17:48:23 -05:00
|
|
|
for CircuitPython libraries that include configs for Travis CI and ReadTheDocs
|
2021-04-20 18:55:50 -04:00
|
|
|
along with a setup.py, license, code of conduct, readme among other files.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
.. code-block::sh
|
|
|
|
|
|
|
|
# The first time
|
|
|
|
pip install cookiecutter
|
|
|
|
|
|
|
|
cookiecutter gh:adafruit/cookiecutter-adafruit-circuitpython
|
|
|
|
|
2018-02-08 15:48:59 -05:00
|
|
|
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.
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
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).
|
|
|
|
|
2018-02-08 15:48:59 -05:00
|
|
|
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
|
2017-02-03 17:48:23 -05:00
|
|
|
not have the ``adafruit_`` module or package prefix.
|
|
|
|
|
|
|
|
Both should have the CircuitPython repository topic on GitHub.
|
|
|
|
|
2020-06-15 17:48:16 -04:00
|
|
|
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.
|
|
|
|
|
|
|
|
|
2017-06-07 17:39:12 -04:00
|
|
|
.. _lifetime-and-contextmanagers:
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
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``.
|
|
|
|
|
2017-06-07 17:39:12 -04:00
|
|
|
For example, a user can then use ``deinit()```::
|
|
|
|
|
|
|
|
import digitalio
|
|
|
|
import board
|
2019-06-25 08:36:44 -04:00
|
|
|
import time
|
2017-06-07 17:39:12 -04:00
|
|
|
|
|
|
|
led = digitalio.DigitalInOut(board.D13)
|
2017-06-19 12:06:46 -04:00
|
|
|
led.direction = digitalio.Direction.OUTPUT
|
2017-06-07 17:39:12 -04:00
|
|
|
|
|
|
|
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
|
2019-06-25 08:36:44 -04:00
|
|
|
import time
|
2017-06-07 17:39:12 -04:00
|
|
|
|
|
|
|
with digitalio.DigitalInOut(board.D13) as led:
|
2017-06-19 12:06:46 -04:00
|
|
|
led.direction = digitalio.Direction.OUTPUT
|
2017-06-07 17:39:12 -04:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
Verify your device
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
Whenever possible, make sure device you are talking to is the device you expect.
|
2018-02-08 15:48:59 -05:00
|
|
|
If not, raise a RuntimeError. Beware that I2C addresses can be identical on
|
2017-02-03 17:48:23 -05:00
|
|
|
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>`_.
|
|
|
|
|
2021-02-11 17:28:58 -05:00
|
|
|
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."
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
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.
|
|
|
|
|
2018-02-08 15:48:59 -05:00
|
|
|
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. Terminal related functions were moved into
|
|
|
|
`multiterminal`. 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.
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
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 unmount drives is not a part of CPython so it should be done in a
|
2017-07-19 15:11:19 -04:00
|
|
|
module, such as a new ``storage`` module, that is only available in CircuitPython.
|
2017-02-03 17:48:23 -05:00
|
|
|
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>` - <Short description>
|
|
|
|
=================================================
|
|
|
|
<Longer description.>
|
|
|
|
"""
|
|
|
|
|
|
|
|
Class description
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
At the class level document what class does and how to initialize it::
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
class DS3231:
|
2018-02-07 16:08:42 -05:00
|
|
|
"""DS3231 real-time clock.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus the DS3231 is connected to.
|
2021-04-20 18:55:50 -04:00
|
|
|
:param int address: The I2C address of the device. Defaults to :const:`0x40`
|
2018-02-07 16:08:42 -05:00
|
|
|
"""
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
def __init__(self, i2c_bus, address=0x40):
|
|
|
|
self._i2c = i2c_bus
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
Renders as:
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
.. py:class:: DS3231(i2c_bus, address=64)
|
|
|
|
:noindex:
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
DS3231 real-time clock.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus the DS3231 is connected to.
|
2021-04-20 18:55:50 -04:00
|
|
|
:param int address: The I2C address of the device. Defaults to :const:`0x40`
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
Attributes
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-27 18:24:16 -05:00
|
|
|
Attributes are state on objects. (See `Getters/Setters`_ above for more discussion
|
2018-02-07 16:08:42 -05:00
|
|
|
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.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
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
|
2018-02-08 15:48:59 -05:00
|
|
|
the first line for attributes that are not both readable and writable.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
Instance attributes
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
Comment comes from after the assignment::
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
def __init__(self, drive_mode):
|
|
|
|
self.drive_mode = drive_mode
|
|
|
|
"""
|
|
|
|
The pin drive mode. One of:
|
|
|
|
|
|
|
|
- `digitalio.DriveMode.PUSH_PULL`
|
|
|
|
- `digitalio.DriveMode.OPEN_DRAIN`
|
2017-02-03 17:48:23 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
Renders as:
|
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
.. py:attribute:: drive_mode
|
|
|
|
:noindex:
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
The pin drive mode. One of:
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
- `digitalio.DriveMode.PUSH_PULL`
|
|
|
|
- `digitalio.DriveMode.OPEN_DRAIN`
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
Property description
|
2018-02-07 16:08:42 -05:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
Comment comes from the getter::
|
|
|
|
|
|
|
|
@property
|
|
|
|
def datetime(self):
|
2018-02-07 16:08:42 -05:00
|
|
|
"""The current date and time as a `time.struct_time`."""
|
2017-02-03 17:48:23 -05:00
|
|
|
return self.datetime_register
|
|
|
|
|
|
|
|
@datetime.setter
|
|
|
|
def datetime(self, value):
|
|
|
|
pass
|
|
|
|
|
|
|
|
Renders as:
|
|
|
|
|
|
|
|
.. py:attribute:: datetime
|
2018-02-07 16:08:42 -05:00
|
|
|
:noindex:
|
|
|
|
|
|
|
|
The current date and time as a `time.struct_time`.
|
|
|
|
|
|
|
|
Read-only example::
|
|
|
|
|
|
|
|
@property
|
|
|
|
def temperature(self):
|
|
|
|
"""
|
2018-02-08 15:48:59 -05:00
|
|
|
The current temperature in degrees Celsius. (read-only)
|
2018-02-07 16:08:42 -05:00
|
|
|
|
|
|
|
The device may require calibration to get accurate readings.
|
|
|
|
"""
|
|
|
|
return self._read(TEMPERATURE)
|
|
|
|
|
|
|
|
|
|
|
|
Renders as:
|
|
|
|
|
|
|
|
.. py:attribute:: temperature
|
|
|
|
:noindex:
|
|
|
|
|
2018-02-08 15:48:59 -05:00
|
|
|
The current temperature in degrees Celsius. (read-only)
|
2018-02-07 16:08:42 -05:00
|
|
|
|
|
|
|
The device may require calibration to get accurate readings.
|
|
|
|
|
|
|
|
Data descriptor description
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
2018-02-09 01:19:00 -05:00
|
|
|
Comment is after the definition::
|
2018-02-07 16:08:42 -05:00
|
|
|
|
|
|
|
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)
|
2017-02-03 17:48:23 -05:00
|
|
|
:noindex:
|
|
|
|
|
2018-02-07 16:08:42 -05:00
|
|
|
Turns the bot ``degrees`` right.
|
|
|
|
|
|
|
|
:param float degrees: Degrees to turn right
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
Use BusDevice
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2017-12-02 17:21:45 -05:00
|
|
|
`BusDevice <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_ is an
|
2017-07-19 15:11:19 -04:00
|
|
|
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.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
I2C Example
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
from adafruit_bus_device import i2c_device
|
|
|
|
|
2017-10-25 20:32:00 -04:00
|
|
|
DEVICE_DEFAULT_I2C_ADDR = 0x42
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
class Widget:
|
|
|
|
"""A generic widget."""
|
|
|
|
|
2017-10-25 20:32:00 -04:00
|
|
|
def __init__(self, i2c, address=DEVICE_DEFAULT_I2C_ADDR):
|
|
|
|
self.i2c_device = i2c_device.I2CDevice(i2c, address)
|
2017-02-03 17:48:23 -05:00
|
|
|
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')
|
2020-12-09 10:52:18 -05:00
|
|
|
spi.readinto(self.buf)
|
2017-02-03 17:48:23 -05:00
|
|
|
return self.buf[0]
|
|
|
|
|
2017-06-07 17:57:55 -04:00
|
|
|
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.
|
|
|
|
|
2018-02-20 20:34:59 -05:00
|
|
|
Another example is to expect a :py:class:`~digitalio.DigitalInOut` for a pin to
|
|
|
|
toggle instead of a :py:class:`~microcontroller.Pin` from `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.
|
2017-06-07 17:57:55 -04:00
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
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
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2020-12-09 10:52:18 -05:00
|
|
|
Although Python doesn't require managing memory, it's still a good practice for
|
2017-02-03 17:48:23 -05:00
|
|
|
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
|
2017-04-10 16:32:19 -04:00
|
|
|
objects. Unified hardware API classes such as `busio.SPI` are design to read and
|
|
|
|
write to subsections of buffers.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2020-12-09 10:52:18 -05:00
|
|
|
It's ok to allocate an object to return to the user. Just beware of causing more
|
2017-02-03 17:48:23 -05:00
|
|
|
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
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
2018-05-23 15:05:38 -04:00
|
|
|
struct.pack
|
2018-02-27 18:24:16 -05:00
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2018-05-23 15:05:38 -04:00
|
|
|
Use `struct.pack_into` instead of `struct.pack`.
|
2017-02-03 17:48:23 -05:00
|
|
|
|
2020-09-28 16:28:38 -04:00
|
|
|
Use of MicroPython ``const()``
|
2020-09-28 16:15:43 -04:00
|
|
|
--------------------------------------------------------------------------------
|
2020-09-28 16:28:38 -04:00
|
|
|
The MicroPython ``const()`` feature, as discussed in `this forum post
|
2020-09-28 16:15:43 -04:00
|
|
|
<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,
|
2020-09-28 16:28:38 -04:00
|
|
|
when using ``const()``, keep in mind these general guide lines:
|
2020-09-28 16:15:43 -04:00
|
|
|
|
2020-09-28 16:28:38 -04:00
|
|
|
- Always use via an import, ex: ``from micropython import const``
|
2020-09-28 16:15:43 -04:00
|
|
|
- Limit use to global (module level) variables only.
|
|
|
|
- If user will not need access to variable, prefix name with a leading
|
2020-09-28 16:28:38 -04:00
|
|
|
underscore, ex: ``_SOME_CONST``.
|
2020-09-28 16:15:43 -04:00
|
|
|
|
2021-04-20 18:55:50 -04:00
|
|
|
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.
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
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 |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2018-01-08 20:38:43 -05:00
|
|
|
| ``magnetic`` | (float, float, float) | x, y, z micro-Tesla (uT) |
|
2017-02-03 17:48:23 -05:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``orientation`` | (float, float, float) | x, y, z degrees |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``gyro`` | (float, float, float) | x, y, z radians per second |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2021-04-20 18:55:50 -04:00
|
|
|
| ``temperature`` | float | degrees Celsius |
|
2017-02-03 17:48:23 -05:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2021-01-26 11:36:40 -05:00
|
|
|
| ``CO2`` | float | measured CO2 in ppm |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``eCO2`` | float | equivalent/estimated CO2 in ppm (estimated from some other measurement) |
|
2017-10-25 12:32:39 -04:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``TVOC`` | float | Total Volatile Organic Compounds in ppb |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2021-04-20 18:55:50 -04:00
|
|
|
| ``distance`` | float | centimeters (cm) |
|
2017-02-03 17:48:23 -05:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2021-04-20 18:55:50 -04:00
|
|
|
| ``proximity`` | int | non-unit-specific proximity values (monotonic but not actual distance) |
|
2020-03-06 16:31:08 -05:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2017-10-25 12:32:39 -04:00
|
|
|
| ``light`` | float | non-unit-specific light levels (should be monotonic but is not lux) |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``lux`` | float | SI lux |
|
2017-02-03 17:48:23 -05:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``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 |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2018-02-08 15:48:59 -05:00
|
|
|
| ``duty_cycle`` | int | 16-bit PWM duty cycle (regardless of output resolution) |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2021-04-20 18:55:50 -04:00
|
|
|
| ``frequency`` | int | Hertz (Hz) |
|
2018-02-08 15:48:59 -05:00
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``value`` | bool | Digital logic |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
|
|
|
| ``value`` | int | 16-bit Analog value, unit-less |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2019-03-25 18:10:15 -04:00
|
|
|
| ``weight`` | float | grams (g) |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2020-03-06 16:31:08 -05:00
|
|
|
| ``sound_level`` | float | non-unit-specific sound level (monotonic but not actual decibels) |
|
|
|
|
+-----------------------+-----------------------+-------------------------------------------------------------------------+
|
2017-02-03 17:48:23 -05:00
|
|
|
|
|
|
|
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``.
|
|
|
|
|
2017-12-14 13:02:43 -05:00
|
|
|
To test your native modules or core enhancements, follow these Adafruit Learning Guides
|
|
|
|
for building local firmware to flash onto your device(s):
|
|
|
|
|
2021-04-20 18:55:50 -04:00
|
|
|
`Build CircuitPython <https://learn.adafruit.com/building-circuitpython>`_
|
2017-12-14 13:02:43 -05:00
|
|
|
|
|
|
|
|
2017-02-03 17:48:23 -05:00
|
|
|
MicroPython compatibility
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
Keeping compatibility with MicroPython isn't a high priority. It should be done
|
2020-12-09 10:52:18 -05:00
|
|
|
when it's not in conflict with any of the above goals.
|