|
| 1 | +# Design Guide |
| 2 | + |
| 3 | +MicroPython has created a great foundation to build upon and to make it even |
| 4 | +better for beginners we've created CircuitPython. This guide covers a number of |
| 5 | +ways the core and libraries are geared towards beginners. |
| 6 | + |
| 7 | +## Start libraries with the cookiecutter |
| 8 | + |
| 9 | +Cookiecutter is a cool tool that lets you bootstrap a new repo based on another |
| 10 | +repo. We've made one [here]() for CircuitPython libraries that include configs |
| 11 | +for Travis CI and ReadTheDocs along with a setup.py, license, code of conduct |
| 12 | +and readme. |
| 13 | + |
| 14 | +```sh |
| 15 | +# The first time |
| 16 | +pip install cookiecutter |
| 17 | + |
| 18 | +cookiecutter gh:adafruit/cookiecutter-adafruit-circuitpython |
| 19 | +``` |
| 20 | + |
| 21 | +## Module Naming |
| 22 | + |
| 23 | +Adafruit funded libraries should be under the |
| 24 | +[adafruit organization](https://github.com/adafruit) and have the format |
| 25 | +Adafruit_CircuitPython_<name> and have a corresponding `adafruit_<name>` |
| 26 | +directory (aka package) or `adafruit_<name>.py` file (aka module). |
| 27 | + |
| 28 | +Community created libraries should have the format CircuitPython_<name> and not |
| 29 | +have the `adafruit_` module or package prefix. |
| 30 | + |
| 31 | +Both should have the CircuitPython repository topic on GitHub. |
| 32 | + |
| 33 | +## Lifetime and ContextManagers |
| 34 | + |
| 35 | +A driver should be initialized and ready to use after construction. If the |
| 36 | +device requires deinitialization, then provide it through `deinit()` and also |
| 37 | +provide `__enter__` and `__exit__` to create a context manager usable with `with`. |
| 38 | + |
| 39 | +## Verify your device |
| 40 | + |
| 41 | +Make sure device you are talking to is the device you expect. If not, raise a |
| 42 | +ValueError. Beware that I2C addresses can be identical on different devices so |
| 43 | +read registers you know to make sure they match your expectation. Validating |
| 44 | +this upfront will help catch mistakes. |
| 45 | + |
| 46 | +## Getters/Setters |
| 47 | + |
| 48 | +When designing a driver for a device, use properties for device state and use |
| 49 | +methods for actions that the device performs. Doing this well helps beginners |
| 50 | +understand when to use what. It is also consistent with |
| 51 | +[Python](https://docs.python.org/3/library/functions.html#property). |
| 52 | + |
| 53 | +## Design for compatibility with CPython |
| 54 | + |
| 55 | +CircuitPython is aimed to be one's first experience with code. It will be the |
| 56 | +first step into the world of hardware and software. To ease one's exploration |
| 57 | +out from this first step, make sure that functionality shared with CPython shares |
| 58 | +the same API. It doesn't need to be the full API it can be a subset. However, do |
| 59 | +not add non-CPython APIs to the same modules. Instead, use separate non-CPython |
| 60 | +modules to add extra functionality. By distinguishing API boundaries at modules |
| 61 | +you increase the likelihood that incorrect expectations are found on import and |
| 62 | +not randomly during runtime. |
| 63 | + |
| 64 | +## Document inline |
| 65 | + |
| 66 | +Whenever possible, document your code right next to the code that implements it. |
| 67 | +This makes it more likely to stay up to date with the implementation itself. Use |
| 68 | +Sphinx's automodule to format these all nicely in ReadTheDocs. The cookiecutter |
| 69 | +helps set these up. |
| 70 | + |
| 71 | +Use [rST](http://docutils.sourceforge.net/docs/user/rst/quickref.html) for markup. |
| 72 | + |
| 73 | +### Module description |
| 74 | + |
| 75 | +After the license comment: |
| 76 | + |
| 77 | +```python |
| 78 | +""" |
| 79 | +`<module name>` - <Short description> |
| 80 | +================================================= |
| 81 | +<Longer description.> |
| 82 | +""" |
| 83 | +``` |
| 84 | + |
| 85 | +### Class description |
| 86 | + |
| 87 | +```python |
| 88 | +class DS3231: |
| 89 | + """Interface to the DS3231 RTC.""" |
| 90 | +``` |
| 91 | + |
| 92 | +### Data descriptor description |
| 93 | + |
| 94 | +Comment is after even though its weird. |
| 95 | + |
| 96 | +```python |
| 97 | +lost_power = i2c_bit.RWBit(0x0f, 7) |
| 98 | +"""True if the device has lost power since the time was set.""" |
| 99 | +``` |
| 100 | + |
| 101 | +### Property description |
| 102 | + |
| 103 | +```python |
| 104 | +@property |
| 105 | +def datetime(self): |
| 106 | + """Gets the current date and time or sets the current date and time then starts the clock.""" |
| 107 | + return self.datetime_register |
| 108 | + |
| 109 | +@datetime.setter |
| 110 | +def datetime(self, value): |
| 111 | + pass |
| 112 | +``` |
| 113 | + |
| 114 | +### Method description |
| 115 | + |
| 116 | +```python |
| 117 | +def datetime(self): |
| 118 | + """Gets the current date and time or sets the current date and time then starts the clock.""" |
| 119 | + return self.datetime_register |
| 120 | +``` |
| 121 | + |
| 122 | +## Use BusDevice |
| 123 | + |
| 124 | +BusDevice is an awesome foundational library that manages talking on a shared |
| 125 | +I2C or SPI device for you. Use it to ensure the library can be used in threads |
| 126 | +later. |
| 127 | + |
| 128 | +## Lots of small modules |
| 129 | + |
| 130 | +CircuitPython boards tend to have a small amount of internal flash and a small |
| 131 | +amount of ram but large amounts of external flash for the file system. So, create |
| 132 | +many small libraries that can be loaded as needed instead of one large file that |
| 133 | +does everything. |
| 134 | + |
| 135 | +## Speed second |
| 136 | + |
| 137 | +Speed isn't as important as API clarity and code size. So, prefer simple APIs |
| 138 | +like properties for state even if it sacrifices a bit of speed. |
| 139 | + |
| 140 | +## Avoid allocations in drivers |
| 141 | + |
| 142 | +Although Python doesn't require managing memory, its still a good practice for |
| 143 | +library writers to think about memory allocations. Avoid them in drivers if |
| 144 | +you can because you never know how much something will be called. Fewer |
| 145 | +allocations means less time spent cleaning up. So, where you can, prefer |
| 146 | +bytearray buffers that are created in `__init__` and used throughout the |
| 147 | +object with methods that read or write into the buffer instead of creating new |
| 148 | +objects. `nativeio` classes are design to read and write to subsections of |
| 149 | +buffers. |
| 150 | + |
| 151 | +**However**, this is a memory tradeoff so do not do it for large or rarely used |
| 152 | +buffers. |
| 153 | + |
| 154 | +### Examples |
| 155 | + |
| 156 | +#### ustruct.pack |
| 157 | +Use `ustruct.pack_into` instead of `ustruct.pack`. |
| 158 | + |
| 159 | +## Sensor properties and units |
| 160 | + |
| 161 | +The [Adafruit Unified Sensor Driver Arduino library](https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver/introduction) has a |
| 162 | +[great list](https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver?view=all#standardised-si-units-for-sensor-data) |
| 163 | +of measurements and their units. Use the same ones including the property name |
| 164 | +itself so that drivers can be used interchangeably when they have the same |
| 165 | +properties. |
| 166 | + |
| 167 | +| Property name | Python type | Units | |
| 168 | +|---------------|-------------|-------| |
| 169 | +| `acceleration` | (float, float, float) | x, y, z meter per second per second | |
| 170 | +| `magnetic` | float | micro-Tesla (uT) | |
| 171 | +| `orientation` | (float, float, float) | x, y, z degrees | |
| 172 | +| `gyro` | (float, float, float) | x, y, z radians per second | |
| 173 | +| `temperature` | float | degrees centigrade | |
| 174 | +| `distance` | float | centimeters | |
| 175 | +| `light` | float | SI lux | |
| 176 | +| `pressure` | float | hectopascal (hPa) | |
| 177 | +| `relative_humidity` | float | percent | |
| 178 | +| `current` | float | milliamps (mA) | |
| 179 | +| `voltage` | float | volts (V) | |
| 180 | +| `color` | int | RGB, eight bits per channel (0xff0000 is red) | |
| 181 | +| `alarm` | (time.struct, str) | Sample alarm time and string to characterize frequency ("secondly", "minutely", "hourly", "daily", "weekly", "monthly") | |
| 182 | +| `datetime` | time.struct | date and time | |
| 183 | + |
| 184 | +## Adding native modules |
| 185 | + |
| 186 | +The Python API for a new module should be defined and documented in |
| 187 | +`shared-bindings` and define an underlying C API. If the implementation is |
| 188 | +port-agnostic or relies on underlying APIs of another module, the code should |
| 189 | +live in `shared-module`. If it is port specific then it should live in `common-hal` |
| 190 | +within the port's folder. In either case, the file and folder structure should |
| 191 | +mimic the structure in `shared-bindings`. |
| 192 | + |
| 193 | +## MicroPython compatibility |
| 194 | + |
| 195 | +Keeping compatibility with MicroPython isn't a high priority. It should be done |
| 196 | +when its not in conflict with any of the above goals. |
0 commit comments