diff --git a/source/api_docs.md b/source/api_docs.md index be441c6..45e867f 100644 --- a/source/api_docs.md +++ b/source/api_docs.md @@ -9,7 +9,7 @@ The APIs documentation can be found here: ```{toctree} :maxdepth: 1 -C ESP32 APIs +C ESP32 APIs Java APIs Python APIs Rust APIs diff --git a/source/features.md b/source/features_split.md similarity index 95% rename from source/features.md rename to source/features_split.md index 8462927..f79d5f6 100644 --- a/source/features.md +++ b/source/features_split.md @@ -1,4 +1,4 @@ -# Features +# Features (multiple-file) A set of possible features supported by the SDKs is presented in this section. Each Astarte device SDK implements a slighly different set of features. While some might be diff --git a/source/features_unified.md b/source/features_unified.md new file mode 100644 index 0000000..10b9475 --- /dev/null +++ b/source/features_unified.md @@ -0,0 +1,134 @@ +# Features (single-file) + +A set of possible features supported by the SDKs is presented in this section. +Each Astarte device SDK implements a slighly different set of features. While some might be +common to all the SDKs, others might be more relevant to some programming language or framework +and subequently be only implemented in a set of SDKs. + +```{contents} Comprehensive list of features for the Astarte device SDKs: +:depth: 2 +:local: true +``` +## Device ID generation + +An Astarte instance identifies each device through an unique identifier. +Some of the Astarte device SDKs provide utilities to generate a device ID in a deterministic or +random manner. Some SDKs can extract information from the hardware to generate an unique identifier. +For more information regarding the device ID format can be found in the +[Astarte documentation](https://docs.astarte-platform.org/astarte/latest/010-design_principles.html#device-id). + +## Device registration + +Registering a new device to the an Astarte instance can be performed in two ways: +1. Using the registration utilities in the Astarte instance itself. Such utilities will produce + a credential secret. Such credential secret can then be transferred to the device an used to + connect to Astarte. +2. Using the on board registration utilities. This device APIs require an authorization JWT + generated in the Astarte instance and then transferred to the device. The registration APIs + will use the JWT token to negotiate a credential secret from the Astarte instance. + +Both methods will require a valid device ID to perform the registration. + +### On board device registration + +Registering the device is performed using the +[Astarte API for device registration](https://docs.astarte-platform.org/astarte/latest/api/index.html?urls.primaryName=Pairing%20API#/agent/registerDevice). + +## Interfaces parsing + +The Astarte device SDKs are capable of parsing interfaces definitions expressed in a `.json` format. +All the interfaces definitions provided to a device make up the introspection of such device. +Each device will communicate its introspection to Astarte during the connection phase. + +For more information regarding the definition of interfaces see the +[Astarte documentation](https://docs.astarte-platform.org/astarte/latest/030-interface.html). + +## Device connection + +Astarte device SDKs make use of platform specific MQTT libraries and they hide all MQTTs connection +management details. + +Astarte devices connect to the remote Astarte instance using mutual TLS authentication. + +### Server TLS certificates support + +Server authentication is performed using a TLS certificate, as such each device will need to +include a set of root certificates in order to properly verify the server certificate. + +### Client TLS certificate support + +Client authentication is performed using a TLS certificate issued by the Astarte instance. +This certificate is obtained through the +[Astarte API for device credentials](https://docs.astarte-platform.org/astarte/latest/api/index.html?urls.primaryName=Pairing%20API#/device/obtainCredentials). +Each device will generate a certificate signing request and the Astarte instance will issue a +corresponding x509 certificate. + +### Client TLS certificate renewal + +Each device SDK is responsible to check the validity of its certificate using the appropriate +[Astarte API for device credentials validation](https://docs.astarte-platform.org/astarte/latest/api/index.html?urls.primaryName=Pairing%20API#/device/obtainCredentials). + +Renewal of the certificate can be performed by requesting a fresh certificate to Astarte. + +### Reconnection + +Astarte device SDKs implement an internal reconnection strategy that uses an exponential backoff +to avoid network overloading. + +## Data streaming + +Each Astarte device SDK can transmit and receive data to one of the interfaces present in its +introspection. + +### Individual data transmission and reception + +Devices can transmit and receive data for individual interfaces. +Depending on the language and framework used by the SDK it might be required to place the data to +transmit in a dedicated structure. + +### Aggregated data transmission and reception + +Devices can transmit and receive data for aggregated interfaces. +Depending on the language and framework used by the SDK it might be required to place the data to +transmit in a dedicated structure. + +### Reliability support + +The devices ensure a level of reliability for all the data to transmitted or received. This level +of reliability is specified in the interfaces definition and is implemented using MQTT QoS levels. + +### Data persistence and automatic retransmission + +Astarte Device SDKs allow configuring persitence for high enough reliability levels. In case of +connection loss data is stored to memory or disk (according to configuration) and they are +automatically retransmitted as soon as the device is back online. + +## Properties + +Astarte has support for the concept of properties, which are kept synchronized between the server +and the device. + +### Setting/unsetting device properties + +Device properties can be set in each device and each value change is communicated to the Astarte +instance. +Unsetting properties will be allowed only for properties are defined as unsettable in their +interface definition. + +### Setting/unsetting server properties + +Server properties are property of the Astarte instance and can't be modified by the device. +However they can be added to the device introspection and any change for their value will be +communicated to the device. +Unsetting properties will be allowed only for properties are defined as unsettable in their +interface definition. + +### Properties presistency + +Astarte device SDKs store in persistent memory their owned properties. +This ensures a correct synchronization between Astarte and the device in cases of re-connections. + +## Data Validation + +The Astarte device SDKs validate user data before transmission, hence errors are reported locally +on the device improving troubleshooting experience. diff --git a/source/get_started.md b/source/get_started_unified.md similarity index 55% rename from source/get_started.md rename to source/get_started_unified.md index 8a401b5..6de8009 100644 --- a/source/get_started.md +++ b/source/get_started_unified.md @@ -1,20 +1,27 @@ -# Getting started guide +# Getting started guide (unified) All of the Astarte device SDKs are dependent on an Astarte instance running locally or remotely. As such, if you are not already familiar with setting up an Astarte instance we recommend reading the [Astarte documentation](https://docs.astarte-platform.org/astarte/latest/001-intro_user.html). Specifically the [Astarte in 5 minutes](https://docs.astarte-platform.org/astarte/latest/010-astarte_in_5_minutes.html) -page can help setting up quicly an instance of Astarte for testing purposes. +page can help setting up quickly an instance of Astarte for testing purposes. Assuming a correctly configured Astarte instance is present, any of the Astarte device SDKs can be used to interact with it. ## Generating a device ID -Some of the Astarte device SDKs provide utilities to generate an unique or random device identifier -based on hardware information. This is only useful when registering the device using a JWT token -and the provided registration APIs. +A device ID will be required to uniquely identify a device in an Astarte instance. +Some of the Astarte device SDKs provide utilities to generate a deterministic or random device +identifier, in some cases based on hardware information. + +This step is only useful when registering a device using a JWT token and the provided Astarte device +SDKs registration APIs. Registration of a device can also be performed outside the device in the +Astarte instance. In such cases the device ID should be obtained via +[astartectl](https://github.com/astarte-platform/astartectl), or the +[Astarte dashboard](https://docs.astarte-platform.org/astarte/latest/015-astarte_dashboard.html). +The device ID should then be loaded manually on the device. ::::{tab-set} @@ -82,10 +89,19 @@ let namespaced_id = astarte_sdk::registration::generate_uuid(namespaceUuid, &pay ## Registering a device -This step will depend on your preferred approach. If you already registered a new device using the -utilities provided by the Astarte instance and are in possession of a credential secret then -skip this step. If instead you have generated a registration JWT from Astarte you will need -to pass the JWT to the device and then use the registration APIs to register the device. +In order for a device to connect to Astarte a registration procedure is required. This registration +will produce a device specific credential secret that will be used when connecting to Astarte. +Some of the Astarte device SDKs provide utilities to perform a device registration directly on +the device. Those APIs will require a registration JWT to be uploaded to the device. Such JWT should +be discarded following the registration procedure. + +This step is only useful when registering the device through the APIs using the JWT token. +Registration of a device can also be performed outside the device in the Astarte instance using +tools such as [astartectl](https://github.com/astarte-platform/astartectl), the +[Astarte dashboard](https://docs.astarte-platform.org/astarte/latest/015-astarte_dashboard.html), +or the dedicated +[Astarte API for device registration](https://docs.astarte-platform.org/astarte/latest/api/index.html?urls.primaryName=Pairing%20API). +The generated credential secret should then be loaded manually on the device. ::::{tab-set} @@ -303,15 +319,20 @@ let mut device = AstarteDeviceSdk::new(&sdk_options).await.unwrap(); :::: -## Streaming individual data +## Streaming data + +All Astarte Device SDKs include primitives for sending data to a remote Astarte instance. +Streaming of data could be performed for device owned interfaces of `individual` or `object` +aggregation type. + +### Streaming individual data -All Astarte Device SDKs have primitives for sending data to a remote Astarte instance. In Astarte interfaces with `individual` aggregation, each mapping is treated as an independent value and is managed individually. -Following examples show how to send a value that will be inserted into the `"/test0/value"` time -series which is defined by `"/%{sensor_id}/value"` parametric endpoint (that is part of -`"org.astarte-platform.genericsensors.Values"` datastream interface). +The snippet bellow shows how to send a value that will be inserted into the `"/test0/value"` +datastream which is defined by `"/%{sensor_id}/value"` parametric endpoint, that is part of +`"org.astarte-platform.genericsensors.Values"` datastream interface. ::::{tab-set} @@ -367,3 +388,192 @@ device.send_with_timestamp("org.astarte-platform.genericsensors.Values", "/test0 ::: :::: + +### Streaming aggregated data + +In Astarte interfaces with `object` aggregation, Astarte expects the owner to send all of the +interface's mappings at the same time, packed in a single message. + +The following snippet shows how to send a value for an object aggregated interface. In this +examples, `lat` and `long` will be sent together and will be inserted into the `"/coords"` +datastream which is defined by the `"/coords"` endpoint, that is part of `"com.example.GPS"` +datastream interface. + +::::{tab-set} + +:::{tab-item} C (ESP32) +```C +astarte_bson_serializer_init(&bs); +astarte_bson_serializer_append_double(&bs, "lat", 45.409627); +astarte_bson_serializer_append_double(&bs, "long", 11.8765254); +astarte_bson_serializer_append_end_of_document(&bs); +int size; +const void *coord = astarte_bson_serializer_get_document(&bs, &size); + +struct timeval tv; +gettimeofday(&tv, NULL); +uint64_t ts = tv->tv_sec * 1000 + tv->tv_usec / 1000; + +astarte_device_stream_aggregate_with_timestamp(device, "com.example.GPS", "/coords", coords, ts, 0); +``` +::: + +:::{tab-item} C++ (Qt5) +```C++ +QVariantHash coords; +coords.insert(QStringLiteral("lat"), 45.409627); +coords.insert(QStringLiteral("long"), 11.8765254); +m_sdk->sendData("com.example.GPS", "/coords", coords, QDateTime::currentDateTime()); +``` +::: + +:::{tab-item} Elixir +```elixir +coords = %{lat: 45.409627, long: 11.8765254} +Device.send_datastream(pid, "com.example.GPS", "/coords", coords, timestamp: DateTime.utc_now()) +``` +::: + +:::{tab-item} Go +```go +coords := map[string]double{"lat": 45.409627, "long": 11.8765254} +d.SendAggregateMessageWithTimestamp("com.example.GPS", "/coords", coords, time.Now()) +``` +::: + +:::{tab-item} Java/Android +```java +Map coords = new HashMap() +{ + { + put("lat", 45.409627); + put("long", 11.8765254); + } +}; + +exampleGPSInterface.streamData("/coords", coords, DateTime.now()); +``` +::: + +:::{tab-item} Python +```python +coords = {'lat': 45.409627, 'long': 11.8765254} +device.send_aggregate("com.example.GPS", "/coords", coords, timestamp=datetime.now()) +``` +::: + +:::{tab-item} Rust +```rust +use astarte_device_sdk_derive::AstarteAggregate; +/// Coords must derive AstarteAggregate +#[derive(AstarteAggregate)] +struct Coords { + lat: f64, + long: f64, +} +[...] +let coords = Coords{lat: 45.409627, long: 11.8765254}; + +// stream data with an explicit timestamp +let timestamp = Utc.timestamp(1537449422, 0); +device.send_object_with_timestamp("com.example.GPS", "/coords", coords, timestamp).await?; + +// stream data without an explicit timestamp +device.send_object("com.example.GPS", "/coords", coords).await?; +``` +::: + +:::: + +## Setting and unsetting properties + +Interfaces of `property` type represent a persistent, stateful, synchronized state with no concept +of history or timestamping. From a programming point of view, setting and unsetting properties of +device-owned interface is rather similar to sending messages on datastream interfaces. + +The following snippet shows how to set a value that will be inserted into the `"/sensor0/name"` +property which is defined by `"/%{sensor_id}/name"` parametric endpoint, that is part of `"org.astarte-platform.genericsensors.AvailableSensors"` device-owned properties interface. + +It should be noted how a property should be marked as unsettable in its interface definition to +be able to use the unsetting method on it. + +::::{tab-set} + +:::{tab-item} C (ESP32) +Set property: +```C +astarte_device_set_string_property(device, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar"); +``` +Unset property: +```C +astarte_device_unset_path(device, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name"); +``` +::: + +:::{tab-item} C++ (Qt5) +Set property: +```C++ +m_sdk->sendData(m_interface, m_path, value, QDateTime::currentDateTime()); +``` +Unset property: +```C++ +m_sdk->sendUnset(m_interface, m_path); +``` +::: + +:::{tab-item} Elixir +Set property: +```elixir +Device.set_property(pid, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar") +``` +Unset property: +```elixir +Device.unset_property(pid, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name") +``` +::: + +:::{tab-item} Go +Set property: +```go +d.SetProperty("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar") +``` +Unset property: +```go +d.UnsetProperty("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name") +``` +::: + +:::{tab-item} Java/Android +Set property: +```java +availableSensorsInterface.setProperty("/sensor0/name", "foobar"); +``` +Unset property: +```java +propertyInterface.unsetProperty("/sensor0/name"); +``` +::: + +:::{tab-item} Python +Set property: +```python +device.send("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar") +``` +Unset property: +```python +device.unset_property("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name") +``` +::: + +:::{tab-item} Rust +Set property: +```rust +device.send("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar").await?; +``` +Unset property: +```rust +device.unset("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name").await?; +``` +::: + +:::: diff --git a/source/index.md b/source/index.md index 1c1ebbd..3efe09c 100644 --- a/source/index.md +++ b/source/index.md @@ -1,8 +1,9 @@ ```{toctree} :hidden: true :maxdepth: 1 -get_started.md -features.md +get_started_unified.md +features_split.md +features_unified.md api_docs.md ```