Ligue o seu dispositivo ao acelerador de solução de monitorização remota (Linux)

Neste tutorial, implementa um dispositivo Chiller que envia a seguinte telemetria para o acelerador de solução de monitorização remota:

  • Temperatura
  • Pressão
  • Humidade

Para simplificar, o código gera valores de telemetria de amostra para o Refrigerador. Pode estender a amostra ligando sensores reais ao seu dispositivo e enviando telemetria real.

O dispositivo de amostra também:

  • Envia metadados para a solução para descrever as suas capacidades.
  • Responde às ações desencadeadas a partir da página dispositivos na solução.
  • Responde às alterações de configuração enviadas a partir da página Dispositivos na solução.

Para concluir este tutorial, precisa de uma conta ativa do Azure. Se não tiver uma conta, pode criar uma de avaliação gratuita em apenas alguns minutos. Para obter mais detalhes, consulte Avaliação Gratuita do Azure.

Antes de começar

Antes de escrever qualquer código para o seu dispositivo, coloque o acelerador de solução de Monitorização Remota e adicione um novo dispositivo real à solução.

Implemente o seu acelerador de solução de monitorização remota

O dispositivo Chiller que cria neste tutorial envia dados para uma instância do acelerador de solução de monitorização remota . Se ainda não disponibilizou o acelerador de solução de monitorização remota na sua conta Azure, consulte implementar o acelerador de solução de monitorização remota

Quando o processo de implementação da solução de Monitorização Remota terminar, clique em Lançamento para abrir o painel de instrumentos de solução no seu navegador.

O painel de solução

Adicione o seu dispositivo à solução de Monitorização Remota

Nota

Se já adicionou um dispositivo na sua solução, pode saltar este passo. No entanto, o passo seguinte requer a ligação do dispositivo. Pode recuperar a cadeia de ligação de um dispositivo a partir do portal do Azure ou utilizando a ferramenta Az iot CLI.

Para que um dispositivo se conecte ao acelerador de solução, deve identificar-se para Hub IoT utilizando credenciais válidas. Tem a oportunidade de guardar a cadeia de ligação do dispositivo que contém estas credenciais quando adiciona o dispositivo à solução. Inclui a cadeia de ligação do dispositivo na aplicação do seu cliente mais tarde neste tutorial.

Para adicionar um dispositivo à sua solução de Monitorização Remota, complete os seguintes passos na página do Explorador do Dispositivo na solução:

  1. Escolha + novo dispositivo e, em seguida, escolha Real como o tipo de Dispositivo:

    Adicione um dispositivo real

  2. Introduza o refrigerador físico como o ID do dispositivo. Escolha a chave simétrica e gere as opções de teclas automáticas :

    Escolha as opções do dispositivo

  3. Escolha Aplicar. Em seguida, tome nota dos valores primários de ID do dispositivo, chave primária e ligação :

    Recuperar credenciais

Adicionou agora um dispositivo real ao acelerador de solução de monitorização remota e observou a sua cadeia de ligação ao dispositivo. Nas seguintes secções, implementa a aplicação do cliente que utiliza o fio de ligação do dispositivo para se ligar à sua solução.

A aplicação do cliente implementa o modelo de dispositivo Chiller incorporado. Um modelo de dispositivo acelerador de solução especifica o seguinte sobre um dispositivo:

  • As propriedades que o dispositivo reporta à solução. Por exemplo, um dispositivo Chiller informa informações sobre o seu firmware e localização.
  • Os tipos de telemetria que o dispositivo envia para a solução. Por exemplo, um dispositivo Chiller envia valores de temperatura, humidade e pressão.
  • Os métodos que pode agendar a partir da solução para executar no dispositivo. Por exemplo, um dispositivo Chiller deve implementar métodos Reboot, FirmwareUpdate, EmergencyValveRelease e IncreasePressure .

Este tutorial mostra-lhe como ligar um dispositivo real ao acelerador de solução de monitorização remota.

Tal como acontece com a maioria das aplicações incorporadas que funcionam em dispositivos constrangidos, o código do cliente para a aplicação do dispositivo está escrito em C. Neste tutorial, você constrói a aplicação numa máquina que funciona Ubuntu (Linux).

Se preferir simular um dispositivo, consulte criar e testar um novo dispositivo simulado.

Pré-requisitos

Para completar os passos neste guia de como fazer, precisa de um dispositivo que execute a versão Ubuntu 15.04 ou posterior. Antes de prosseguir, crie o seu ambiente de desenvolvimento Linux.

Ver o código

O código de amostra utilizado neste guia está disponível no repositório Azure IoT C SDKs GitHub.

Descarregue o código fonte e prepare o projeto

Para preparar o projeto, clone ou descarregue o repositório Azure IoT C SDKs do GitHub.

A amostra está localizada nas amostras/soluções/remote_monitoring_client pasta.

Abra o ficheiro remote_monitoring.c na pasta samples/solutions/remote_monitoring_client num editor de texto.

Instruções de código

Esta secção descreve algumas das partes-chave do código de amostra e explica como se relacionam com o acelerador de solução de monitorização remota.

O seguinte corte mostra como as propriedades relatadas que descrevem as capacidades do dispositivo são definidas. Estas propriedades incluem:

  • A localização do dispositivo para permitir que o acelerador de solução adicione o dispositivo ao mapa.
  • A versão atual do firmware.
  • A lista de métodos que o dispositivo suporta.
  • O esquema das mensagens de telemetria enviadas pelo 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;

A amostra inclui uma função serializeToJson que serializa esta estrutura de dados usando a biblioteca Parson.

A amostra inclui várias funções de retorno que imprimem informações para a consola à medida que o cliente interage com o acelerador de solução:

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback

O seguinte corte mostra a função device_method_callback . Esta função determina a ação a tomar quando uma chamada de método é recebida do acelerador de solução. A função recebe uma referência à estrutura de dados Chiller no parâmetro userContextCallback . O valor do userContextCallback é definido quando a função de retorno é configurada na função principal :

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 o acelerador de solução chama o método de atualização do firmware, a amostra deseriza a carga útil JSON e inicia um fio de fundo para completar o processo de atualização. O seguinte corte mostra o do_firmware_update que corre no fio:

/*
 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;
}

O seguinte corte mostra como o cliente envia uma mensagem de telemetria para o acelerador de solução. As propriedades da mensagem incluem o esquema de mensagem para ajudar o acelerador de solução a exibir a telemetria no painel de instrumentos:

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

A função principal da amostra:

  • Inicializa e desliga o subsistema SDK.
  • Inicializa a estrutura de dados Chiller .
  • Envia as propriedades reportadas para o acelerador de solução.
  • Configura a função de retorno do método do dispositivo.
  • Envia valores de telemetria simulados ao acelerador de solução.
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;
}

Compilar e executar a aplicação

Os passos seguintes descrevem como usar o CMake para construir a aplicação do cliente. A aplicação do cliente de monitorização remota é construída como parte do processo de construção do SDK.

  1. Edite o ficheiro remote_monitoring.c para substituir <connectionstring> pela cadeia de ligação do dispositivo que observou no início deste guia quando adicionou um dispositivo ao acelerador de solução.

  2. Navegue até à raiz da sua cópia clonada do repositório de repositório Azure IoT C SDKs e execute os seguintes comandos para construir a aplicação do cliente:

    mkdir cmake
    cd cmake
    cmake ../
    make
    
  3. Executar o pedido de cliente e enviar telemetria para Hub IoT:

    ./samples/solutions/remote_monitoring_client/remote_monitoring_client
    

    A consola apresenta mensagens como:

    • A aplicação envia telemetria de amostra para o acelerador de solução.
    • Responde aos métodos invocados do painel de instrumentos de solução.

Ver a telemetria do dispositivo

Pode ver a telemetria enviada do seu dispositivo na página Device Explorer na solução.

  1. Selecione o dispositivo que forvisionou na lista de dispositivos na página Device Explorer . Um painel apresenta informações sobre o seu dispositivo, incluindo um enredo da telemetria do dispositivo:

    Ver detalhes do dispositivo

  2. Escolha a pressão para alterar o visor da telemetria:

    Ver telemetria de pressão

  3. Para visualizar informações de diagnóstico sobre o seu dispositivo, desloque-se até ao Diagnóstico:

    Ver diagnósticos de dispositivos

Agir no seu dispositivo

Para invocar métodos nos seus dispositivos, utilize a página Explorer do Dispositivo na solução de Monitorização Remota. Por exemplo, na solução de monitorização remota, os dispositivos Chiller implementam um método Reboot .

  1. Escolha dispositivos para navegar para a página do Explorador de Dispositivos na solução.

  2. Selecione o dispositivo que alistou na lista de dispositivos na página Device Explorer :

    Selecione o seu verdadeiro dispositivo

  3. Para apresentar uma lista dos métodos que pode recorrer no seu dispositivo, escolha Jobs e, em seguida, Métodos. Para agendar um trabalho para executar em vários dispositivos, pode selecionar vários dispositivos na lista. O painel Jobs mostra os tipos de métodos comuns a todos os dispositivos selecionados.

  4. Escolha Reboot, desfie o nome de trabalho para RebootPhysicalChiller e, em seguida, escolha Aplicar:

    Agende a atualização do firmware

  5. Uma sequência de mensagens aparece na consola a executar o código do dispositivo enquanto o dispositivo simulado manuseia o método.

Nota

Para acompanhar o estado do trabalho na solução, escolha Ver Estado do Trabalho.

Passos seguintes

O artigo Personalize o acelerador de solução de monitorização remota descreve algumas formas de personalizar o acelerador de solução.