You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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, its 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 dont 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 vendors namespace. The vendor identifier is usually
devised from an ASCII representation of the vendors 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 C99 or C++11, 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 its possible to build a nanoapp with a different build system, just as it
is for the CHRE framework, its 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 nanoapps 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 devices 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 Javas 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 its 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 nanoapps 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 nanoapps Makefile, populate `NANOPB_SRCS` with the desired
`.proto` file(s). Thats 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 Linuxs 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 doesnt 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 doesnt
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++11 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 C99 or C++11 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 - its 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.