Esplorare l'architettura di Azure IoT Edge in Windows

Questo articolo descrive la procedura dettagliata del codice di esempio Hello World per illustrare i componenti fondamentali dell'architettura di Azure IoT Edge. L'esempio usa Azure IoT Edge per creare un gateway semplice che registri un messaggio "hello world" in un file ogni cinque secondi.

In questa procedura dettagliata verranno trattati i seguenti argomenti:

  • Architettura di esempio Hello World: descrive in che modo si applicano i concetti dell'architettura di Azure IoT Edge all'esempio Hello World e come vengono assemblati i componenti.
  • Come compilare l'esempio: i passaggi richiesti per compilare l'esempio.
  • Come eseguire l'esempio: i passaggi richiesti per eseguire l'esempio.
  • Output tipico: un esempio del possibile output risultante quando si esegue l'esempio.
  • Frammenti di codice: raccolta di frammenti di codice per mostrare come l'esempio Hello World implementa i componenti principali del gateway IoT Edge.

Architettura di esempio Hello World

L'esempio Hello World illustra i concetti descritti nella sezione precedente. L'esempio Hello World implementa un gateway IoT Edge la cui pipeline è costituita da due moduli di IoT Edge:

  • Il modulo hello world crea un messaggio ogni cinque secondi e lo passa al modulo logger.
  • Il modulo logger scrive i messaggi che riceve in un file.

Architettura dell'esempio Hello World compilato con Azure IoT Edge

Come descritto nella sezione precedente, il modulo Hello World non passa i messaggi direttamente al modulo logger ogni cinque secondi, ma pubblica un messaggio nel broker ogni cinque secondi.

Il modulo logger riceve il messaggio dal broker e interviene, scrivendo il contenuto del messaggio in un file.

Il modulo logger utilizza solo i messaggi provenienti dal broker, senza mai pubblicare nuovi messaggi nel broker.

Modo in cui il broker indirizza i messaggi tra i moduli in Azure IoT Edge

La figura precedente illustra l'architettura dell'esempio Hello World e i percorsi relativi ai file di origine che implementano parti diverse dell'esempio nel repository. Esplorare il codice per conto proprio o usare i frammenti di codice seguente come guida.

Installare i prerequisiti

  1. Installare Visual Studio 2015 o 2017. Se si soddisfano i requisiti di licenza, è possibile usare la versione gratuita Community Edition. Assicurarsi di includere Visual C++ e Gestione pacchetti NuGet.

  2. Installare git e verificare di riuscire a eseguire git.exe dalla riga di comando.

  3. Installare CMake e verificare di riuscire a eseguire cmake.exe dalla riga di comando. È consigliabile usare CMake versione 3.7.2 o successive. Il programma di installazione .msi è l'opzione più semplice in Windows. Aggiungere CMake alla variabile di ambiente PATH almeno per l'utente corrente quando viene dal programma di installazione.

  4. Installare Python 2.7. Assicurarsi di aggiungere Python alla variabile di ambiente PATH in Pannello di controllo -> Sistema -> Impostazioni di sistema avanzate -> Variabili di ambiente.

  5. Al prompt dei comandi, eseguire il comando seguente per clonare il repository GitHub Azure IoT Edge nel computer locale:

    git clone https://github.com/Azure/iot-edge.git
    

Come compilare l'esempio

È ora possibile compilare il runtime e gli esempi di IoT Edge nel computer locale:

  1. Aprire un prompt dei comandi per gli sviluppatori per VS 2015 o un prompt dei comandi per gli sviluppatori per VS 2017.

  2. Accedere alla cartella radice nella copia locale del repository iot-edge.

  3. Eseguire lo script di compilazione come segue:

    tools\build.cmd --disable-native-remote-modules
    

Questo script crea un file di soluzione Visual Studio e compila la soluzione. È possibile trovare la soluzione di Visual Studio nella cartella build nella copia locale del repository iot-edge. Per compilare ed eseguire unit test, aggiungere il parametro --run-unittests. Per compilare ed eseguire test end-to-end, aggiungere il parametro --run-e2e-tests.

Nota

Ogni volta che si esegue lo script build.cmd, la cartella build viene eliminata e ricreata nella cartella radice della copia locale del repository iot-edge.

Per eseguire l'esempio

Lo script build.cmd genera l'output nella cartella build nella copia locale dell'archivio iot-edge. Questo output include anche i due moduli di IoT Edge usati in questo esempio.

Lo script di compilazione inserisce logger.dll nella cartella build\modules\logger\Debug e hello_world.dll nella cartella build\modules\hello_world\Debug. Usare questi percorsi per i valori module path, come illustrato nel file di impostazioni JSON seguente.

Il processo hello_world_sample accetta il percorso di un file di configurazione JSON come argomento della riga di comando. Il file JSON di esempio seguente è disponibile nel repository dell'SDK in samples\hello_world\src\hello_world_win.json. Questo file di configurazione funziona così com'è, a meno che non si modifichi lo script di compilazione per inserire moduli di IoT Edge o file eseguibili di esempio in percorsi non predefiniti.

Nota

I percorsi dei moduli sono relativi alla directory in cui si trova il file hello_world_sample.exe. Per impostazione predefinita, il file di configurazione JSON di esempio prevede la scrittura del file "log.txt" nella directory di lavoro corrente.

{
  "modules": [
    {
      "name": "logger",
      "loader": {
        "name": "native",
        "entrypoint": {
          "module.path": "..\\..\\..\\modules\\logger\\Debug\\logger.dll"
        }
      },
      "args": { "filename": "log.txt" }
    },
    {
      "name": "hello_world",
      "loader": {
        "name": "native",
        "entrypoint": {
          "module.path": "..\\..\\..\\modules\\hello_world\\Debug\\hello_world.dll"
        }
      },
      "args": null
      }
  ],
  "links": [
    {
      "source": "hello_world",
      "sink": "logger"
    }
  ]
}
  1. Accedere alla cartella build nella radice della copia locale dell'archivio iot-edge.

  2. Eseguire il comando seguente:

    samples\hello_world\Debug\hello_world_sample.exe ..\samples\hello_world\src\hello_world_win.json
    

Output tipico

L'esempio seguente mostra l'output scritto nel file di log dall'esempio Hello World. L'output è formattato per migliorare la leggibilità:

[{
    "time": "Mon Apr 11 13:48:07 2016",
    "content": "Log started"
}, {
    "time": "Mon Apr 11 13:48:48 2016",
    "properties": {
        "helloWorld": "from Azure IoT Gateway SDK simple sample!"
    },
    "content": "aGVsbG8gd29ybGQ="
}, {
    "time": "Mon Apr 11 13:48:55 2016",
    "properties": {
        "helloWorld": "from Azure IoT Gateway SDK simple sample!"
    },
    "content": "aGVsbG8gd29ybGQ="
}, {
    "time": "Mon Apr 11 13:49:01 2016",
    "properties": {
        "helloWorld": "from Azure IoT Gateway SDK simple sample!"
    },
    "content": "aGVsbG8gd29ybGQ="
}, {
    "time": "Mon Apr 11 13:49:04 2016",
    "content": "Log stopped"
}]

Frammenti di codice

In questa sezione vengono descritte alcune sezioni chiave del codice nell'esempio hello_world.

Creazione del gateway IoT Edge

È necessario implementare un processo del gateway. Questo programma crea l'infrastruttura interna, ovvero il broker, carica i moduli di IoT Edge e configura il processo del gateway. IoT Edge specifica la funzione Gateway_Create_From_JSON per consentire di avviare un gateway da un file JSON. Per usare la funzione Gateway_Create_From_JSON, chiamarla dal percorso di un file JSON che specifica i moduli di IoT Edge da caricare.

È possibile trovare il codice per il processo del gateway nell'esempio Hello World nel file main.c. Per migliorare la leggibilità, il frammento di codice seguente illustra una versione abbreviata del codice del processo del gateway. Questo programma di esempio crea un gateway e quindi attende che l'utente prema INVIO prima di rimuove il gateway.

int main(int argc, char** argv)
{
    GATEWAY_HANDLE gateway;
    if ((gateway = Gateway_Create_From_JSON(argv[1])) == NULL)
    {
        printf("failed to create the gateway from JSON\n");
    }
    else
    {
        printf("gateway successfully created from JSON\n");
        printf("gateway shall run until ENTER is pressed\n");
        (void)getchar();
        Gateway_LL_Destroy(gateway);
    }
    return 0;
}

Il file di impostazioni JSON contiene un elenco di moduli di IoT Edge da caricare e i collegamenti tra i moduli. Ogni modulo di IoT Edge deve specificare:

  • name: nome univoco per il modulo.
  • loader: un caricatore che riesca a caricare il modulo appropriato. I caricatori sono punti di estensione per il caricamento di tipi diversi di moduli. IoT Edge offre caricatori da usare con moduli scritti in C, Node.js, Java e .NET nativi. L'esempio Hello World usa solo il caricatore C nativo perché tutti i moduli in questo esempio sono librerie dinamiche scritte in C. Per altre informazioni su come usare moduli di IoT Edge scritti in linguaggi diversi, vedere gli esempi Node.js, Java o .NET.

    • name: nome del caricatore usato per caricare il modulo.
    • entrypoint: percorso della libreria che contiene il modulo. In Linux questa libreria è un file con estensione so, in Windows è un file con estensione dll. Il punto di ingresso è specifico per il tipo di caricatore in uso. Il punto di ingresso del caricatore Node.js è un file con estensione js. Il punto di ingresso del caricatore Java è un percorso di classe e un nome di classe. Il punto di ingresso del caricatore .NET è un nome di assembly e un nome di classe.
  • args: le informazioni di configurazione necessarie per il modulo.

Il codice seguente illustra l'uso di JSON per dichiarare tutti i moduli di IoT Edge per l'esempio Hello World in Linux. L'uso di argomenti nei moduli dipende dalla loro struttura. In questo esempio, il modulo logger include un argomento che specifica il percorso del file di output e il modulo hello_world non include nessun argomento.

"modules" :
[
    {
        "name" : "logger",
        "loader": {
          "name": "native",
          "entrypoint": {
            "module.path": "./modules/logger/liblogger.so"
        }
        },
        "args" : {"filename":"log.txt"}
    },
    {
        "name" : "hello_world",
        "loader": {
          "name": "native",
          "entrypoint": {
            "module.path": "./modules/hello_world/libhello_world.so"
        }
        },
        "args" : null
    }
]

Il file JSON contiene anche i collegamenti tra i moduli che vengono passati al broker. Un collegamento ha due proprietà:

  • source: il nome di un modulo dalla sezione modules oppure \*.
  • sink: il nome di un modulo dalla sezione modules.

Ogni collegamento definisce una route messaggi e una direzione. I messaggi dal modulo source vengono recapitati al modulo sink. È possibile impostare il modulo source su \*, che indica che il modulo sink riceve messaggi da qualsiasi modulo.

Il codice seguente illustra l'uso di JSON per configurare i collegamenti tra i moduli usati nell'esempio hello_world in Linux. Tutti i messaggi generati dal modulo hello_world vengono utilizzati dal modulo logger.

"links":
[
    {
        "source": "hello_world",
        "sink": "logger"
    }
]

Pubblicazione dei messaggi del modulo hello_world

Il codice usato dal modulo hello_world per pubblicare i messaggi è disponibile nel file "hello_world.c". Il frammento di codice seguente riporta una versione modificata, per una maggior leggibilità, in cui sono stati aggiunti commenti ed è stata rimossa parte del codice per la gestione degli errori:

int helloWorldThread(void *param)
{
    // create data structures used in function.
    HELLOWORLD_HANDLE_DATA* handleData = param;
    MESSAGE_CONFIG msgConfig;
    MAP_HANDLE propertiesMap = Map_Create(NULL);

    // add a property named "helloWorld" with a value of "from Azure IoT
    // Gateway SDK simple sample!" to a set of message properties that
    // will be appended to the message before publishing it. 
    Map_AddOrUpdate(propertiesMap, "helloWorld", "from Azure IoT Gateway SDK simple sample!")

    // set the content for the message
    msgConfig.size = strlen(HELLOWORLD_MESSAGE);
    msgConfig.source = HELLOWORLD_MESSAGE;

    // set the properties for the message
    msgConfig.sourceProperties = propertiesMap;

    // create a message based on the msgConfig structure
    MESSAGE_HANDLE helloWorldMessage = Message_Create(&msgConfig);

    while (1)
    {
        if (handleData->stopThread)
        {
            (void)Unlock(handleData->lockHandle);
            break; /*gets out of the thread*/
        }
        else
        {
            // publish the message to the broker
            (void)Broker_Publish(handleData->brokerHandle, helloWorldMessage);
            (void)Unlock(handleData->lockHandle);
        }

        (void)ThreadAPI_Sleep(5000); /*every 5 seconds*/
    }

    Message_Destroy(helloWorldMessage);

    return 0;
}

Elaborazione dei messaggi del modulo hello_world

Il modulo hello_world non elabora mai messaggi pubblicati da altri moduli di IoT Edge nel broker. L'implementazione del callback dei messaggi nel modulo hello_world è quindi una funzione no-op.

static void HelloWorld_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE messageHandle)
{
    /* No action, HelloWorld is not interested in any messages. */
}

Elaborazione e pubblicazione dei messaggi del modulo di logger

Il modulo logger riceve messaggi dal broker e li scrive in un file, ma non pubblica mai messaggi. Il codice del modulo logger quindi non chiama mai la funzione Broker_Publish.

La funzione Logger_Receive nel file logger.c è il callback che viene richiamato dal broker per recapitare i messaggi al modulo logger. Il frammento di codice seguente riporta una versione modificata, per una maggior leggibilità, in cui sono stati aggiunti commenti ed è stata rimossa parte del codice per la gestione degli errori:

static void Logger_Receive(MODULE_HANDLE moduleHandle, MESSAGE_HANDLE messageHandle)
{

    time_t temp = time(NULL);
    struct tm* t = localtime(&temp);
    char timetemp[80] = { 0 };

    // Get the message properties from the message
    CONSTMAP_HANDLE originalProperties = Message_GetProperties(messageHandle); 
    MAP_HANDLE propertiesAsMap = ConstMap_CloneWriteable(originalProperties);

    // Convert the collection of properties into a JSON string
    STRING_HANDLE jsonProperties = Map_ToJSON(propertiesAsMap);

    //  base64 encode the message content
    const CONSTBUFFER * content = Message_GetContent(messageHandle);
    STRING_HANDLE contentAsJSON = Base64_Encode_Bytes(content->buffer, content->size);

    // Start the construction of the final string to be logged by adding
    // the timestamp
    STRING_HANDLE jsonToBeAppended = STRING_construct(",{\"time\":\"");
    STRING_concat(jsonToBeAppended, timetemp);

    // Add the message properties
    STRING_concat(jsonToBeAppended, "\",\"properties\":"); 
    STRING_concat_with_STRING(jsonToBeAppended, jsonProperties);

    // Add the content
    STRING_concat(jsonToBeAppended, ",\"content\":\"");
    STRING_concat_with_STRING(jsonToBeAppended, contentAsJSON);
    STRING_concat(jsonToBeAppended, "\"}]");

    // Write the formatted string
    LOGGER_HANDLE_DATA *handleData = (LOGGER_HANDLE_DATA *)moduleHandle;
    addJSONString(handleData->fout, STRING_c_str(jsonToBeAppended);
}

Passaggi successivi

In questo articolo è stato eseguito un gateway IoT Edge semplice che ha scritto messaggi in un file di log. Per eseguire un esempio che invia messaggi all'hub IoT, vedere IoT Edge: inviare messaggi da dispositivo a cloud con un dispositivo simulato usando Linux o IoT Edge: inviare messaggi da dispositivo a cloud con un dispositivo simulato usando Windows.