Conexión del dispositivo Raspberry Pi al acelerador de la solución de supervisión remota (C)

En este tutorial, implementará un dispositivo Refrigerador que envía la siguiente telemetría al acelerador de soluciones de supervisión remota:

  • Temperatura
  • Presión
  • Humedad

Para simplificar, el código genera valores de telemetría de ejemplo para el Chiller. Puede ampliar el ejemplo conectando sensores reales al dispositivo y enviando telemetría real.

El dispositivo de ejemplo también:

  • Envía los metadatos a la solución para describir sus funcionalidades.
  • Responde a las acciones que se desencadenan desde la página Dispositivos de la solución.
  • Responde a los cambios de configuración que se envían desde la página Dispositivos de la solución.

Para completar este tutorial, deberá tener una cuenta activa de Azure. En caso de no tener cuenta, puede crear una de evaluación gratuita en tan solo unos minutos. Para obtener más información, consulte Evaluación gratuita de Azure.

Antes de comenzar

Antes de escribir ningún código para el dispositivo, implemente el acelerador de soluciones de supervisión remota y agregue un nuevo dispositivo real a la solución.

Implementación del acelerador de soluciones de supervisión remota

El dispositivo Refrigerador que se crea en este tutorial envía datos a una instancia del acelerador de soluciones de supervisión remota. Si todavía no ha aprovisionado el acelerador de soluciones de supervisión remota en su cuenta de Azure, consulte Implementación del acelerador de soluciones de supervisión remota.

Cuando finalice el proceso de implementación para la solución de supervisión remota, haga clic en Iniciar para abrir el panel de la solución en el explorador.

El panel de soluciones

Adición del dispositivo a la solución de supervisión remota

Nota

Si ya ha agregado un dispositivo a la solución, puede omitir este paso. Sin embargo, el paso siguiente requiere la cadena de conexión del dispositivo. Puede recuperar la cadena de conexión de un dispositivo desde Azure Portal o con la herramienta CLI az iot.

Para que un dispositivo se conecte al acelerador de soluciones, debe identificarse en IoT Hub con credenciales válidas. Tendrá la oportunidad de guardar la cadena de conexión del dispositivo que contiene estas credenciales cuando agregue el dispositivo a la solución. Más adelante en este tutorial incluirá esta cadena en su aplicación cliente.

Para agregar un dispositivo a la solución de supervisión remota, realice los pasos siguientes en la página Explorador de dispositivos de la solución:

  1. Elija + Nuevo dispositivo y, luego, Real en Tipo de dispositivo:

    Adición de un dispositivo real

  2. Escriba Refrigerador físico como identificador de dispositivo. Elija las opciones Clave simétrica y Generar claves automáticamente:

    Elección de las opciones de dispositivo

  3. Elija Aplicar. A continuación, anote los valores de Id. de dispositivo, Clave principal y Cadena de conexión: clave principal:

    Recuperación de las credenciales

Ya ha agregado un dispositivo real al acelerador de soluciones de supervisión remota y ha anotado la cadena de conexión del dispositivo. En las siguientes secciones, implementará la aplicación cliente que utiliza la cadena de conexión del dispositivo para conectarse a la solución.

La aplicación cliente implementa el modelo de dispositivo Chiller integrado. Un modelo de dispositivo de acelerador de soluciones especifica lo siguiente acerca de un dispositivo:

  • Las propiedades que el dispositivo notifica a la solución. Por ejemplo, un dispositivo Chiller notifica información acerca de su ubicación y firmware.
  • Los tipos de telemetría que el dispositivo envía a la solución. Por ejemplo, un dispositivo Chiller envía los valores de temperatura, humedad y presión.
  • Los métodos que puede programar desde la solución para ejecutarse en el dispositivo. Por ejemplo, un dispositivo refrigerador debe implementar los métodos Reboot, FirmwareUpdate, EmergencyValveRelease e IncreasePressuree.

En este tutorial se muestra cómo conectar un dispositivo real al acelerador de soluciones de supervisión remota. Como sucede con la mayoría de aplicaciones insertadas que se ejecutan en dispositivos restringidos, el código cliente para la aplicación del dispositivo Raspberry Pi se escribe en C. En este tutorial, compilará la aplicación en un dispositivo Raspberry Pi que ejecuta el sistema operativo Raspbian.

Si prefiere simular un dispositivo, consulte Crear y probar un nuevo dispositivo simulado.

Requisitos de hardware

Un equipo de escritorio que permita conectarse remotamente a la línea de comandos en Raspberry Pi.

Kit de inicio de Microsoft IoT para Raspberry Pi 3 o componentes equivalentes. En este tutorial se usan los siguientes elementos del kit:

  • Raspberry Pi 3
  • Tarjeta MicroSD (con NOOBS)
  • Un cable USB mini
  • Un cable Ethernet

Requiere el software de escritorio

Necesita el cliente SSH en su máquina de escritorio para poder acceder de forma remota a la línea de comandos en su Raspberry Pi.

  • Windows no incluye ningún cliente SSH. Se recomienda usar PuTTY.
  • La mayoría de las distribuciones de Linux y Mac OS incluyen la utilidad de línea de comandos de SSH. Para más información, consulte SSH Using Linux or Mac OS (SSH cuando se usa Linux o Mac OS).

Software necesario de Raspberry Pi

En este artículo se supone que instaló la versión más reciente del sistema operativo Raspbian OS en un dispositivo Raspberry Pi.

Los pasos siguientes muestran cómo preparar el dispositivo Raspberry Pi para compilar una aplicación C que se conecta al acelerador de la solución:

  1. Conecte con su dispositivo Raspberry Pi mediante ssh. Para obtener más información, consulte SSH (Secure Shell) en el sitio web de Raspberry Pi.

  2. Use el siguiente comando para actualizar Raspberry Pi:

    sudo apt-get update
    
  3. Para completar los pasos descritos en esta guía de procedimientos, siga los pasos indicados en Set up a Linux development environment (Configurar un entorno de desarrollo de Linux) para agregar las bibliotecas y las herramientas de desarrollo necesarias a su Raspberry Pi.

Ver el código

El código de ejemplo que se usa en esta guía está disponible en el repositorio de GitHub de los SDK de C para Azure IoT.

Descarga del código fuente y preparación del proyecto

Para preparar el proyecto, clone o descargue el repositorio de SDK de C para Azure IoT en GitHub.

El ejemplo se encuentra en la carpeta samples/solutions/remote_monitoring_client.

Abra el archivo remote_monitoring.c de la carpeta samples/solutions/remote_monitoring_client en un editor de texto.

Tutorial de código

En esta sección se describen algunas de las partes principales del código de ejemplo y se explica cómo se relacionan con el acelerador de soluciones de supervisión remota.

El siguiente fragmento de código muestra cómo se definen las propiedades notificadas que describen las funcionalidades del dispositivo. Estas propiedades incluyen:

  • La ubicación del dispositivo para habilitar que el acelerador de soluciones agregue el dispositivo a la asignación.
  • La versión actual del firmware.
  • La lista de métodos que admite el dispositivo.
  • El esquema de los mensajes de telemetría enviados por el 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;

El ejemplo incluye una función serializeToJson que serializa esta estructura de datos mediante la biblioteca Parson.

El ejemplo incluye varias funciones de devolución de llamada que imprimen información en la consola cuando el cliente interactúa con el acelerador de soluciones:

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback

El siguiente fragmento de código muestra la función device_method_callback. Esta función determina la acción que se realiza cuando se recibe la llamada a un método desde el acelerador de soluciones. La función recibe una referencia a la estructura de datos de Chiller en el parámetro userContextCallback. El valor de userContextCallback se establece cuando la función de devolución de llamada se configura en la función 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;
}

Cuando el acelerador de soluciones llama al método de actualización del firmware, el ejemplo deserializa la carga de JSON e inicia un subproceso en segundo plano para completar el proceso de actualización. El siguiente fragmento de código muestra el do_firmware_update que se ejecuta en el subproceso:

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

El siguiente fragmento de código muestra la forma en que el cliente envía un mensaje de telemetría al acelerador de soluciones. Las propiedades del mensaje incluyen el esquema del mensaje que ayuda al acelerador de soluciones a mostrar la telemetría en el panel:

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 función main del ejemplo:

  • Inicializa y cierra el subsistema del SDK.
  • Inicializa la estructura de datos Chiller.
  • Envía las propiedades notificadas al acelerador de soluciones.
  • Configura la función de devolución de llamada del método del dispositivo.
  • Envía valores de telemetría simulados al acelerador de soluciones.
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;
}

Compilación y ejecución de la aplicación

En los pasos siguientes se describe cómo se puede usar CMake para compilar la aplicación cliente. La aplicación cliente de supervisión remota se compila como parte del proceso de compilación del SDK.

  1. Edite el archivo remote_monitoring.c para reemplazar <connectionstring> por la cadena de conexión de dispositivo que ha anotado al principio de esta guía paso a paso al agregar un dispositivo al acelerador de soluciones.

  2. Vaya a la raíz de la copia clonada del repositorio de SDK de C para Azure IoT y ejecute los comandos siguientes para compilar la aplicación cliente:

    mkdir cmake
    cd cmake
    cmake ../
    make
    
  3. Ejecute la aplicación cliente y envíe los datos telemetría a IoT Hub:

    ./samples/solutions/remote_monitoring_client/remote_monitoring_client
    

    La consola muestra mensajes como:

    • La aplicación envía datos de telemetría de ejemplo al acelerador de la solución.
    • Responde a los métodos invocados desde el panel de la solución.

Ver la telemetría de dispositivo

Puede ver la telemetría enviada desde el dispositivo en la página Explorador de dispositivos de la solución.

  1. Seleccione el dispositivo que ha aprovisionado en la lista de dispositivos de la página Explorador de dispositivos. Un panel muestra información sobre el dispositivo incluyendo un trazado de su telemetría:

    Ver detalles del dispositivo

  2. Elija Presión para cambiar la presentación de telemetría:

    Ver la telemetría de presión

  3. Para ver información de diagnóstico sobre el dispositivo, desplácese hacia abajo hasta Diagnósticos:

    Ver el diagnóstico de los dispositivos

Actúe en su dispositivo

Para invocar métodos en los dispositivos, use la página Explorador de dispositivos de la solución de supervisión remota. Por ejemplo, en los dispositivos Chiller de la solución de supervisión remota, implemente un método Reboot.

  1. Elija Dispositivos para ir hasta la página Explorador de dispositivos de la solución.

  2. Seleccione el dispositivo que ha aprovisionado en la lista de dispositivos de la página Explorador de dispositivos:

    Selección del dispositivo real

  3. Para mostrar una lista de los métodos que se pueden llamar en el dispositivo, elija Trabajos y, luego, Métodos. Para programar un trabajo para ejecutarse en varios dispositivos, puede seleccionarlos en la lista. El panel Trabajos muestra los tipos de métodos comunes a todos los dispositivos seleccionados.

  4. Elija Arrancar, establezca el nombre del trabajo en RebootPhysicalChiller y posteriormente elija Aplicar:

    Programación de la actualización de firmware

  5. Aparece una secuencia de mensajes en la consola donde se ejecuta el código del dispositivo cuando el dispositivo controla el método.

Nota

Para realizar el seguimiento del estado del trabajo en la solución, elija Ver estado de trabajo.

Pasos siguientes

En el artículo Personalización de la solución preconfigurada de supervisión remota se describen algunas maneras de personalizar el acelerador de soluciones.