| # Nanoapp Developer Guide |
| |
| [TOC] |
| |
| Since CHRE is an open platform, anyone can write nanoapps. However, deploying to |
| a device requires cooperation of the device manufacturer, because CHRE is a |
| security-sensitive trusted environment, similar to other low-level firmware, and |
| it typically has tight resource constraints. This section assumes you have a |
| basic understanding of what a nanoapp is (if not, see the Nanoapp Overview |
| section), and provides some simple instructions to help you get started with |
| developing your own nanoapp. |
| |
| ## Getting Started |
| |
| When starting a new nanoapp, it’s helpful to start with the skeleton of an |
| existing nanoapp. The simplest example can be found at `apps/hello_world`. Start |
| by copying this folder to the location where you will develop the nanoapp - it |
| can be outside of the `system/chre` project, for example in a vendor-specific |
| Git repository in the `vendor/` folder of the Android tree. |
| |
| If you don’t plan to use this nanoapp as a *static nanoapp* (see the Nanoapp |
| Overview for details), remove the `hello_world.mk` file and delete the code |
| blocks wrapped in `#ifdef CHRE_NANOAPP_INTERNAL`. Rename the remaining files to |
| match your nanoapp. |
| |
| ### Picking a Nanoapp ID |
| |
| Nanoapps are uniquely identified by a 64-bit number. The most significant 5 |
| bytes of this number are the vendor identifier, and the remaining bytes identify |
| the nanoapp within the vendor’s namespace. The vendor identifier is usually |
| devised from an ASCII representation of the vendor’s name, for example Google |
| uses 0x476F6F676C (“Googl”). The remaining portion of the ID is typically just |
| an incrementing value for each nanoapp. |
| |
| Refer to `system/chre/chre_api/include/chre_api/chre/common.h` and |
| `util/include/chre/util/nanoapp/app_id.h` for some examples and utilities. |
| |
| Be sure to pick a unique nanoapp ID when creating a new nanoapp. |
| |
| ### Picking a Language |
| |
| CHRE guarantees support for nanoapps written in C11 or C++17, though not all |
| standard library functions are supported (see below for details). For a |
| device-specific nanoapp, additional programming languages/versions *may* be |
| supported, but this can impact portability. |
| |
| ### Building the Nanoapp Binary |
| |
| While it’s possible to build a nanoapp with a different build system, just as it |
| is for the CHRE framework, it’s recommended to use the common build system |
| included in this project, as it makes it easy to support a variety of target |
| platforms. The rest of this section assumes you are using the CHRE build system |
| to create a non-static nanoapp. |
| |
| Update the `Makefile` in your nanoapp’s directory to: |
| |
| * Define nanoapp metadata, including: |
| * `NANOAPP_NAME`: sets the output filename of the binary |
| * `NANOAPP_ID`: 64-bit identifier, in hexadecimal format |
| * `NANOAPP_VERSION`: 32-bit version, in hexadecimal format (see versioning |
| section below) |
| * `NANOAPP_NAME_STRING`, `NANOAPP_VENDOR_STRING`: human-readable strings for |
| the name of the nanoapp and vendor, respectively |
| * `NANOAPP_IS_SYSTEM_NANOAPP`: 0 or 1 (see Nanoapp Overview) |
| * Populate `COMMON_SRCS` with the C or C++ source files to compile |
| * Populate `COMMON_CFLAGS` with compiler flags, like additional include paths |
| * Include any additional `.mk` files for vendor extensions, etc. before `app.mk` |
| |
| Refer to `build/nanoapp/app.mk` for full details. |
| |
| The nanoapp can then be built using a command like ``OPT_LEVEL=s make |
| <build_target> -j`nproc` `` (see the CHRE Framework Build System section for |
| details on build targets), which will produce build artifacts at |
| `out/<build_target>/<nanoapp_name>.*`. |
| |
| ### Loading onto a Device |
| |
| Exact steps to load a nanoapp binary can vary by device, but for developing a |
| preloaded nanoapp, this typically involves the following steps: |
| |
| * Perform any needed post-processing of the nanoapp binary to permit it to be |
| loaded (such as signing with a development or production key) |
| * Write the binary to the device’s storage (for example, using `adb push`) |
| * Update `preloaded_nanoapps.json` or other configuration as needed, so that |
| CHRE knows to load the new nanoapp |
| * Restart CHRE to reload all nanoapps, including the new one |
| |
| ## Nanoapp Versioning |
| |
| While not strictly enforced, nanoapps are recommended to follow the convention |
| of the CHRE framework and use [Semantic Versioning](http://semver.org). In the |
| case of a nanoapp, the key versioned “API” is considered the interface between |
| the client and nanoapp. Nanoapp versions are represented as a 32-bit integer, |
| where the most significant byte represents the major version, followed by one |
| byte for the minor version, and two bytes for the patch version. |
| |
| ## Using the CHRE API |
| |
| The CHRE API is the key interface between each nanoapp and the underlying |
| system. Refer to the extensive API documentation in the header files at |
| `chre_api/include`, as well as usage of the APIs by sample nanoapps. The CHRE |
| API is normally included via `#include <chre.h>`. |
| |
| ## Utility Libraries |
| |
| Some source and header files under `util` are specifically designed to aid in |
| nanoapp development, and others were initially created for use in the framework |
| but can be leveraged by nanoapps as well. In general, any source and header file |
| there that does **not** include a header from `chre/platform` (part of the |
| internal CHRE framework implementation details) may be used by a nanoapp, and |
| files within a subdirectory called `nanoapp` are specifically targeted for use |
| by nanoapps. |
| |
| This includes `util/include/chre/util/nanoapp/log.h` (meant to be included via |
| `#include “chre/util/nanoapp/log.h”`), which provides macros like `LOGD` which |
| can be conditionally compiled, include a configurable prefix to help identify |
| the sender, and suppress double promotion warnings. |
| |
| The utilities library also includes a number of container classes, which are |
| meant to mimic the C++ standard library, but with a lightweight, CHRE-compatible |
| implementation. This includes: |
| |
| * `chre::DynamicVector`: an `std::vector` analogue |
| * `chre::FixedSizeVector`: accessed like `std::vector`, but only uses statically |
| allocated memory |
| * `chre::ArrayQueue`: can be used as a circular buffer |
| * `chre::UniquePtr`: an `std::unique_ptr` analogue |
| * `chre::Optional`: an analogue to `std::optional` from C++17 |
| * `chre::Singleton`: a container for a statically allocated object with explicit |
| initialization and deinitialization (e.g. enables construction of a global |
| object to be deferred until `nanoappStart()`) |
| |
| ## Interacting with the Host |
| |
| Nanoapps can interact with one or more clients on the host (applications |
| processor) through a flexible binary message-passing interface. For simple |
| interactions in cases where the lowest memory footprint is desired, using only |
| the built-in message type field with no additional payload, or passing |
| hand-rolled packed C-style structures (e.g. using Java’s ByteBuffer on the |
| client side) can work, though this approach can be error-prone. Using a |
| well-defined serialization format, such as Protocol Buffers (see the Using |
| NanoPB section below) or FlatBuffers, is usually a better choice. |
| |
| There are a few common tips to keep in mind when interacting with the host: |
| |
| 1. Nanoapp binaries are usually updated independently from client code - watch |
| out for compatibility issues arising from changes to the messaging protocol, and |
| use a serialization format like Protocol Buffers if possible. |
| |
| 2. Nanoapp messages to the host always wake it up if it’s asleep. If this is not |
| required, nanoapps are encouraged to batch their messages and opportunistically |
| send when the host wakes up for another reason (see |
| `chreConfigureHostSleepStateEvents()`). |
| |
| 3. After calling `chreSendMessageToHostEndpoint()`, ownership of the memory |
| associated with the message payload is assigned to the framework. Do not modify |
| it until the free callback is invoked. |
| |
| 4. Nanoapp messaging should be unicast, unless broadcast messaging is strictly |
| necessary. Design the messaging protocol such that the client initiates |
| communication, and save the host endpoint ID in the nanoapp to use when sending |
| replies. |
| |
| ## Interacting with Other Nanoapps |
| |
| While most nanoapps are only concerned with providing functionality for a single |
| client on the host, it is possible for a nanoapp to provide services to other |
| nanoapps within CHRE. Similar to how nanoapps communicate with the host by |
| passing *messages*, nanoapps can communicate with one another by passing |
| *events* with arbitrary binary payload. Event IDs starting in the range |
| `CHRE_EVENT_FIRST_USER_VALUE` are reserved for this purpose. |
| |
| Typically a nanoapp creates a *nanoapp client library* which other nanoapps can |
| include, which presents a simple, expressive API, and handles the implementation |
| details of passing events to the target nanoapp, and interpreting incoming |
| messages. |
| |
| Refer to the functions defined in `chre/event.h` for more details. |
| |
| ## Using TensorFlow Lite for Microcontrollers |
| |
| Many nanoapps use machine learning techniques to accomplish their functionality. |
| The CHRE build system has built-in support for integrating [TensorFlow Lite for |
| Microcontrollers](https://www.tensorflow.org/lite/microcontrollers) (TFLM) into |
| a nanoapp. Sync the TFLM sources, set `TFLM_PATH`, and define `USE_TFLM=true` in |
| your Makefile - see `apps/tflm_demo/README` for details and an example nanoapp. |
| |
| ## Using Nanopb |
| |
| The CHRE build system has integrated support for using |
| [Nanopb](https://jpa.kapsi.fi/nanopb/) to provide support for [Protocol |
| Buffers](https://developers.google.com/protocol-buffers) in a nanoapp. To |
| integrate this into your nanoapp’s Makefile, first install and configure |
| dependencies: |
| |
| * Sync the Nanopb source tree (e.g. from a release on GitHub), and define the |
| `NANOPB_PREFIX` environment variable to its path |
| * Download and install the protobuf compiler `protoc` and make it available in |
| your `$PATH`, or set the `PROTOC` environment variable |
| |
| Then in your nanoapp’s Makefile, populate `NANOPB_SRCS` with the desired |
| `.proto` file(s). That’s it! Though some additional options/parameters are |
| available - see `build/nanopb.mk` for details. |
| |
| ## Nanoapp Development Best Practices |
| |
| Even though CHRE aims to provide an environment for low-power and low-latency |
| contextual signal processing, these two are often conflicting goals. In |
| addition, CHRE is usually implemented in a resource-constrained environment with |
| limited memory available. |
| |
| As it requires collaboration from all nanoapps to optimize their resource usage |
| for CHRE to live up to its promises, some best practices are provided here as |
| guidance for nanoapp development. |
| |
| ### Memory Efficiency |
| |
| #### Avoid dynamic heap allocations where possible |
| |
| As CHRE is designed in a resource-constrained environment, there is no guarantee |
| runtime memory allocation will succeed. In addition, dynamic heap allocations |
| make it difficult to estimate the memory usage in advance. Developers are |
| therefore encouraged to use static allocations where possible. |
| |
| #### Be careful of stack usage |
| |
| Unlike Linux’s default stack of 8MB that scales dynamically, CHRE only has a |
| fixed stack of limited size (8KB is typical). Ensure you keep any allocations to |
| an absolute minimum and any large allocations should go out of scope prior to |
| navigating deeper into a stack. |
| |
| #### Prefer in-place algorithms |
| |
| Prefer in-place algorithms over out-of-place ones where efficiency allows to |
| minimize additional memory requirements. |
| |
| ### Power Efficiency |
| |
| #### Be opportunistic when possible |
| |
| Examples include: |
| |
| * If the host is asleep and doesn’t need to act on a nanoapp message |
| immediately, buffer until it wakes up for another reason. |
| * Make a WiFi on-demand scan request only if the WiFi scan monitor doesn’t |
| provide a scan result in time. |
| |
| #### Batch data at the source where possible |
| |
| By batching data at the source, it reduces the data delivery frequency and helps |
| keep CHRE asleep and improve power efficiency. Clients should make data requests |
| with the longest batch interval that still meets the latency requirement. |
| Examples include: |
| |
| * Make a sensor data request with the longest ``latency`` possible. |
| * Make an audio data request with the longest ``deliveryInterval`` possible. |
| |
| ### Standard Library Usage |
| |
| CHRE implementations are only required to support a subset of the standard C and |
| C++ libraries, as well as language features requiring run-time support. This |
| list is carefully considered to ensure memory usage and implementation |
| complexity are minimized. Following these principles, some features are |
| explicitly excluded due to their memory and/or extensive OS-level dependencies, |
| and others because they are supplanted by more suitable CHRE-specific APIs. |
| While not meant to be an exhaustive list and some platforms may differ, the |
| following standard library features are not meant to be used by nanoapps: |
| |
| * C++ exceptions and run-time type information (RTTI) |
| * Standard library multi-threading support, including C++ library headers |
| ` <thread>`, `<mutex>`, `<atomic>`, `<future>`, etc. |
| * C and C++ Standard Input/Output libraries |
| * C++ Standard Template Library (STL) |
| * C++ Standard Regular Expressions library |
| * Dynamic memory allocation (`malloc`, `calloc`, `realloc`, `free`), and |
| libraries that inherently use dynamic allocation, such as `std::unique_ptr` |
| * Localization and Unicode character support |
| * Date and time libraries |
| * Functions that modify normal program flow, including `<setjmp.h>`, |
| `<signal.h>`, `abort`, `std::terminate`, etc. |
| * Accessing the host environment, including `system`, `getenv`, etc. |
| * POSIX or other libraries not included in the C11 or C++17 language standards |
| |
| In many cases, equivalent functionality is available from CHRE API functions |
| and/or utility libraries. For example, `chreLog` may be used for debug logging, |
| where a more traditional program might use `printf`. |
| |
| ## Debugging |
| |
| Similar to the framework debugging methods, each has its nanoapp counterpart to |
| support nanoapp debugging through the framework. Please see the Framework |
| Debugging section for reference/context. |
| |
| ### Logging |
| |
| CHRE API `chreLog()` logs information into the system as part of the CHRE logs. |
| Normally this appears in logcat, but some platforms may route it to a different |
| logging system (a future version of the CHRE API is expected to make logcat |
| logging mandatory). |
| |
| Nanoapps are encouraged to `#include "chre/util/nanoapp/log.h"` and use the |
| `LOGx()` macros defined therein, which requires these additional steps: |
| |
| * Define `LOG_TAG` to a short, human-readable identifier for your nanoapp, as |
| this gets prepended to logs |
| * Define `NANOAPP_MINIMUM_LOG_LEVEL` to a `CHRE_LOG_LEVEL_\*` value in your |
| Makefile for compile time log level filtering - it’s recommended to use |
| `CHRE_LOG_LEVEL_DEBUG` for development, and `CHRE_LOG_LEVEL_INFO` for release |
| |
| See also the Framework Debugging section for more general guidance on logging in |
| CHRE. |
| |
| ### Debug Dump |
| |
| When running on CHRE v1.4+, nanoapps can also append information to the CHRE |
| framework debug dump. Nanoapps interested in using this capability should call |
| `chreConfigureDebugDumpEvent(true)` in `nanoappStart()`, then when |
| `CHRE_EVENT_DEBUG_DUMP` is received in `nanoappHandleEvent()`, use |
| `chreDebugDumpLog()` to write human-readable output to the debug dump, which |
| appears in bug reports under the Context Hub HAL debug section. In the reference |
| CHRE framework implementation, nanoapp debug dumps have the nanoapp name and ID |
| automatically prepended, for example: |
| |
| ``` |
| Nanoapp debug dumps: |
| |
| DebugDumpWorld 0x0123456789000011: |
| Debug event count: 2 |
| Total dwell time: 92 us |
| ``` |
| |
| Refer to the associated CHRE API documentation and Framework Debugging section |
| for more information. |
| |
| ### CHRE_ASSERT |
| |
| To help catch programming errors or other unexpected conditions, nanoapps can |
| use the `CHRE_ASSERT` macro provided by `#include "chre/util/nanoapp/assert.h"`. |
| Keep in mind that if one nanoapp encounters an assertion failure, it most likely |
| will cause a reset of the processor where CHRE is running, impacting other |
| functionality (though this can vary by platform). Therefore, assertions are only |
| recommended to be used during development. Define the `CHRE_ASSERTIONS_ENABLED` |
| variable in your Makefile to `false` to disable assertions at compile time. |