Configurazione

La configurazione minima per implementare un client Trackle è la seguente:

#include <trackle_interface.h>

int main(int argc, char *argv[]) {
    Trackle *trackle_s = newTrackle();
    // Inizializzazione
    trackleInit(trackle_s);
    trackleSetMillis(trackle_s, get_millis_cb);
    // Autenticazione
    trackleSetDeviceId(trackle_s, DEVICE_ID);
    trackleSetKeys(trackle_s, PRIVATE_KEY);
    // Configurazione del socket di comunicazione 
    trackleSetSendCallback(trackle_s, send_cb_udp);
    trackleSetReceiveCallback(trackle_s, receive_cb_udp);
    trackleSetConnectCallback(trackle_s, connect_cb_udp);
    trackleSetDisconnectCallback(trackle_s, disconnect_cb);
    // Connessione
    trackleConnect(trackle_s);
    // Loop
    while (1)
    {
        trackleLoop(trackle_s);
        ...
        // Application Loop
        ...
	usleep(20 * 1000);
    }
    return 0;
}

Inizializzazione

Trackle.setMillis()

Imposta una callback che ritorna il numero di millisecondi da cui il software è in esecuzione.

static system_tick_t get_millis_cb(void) {
    struct timeval tp;
    gettimeofday(&tp, NULL);
    long int ms = tp.tv_sec * 1000 + tp.tv_usec / 1000;
    return (uint32_t)ms;
}

trackleSetMillis(trackle_s, get_millis_cb);

Autenticazione

Per ogni dispositivo che si vuole connettere a Trackle è necessario possedere un ID Dispositivo e una chiave privata.

Per ottenere un ID dispositivo e una chiave privata puoi seguire questi passaggi:

  • Crea un account su Trackle Cloud attraverso la Console (https://trackle.cloud/)

  • Dalla pagina "Dispositivi" clicca sul bottone "Claim di un dispositivo"

  • Clicca sul link "Non hai un ID dispositivo?", poi su continua

  • L'ID Dispositivo verrà mostrato sullo schermo e il file della chiave privata verrà scaricato automaticamente dal browser con il nome <id_dispositivo>.der

La connessione verso Trackle Cloud è reciprocamente autenticata utilizzando coppie di chiavi pubbliche / private in formato RPK (Raw Public Key).

La chiave privata del dispositivo ottenuta al passaggio precedente deve essere memorizzata sul dispositivo e deve essere essere mantenuta segreta. Trackle Cloud memorizza la chiave pubblica di ogni dispositivo.

Per quanto riguarda il cloud, la chiave privata del cloud viene mantenuta segreta, mentre tutti i dispositivi conoscono la chiave pubblica del cloud. La chiave pubblica del cloud non è un segreto.

Trackle.setDeviceId()

Configura l'ID univoco del dispositivo (DeviceID) ottenuto dalla Console. L'ID dispositivo deve essere un numero di 12 byte che identifica univocamente il dispositivo.

Trackle.setKeys()

Configura la chiave privata per questo dispositivo. Non è necessario impostare la chiave pubblica del cloud in quanto è già codificata nella libreria.

Per ottenere l'array esadecimale dal file .der della chiave privata scaricata dalla Console è possibile usare questo comando su sistemi unix:

cat private_key.der | xxd -i

// ID univoco del dispositivo
char DEVICE_ID[13] = {0xd1, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06};

// chiave privata del dispositivo
const uint8_t PRIVATE_KEY[PRIVATE_KEY_LENGTH] =
  {0x30, 0x77, 0x02, 0x01, 0x01, 0x04, ... };
  
trackleSetDeviceId(trackle_s, DEVICE_ID);
trackleSetKeys(trackle_s, PRIVATE_KEY);

Comunicazione

Trackle Library si collega al Cloud attraverso il protocollo di comunicazione CoAP (IETF RFC 7252), acronimo di Constrained Application Protocol. CoAP è un protocollo leggero, appositamente progettato per dispositivi IoT con capacità di elaborazione limitate che comunicano su reti con banda disponibile ridotta.

CoAP utilizza UDP come protocollo di trasporto predefinito (ogni messaggio CoAP viene inviato all'interno di un datagram UDP) e implementa le funzionalità software per garantire l'invio, la ricezione e l'ordinamento dei pacchetti (ACK e messageId). La sicurezza della comunicazione è assicurata dall’implementazione dello standard DTLS (Datagram Transport Layer Security), un protocollo progettato per proteggere la privacy dei dati e prevenire intercettazioni e manomissioni.

Per permettere ad un client di comunicare con il cloud è necessario implementare le callback che definiscono come creare e distruggere un socket UDP e come inviare e ricevere dati su quel socket UDP. La libreria si occupa di cifrare la comunicazione e di mantenere il canale sempre attivo.

Trackle.setConnectCallback()

Imposta una callback che crea il socket UDP verso il cloud e ritorna il risultato.

struct sockaddr_in servaddr;
int cloud_socket;

int connect_cb_udp(const char *address, int port)
{
    printf("Connecting socket");
    int addr_family;
    int ip_protocol;
    char addr_str[128];

    struct hostent *res = gethostbyname(address);
    if (res)
    {
        printf("Dns address %s resolved", address);
    }
    else
    {
        printf("error resolving gethostbyname %s resolved", address);
        return -1;
    }

    memcpy(&cloud_addr.sin_addr.s_addr, res->h_addr, sizeof(cloud_addr.sin_addr.s_addr));

    cloud_addr.sin_family = AF_INET;
    cloud_addr.sin_port = htons(port);
    addr_family = AF_INET;
    ip_protocol = IPPROTO_IP;
    inet_ntoa_r(cloud_addr.sin_addr, addr_str, sizeof(addr_str) - 1);

    cloud_socket = socket(addr_family, SOCK_DGRAM, ip_protocol);
    if (cloud_socket < 0)
    {
        printf("Unable to create socket: errno %d", errno);
    }
    printf("Socket created, sending to %s:%d", address, port);

    // setto i timeout di lettura/scrittura del socket
    struct timeval socket_timeout;
    socket_timeout.tv_sec = 0;
    socket_timeout.tv_usec = 1000; // 1ms
    setsockopt(cloud_socket, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&socket_timeout, sizeof(struct timeval));

    return 1;
}

trackleSetConnectCallback(trackle_s, connect_cb_udp);

Trackle.setDisconnectCallback()

Imposta una callback che esegue la chiusura del socket UDP e ritorna il risultato.

int disconnect_cb()
{
    if (cloud_socket)
        close(cloud_socket);
    return 1;
}

trackleSetDisconnectCallback(trackle_s, disconnect_cb);

Trackle.setSendCallback()

Imposta una callback che esegue la scrittura di un buffer dati sul socket UDP e ritorna il numero di byte inviati.

int send_cb_udp(const unsigned char *buf, uint32_t buflen, void *tmp)
{
    size_t sent = sendto(cloud_socket, (const char *)buf, buflen, 0, (struct sockaddr *)&cloud_addr, sizeof(cloud_addr));
    return (int)sent;
}

trackleSetSendCallback(trackle_s, send_cb_udp);

Trackle.setReceiveCallback()

Imposta una callback che legge il socket UDP e ritorna il numero di byte ricevuti.

int receive_cb_udp(unsigned char *buf, uint32_t buflen, void *tmp)
{
    size_t res = recvfrom(cloud_socket, (char *)buf, buflen, 0, (struct sockaddr *)NULL, NULL);

    // on timeout error, set bytes received to 0
    if ((int)res < 0 && errno == 11)
    {
        res = 0;
    }

    return (int)res;
}

trackleSetReceiveCallback(trackle_s, receive_cb_udp);

Trackle.setRandomCallback

Imposta una callback per la generazione di un numero random. Se non definita viene utilizzata la funziona rand() che ritorna un numero pseudo-casuale.

// SINTASSI
typedef uint32_t(randomNumberCallback)(void);
void trackleSetRandomCallback(Trackle *v, randomNumberCallback *random);

// ESEMPIO
uint32_t random_callback() {
    return rand();
}

trackleSetRandomCallback(c, random_callback);

Se l'hardware utilizzato dispone di metodi più sicuri per la generazione di un numero random, è possibile configurarli attraverso questa callback.

Connessione

Dopo aver implementato la gestione del socket il client può tentare la connessione al cloud.

Trackle.connect()

Tenta la connessione al cloud. Restituisce il valore 0 se non ci sono errori, un valore <0 in caso di errore.

Trackle.connected()

Ritorna true se il dispositivo è connesso al cloud, altrimenti false .

Trackle.loop()

Esegue il background loop che si occupa della comunicazione bidirezionale tra dispositivo e cloud di mantenere il canale attivo. Se non viene chiamata abbastanza frequentemente, la connessione con il Cloud verrà persa.

Trackle.loop() è una funzione che blocca l'esecuzione del firmware per alcuni millisecondi. Più frequentemente viene chiamata, più il tempo di esecuzione si riduce e più il dispositivo risponde con velocità.

int main() {
    trackleConnect(trackle_s);
    while (1)
    {
        trackleLoop(trackle_s);
        ...
        // Application Loop
        ...
        usleep(20 * 1000);
    }
}

Trackle.disconnect()

Tenta di disconnettere il dispositivo dal cloud.

int counter = 10000;

bool needConnection() {
  --counter;
  if (0 == counter)
    counter = 10000;
  return (2000 > counter);
}

int main() {
  while(1) {
    if (needConnection()) {
      if (!trackleConnected(trackle_s))
        trackleConnect(trackle_s);
    } else {
      if (trackleConnected(trackle_s))
        trackleDisconnect(trackle_s);
    }
    trackleLoop(trackle_s);
    ...
    // Application Loop
    ...
    usleep(20 * 1000);
  }
  return 0;
}

Quando il dispositivo non è connesso, diverse funzionalità come gli aggiornamenti OTA, Trackle.get e Trackle.post non sono disponibili.

Prodotti

Per i dispositivi che fanno parte di un prodotto e' necessario specificare anche un ID Prodotto e una versione del firmware.

Trackle.setProductId()

Configura l'ID prodotto di cui è parte il dispositivo.

Trackle.setFirmwareVersion()

Configura la versione firmware del prodotto.

const uint_16_t PRODUCT_ID = 20;
const uint_16_t PRODUCT_FIRMWARE_VERSION = 1;

trackleSetProductId(trackle_s, PRODUCT_ID);
trackleSetFirmwareVersion(trackle_s, PRODUCT_FIRMWARE_VERSION);

Trackle.setComponentsList()

Permette di specificare una lista di componenti utilizzati ed inviarli al cloud. Questa informazione può essere utile per monitorare in modo più dettagliato le risorse e le funzionalità disponibili sul dispositivo.

trackleSetComponentsList(trackle_s, "trackle-library-esp-idf:v2.2.1");

Last updated