Conectar seu dispositivo ao acelerador da solução de monitoramento remoto (Windows)

Neste tutorial, você implementará um dispositivo Resfriador que envia a seguinte telemetria para o acelerador de solução de Monitoramento Remoto:

  • Temperatura
  • Pressão
  • Umidade

Para simplificar, o código gera valores de telemetria de exemplo para o Resfriador. Você pode estender o exemplo conectando sensores reais ao dispositivo e enviando telemetria real.

O dispositivo de exemplo também:

  • Envia os metadados à solução para descrever seus recursos.
  • Responde às ações disparadas da página Dispositivos na solução.
  • Responde às alterações de configuração da página Dispositivos na solução.

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

Antes de começar

Antes de escrever qualquer código para o dispositivo, implante o acelerador de solução de Monitoramento Remoto e adicione um novo dispositivo real à solução.

Implantar o acelerador de solução de Monitoramento Remoto

O dispositivo Resfriador criado neste tutorial envia os dados para uma instância do acelerador de solução de Monitoramento Remoto. Caso ainda não tenha provisionado o acelerador de solução de monitoramento remoto em sua conta do Azure, confira Implantar o acelerador de solução de monitoramento remoto

Quando o processo de implantação para a solução de Monitoramento Remoto for concluído, clique em Inicialização para abrir o painel da solução em seu navegador.

O painel da solução

Adicionar o dispositivo à solução de Monitoramento Remoto

Observação

Se você já adicionou um dispositivo em sua solução, poderá ignorar esta etapa. No entanto, a próxima etapa exige a cadeia de conexão do dispositivo. É possível recuperar a cadeia de conexão de um dispositivo a partir do Portal do Azure ou usando uma ferramente da CLI az iot.

Para que um dispositivo se conecte ao acelerador de solução, ele precisa se identificar para o Hub IoT usando credenciais válidas. É possível salvar a cadeia de conexão do dispositivo contendo essas credenciais, quando você adicionar o dispositivo à solução. Você incluirá a cadeia de conexão de dispositivo no seu aplicativo cliente, mais adiante neste tutorial.

Para adicionar um dispositivo à sua solução de Monitoramento Remoto, conclua as seguintes etapas na página Gerenciador de Dispositivos na solução:

  1. Escolha + Novo dispositivo e, em seguida, escolha Real como o Tipo de dispositivo:

    Adicionar um dispositivo real

  2. Insira Physical-chiller como a ID do dispositivo. Escolha as opções Chave Simétrica e Gerar chaves automaticamente:

    Escolher as opções do dispositivo

  3. Escolha Aplicar. Em seguida, anote os valores ID do dispositivo, Chave Primária e Cadeia de conexão – Chave primária:

    Recuperar credenciais

Agora, você adicionou um dispositivo real ao acelerador de solução de Monitoramento Remoto e anotou a cadeia de conexão do dispositivo. Nas seções a seguir, você implementará o aplicativo cliente que usa a cadeia de conexão do dispositivo para conectar-se à solução.

O aplicativo cliente implementa o modelo de dispositivo Resfriador interno. Um modelo de dispositivo de acelerador de solução especifica o seguinte sobre um dispositivo:

  • As propriedades que o dispositivo relata à solução. Por exemplo, um dispositivo Resfriador relata informações sobre seu firmware e sua localização.
  • Os tipos de telemetria que o dispositivo envia para a solução. Por exemplo, um dispositivo Resfriador envia valores de temperatura, umidade e pressão.
  • Os métodos que você pode agendar na solução para serem executados no dispositivo. Por exemplo, um dispositivo Resfriador deve implementar os métodos Reiniciar, FirmwareUpdate, EmergencyValveRelease e IncreasePressure.

Este tutorial mostra como conectar um dispositivo real ao acelerador de solução de Monitoramento Remoto.

Assim como acontece com a maioria dos aplicativos inseridos que são executados em dispositivos restritos, o código do cliente do aplicativo do dispositivo é escrito em C. Neste tutorial, você cria o aplicativo cliente do dispositivo em um computador com Windows.

Se você preferir simular um dispositivo, consulte Criar e testar um novo dispositivo simulado.

Pré-requisitos

Para concluir as etapas neste guia de instruções, siga as etapas em configurar o ambiente de desenvolvimento Windows para adicionar as bibliotecas e ferramentas de desenvolvimento necessárias para seu computador Windows.

Exibir o código

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

Baixar o código-fonte e preparar o projeto

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

O exemplo está localizado na pasta samples/solutions/remote_monitoring_client.

Abra o arquivo remote_monitoring.c na pasta samples/solutions/remote_monitoring_client em um editor de texto.

Passo a passo do código

Esta seção descreve algumas das partes principais do código de exemplo e explica como se relacionam com o acelerador de solução de monitoramento remoto.

O snippet a seguir mostra como as propriedades relatadas que descrevem os recursos do dispositivo são definidas. Essas 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 com suporte no dispositivo.
  • 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;

O exemplo inclui uma função serializeToJson que serializa essa estrutura de dados usando a biblioteca Parson.

O exemplo inclui várias funções de retorno de chamada que imprimem informações no console enquanto o cliente interage com o acelerador de solução:

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback

O snippet a seguir mostra a função device_method_callback. Essa função determina a ação a ser tomada quando uma chamada de método é recebida do acelerador de solução. A função recebe uma referência para a estrutura de dados do Resfriador no parâmetro userContextCallback. O valor de userContextCallback é definido quando a função de retorno de chamada é 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 de firmware, o exemplo desserializa a carga JSON e inicia um thread de segundo plano para concluir o processo de atualização. O snippet a seguir mostra o do_firmware_update que executa no 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;
}

O snippet a seguir mostra como o cliente envia uma mensagem de telemetria ao 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:

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 no exemplo:

  • Inicializa e desliga o subsistema SDK.
  • Inicializa a estrutura de dados do Resfriador.
  • Envia as propriedades relatadas ao acelerador de solução.
  • Configura a função de retorno de chamada 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;
}

Criar e executar a amostra

  1. Edite o arquivo remote_monitoring para substituir <connectionstring> pela cadeia de conexão do dispositivo que você anotou no início deste guia de instruções, quando adicionou um dispositivo do acelerador de solução.

  2. Siga as etapas em Criar o SDK C no Windows para compilar o SDK e o aplicativo cliente de monitoramento remoto.

  3. No prompt de comando usado para criar a solução, execute:

    samples\solutions\remote_monitoring_client\Release\remote_monitoring_client.exe
    

    O console exibe mensagens, como:

    • O aplicativo envia a telemetria de amostra para o acelerador de solução.
    • Responde a métodos invocados do painel de solução.

Exibir telemetria de dispositivo

Você pode exibir a telemetria enviada do dispositivo na página Gerenciador de Dispositivos na solução.

  1. Selecione o dispositivo que você provisionou na lista de dispositivos na página Gerenciador de Dispositivos. Um painel exibe informações sobre seu dispositivo, incluindo um gráfico de telemetria do dispositivo:

    Consultar detalhes do dispositivo

  2. Escolha Pressão para alterar a exibição de telemetria:

    Exibir telemetria de pressão

  3. Para exibir informações de diagnóstico sobre o dispositivo, role para baixo até Diagnóstico:

    Exibir diagnóstico do dispositivo

Agir no dispositivo

Para invocar métodos nos seus dispositivos, use a página Gerenciador de Dispositivos na solução de Monitoramento Remoto. Por exemplo, na solução de Monitoramento Remoto, os dispositivos Resfriadores implementam um método Reiniciar.

  1. Escolha Dispositivos para navegar até a página Gerenciador de Dispositivos na solução.

  2. Selecione o dispositivo que você provisionou na lista de dispositivos na página Gerenciador de Dispositivos:

    Selecione seu dispositivo real

  3. Para exibir uma lista dos métodos que você pode chamar no seu dispositivo, escolha Trabalhos e, em seguida, Métodos. Para agendar um método a ser executado em vários dispositivos, você pode selecionar vários dispositivos na lista. O painel Trabalhos mostra os tipos de método comuns a todos os dispositivos selecionados.

  4. Escolha Reinicializar, defina o nome do trabalho como RebootPhysicalChiller e, em seguida, escolha Aplicar:

    Agendar a atualização do firmware

  5. Uma sequência de mensagens é exibida no console executando o código de dispositivo, enquanto o dispositivo simulado manipula o método.

Observação

Para acompanhar o status do trabalho na solução, escolha Exibir Status do Trabalho.

Próximas etapas

O artigo Personalizar o acelerador de solução de Monitoramento Remoto pré-configurado descreve algumas maneiras de personalizar o acelerador da solução.