Astarte Device SDKs

Introduction

Astarte Device SDKs are ready to use libraries that provide communication and pairing primitives. They allow to connect any device to an Astarte instance. While an SDK is not strictly required to connect an application to Astarte using MQTT, it enables rapid development and a pleasant developer experience.

Astarte Device SDKs should not be confused with client SDKs, as they are not meant for client to device communications. If one is interested in an abstraction layer on top of existing APIs instead, an optional Astarte Client SDK (such as astarte-go ) is to be used.

Under the hood Astarte Device SDKs make use of MQTT, BSON, HTTP, persistence and crypto libraries to implement Astarte MQTT v1 Protocol and all the other useful features.

They can be easily integrated into new or existing IoT projects written in any of the supported languages or platforms. At the moment the following SDKs are available:

Further languages and platforms will be supported in the near future. Requests for new SDKs are welcome.

SDKs Features

MQTT Connection

Astarte Device SDKs make use of platform specific MQTT libraries and they hide all MQTT connection management details, including smart reconnection (randomized reconnection backoff is used).

Device ID Generation

Some of the Astarte Device SDKs (such as the ESP32) offer optional device id generation utils that can use the hardware id as seed.

Automatic Registration (Agent)

Astarte Device SDKs can provide an optional automatic registration mechanism that can be used on the field, avoiding any manual data entry or additional operations. This optional component can be disabled when performing registration during manufactoring process.

Client SSL Certs Request and Renewal

Astarte Device SDKs make use of short lived SSL certificates which are automatically renewed before their expiration.

Astarte Device SDKs take care of the complete process from the certificate generation to the certificate signing request.

Data Serialization and Protocol Management

MQTT payloads are format agnostic, hence a serialization format should be used before transmitting data. For this specific purpose Astarte makes use of BSON format which easily maps to JSON.

Astarte Device SDKs take care on user behalf of data serialization to BSON. Last but not least some additional signaling messages are exchanged such as the introspection, Astarte Device SDKs take care of automatically sending them and applying data compression when necessary.

Data Persistence and Automatic Retransmission

Astarte Device SDKs allow configuring persitence and reliability policies. In case of connection loss data is stored to memory or disk (according to mappings configuration) and they are automatically retransmitted as soon as the device is back online.

This feature is not available yet on Elixir, ESP32, Go and Python SDKs and might be not avilable on other platforms with constrained resources.

Smart Properties Sync

Astarte has support for the concept of properties, which are kept synchronized between the server and the device.

Thanks to the Astarte MQTT v1 Protocol an incremental approach is employed therefore only changed properties are synchronized. This feature is not available yet on Elixir, Go and Python SDKs and might be not avilable on other SDKs with no session_present support.

Data Validation

Astarte Device SDKs take care of data validation before sending any data, hence errors are reported locally on the device improving troubleshooting experience.

This feature is not available yet on ESP32 and is WIP on Rust and Python.

Device Registration

A device must be registered beforehand to obtain its credentials-secret. While there are some manual options (such as using the astartectl command or using the Astarte Dashboard ), almost all Astarte Device SDKs allow to programmatically register a Device. For Go you can use the astarte_go client.

C
C++
Elixir
Go
Java/Android
Python
Rust

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

C (ESP32) with an unique hardware ID using device MAC address and other identification bits:

// deterministic id
astarte_err_t astarte_hwid_get_id(&hw_id);

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

C (ESP32):

astarte_pairing_config cfg =
{
    .base_url = &base_astarte_url;
    .jwt = &jwt_token;
    .realm = &realm;
    .hw_id = &device_id;
    .credentials_secret = &credentials_secret;
};

astarte_err_t err = astarte_pairing_register_device(&astarte_pairing_config);

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

C (ESP32): not supported.

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

C (ESP32):

astarte_device_config_t cfg = {
    .data_event_callback = astarte_data_events_handler,
    .connection_event_callback = astarte_connection_events_handler,
    .disconnection_event_callback = astarte_disconnection_events_handler,
};

astarte_device_handle_t device = astarte_device_init(&cfg);
if (!device) {
    ESP_LOGE(TAG, "Failed to init astarte device");
    return;
}

astarte_device_add_interface(device, &device_example_interface);
if (astarte_device_start(device) != ASTARTE_OK) {
    ESP_LOGE(TAG, "Failed to start astarte device");
    return;
}

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

C (ESP32):

struct timeval tv;
gettimeofday(&tv, NULL);
uint64_t ts = tv->tv_sec * 1000 + tv->tv_usec / 1000;

astarte_err_t err = astarte_device_stream_double_with_timestamp(device, "org.astarte-platform.genericsensors.Values", "/test0/value", 0.3, ts, 0);

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

C (ESP32):

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);

Setting and Unsetting Properties

properties 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.

Following examples show how to send 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).

C (ESP32):

// set property (one function for each type)
astarte_device_set_string_property(device, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar");

// unset property
astarte_device_unset_path(device, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name");

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

C++ (Qt5): not supported.

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

C++ (Qt5): registration is done on device instantiation, see the next section.

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

C++ (Qt5): not supported.

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

C++ (Qt5):

// declare device options and interfaces
m_sdk = new AstarteDeviceSDK(QDir::currentPath() + QStringLiteral("./examples/device_sdk.conf").arg(deviceId), QDir::currentPath() + QStringLiteral("./examples/interfaces"), deviceId.toLatin1());

// initialize device
connect(m_sdk->init(), &Hemera::Operation::finished, this, &AstarteStreamQt5Test::checkInitResult);

// set data handlers
connect(m_sdk, &AstarteDeviceSDK::dataReceived, this, &AstarteStreamQt5Test::handleIncomingData)

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

C++ (Qt5):

m_sdk->sendData("org.astarte-platform.genericsensors.Values", "/test0/value", 0.3, QDateTime::currentDateTime());

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

C++ (Qt5):

QVariantHash coords;
coords.insert(QStringLiteral("lat"), 45.409627);
coords.insert(QStringLiteral("long"), 11.8765254);
m_sdk->sendData("com.example.GPS", "/coords", coords, QDateTime::currentDateTime());

Setting and Unsetting Properties

properties 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.

Following examples show how to send 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).

C++ (Qt5):

// set property (same as datastream)
m_sdk->sendData(m_interface, m_path, value, QDateTime::currentDateTime());

// unset property
m_sdk->sendUnset(m_interface, m_path);

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

Elixir: UUIDv5 can be obtained using the elixir_uuid library.

# random id
device_id = :crypto.strong_rand_bytes(16)|> Base.url_encode64(padding: false)

#deterministic id
device_id = UUID.uuid5(namespace_uuid, payload, :raw)
        |> Astarte.Core.Device.encode_device_id()

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

Elixir:

{:ok, %{body: %{"data" => %{"credentials_secret" => credentials_secret}}}} = Agent.register_device(client, device_id)

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

For Go and Elixir, you can also do this programmatically.


Elixir:

:ok = Agent.unregister_device(client, device_id)

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

Elixir:

# declare device options
opts = [pairing_url: pairing_url, realm: realm, device_id: device_id, interface_provider: "./examples/interfaces", credentials_secret: credentials_secret]

# start device and connect asynchronously
{:ok, pid} = Device.start_link(opts)

# blocking (optional)
:ok <- Device.wait_for_connection(device_pid)

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

Elixir:

Device.send_datastream(pid, "org.astarte-platform.genericsensors.Values", "/test0/value", 0.3, timestamp: DateTime.utc_now())

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

Elixir:


coords = %{lat: 45.409627, long: 11.8765254}
Device.send_datastream(pid, "com.example.GPS", "/coords", coords, timestamp: DateTime.utc_now())

Setting and Unsetting Properties

properties 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.

Following examples show how to send 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).

Elixir:

# set property (same as datastream)
Device.set_property(pid, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar")

# unset property
Device.unset_property(pid, "org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name")

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

Go (using the Astarte Go Client ):

// Random id
random_id, err := GenerateRandomAstarteId()

// Namespaced id
namespaced_id, err := GetNamespacedAstarteDeviceID(namespaceUuid,payload)

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

Go (using the Astarte Go Client ):

credentials_secret, err := client.Pairing.RegisterDevice(realm, deviceID) 

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

For Go and Elixir, you can also do this programmatically.


Go (using the Astarte Go Client ):

err := client.Pairing.UnregisterDevice(realm, deviceID) 

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

Go:

    // Create device
    d, err := device.NewDevice(deviceID, deviceRealm, credentialsSecret, apiEndpoint)
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    // Load interface - fix this path(s) to load the right interface
    byteValue, err := ioutil.ReadFile("/examples/interfaces/com.example.Interface.json")
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
    iface := interfaces.AstarteInterface{}
    if iface, err = interfaces.ParseInterface(byteValue); err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    if err = d.AddInterface(iface); err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    // Set up callbacks
    d.OnConnectionStateChanged = func(d *device.Device, state bool) {
        fmt.Printf("Device connection state: %t\n", state)
    }

    // Connect the device and listen to the connection status channel
    c := make(chan error)
    d.Connect(c)
    if err := <-c; err == nil {
        fmt.Println("Connected successfully")
    } else {
        fmt.Println(err.Error())
        os.Exit(1)
    }

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

Go:

d.SendIndividualMessageWithTimestamp("org.astarte-platform.genericsensors.Values", "/test0/value", 0.3, time.Now())

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

Go:

coords := map[string]double{"lat": 45.409627, "long": 11.8765254}
d.SendAggregateMessageWithTimestamp("com.example.GPS", "/coords", coords, time.Now())

Setting and Unsetting Properties

properties 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.

Following examples show how to send 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).

Go:

// set property
d.SetProperty("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar")

// unset property
d.UnsetProperty("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name")

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

Java/Android:

// Random id
String randomID = AstarteDeviceIdUtils.generateId();

// Namespaced id
String deviceID = AstarteDeviceIdUtils.generateId(namespaceUuid, payload);

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

Java/Android:

AstartePairingService astartePairingService = new AstartePairingService(pairing_url, realm);
String credentialsSecret = astartePairingService.registerDevice(jwt_token, device_id);

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

Java/Android: not supported.

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

Java:

// Device creation
// connectionSource allows to connect to a db for persistency
// The interfaces supported by the device are populated by ExampleInterfaceProvider
AstarteDevice device =
    new AstarteGenericDevice(
        deviceId,
        realm,
        credentialsSecret,
        new ExampleInterfaceProvider(),
        pairingUrl,
        connectionSource);

// ExampleMessageListener listens for device connection, disconnection and failure.
device.setAstarteMessageListener(new ExampleMessageListener());

// Connect the device
device.connect();
          

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

Java:

genericSensorsValuesInterface.streamData("/test0/value", 0.3, DateTime.now());

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

Java:


Map<String, Double> coords = new HashMap<String, Double>()
{
    {
        put("lat", 45.409627);
        put("long", 11.8765254);
    }
};

exampleGPSInterface.streamData("/coords", coords, DateTime.now());

Setting and Unsetting Properties

properties 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.

Following examples show how to send 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).

Java:

// set property
availableSensorsInterface.setProperty("/sensor0/name", "foobar");

// unset property
propertyInterface.unsetProperty("/sensor0/name");

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

Python: not supported.

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

Python:

credentials_secret = register_device_with_jwt_token(device_id, realm, jwt_token, pairing_base_url)

or

credentials_secret = register_device_with_private_key(device_id, realm, private_key_file, pairing_base_url)

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

Python: not supported.

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

Python:

# declare device options
device = Device(device_id, realm, credentials_secret, pairing_base_url)

# load device interfaces
device.add_interface(json.loads("/examples/interfaces/com.example.Interface.json"))

#register a callback that will be invoked everytime the device successfully connects
device.on_connected(callback)

#connect the device asynchronously
device.connect()

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

Python:

device.send("org.astarte-platform.genericsensors.Values", "/test0/value", 0.3, timestamp=datetime.now())

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

Python:

coords = {'lat': 45.409627, 'long': 11.8765254}
device.send_aggregate("com.example.GPS", "/coords", coords, timestamp=datetime.now())

Setting and Unsetting Properties

properties 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.

Following examples show how to send 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).

Python:

# set property (same as datastream)
device.send("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar")

# unset property
device.unset_property("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name")

Device id

Device ids are 128-bit long url-safe base64 strings without padding. They can be deterministic (UUID v5) or random (UUID v4). UUID v5 are obtained from a namespace UUID and a payload (a string). While all SDKs work with user-provided device ids, some also provide utilities to for UUID generation.

Rust:

/// Random id
let random_uuid = astarte_sdk::registration::generate_random_uuid();

///Namespaced id
let namespaced_id = astarte_sdk::registration::generate_uuid(namespaceUuid, &payload);

Automatic Registration (Agent)

You can refer to the Astarte API for device registration for more details.

Rust:

let credentials_secret =
    astarte_sdk::registration::register_device(&jwt_token, &pairing_url, &realm, &device_id)
        .await?;

Device Unregistration

Unregistering a device boils down to making its credentials secret invalid.
Just as device registration, there are manual or programmatic options. In all cases, you can use the astartectl command astartectl , the Astarte Dashboard ), or the Astarte API for device unregistration.

Rust: not supported.

Declaring interfaces / Introspection

Each device must declare the set of supported interfaces and their version. Astarte needs to know which interfaces the device advertises before processing any further data publish. This message in Astarte jargon is called introspection and it's performed by publishing on the device root topic the list of interfaces that are installed on the device.

The Astarte Device SDKs take care of performing the introspection on user behalf. In order to do so, the Astarte Device SDKs need to have some informations about the registered device:

  • the Astarte realm in which the device is registered
  • its device id
  • its credentials_secret
  • the url of Astarte pairing service
  • the path of the desired interfaces.

Then the Astarte Device SDKs will be able to connect the device to Astarte and perform introspection.

Rust:

    /// declare device options
let mut sdk_options =
    AstarteOptions::new(&realm, &device_id, &credentials_secret, &pairing_url);

/// load interfaces from a directory
sdk_options
    .add_interface_files("./examples/interfaces")
    .unwrap();

/// instance and connect the device.
let mut device = AstarteDeviceSdk::new(&sdk_options).await.unwrap();

Streaming data

All Astarte Device SDKs have a primitive for sending data to a remote Astarte instance.

Using Individual Aggregated Interfaces

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).

Rust

/// send data without an explicit timestamp
  device.send("org.astarte-platform.genericsensors.Values", "/test0/value", 3).await?;

  /// send data with an explicit timestamp
  let timestamp = Utc.timestamp(1537449422, 0);
  device.send_with_timestamp("org.astarte-platform.genericsensors.Values", "/test0/value", 3, timestamp).await?;

Using Object Aggregated Interfaces

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. In this case, all of the mappings share some core properties.

Following examples show 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" time series which is defined by "/coords" endpoint (that is part of "com.example.GPS" datastream interface).

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

properties 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.

Following examples show how to send 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).

Rust:

/// set property (same as datastream)
/// send data without an explicit timestamp
device.send("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar").await?;

/// send data with an explicit timestamp
let timestamp = Utc.timestamp(1537449422, 0);
device.send_with_timestamp("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name", "foobar", timestamp).await?;

/// unset property
device.unset("org.astarte-platform.genericsensors.AvailableSensors", "/sensor0/name").await?;