Skip to content

Commit

Permalink
docs: Get started README
Browse files Browse the repository at this point in the history
Ticket: MEN-7871

Signed-off-by: Daniel Skinstad Drabitzius <[email protected]>
  • Loading branch information
danielskinstad committed Jan 10, 2025
1 parent f3910d9 commit 2de4c32
Showing 1 changed file with 203 additions and 53 deletions.
256 changes: 203 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ creators of [Mender](https://mender.io/). This project is currently under constr
primary goal of enhancing the original work while integrating it more closely with Northern.tech's
suite of products.

-------------------------------------------------------------------------------

![Mender logo](https://github.com/mendersoftware/mender/raw/master/mender_logo.png)

## Project Status

Expand All @@ -28,32 +31,76 @@ The decision to fork the original mender-mcu-client was made to:
* **Provide Official Support**: Ensure that the project receives the necessary attention and
resources from Northern.tech to meet the needs of the community and enterprise users.

## Get started

## Get Started

Since the project is under active development, we recommend watching the repository or checking back
regularly for updates. Detailed documentation and usage instructions will be provided as the project
progresses.


## Get started with Zephyr OS
For using `mender-mcu` for the first time, we strongly recommend you start with our [Zephyr reference application](https://github.com/mendersoftware/mender-mcu-integration).

For using `mender-mcu` for the first time, we strongly recommend you start with our Zephyr reference
application.

It can be found in the repository
[mender-mcu-integration](https://github.com/mendersoftware/mender-mcu-integration). The policy is
that the reference application's `main` branch will follow the latest stable branch of `mender-mcu`
The policy is that the reference application's `main` branch will follow the latest stable branch of `mender-mcu`
(this repository).

In the `mender-mcu-repository` you will find:
In the `mender-mcu-integration` repository you will find:
* A reference `west` workspace for Zephyr OS builds, including `CMake` and `KConfig` configurations.
* Sample application for how to use `mender-mcu` APIs.
* A list of boards that we have so far tested.

We recommend starting there to understand how we have designed our solution and then coming back
here for more in-depth information.

### Compatibility
| Zephyr OS version |
|-------------------|
| v3.7.0 |

### Boards
The reference board for `mender-mcu` is the [ESP32-S3-DevKitC](https://docs.zephyrproject.org/latest/boards/espressif/esp32s3_devkitc/doc/index.html).

### Setting up a West project with `mender-mcu` as a Zephyr module
See the [Zephyr documentation](https://docs.zephyrproject.org/latest/develop/getting_started/index.html) for more information
on getting started with Zephyr.

In order to use `mender-mcu` as a Zephyr [module](https://docs.zephyrproject.org/latest/develop/modules.html#modules-external-projects)
in a [West project](https://docs.zephyrproject.org/latest/develop/west/basics.html#west-workspace), you will
need to add `mender-mcu` to your project's west manifest:

```yaml
manifest:
projects:
- name: mender-mcu
url: https://github.com/mendersoftware/mender-mcu
revision: main
path: modules/mender-mcu
import: true
```
If you already have a west workspace, you can simply add `mender-mcu` with the following command
inside the workspace after adding the project to the manifest:
```
west update
```
If you're starting from scratch, you can initialize a new workspace based on the manifest like so:
```
west init workspace --manifest-url https://url/to/repository-containing-manifest
cd workspace && west update
```

### Configuring the client
Zephyr provides [tools](https://docs.zephyrproject.org/latest/build/kconfig/menuconfig.html) for configuring
projects, which are used to set Kconfig options. These options are used by Zephyr modules to configure their behavior.

The options for configuring `mender-mcu` are found in the menuconfig under `Modules -> mender-mcu`,
and are prefixed with `MENDER_`.

You can use either `west build -t menuconfig` or `west build -t guiconfig` to open
the configuration menu.

The following option **must** be set in any configuration:
* `MENDER_SERVER_TENANT_TOKEN` which is a token that identifies which tenant a device belongs to.

The most common options to modify are:
* `MENDER_SERVER_HOST`
* `MENDER_CLIENT_UPDATE_POLL_INTERVAL`
* `MENDER_CLIENT_INVENTORY_REFRESH_INTERVAL`

See descriptions of the options in the menuconfig for more information.

### The Mender client API

Expand All @@ -74,81 +121,184 @@ always ready.
* Inventory setting. There is an explicit call to set the inventory. This will update the internal
inventory of the client and sent to the Mender Server in the next inventory report.

See the source code for more.

* User Provided Keys. The client can be configured to use a provided a keypair. If set to
`NULL`, the client will auto-generate its own keypair using ECDSA.

### Update Modules API

The Update Modules API is exposed in [mender-update-module.h](include/mender-update-module.h).

Mender MCU models the Update Modules API which we have been using in the regular Mender client for
years. Refer to the [Update Modules chapter in Mender
Docs](https://docs.mender.io/artifact-creation/create-a-custom-update-module) for an introduction.
In Mender MCU, the modules are not "executables" but rather rather a custom C function that is
integrated with the client to handle each type of update.

#### `zephyr-image` Update Module

We provide the `zephyr-image` Update Module, which implements the update process for a Zephyr OS
update integrated with MCUboot. Its [source code](core/src/mender-zephyr-image-update-module.c)
can be inspected for inspiration and a better understanding of the expected behavior of each state.

To use the `zephyr-image` Update Module, you need a board you need a board that supports
MCUboot. The following link will filter the officially supported boards that also support MCUboot:
* [Zephyr Project supported boards with MCU boot](https://docs.zephyrproject.org/latest/gsearch.html?q=MCUboot&check_keywords=yes&area=default#gsc.tab=0&gsc.q=MCUboot&gsc.ref=more%3Aboards&gsc.sort=)

#### Update Modules State Machine

As stated above, an Update Module for Mender MCU is set of customizable C functions. Concretely,
once created, all Update Modules are identified by a C struct in
[mender-zephyr-image-update-module.h](include/mender-zephyr-image-update-module.h). The
most important part of the struct is the array of callbacks, one to be called for each state.
once created, all Update Modules are identified by a C struct. The most important part of
the struct is the array of callbacks, one to be called for each state.

See [the state machine workflow
diagram](https://docs.mender.io/artifact-creation/create-a-custom-update-module#the-state-machine-workflow)
to learn about the flow between each state. An Update Module does not need to implement all of them,
only the ones that are relevant for a particular type of update.

We provide the `zephyr-image` Update Module, which implements the update process for a Zephyr OS
update integrated with MCUboot. Its [source code](core/src/mender-zephyr-image-update-module.c)
can be inspected for inspiration and a better understanding of the expected behavior of each state.

After writing the code, you need to register the Update Module into the Mender MCU. See the register
update module function in [`mender-client.h`](include/mender-client.h).


## Experimental: testing the Mender MCU Client with POSIX
### Network

### Dependencies
- CMake
- libcurl
- cJSON
See this [example](https://github.com/mendersoftware/mender-mcu-integration/blob/main/src/utils/netup.c) from mender-mcu-integration for a demo.
This implementation uses Zephyr's native networking APIs, see [Zephyr's Networking Guide](https://docs.zephyrproject.org/latest/connectivity/networking/index.html).

Example for Ubuntu/Debian:
```
sudo apt install cmake libcurl4-openssl-dev libcjson-dev pkg-config
```
### Building the Client
### Certificates
See this [example](https://github.com/mendersoftware/mender-mcu-integration/blob/main/src/utils/certs.c) from mender-mcu-integration for a demo.

1. Configure the build:
```
cmake -C cmake/CMake_posix_defaults.txt -B build tests/smoke
```
The `mender-mcu` client can take up to two certificates; typically one for the Server API calls, and
one for downloading Mender Artifacts from the Server storage. The latter is optional.

Example of how to add certificates to the client:

2. Build the client:
```cmake
generate_inc_file_for_target(app
"path/to/certificates/Certificate1.cer"
"${ZEPHYR_BINARY_DIR}/include/generated/Cerficiate1.cer.inc"
)
```
cmake --build build --parallel $(nproc --all)
[`generate_inc_file_for_target`](https://github.com/zephyrproject-rtos/zephyr/blob/bc42004d1be40d9b5bec2d3e8c600780b644ff6e/cmake/modules/extensions.cmake#L703)
is a Zephyr CMake extension.

The certificates should be guarded by the `CONFIG_NET_SOCKETS_SOCKOPT_TLS` option,

Use `CONFIG_MENDER_NET_CA_CERTIFICATE_TAG_SECONDARY_ENABLED` to check if a secondary certificate is enabled.

The API for adding the certificates is provided by Zephyr, and can be used by including
`zephyr/include/net/tls_credentials.h`

You can add the certificate by calling `tls_credential_add` with the following arguments:
* `tag` - The tag of the certificate - configured by setting either:
* `CONFIG_MENDER_NET_CA_CERTIFICATE_TAG_PRIMARY` - The tag for the primary certificate
* `CONFIG_MENDER_NET_CA_CERTIFICATE_TAG_SECONDARY` - The tag for the secondary certificate
* `type` - The type of the certificate - should be `TLS_CREDENTIAL_CA_CERTIFICATE`
* `data` - The certificate data - should be an array of unsigned characters
* `len` - The length of the certificate data - should be the size of the array of unsigned characters

Example:
```c
#include <zephyr/net/tls_credentials.h>
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
static const unsigned char ca_certificate_one[] = {
#include "Certificate1.cer.inc"
};
#endif
int add_cert(void) {
//...
ret = tls_credential_add(CONFIG_MENDER_NET_CA_CERTIFICATE_TAG_PRIMARY, TLS_CREDENTIAL_CA_CERTIFICATE, ca_certificate_one, sizeof(ca_certificate_one));
//...
}
```

### Running the Client
You can now run and connect the client to e.g. hosted Mender:
### Starting the client
See this [example](https://github.com/mendersoftware/mender-mcu-integration/blob/main/src/main.c) from mender-mcu-integration for a demo.

#### Initializing the client
Before intializing the client, you must set up the network connection, certificates, and an Update Module.

You must also define two structs:

`mender_client_config_t` with the fields defined in [mender-client.h](include/mender-client.h).
* The `device_type` can be set to `NULL`, as it is defined at build time

and

`mender_client_callbacks_t` with the callbacks defined in [mender-client.h](include/mender-client.h).

See [The Mender client API ](#the-mender-client-api).

After implementing the necessary callbacks and creating the structs,
the client can be initialized by calling `mender_client_init`, which
is defined in [mender-client.h](include/mender-client.h):

Example:
```c
mender_client_init(&mender_client_config, &mender_client_callbacks));
```
export MAC_ADDRESS=<mac_address>
export DEVICE_TYPE=<device_type>
export TENANT_TOKEN=<tenant_token>
export ARTIFACT_NAME=<artifact_name>

./build/mender-mcu-client.elf --mac_address=$MAC_ADDRESS --device_type=$DEVICE_TYPE --tenant_token=$TENANT_TOKEN --artifact_name=$ARTIFACT_NAME
#### Registering the Update Module
As mentioned in [Update Modules API](#update-modules-api), you must also register one or more
Update Modules. Without an Update Module, the client can run an report inventory, but it will
not be able to perform any updates.

Registering `zephyr-image` Update Module (compiled in by default):
```c
mender_zephyr_image_register_update_module());
```
The mac address is an arbitrary identifier. You can use anything as long as it is unique for each device.

The tenant token can be found under `My organization` in hosted Mender, where it's called `Organization token`.
#### Enabling Inventory
Inventory is enabled/disabled by setting the `MENDER_CLIENT_INVENTORY` option.

### Creating an Artifact
Create an artifact (remember to disable compression):
To use inventory, you will need a `mender_keystore_t` struct, which is defined in [mender-client.h](include/mender-client.h).
This struct contains the inventory of the client, and is enabled in the code by calling `mender_inventory_set`.

Example:
```c
mender_keystore_t inventory[] = { { .name = "demo", .value = "demo" }, { .name = "foo", .value = "bar" }, { .name = NULL, .value = NULL } };
mender_inventory_set(inventory));
```
./mender-artifact write module-image -T zephyr-image --compression none --artifact-name <artifact_name> --device-type <device_type> --file <file_name>

#### Activating the Client
Finally, you can activate the client by calling `mender_client_activate`:
```c
mender_client_activate();
```
The `device_type` in the artifact has to match the `device_type` used when running the client.
After the client is activated, it will run on a workqueue thread, either a dedicated one
(default) or the system one (configurable with the options starting with `CONFIG_SYSTEM_WORKQUEUE_`).
From this thread, the client will regularly poll for updates and submit inventory.

### Building the client

See the [building section](https://github.com/mendersoftware/mender-mcu-integration?tab=readme-ov-file#building-the-zephyr-project-mender-reference-app)
in mender-mcu-integration for a list of the boards that we have tested, and for examples
on how to build for them.

### Creating a Mender Artifact

NOTE: Compression of artifacts is **not** supported, which is why we use `--compression none`

After building the client, you can create a [Mender Artifact](https://docs.mender.io/overview/artifact).

The update module must correspond with one of the ones registered during initialization of the client.
The device type must match the one set at build time - if you're unsure what this is, you can
check the value of `MENDER_DEVICE_TYPE` in the menuconfig, by default it uses Zephyr's `BOARD`.

The Artifact Name is arbitrary, but should be unique.

```bash
UPDATE_MODULE=<update_module>
ARTIFACT_NAME=<artifact_name>
DEVICE_TYPE=<device_type>
mender-artifact write module-image \
--type $UPDATE_MODULE \
--file build/zephyr/zephyr.signed.bin \
--artifact-name $ARTIFACT_NAME \
--device-type $DEVICE_TYPE \
--compression none
```
### Deployment
After creating and uploading the artifact to the server, you should be able to deploy it to the device.

Expand Down

0 comments on commit 2de4c32

Please sign in to comment.