This guide might help you port the Golioth SDK to new platforms.
The structural architecture of the SDK is depicted below:
There are 4 main layers in the SDK:
- User API: These are the header files in
include/golioththat represent the entire surface area of the user-facing API (types and functions the user can call). - SDK core: The
.cfiles insrc. Contains a CoAP client implementation (based onlibcoapfor most platforms), and implements the User API layer. This is where most of the actual work is happening in the SDK. - Platform API: Abstraction layer for the platform and OS. This makes it easier
to port the SDK core to different OSes and platforms. This API is defined
in
include/golioth/golioth_sys.handinclude/golioth/fw_update.h. The SDK depends on this API for creating semaphores, timers, threads, and for applying firmware updates (e.g. with MCUboot). - Platform Port: This is the implementation of the Platform API for a specific
platform. These are the files in
port/<platform>. There are multiple platform ports to choose from, but a specific user app will only use one (e.g. an ESP-IDF app will use the ESP-IDF port).
The SDK has three direct external dependencies:
libcoap: Implements CoAP client protocolcJSON: Serialization of application layer data in JSON formatZCBOR: Serialization of application layer data in CBOR format
The libcoap library has its own dependencies on TLS and networking libraries.
For new ports, it may be necessary to also port libcoap to the platform.
This will not be a large effort if the platform already supports mbedtls for TLS
and either POSIX sockets or lwip for networking.
docs/ # Doxygen and documentation
examples/ # Example apps
<platform> # <platform>-specific example apps
...
external/ # External dependencies
include/ # Public facing header files
port/ # Platform ports
<platform>/
libcoap/ # (optional) port of libcoap to <platform>
CMakeLists.txt # Platform-specific build file (could also be Makefile)
fw_update_<platform>.c # Port of golioth_fw_update.h to <platform>
golioth_sys_<platform>.c # Port of golioth_sys.h to <platform>
scripts/ # Misc. scripts
src/ # Core SDK code, platform-agnostic, only .h and .c files
test/ # Unit tests
The porting process is broken down into 2 main steps:
-
Get a basic "Hello, World" project compiling and running
- Contains minimal
main.cthat includes Golioth SDK. - Port layer will be entirely stubs and empty files, just enough to get the app to compile and run.
- Will need to include libcoap in the project, so header files can be resolved (Golioth SDK depends on libcoap headers).
- Contains minimal
-
Get the
golioth_basicsexample compiling and running- Full implementation of port layer
- golioth_sys
- fw_update
- Full implementation of port layer
When in doubt, use the existing platform examples and ports as a reference.
The existing linux port is the simplest to understand, so it may make sense
to study that one first (port/linux and examples/linux).
Create a folder for the hello_world project:
mkdir examples/<platform>/hello_world
Add a main.c file with the following contents:
#include <golioth/client.h>
#include <golioth/golioth_sys.h>
#include <stdio.h>
LOG_TAG_DEFINE(main);
int main(void) {
GLTH_LOGI(TAG, "Hello, World!");
return 0;
}Add build files (e.g. CMakeLists.txt) to build the project on your platform.
It's recommended to build the SDK core (the files in src)
as a library, then link that library into the top-level executable.
You can see an example of this being done in port/esp_idf/components/golioth_sdk
and port/linux/golioth_sdk.
When you try to compile your "Hello, World" project, you will get missing header files and unresolved references.
You will need to create the following files:
touch port/<platform>/golioth_port_config.h
touch port/<platform>/fw_update_<platform>.c
touch port/<platform>/golioth_sys_<platform>.c
You can also use an existing version of these files if it works for
your platform. For instance, if your platform has support for FreeRTOS, then
you could use port/freertos/golioth_sys_freertos.c. Likewise, if your platform
supports MCUboot, you could use port/modus_toolbox/fw_update_mcuboot.c.
Create stub implementations of the required functions to get the project to properly link.
See include/golioth/golioth_sys.h and include/golioth/fw_update.h for the
required functions to implement
With the required files and stub implementations in place, you should
be able to compile and run the hello_world project on your target platform.
The output should be:
I (0) main: Hello, World!
Rename the "Hello, World" project to golioth_basics and include these
files in the build
examples/common/golioth_basics.h
examples/common/golioth_basics.c
Change your main.c file to match this:
#include <golioth/client.h>
#include "golioth_basics.h"
#include <stdio.h>
#include <string.h>
LOG_TAG_DEFINE(main);
int main(void) {
const char* wifi_ssid = "SSID";
const char* wifi_password = "Password";
const char* golioth_psk_id = "device@project";
const char* golioth_psk = "supersecret";
// TODO: hardware init
// e.g. non-volatile storage, wifi
// TODO: wait for connection to network
struct golioth_client_config config = {
.credentials = {
.auth_type = GOLIOTH_TLS_AUTH_TYPE_PSK,
.psk = {
.psk_id = golioth_psk_id,
.psk_id_len = strlen(golioth_psk_id),
.psk = golioth_psk,
.psk_len = strlen(golioth_psk),
}
}
};
struct golioth_client *client = golioth_client_create(&config);
assert(client);
golioth_basics(client);
return 0;
}You will need to fill in those TODOs with your platform-specific
hardware and network initialization code.
You'll need to fill in the implementation of golioth_sys_<platform>.c functions
based on your platform's libararies for semaphores, timers, and threads.
If your platform supports POSIX or FreeRTOS, you should try to use the existing
ports (e.g. port/freertos/golioth_sys_freertos.c).
After you implement golioth_sys, you should be able to run the golioth_basics
app again, and it should connect to Golioth servers (assuming you have set
the proper Golioth credentials in main.c).
The final step is to implement the fw_update port, which handles the platform
side of OTA firmware updates. This entails writing the OTA image to flash,
notifying the bootloader to use the new image, and rolling back to prior boot
images. See the Doxygen descriptions in include/golioth/fw_update.h for
more details.
If your platform supports MCUboot, you can leverage
port/modus_toolbox/fw_update_mcuboot.c instead of writing from scratch.
