Connettere il dispositivo all'acceleratore di soluzioni di monitoraggio remoto (Windows)

In questa esercitazione viene implementato un dispositivo Chiller che invia i dati di telemetria seguenti all'acceleratore di soluzioni Monitoraggio remoto:

  • Temperatura
  • Pressione
  • Umidità

Per semplicità, il codice genera valori di telemetria di esempio per il chiller. È possibile estendere l'esempio connettendo sensori reali al dispositivo e inviando dati di telemetria reali.

Il dispositivo di esempio inoltre:

  • Invia i metadati alla soluzione per descrivere le proprie funzionalità.
  • Risponde alle azioni attivate dalla pagina Dispositivi della soluzione.
  • Risponde alle modifiche di configurazione inviate dalla pagina Dispositivi della soluzione.

Per completare l'esercitazione, è necessario un account Azure attivo. Se non si dispone di un account Azure, è possibile creare un account di valutazione gratuito in pochi minuti. Per informazioni dettagliate, vedere la pagina relativa alla versione di valutazione gratuita di Azure.

Prima di iniziare

Prima di scrivere il codice per il dispositivo, distribuire l'acceleratore di soluzioni Monitoraggio remoto e aggiungere un nuovo dispositivo reale alla soluzione.

Distribuire l'acceleratore di soluzioni Monitoraggio remoto

Il dispositivo Chiller creato in questa esercitazione invia dati a un'istanza dell'acceleratore di soluzioni Monitoraggio remoto. Se nel proprio account Azure non è già stato effettuato il provisioning dell'acceleratore di soluzioni Monitoraggio remoto, vedere Distribuire l'acceleratore di soluzioni Monitoraggio remoto

Al termine del processo di distribuzione della soluzione Monitoraggio remoto, fare clic su Avvia per aprire il dashboard della soluzione nel browser.

Dashboard della soluzione

Aggiungere il dispositivo alla soluzione Monitoraggio remoto

Nota

Se è già stato aggiunto un dispositivo nella soluzione, è possibile saltare questo passaggio. Per il passaggio successivo sarà però necessaria la stringa di connessione del dispositivo. È possibile recuperare la stringa di connessione di un dispositivo dal portale di Azure oppure usando lo strumento az iot dell'interfaccia della riga di comando.

Per connettere un dispositivo all'acceleratore di soluzioni, è necessario che identifichi se stesso nell'hub IoT mediante delle credenziali valide. Quando si aggiunge il dispositivo alla soluzione, si ha la possibilità di salvare la stringa di connessione del dispositivo che contiene queste credenziali. Le istruzioni per includere la stringa di connessione del dispositivo nell'applicazione client sono illustrate più avanti in questa esercitazione.

Per aggiungere un dispositivo alla soluzione di monitoraggio remoto, completare i passaggi seguenti nella pagina Esplora dispositivi della soluzione:

  1. Scegliere + Nuovo dispositivo, quindi scegliere Fisico come Tipo di dispositivo:

    Aggiungere un dispositivo reale

  2. Immettere Fisico-chiller come ID del dispositivo. Scegliere le opzioni Chiave simmetrica e Genera chiavi automaticamente:

    Scegliere le opzioni per il dispositivo

  3. Scegliere Applica. Prendere quindi nota dei valori di ID dispositivo, Chiave primaria, e Connection string primary key (Chiave primaria della stringa di connessione):

    Recuperare le credenziali

A questo punto è stato aggiunto un dispositivo reale all'acceleratore di soluzioni Monitoraggio remoto e ne è stata annotata la stringa di connessione. Nelle sezioni seguenti si implementerà l'applicazione client che usa la stringa di connessione del dispositivo per connettersi alla soluzione.

L'applicazione client implementa il modello di dispositivo Chiller predefinito. Un modello di dispositivo per l'acceleratore di soluzioni specifica le informazioni seguenti per il dispositivo:

  • Le proprietà che il dispositivo segnala alla soluzione. Ad esempio, un dispositivo Chiller segnala informazioni su firmware e posizione.
  • Tipi di dati di telemetria che il dispositivo invia alla soluzione. Ad esempio, un dispositivo Chiller invia valori di temperatura, umidità e pressione.
  • I metodi che possono essere pianificati nella soluzione per l'esecuzione nel dispositivo. Un dispositivo Chiller, ad esempio, deve implementare i metodi Reboot, FirmwareUpdate, EmergencyValveRelease e IncreasePressure.

Questa esercitazione mostra come connettere un dispositivo reale all'acceleratore di soluzioni di monitoraggio remoto.

Come per la maggior parte delle applicazioni incorporate in esecuzione su dispositivi vincolati, il codice client per l'applicazione per dispositivi è scritto in C. In questa esercitazione si compilerà l'applicazione client per dispositivi in un computer che esegue Windows.

Se si preferisce simulare un dispositivo, vedere Creare e testare un nuovo dispositivo simulato.

Prerequisiti

Per completare la procedura in questa guida pratica seguire i passaggi descritti in Configurare l'ambiente di sviluppo Windows per aggiungere le librerie e gli strumenti di sviluppo necessari per il computer Windows in uso.

Visualizzare il codice

Il codice di esempio usato in questa guida è disponibile nel repository GitHub degli SDK di Azure IoT per C.

Eseguire il download del codice sorgente e preparare il progetto

Per preparare il progetto, clonare il repository degli SDK di Azure IoT per C da GitHub.

L'esempio è disponibile nella cartella samples/solutions/remote_monitoring_client.

In un editor di testo aprire il file remote_monitoring.c nella cartella samples/solutions/remote_monitoring_client.

Procedura dettagliata per il codice

Questa sezione descrive alcune delle parti principali del codice di esempio e illustra come queste interagiscono con l'acceleratore di soluzioni Monitoraggio remoto.

Il frammento di codice seguente illustra come vengono definite le proprietà segnalate che descrivono le funzionalità del dispositivo. Queste proprietà includono:

  • La posizione del dispositivo per consentire all'acceleratore di soluzioni di aggiungere il dispositivo alla mappa.
  • La versione corrente del firmware.
  • L'elenco dei metodi supportati dal dispositivo.
  • Lo schema dei messaggi di telemetria inviati dal dispositivo.
typedef struct MESSAGESCHEMA_TAG
{
    char* name;
    char* format;
    char* fields;
} MessageSchema;

typedef struct TELEMETRYSCHEMA_TAG
{
    MessageSchema messageSchema;
} TelemetrySchema;

typedef struct TELEMETRYPROPERTIES_TAG
{
    TelemetrySchema temperatureSchema;
    TelemetrySchema humiditySchema;
    TelemetrySchema pressureSchema;
} TelemetryProperties;

typedef struct CHILLER_TAG
{
    // Reported properties
    char* protocol;
    char* supportedMethods;
    char* type;
    char* firmware;
    FIRMWARE_UPDATE_STATUS firmwareUpdateStatus;
    char* location;
    double latitude;
    double longitude;
    TelemetryProperties telemetry;

    // Manage firmware update process
    char* new_firmware_version;
    char* new_firmware_URI;
} Chiller;

L'esempio include una funzione serializeToJson che serializza questa struttura dei dati usando la libreria Parson.

L'esempio include diverse funzioni di callback che stampano informazioni nella console quando il client interagisce con l'acceleratore di soluzioni:

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback

Il frammento di codice seguente illustra la funzione device_method_callback. Questa funzione determina l'azione da intraprendere quando viene ricevuta una chiamata al metodo dall'acceleratore di soluzioni. La funzione riceve un riferimento alla struttura dei dati Chiller nel parametro userContextCallback. Il valore di userContextCallback è impostato quando la funzione di callback è configurata nella funzione main:

static int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* response_size, void* userContextCallback)
{
    Chiller *chiller = (Chiller *)userContextCallback;

    int result;

    (void)printf("Direct method name:    %s\r\n", method_name);

    (void)printf("Direct method payload: %.*s\r\n", (int)size, (const char*)payload);

    if (strcmp("Reboot", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Rebooting\" }")
    }
    else if (strcmp("EmergencyValveRelease", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Releasing emergency valve\" }")
    }
    else if (strcmp("IncreasePressure", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Increasing pressure\" }")
    }
    else if (strcmp("FirmwareUpdate", method_name) == 0)
    {
        if (chiller->firmwareUpdateStatus != IDLE)
        {
            (void)printf("Attempt to invoke firmware update out of order\r\n");
            MESSAGERESPONSE(400, "{ \"Response\": \"Attempting to initiate a firmware update out of order\" }")
        }
        else
        {
            getFirmwareUpdateValues(chiller, payload);

            if (chiller->new_firmware_version != NULL && chiller->new_firmware_URI != NULL)
            {
                // Create a thread for the long-running firmware update process.
                THREAD_HANDLE thread_apply;
                THREADAPI_RESULT t_result = ThreadAPI_Create(&thread_apply, do_firmware_update, chiller);
                if (t_result == THREADAPI_OK)
                {
                    (void)printf("Starting firmware update thread\r\n");
                    MESSAGERESPONSE(201, "{ \"Response\": \"Starting firmware update thread\" }")
                }
                else
                {
                    (void)printf("Failed to start firmware update thread\r\n");
                    MESSAGERESPONSE(500, "{ \"Response\": \"Failed to start firmware update thread\" }")
                }
            }
            else
            {
                (void)printf("Invalid method payload\r\n");
                MESSAGERESPONSE(400, "{ \"Response\": \"Invalid payload\" }")
            }
        }
    }
    else
    {
        // All other entries are ignored.
        (void)printf("Method not recognized\r\n");
        MESSAGERESPONSE(400, "{ \"Response\": \"Method not recognized\" }")
    }

    return result;
}

Quando l'acceleratore di soluzioni chiama il metodo di aggiornamento del firmware, l'esempio deserializza il payload JSON e avvia un thread in background per completare il processo di aggiornamento. Il frammento di codice seguente illustra l'elemento do_firmware_update che viene eseguito sul thread:

/*
 This is a thread allocated to process a long-running device method call.
 It uses device twin reported properties to communicate status values
 to the Remote Monitoring solution accelerator.
*/
static int do_firmware_update(void *param)
{
    Chiller *chiller = (Chiller *)param;
    printf("Running simulated firmware update: URI: %s, Version: %s\r\n", chiller->new_firmware_URI, chiller->new_firmware_version);

    printf("Simulating download phase...\r\n");
    chiller->firmwareUpdateStatus = DOWNLOADING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating apply phase...\r\n");
    chiller->firmwareUpdateStatus = APPLYING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating reboot phase...\r\n");
    chiller->firmwareUpdateStatus = REBOOTING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    size_t size = strlen(chiller->new_firmware_version) + 1;
    (void)memcpy(chiller->firmware, chiller->new_firmware_version, size);

    chiller->firmwareUpdateStatus = IDLE;
    sendChillerReportedProperties(chiller);

    return 0;
}

Il frammento di codice seguente illustra come il client invia un messaggio di telemetria all'acceleratore di soluzioni. Le proprietà del messaggio includono lo schema del messaggio per consentire all'acceleratore di soluzioni di visualizzare i dati di telemetria nel dashboard:

static void send_message(IOTHUB_DEVICE_CLIENT_HANDLE handle, char* message, char* schema)
{
    IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(message);
    if (message_handle != NULL)
    {
        // Set system properties
        (void)IoTHubMessage_SetMessageId(message_handle, "MSG_ID");
        (void)IoTHubMessage_SetCorrelationId(message_handle, "CORE_ID");
        (void)IoTHubMessage_SetContentTypeSystemProperty(message_handle, "application%2fjson");
        (void)IoTHubMessage_SetContentEncodingSystemProperty(message_handle, "utf-8");

        // Set application properties
        MAP_HANDLE propMap = IoTHubMessage_Properties(message_handle);
        (void)Map_AddOrUpdate(propMap, "$$MessageSchema", schema);
        (void)Map_AddOrUpdate(propMap, "$$ContentType", "JSON");

        time_t now = time(0);
        struct tm* timeinfo;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996) /* Suppress warning about possible unsafe function in Visual Studio */
#endif
        timeinfo = gmtime(&now);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
        char timebuff[50];
        strftime(timebuff, 50, "%Y-%m-%dT%H:%M:%SZ", timeinfo);
        (void)Map_AddOrUpdate(propMap, "$$CreationTimeUtc", timebuff);

        IoTHubDeviceClient_SendEventAsync(handle, message_handle, send_confirm_callback, NULL);

        IoTHubMessage_Destroy(message_handle);
    }
}

La funzione main nell'esempio:

  • Inizializza e arresta il sottosistema SDK.
  • Inizializza la struttura dei dati Chiller.
  • Invia le proprietà segnalate all'acceleratore di soluzioni.
  • Configura la funzione di callback del metodo del dispositivo.
  • Invia i valori dei dati di telemetria simulati all'acceleratore di soluzioni.
int main(void)
{
    srand((unsigned int)time(NULL));
    double minTemperature = 50.0;
    double minPressure = 55.0;
    double minHumidity = 30.0;
    double temperature = 0;
    double pressure = 0;
    double humidity = 0;

    (void)printf("This sample simulates a Chiller device connected to the Remote Monitoring solution accelerator\r\n\r\n");

    // Used to initialize sdk subsystem
    (void)IoTHub_Init();

    (void)printf("Creating IoTHub handle\r\n");
    // Create the iothub handle here
    device_handle = IoTHubDeviceClient_CreateFromConnectionString(connectionString, MQTT_Protocol);
    if (device_handle == NULL)
    {
        (void)printf("Failure creating IotHub device. Hint: Check your connection string.\r\n");
    }
    else
    {
        // Setting connection status callback to get indication of connection to iothub
        (void)IoTHubDeviceClient_SetConnectionStatusCallback(device_handle, connection_status_callback, NULL);

        Chiller chiller;
        memset(&chiller, 0, sizeof(Chiller));
        chiller.protocol = "MQTT";
        chiller.supportedMethods = "Reboot,FirmwareUpdate,EmergencyValveRelease,IncreasePressure";
        chiller.type = "Chiller";
        size_t size = strlen(initialFirmwareVersion) + 1;
        chiller.firmware = malloc(size);
        if (chiller.firmware == NULL)
        {
            (void)printf("Chiller Firmware failed to allocate memory.\r\n");
        }
        else
        {
            memcpy(chiller.firmware, initialFirmwareVersion, size);
            chiller.firmwareUpdateStatus = IDLE;
            chiller.location = "Building 44";
            chiller.latitude = 47.638928;
            chiller.longitude = -122.13476;
            chiller.telemetry.temperatureSchema.messageSchema.name = "chiller-temperature;v1";
            chiller.telemetry.temperatureSchema.messageSchema.format = "JSON";
            chiller.telemetry.temperatureSchema.messageSchema.fields = "{\"temperature\":\"Double\",\"temperature_unit\":\"Text\"}";
            chiller.telemetry.humiditySchema.messageSchema.name = "chiller-humidity;v1";
            chiller.telemetry.humiditySchema.messageSchema.format = "JSON";
            chiller.telemetry.humiditySchema.messageSchema.fields = "{\"humidity\":\"Double\",\"humidity_unit\":\"Text\"}";
            chiller.telemetry.pressureSchema.messageSchema.name = "chiller-pressure;v1";
            chiller.telemetry.pressureSchema.messageSchema.format = "JSON";
            chiller.telemetry.pressureSchema.messageSchema.fields = "{\"pressure\":\"Double\",\"pressure_unit\":\"Text\"}";

            sendChillerReportedProperties(&chiller);

            (void)IoTHubDeviceClient_SetDeviceMethodCallback(device_handle, device_method_callback, &chiller);

            while (1)
            {
                temperature = minTemperature + ((double)(rand() % 10) + 5);
                pressure = minPressure + ((double)(rand() % 10) + 5);
                humidity = minHumidity + ((double)(rand() % 20) + 5);

                if (chiller.firmwareUpdateStatus == IDLE)
                {
                    (void)printf("Sending sensor value Temperature = %f %s,\r\n", temperature, "F");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"temperature\":%.2f,\"temperature_unit\":\"F\"}", temperature);
                    send_message(device_handle, msgText, chiller.telemetry.temperatureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Pressure = %f %s,\r\n", pressure, "psig");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"pressure\":%.2f,\"pressure_unit\":\"psig\"}", pressure);
                    send_message(device_handle, msgText, chiller.telemetry.pressureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Humidity = %f %s,\r\n", humidity, "%");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"humidity\":%.2f,\"humidity_unit\":\"%%\"}", humidity);
                    send_message(device_handle, msgText, chiller.telemetry.humiditySchema.messageSchema.name);
                }

                ThreadAPI_Sleep(5000);
            }

            (void)printf("\r\nShutting down\r\n");

            // Clean up the iothub sdk handle and free resources
            IoTHubDeviceClient_Destroy(device_handle);
            free(chiller.firmware);
            free(chiller.new_firmware_URI);
            free(chiller.new_firmware_version);
        }
    }
    // Shutdown the sdk subsystem
    IoTHub_Deinit();

    return 0;
}

Compilare ed eseguire l'esempio

  1. Modificare il file remote_monitoring.c per sostituire <connectionstring> con la stringa di connessione del dispositivo annotata all'inizio di questa guida pratica, al momento dell'aggiunta di un dispositivo all'acceleratore della soluzione.

  2. Seguire i passaggi descritti nell'argomento relativo alla compilazione dell'SDK C in Windows per compilare l'SDK e l'applicazione client di monitoraggio remoto.

  3. Al prompt dei comandi usato per compilare la soluzione, eseguire:

    samples\solutions\remote_monitoring_client\Release\remote_monitoring_client.exe
    

    La console visualizza messaggi relativi alle operazioni seguenti:

    • L'applicazione invia dati di telemetria di esempio all'acceleratore della soluzione.
    • Risponde ai metodi richiamati dal dashboard della soluzione.

Visualizzare la telemetria dei dispositivi

È possibile visualizzare i dati di telemetria inviati dal dispositivo nella pagina Esplora dispositivi della soluzione.

  1. Selezionare il dispositivo del quale è stato effettuato il provisioning nell'elenco dei dispositivi della pagina Esplora dispositivi. Un pannello visualizza le informazioni sul dispositivo, incluso un tracciato dei dati di telemetria del dispositivo:

    Vedere i dettagli del dispositivo

  2. Scegliere Pressione per modificare la visualizzazione dei dati di telemetria:

    Visualizzare i dati di telemetria per la pressione

  3. Per visualizzare le informazioni di diagnostica sul dispositivo, scorrere fino a Diagnostica:

    Visualizzazione della diagnostica del dispositivo

Agire sul dispositivo

Per richiamare i metodi nei dispositivi, usare la pagina Esplora dispositivi della soluzione di monitoraggio remoto. Nella soluzione di monitoraggio remoto, i dispositivi Chiller implementano ad esempio un metodo Reboot.

  1. Scegliere Dispositivi per passare alla pagina Esplora dispositivi della soluzione.

  2. Selezionare il dispositivo del quale è stato effettuato il provisioning nell'elenco dei dispositivi della pagina Esplora dispositivi:

    Selezionare il dispositivo reale

  3. Per visualizzare un elenco dei metodi che è possibile chiamare per un dispositivo, scegliere Jobs (Processi), quindi Metodi. Per pianificare un processo in modo che venga eseguito su più dispositivi, è possibile selezionare più dispositivi nell'elenco. Il pannello Jobs (Processi) visualizza i tipi di metodi comuni a tutti i dispositivi selezionati.

  4. Scegliere Reboot, impostare il nome processo su RebootPhysicalChiller e quindi fare clic su Applica:

    Pianificare l'aggiornamento del firmware

  5. Nella console che esegue il codice del dispositivo mentre il dispositivo simulato gestisce il metodo viene visualizzata una sequenza di messaggi.

Nota

Per tenere traccia dello stato del processo nella soluzione, scegliere View Job Status (Visualizza stato del processo).

Passaggi successivi

L'articolo Personalizzare l'acceleratore di soluzioni Monitoraggio remoto descrive alcuni modi per personalizzare l'acceleratore di soluzioni.