Compartir a través de


Tutorial: Creación y conexión de un aplicación cliente a la aplicación de Azure IoT Central

Importante

En este artículo se incluyen los pasos para conectar un dispositivo mediante una firma de acceso compartido, también denominada autenticación de clave simétrica. Este método de autenticación es cómodo para probar y evaluar, pero autenticar un dispositivo mediante certificados X.509 es un enfoque más seguro. Para obtener más información, consulte Procedimientos recomendados de > Seguridad de la conexión.

En este tutorial se muestra cómo puede conectar una aplicación cliente a la aplicación de Azure IoT Central. La aplicación simula el comportamiento de un dispositivo de control de la temperatura. Cuando la aplicación se conecta a IoT Central, envía el identificador del modelo de dispositivo de control de temperatura. IoT Central usa el identificador de modelo para recuperar el modelo de dispositivo y crear una plantilla de dispositivo automáticamente. Agregue vistas a la plantilla de dispositivo para permitir que un operador interactúe con un dispositivo.

En este tutorial, aprenderá a:

  • Crear y ejecutar el código del dispositivo y ver si se conecta a la aplicación de IoT Central.
  • Ver los datos de telemetría simulados que envía el dispositivo.
  • Agregar vistas personalizadas a una plantilla de dispositivo.
  • Publicar la plantilla de dispositivo.
  • Usar una vista para administrar las propiedades del dispositivo.
  • Llamar a un comando para controlar el dispositivo.

Examinar el código

Requisitos previos

Para completar los pasos de este tutorial, necesitará lo siguiente:

Para este tutorial se puede ejecutar Linux o Windows. Los comandos de shell de este tutorial siguen la convención de Linux para los separadores de ruta de acceso "/". Si utiliza Windows, asegúrese de cambiar estos separadores por "\".

Los requisitos previos difieren en función del sistema operativo:

Linux

En este tutorial se da por supuesto que usa Ubuntu Linux. Los pasos de este tutorial se han probado con Ubuntu 18.04.

Para realizar este tutorial en Linux, instale el siguiente software en el entorno de Linux local:

Instale GCC, Git, cmake y todas las dependencias necesarias con el comando apt-get:

sudo apt-get update
sudo apt-get install -y git cmake build-essential curl libcurl4-openssl-dev libssl-dev uuid-dev

Compruebe que la versión de cmake sea superior a 2.8.12 y que la versión de GCC sea superior a 4.4.7.

cmake --version
gcc --version

Windows

Para completar este tutorial en Windows, instale el siguiente software en el entorno Windows local:

Descargar el código

En este tutorial, se prepara un entorno de desarrollo que se puede usar para clonar y compilar el SDK de dispositivos de Azure IoT Hub para C.

Abra un símbolo del sistema en el directorio que prefiera. Ejecute el siguiente comando para clonar el repositorio de GitHub de las bibliotecas y los SDK de C de Azure IoT en esta ubicación:

git clone https://github.com/Azure/azure-iot-sdk-c.git
cd azure-iot-sdk-c
git submodule update --init

Esta operación puede tardar varios minutos en completarse.

Revisión del código

En la copia del SDK de Microsoft Azure IoT para C que descargó anteriormente, abra los archivos azure-iot-sdk-c/iothub_client/samples/pnp/pnp_temperature_controller/pnp_temperature_controller.c y azure-iot-sdk-c/iothub_client/samples/pnp/pnp_temperature_controller/pnp_thermostat_component.c en un editor de texto.

El ejemplo implementa el modelo de lenguaje de definición de gemelo digital del Controlador de temperatura de varios componentes.

Al ejecutar el ejemplo para conectarse a IoT Central, usa Device Provisioning Service (DPS) para registrar el dispositivo y generar una cadena de conexión. En el ejemplo se recupera la información de conexión de DPS que necesita del entorno de línea de comandos.

En pnp_temperature_controller.c, la función main llama primero a CreateDeviceClientAndAllocateComponents para:

  • Establecer el identificador del modelo dtmi:com:example:Thermostat;1. IoT Central usa el identificador de modelo para identificar o generar la plantilla de dispositivo específico. Para más información, consulte Asignación de un dispositivo a una plantilla de dispositivo.
  • Usar DPS para aprovisionar y registrar el dispositivo.
  • Crea un controlador de cliente de dispositivo y lo conecta a la aplicación de IoT Central.
  • Crea un controlador para los comandos del componente del controlador de temperatura.
  • Crea un controlador para las actualizaciones de propiedades del componente del controlador de temperatura.
  • Crea los dos componentes de termostato.

A continuación, la función main:

  • Notifica algunos valores de propiedad iniciales para todos los componentes.
  • Inicia un bucle para enviar la telemetría de todos los componentes.

Después, la función main inicia un subproceso para enviar la telemetría periódicamente.

int main(void)
{
    IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient = NULL;

    g_pnpDeviceConfiguration.modelId = g_temperatureControllerModelId;
    g_pnpDeviceConfiguration.enableTracing = g_hubClientTraceEnabled;

    // First determine the IoT Hub / credentials / device to use.
    if (GetConnectionSettingsFromEnvironment(&g_pnpDeviceConfiguration) == false)
    {
        LogError("Cannot read required environment variable(s)");
    }
    // Creates the thermostat subcomponents defined by this model.  Since everything
    // is simulated, this setup stage just creates simulated objects in memory.
    else if (AllocateThermostatComponents() == false)
    {
        LogError("Failure allocating thermostat components");
    }
    // Create a handle to device client handle.  Note that this call may block
    // for extended periods of time when using DPS.
    else if ((deviceClient = CreateAndConfigureDeviceClientHandleForPnP()) == NULL)
    {
        LogError("Failure creating Iot Hub device client");
        PnP_ThermostatComponent_Destroy(g_thermostatHandle1);
        PnP_ThermostatComponent_Destroy(g_thermostatHandle2);
    }
    else
    {
        LogInfo("Successfully created device client.  Hit Control-C to exit program\n");

        int numberOfIterations = 0;

        // During startup, send what DTDLv2 calls "read-only properties" to indicate initial device state.
        PnP_TempControlComponent_ReportSerialNumber_Property(deviceClient);
        PnP_DeviceInfoComponent_Report_All_Properties(g_deviceInfoComponentName, deviceClient);
        PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle1, deviceClient);
        PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle2, deviceClient);

        while (true)
        {
            // Wake up periodically to poll.  Even if we do not plan on sending telemetry, we still need to poll periodically in order to process
            // incoming requests from the server and to do connection keep alives.
            if ((numberOfIterations % g_sendTelemetryPollInterval) == 0)
            {
                PnP_TempControlComponent_SendWorkingSet(deviceClient);
                PnP_ThermostatComponent_SendCurrentTemperature(g_thermostatHandle1, deviceClient);
                PnP_ThermostatComponent_SendCurrentTemperature(g_thermostatHandle2, deviceClient);
            }

            IoTHubDeviceClient_LL_DoWork(deviceClient);
            ThreadAPI_Sleep(g_sleepBetweenPollsMs);
            numberOfIterations++;
        }

        // The remainder of the code is used for cleaning up our allocated resources. It won't be executed in this 
        // sample (because the loop above is infinite and is only broken out of by Control-C of the program), but 
        // it is included for reference.

        // Free the memory allocated to track simulated thermostat.
        PnP_ThermostatComponent_Destroy(g_thermostatHandle1);
        PnP_ThermostatComponent_Destroy(g_thermostatHandle2);

        // Clean up the IoT Hub SDK handle.
        IoTHubDeviceClient_LL_Destroy(deviceClient);
        // Free all IoT Hub subsystem.
        IoTHub_Deinit();
    }

    return 0;
}

En pnp_thermostat_component.c, la función PnP_ThermostatComponent_SendCurrentTemperature muestra cómo el dispositivo envía la telemetría de temperatura de un componente a IoT Central:

void PnP_ThermostatComponent_SendCurrentTemperature(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    IOTHUB_MESSAGE_HANDLE messageHandle = NULL;
    IOTHUB_MESSAGE_RESULT messageResult;
    IOTHUB_CLIENT_RESULT iothubClientResult;

    char temperatureStringBuffer[CURRENT_TEMPERATURE_BUFFER_SIZE];

    // Create the telemetry message body to send.
    if (snprintf(temperatureStringBuffer, sizeof(temperatureStringBuffer), g_temperatureTelemetryBodyFormat, pnpThermostatComponent->currentTemperature) < 0)
    {
        LogError("snprintf of current temperature telemetry failed");
    }
    // Create the message handle and specify its metadata.
    else if ((messageHandle = IoTHubMessage_CreateFromString(temperatureStringBuffer)) == NULL)
    {
        LogError("IoTHubMessage_PnP_CreateFromString failed");
    }
    else if ((messageResult = IoTHubMessage_SetContentTypeSystemProperty(messageHandle, g_jsonContentType)) != IOTHUB_MESSAGE_OK)
    {
        LogError("IoTHubMessage_SetContentTypeSystemProperty failed, error=%d", messageResult);
    }
    else if ((messageResult = IoTHubMessage_SetContentEncodingSystemProperty(messageHandle, g_utf8EncodingType)) != IOTHUB_MESSAGE_OK)
    {
        LogError("IoTHubMessage_SetContentEncodingSystemProperty failed, error=%d", messageResult);
    }
    else if ((messageResult = IoTHubMessage_SetComponentName(messageHandle, pnpThermostatComponent->componentName)) != IOTHUB_MESSAGE_OK)
    {
        LogError("IoTHubMessage_SetContentEncodingSystemProperty failed, error=%d", messageResult);
    }
    // Send the telemetry message.
    else if ((iothubClientResult = IoTHubDeviceClient_LL_SendTelemetryAsync(deviceClient, messageHandle, NULL, NULL)) != IOTHUB_CLIENT_OK)
    {
        LogError("Unable to send telemetry message, error=%d", iothubClientResult);
    }

    IoTHubMessage_Destroy(messageHandle);
}

En pnp_thermostat_component.c, la función PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property envía una actualización de la propiedad maxTempSinceLastReboot del componente a IoT Central:

void PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    char maximumTemperatureAsString[MAX_TEMPERATURE_SINCE_REBOOT_BUFFER_SIZE];
    IOTHUB_CLIENT_RESULT iothubClientResult;

    if (snprintf(maximumTemperatureAsString, sizeof(maximumTemperatureAsString), g_maxTempSinceLastRebootPropertyFormat, pnpThermostatComponent->maxTemperature) < 0)
    {
        LogError("Unable to create max temp since last reboot string for reporting result");
    }
    else
    {
        IOTHUB_CLIENT_PROPERTY_REPORTED maxTempProperty;
        maxTempProperty.structVersion = IOTHUB_CLIENT_PROPERTY_REPORTED_STRUCT_VERSION_1;
        maxTempProperty.name = g_maxTempSinceLastRebootPropertyName;
        maxTempProperty.value =  maximumTemperatureAsString;

        unsigned char* propertySerialized = NULL;
        size_t propertySerializedLength;

        // The first step of reporting properties is to serialize IOTHUB_CLIENT_PROPERTY_WRITABLE_RESPONSE into JSON for sending.
        if ((iothubClientResult = IoTHubClient_Properties_Serializer_CreateReported(&maxTempProperty, 1, pnpThermostatComponent->componentName, &propertySerialized, &propertySerializedLength)) != IOTHUB_CLIENT_OK)
        {
            LogError("Unable to serialize reported state, error=%d", iothubClientResult);
        }
        // The output of IoTHubClient_Properties_Serializer_CreateReported is sent to IoTHubDeviceClient_LL_SendPropertiesAsync to perform network I/O.
        else if ((iothubClientResult = IoTHubDeviceClient_LL_SendPropertiesAsync(deviceClient, propertySerialized, propertySerializedLength,  NULL, NULL)) != IOTHUB_CLIENT_OK)
        {
            LogError("Unable to send reported state, error=%d", iothubClientResult);
        }
        else
        {
            LogInfo("Sending %s property to IoTHub for component %s", g_maxTempSinceLastRebootPropertyName, pnpThermostatComponent->componentName);
        }
        IoTHubClient_Properties_Serializer_Destroy(propertySerialized);
    }
}

En pnp_thermostat_component.c, la función PnP_ThermostatComponent_ProcessPropertyUpdate controla las actualizaciones de las propiedades grabables de IoT Central:

void PnP_ThermostatComponent_ProcessPropertyUpdate(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient, const char* propertyName, const char* propertyValue, int version)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;

    if (strcmp(propertyName, g_targetTemperaturePropertyName) != 0)
    {
        LogError("Property %s was requested to be changed but is not part of the thermostat interface definition", propertyName);
    }
    else
    {
        char* next;
        double targetTemperature = strtod(propertyValue, &next);
        if ((propertyValue == next) || (targetTemperature == HUGE_VAL) || (targetTemperature == (-1*HUGE_VAL)))
        {
            LogError("Property %s is not a valid number", propertyValue);
            SendTargetTemperatureResponse(pnpThermostatComponent, deviceClient, propertyValue, PNP_STATUS_BAD_FORMAT, version, g_temperaturePropertyResponseDescriptionNotInt);
        }
        else
        {
            LogInfo("Received targetTemperature %f for component %s", targetTemperature, pnpThermostatComponent->componentName);
            
            bool maxTempUpdated = false;
            UpdateTemperatureAndStatistics(pnpThermostatComponent, targetTemperature, &maxTempUpdated);

            // The device needs to let the service know that it has received the targetTemperature desired property.
            SendTargetTemperatureResponse(pnpThermostatComponent, deviceClient, propertyValue, PNP_STATUS_SUCCESS, version, NULL);
            
            if (maxTempUpdated)
            {
                // If the maximum temperature has been updated, we also report this as a property.
                PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(pnpThermostatComponent, deviceClient);
            }
        }
    }
}

En pnp_thermostat_component.c, la función PnP_ThermostatComponent_ProcessCommand controla los comandos a los que se llama desde IoT Central:

void PnP_ThermostatComponent_ProcessCommand(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, const char *pnpCommandName, JSON_Value* commandJsonValue, IOTHUB_CLIENT_COMMAND_RESPONSE* commandResponse)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    const char* sinceStr;

    if (strcmp(pnpCommandName, g_getMaxMinReportCommandName) != 0)
    {
        LogError("Command %s is not supported on thermostat component", pnpCommandName);
        commandResponse->statusCode = PNP_STATUS_NOT_FOUND;
    }
    // See caveats section in ../readme.md; we don't actually respect this sinceStr to keep the sample simple,
    // but want to demonstrate how to parse out in any case.
    else if ((sinceStr = json_value_get_string(commandJsonValue)) == NULL)
    {
        LogError("Cannot retrieve JSON string for command");
        commandResponse->statusCode = PNP_STATUS_BAD_FORMAT;
    }
    else if (BuildMaxMinCommandResponse(pnpThermostatComponent, commandResponse) == false)
    {
        LogError("Unable to build response for component %s", pnpThermostatComponent->componentName);
        commandResponse->statusCode = PNP_STATUS_INTERNAL_ERROR;
    }
    else
    {
        LogInfo("Returning success from command request for component %s", pnpThermostatComponent->componentName);
        commandResponse->statusCode = PNP_STATUS_SUCCESS;
    }
}

Compilación del código

Para compilar el código de ejemplo incluido, se usa el SDK del dispositivo:

  1. Cree un subdirectorio cmake en la carpeta raíz del SDK del dispositivo y vaya a esa carpeta:

    cd azure-iot-sdk-c
    mkdir cmake
    cd cmake
    
  2. Ejecute los siguientes comandos para compilar el SDK y los ejemplos:

    cmake -Duse_prov_client=ON -Dhsm_type_symm_key=ON -Drun_e2e_tests=OFF ..
    cmake --build .
    

Obtención de información sobre la conexión

Al ejecutar la aplicación de dispositivo de ejemplo más adelante en este tutorial, necesitará los siguientes valores de configuración:

  • Ámbito de identificador: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo. Anote el valor de Ámbito de id.
  • Clave principal del grupo: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo > SAS-IoT-Devices (Dispositivos SAS-IoT). Tome nota del valor de Primary key (Clave principal) de la firma de acceso compartido.

Use Azure Cloud Shell para generar una clave de dispositivo a partir de la clave principal que ha recuperado:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

Anote la clave de dispositivo generada, ya que la usará más adelante en este tutorial.

Nota

Para ejecutar este ejemplo, no es necesario registrar el dispositivo de antemano en la aplicación de IoT Central. En el ejemplo se usa la funcionalidad de IoT Central para registrar automáticamente los dispositivos cuando se conectan por primera vez.

Ejecución del código

Para ejecutar la aplicación de ejemplo, abra un entorno de línea de comandos y vaya a la carpeta azure-iot-sdk-c\cmake.

Establezca las variables de entorno para configurar el ejemplo. El siguiente fragmento de código muestra cómo establecer las variables de entorno en un símbolo del sistema de Windows. Si una un shell de bash, reemplace los comandos set por los comandos export:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

Para ejecutar el ejemplo:

# Bash
cd iothub_client/samples/pnp/pnp_temperature_controller/
./pnp_temperature_controller
REM Windows
cd iothub_client\samples\pnp\pnp_temperature_controller\Debug
.\pnp_temperature_controller.exe

La salida siguiente muestra el registro del dispositivo y la conexión a IoT Central. El ejemplo empieza a enviar telemetría:

Info: Initiating DPS client to retrieve IoT Hub connection information
-> 09:43:27 CONNECT | VER: 4 | KEEPALIVE: 0 | FLAGS: 194 | USERNAME: 0ne0026656D/registrations/sample-device-01/api-version=2019-03-31&ClientVersion=1.6.0 | PWD: XXXX | CLEAN: 1
<- 09:43:28 CONNACK | SESSION_PRESENT: false | RETURN_CODE: 0x0
-> 09:43:29 SUBSCRIBE | PACKET_ID: 1 | TOPIC_NAME: $dps/registrations/res/# | QOS: 1
<- 09:43:30 SUBACK | PACKET_ID: 1 | RETURN_CODE: 1
-> 09:43:30 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/PUT/iotdps-register/?$rid=1 | PAYLOAD_LEN: 102
<- 09:43:31 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/202/?$rid=1&retry-after=3 | PACKET_ID: 2 | PAYLOAD_LEN: 94
-> 09:43:31 PUBACK | PACKET_ID: 2
-> 09:43:33 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/GET/iotdps-get-operationstatus/?$rid=2&operationId=4.2f792ade0a5c3e68.baf0e879-d88a-4153-afef-71aff51fd847 | PAYLOAD_LEN: 102
<- 09:43:34 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/202/?$rid=2&retry-after=3 | PACKET_ID: 2 | PAYLOAD_LEN: 173
-> 09:43:34 PUBACK | PACKET_ID: 2
-> 09:43:36 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/GET/iotdps-get-operationstatus/?$rid=3&operationId=4.2f792ade0a5c3e68.baf0e879-d88a-4153-afef-71aff51fd847 | PAYLOAD_LEN: 102
<- 09:43:37 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/200/?$rid=3 | PACKET_ID: 2 | PAYLOAD_LEN: 478
-> 09:43:37 PUBACK | PACKET_ID: 2
Info: Provisioning callback indicates success.  iothubUri=iotc-60a....azure-devices.net, deviceId=sample-device-01
-> 09:43:37 DISCONNECT
Info: DPS successfully registered.  Continuing on to creation of IoTHub device client handle.
Info: Successfully created device client.  Hit Control-C to exit program

Info: Sending serialNumber property to IoTHub
Info: Sending device information property to IoTHub.  propertyName=swVersion, propertyValue="1.0.0.0"
Info: Sending device information property to IoTHub.  propertyName=manufacturer, propertyValue="Sample-Manufacturer"
Info: Sending device information property to IoTHub.  propertyName=model, propertyValue="sample-Model-123"
Info: Sending device information property to IoTHub.  propertyName=osName, propertyValue="sample-OperatingSystem-name"
Info: Sending device information property to IoTHub.  propertyName=processorArchitecture, propertyValue="Contoso-Arch-64bit"
Info: Sending device information property to IoTHub.  propertyName=processorManufacturer, propertyValue="Processor Manufacturer(TM)"
Info: Sending device information property to IoTHub.  propertyName=totalStorage, propertyValue=10000
Info: Sending device information property to IoTHub.  propertyName=totalMemory, propertyValue=200
Info: Sending maximumTemperatureSinceLastReboot property to IoTHub for component=thermostat1
Info: Sending maximumTemperatureSinceLastReboot property to IoTHub for component=thermostat2
-> 09:43:44 CONNECT | VER: 4 | KEEPALIVE: 240 | FLAGS: 192 | USERNAME: iotc-60a576a2-eec7-48e2-9306-9e7089a79995.azure-devices.net/sample-device-01/?api-version=2020-09-30&DeviceClientType=iothubclient%2f1.6.0%20(native%3b%20Linux%3b%20x86_64)&model-id=dtmi%3acom%3aexample%3aTemperatureController%3b1 | PWD: XXXX | CLEAN: 0
<- 09:43:44 CONNACK | SESSION_PRESENT: false | RETURN_CODE: 0x0
-> 09:43:44 SUBSCRIBE | PACKET_ID: 2 | TOPIC_NAME: $iothub/twin/res/# | QOS: 0 | TOPIC_NAME: $iothub/methods/POST/# | QOS: 0
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/ | PACKET_ID: 3 | PAYLOAD_LEN: 19
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/%24.sub=thermostat1 | PACKET_ID: 4 | PAYLOAD_LEN: 21
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/%24.sub=thermostat2 | PACKET_ID: 5 | PAYLOAD_LEN: 21

Como operador de la aplicación de Azure IoT Central, puede:

  • Ver la telemetría enviada por los dos componentes del termostato en la página Overview (Información general):

    Captura de pantalla que muestra la página de información general del dispositivo.

  • Consulte las propiedades del dispositivo en la página About (Acerca de). En esta página se muestran las propiedades del componente de información del dispositivo y los dos componentes del termostato:

    Captura de pantalla que muestra la vista de propiedades del dispositivo.

Personalización de la plantilla de dispositivo

Como desarrollador de soluciones, puede personalizar la plantilla de dispositivo que IoT Central creó automáticamente al conectar el dispositivo de control de la temperatura.

Para agregar una propiedad de nube para almacenar el nombre de cliente asociado al dispositivo:

  1. En la aplicación de IoT Central, vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. En el modelo de controlador de temperatura, seleccione +Agregar funcionalidad.

  3. Indique el Nombre del cliente como el Nombre para mostrar, seleccione Propiedad en la nube como Tipo de funcionalidad, expanda la entrada y elija Cadena como el Esquema. Después, seleccione Guardar.

Para personalizar cómo se muestran los comandos Get Max-Min report cen la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para getMaxMinReport (thermostat1), reemplace Get Max-Min report por Get thermostat1 status report.

  3. Para getMaxMinReport (thermostat2), reemplace Get Max-Min report por Get thermostat2 status report.

  4. Seleccione Guardar.

Para personalizar cómo se muestran las propiedades de escritura Target Temperature en la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para targetTemperature (thermostat1) , reemplace Target Temperature por Target Temperature (1) .

  3. Para targetTemperature (thermostat2) , reemplace Target Temperature por Target Temperature (2) .

  4. Seleccione Guardar.

Los componentes del termostato del modelo Thermostat Controller (Controlador del termostato) incluyen la propiedad grabableTarget Temperature; la plantilla de dispositivo incluye la propiedad en la nube Customer Name. Cree una vista que pueda usar el operador para editar estas propiedades:

  1. Seleccione Views (Vistas) y el icono Editing device and cloud data (Edición de los datos del dispositivo y de la nube).

  2. Escriba Properties (Propiedades) como nombre del formulario.

  3. Seleccione las propiedades Target Temperature (1), Target Temperature (2) y Customer Name. Después, seleccione Add section (Agregar sección).

  4. Guarde los cambios.

Captura de pantalla que muestra una vista para actualizar los valores de propiedad.

Publicación de la plantilla de dispositivo

Para que un operador pueda ver y usar las personalizaciones realizadas, debe publicar la plantilla de dispositivo.

Seleccione Publish (Publicar) en la plantilla del dispositivo Thermostat. En el panel Publicar esta plantilla de dispositivo en la aplicación, seleccione Publicar.

Ahora los operadores podrán usar la vista Properties (Propiedades) para actualizar los valores de propiedad y llamar a comandos denominados Get thermostat1 status report y Get thermostat2 status report en la página de comandos del dispositivo:

  • Actualice los valores de la propiedad grabable en la página Properties (Propiedades):

    Captura de pantalla que muestra la actualización de las propiedades del dispositivo.

  • Llame al comando desde la página Commands (Comandos). Si ejecuta el comando de informe de estado, seleccione una fecha y hora para el parámetro Desde antes de la ejecución del comando:

    Captura de pantalla que muestra la llamada a un comando.

    Captura de pantalla que muestra una respuesta de comando.

Puede ver cómo responde el dispositivo a los comandos y las actualizaciones de propiedades:

<- 09:49:03 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/methods/POST/thermostat1*getMaxMinReport/?$rid=1 | PAYLOAD_LEN: 26
Info: Received PnP command for component=thermostat1, command=getMaxMinReport
Info: Returning success from command request for component=thermostat1
-> 09:49:03 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/methods/res/200/?$rid=1 | PAYLOAD_LEN: 117

...

<- 09:50:04 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/twin/PATCH/properties/desired/?$version=2 | PAYLOAD_LEN: 63
Info: Received targetTemperature=67.000000 for component=thermostat2
Info: Sending acknowledgement of property to IoTHub for component=thermostat2

Examinar el código

Prerrequisitos

Para completar los pasos de este artículo, necesitará los siguientes recursos:

Revisión del código

En la copia del repositorio de SDK de IoT de Microsoft Azure para C# que descargó anteriormente, abra el archivo de la solución azure-iot-sdk-csharp-main\azureiot.sln en Visual Studio. En Explorador de soluciones, expanda la carpeta PnpDeviceSamples > TemperatureController y abra los archivos Program.cs y TemperatureControllerSample.cs para ver el código de este ejemplo.

El ejemplo implementa el modelo de lenguaje de definición de gemelo digital del Controlador de temperatura de varios componentes.

Al ejecutar el ejemplo para conectarse a IoT Central, usa Device Provisioning Service (DPS) para registrar el dispositivo y generar una cadena de conexión. En el ejemplo se recupera la información de conexión de DPS que necesita del entorno.

En Program.cs, el método Main llama a SetupDeviceClientAsync para:

  • Utilice el identificador de modelo dtmi:com:example:TemperatureController;2 al aprovisionar el dispositivo con DPS. IoT Central usa el identificador de modelo para identificar o generar la plantilla de dispositivo específico. Para más información, consulte Asignación de un dispositivo a una plantilla de dispositivo.
  • Cree una instancia de DeviceClient para conectarse a IoT Central.
private static async Task<DeviceClient> SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken)
{
  DeviceClient deviceClient;
  switch (parameters.DeviceSecurityType.ToLowerInvariant())
  {
    case "dps":
      DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken);
      var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey);
      deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod);
      break;

    case "connectionstring":
      // ...

    default:
      // ...
  }
  return deviceClient;
}

A continuación, el método Main crea una instancia de TemperatureControllerSample y llama al método PerformOperationsAsync para controlar las interacciones con IoT Central.

En TemperatureControllerSample, el método PerformOperationsAsync:

  • Establece un controlador para el comando reboot en el componente predeterminado.
  • Establece controladores para los comandos getMaxMinReport en los dos componentes de termostato.
  • Establece controladores para recibir actualizaciones de propiedades de temperatura de destino en los dos componentes de termostato.
  • Envía actualizaciones de las propiedades iniciales de información de dispositivos.
  • Envía periódicamente la telemetría de temperatura de los dos componentes de termostato.
  • Envía periódicamente la telemetría del espacio de trabajo del componente predeterminado.
  • Envía la temperatura máxima desde el último reinicio siempre que se alcance una nueva temperatura máxima en los dos componentes de termostato.
public async Task PerformOperationsAsync(CancellationToken cancellationToken)
{
  await _deviceClient.SetMethodHandlerAsync("reboot", HandleRebootCommandAsync, _deviceClient, cancellationToken);

  // For a component-level command, the command name is in the format "<component-name>*<command-name>".
  await _deviceClient.SetMethodHandlerAsync("thermostat1*getMaxMinReport", HandleMaxMinReportCommand, Thermostat1, cancellationToken);
  await _deviceClient.SetMethodHandlerAsync("thermostat2*getMaxMinReport", HandleMaxMinReportCommand, Thermostat2, cancellationToken);

  await _deviceClient.SetDesiredPropertyUpdateCallbackAsync(SetDesiredPropertyUpdateCallback, null, cancellationToken);
  _desiredPropertyUpdateCallbacks.Add(Thermostat1, TargetTemperatureUpdateCallbackAsync);
  _desiredPropertyUpdateCallbacks.Add(Thermostat2, TargetTemperatureUpdateCallbackAsync);

  await UpdateDeviceInformationAsync(cancellationToken);
  await SendDeviceSerialNumberAsync(cancellationToken);

  bool temperatureReset = true;
  _maxTemp[Thermostat1] = 0d;
  _maxTemp[Thermostat2] = 0d;

  while (!cancellationToken.IsCancellationRequested)
  {
    if (temperatureReset)
    {
      // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.
      _temperature[Thermostat1] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1);
      _temperature[Thermostat2] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1);
    }

    await SendTemperatureAsync(Thermostat1, cancellationToken);
    await SendTemperatureAsync(Thermostat2, cancellationToken);
    await SendDeviceMemoryAsync(cancellationToken);

    temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0;
    await Task.Delay(5 * 1000);
  }
}

El método SendTemperatureAsync muestra cómo el dispositivo envía la telemetría de temperatura de un componente a IoT Central. El método SendTemperatureTelemetryAsync utiliza la clase PnpConvention para generar el mensaje:

private async Task SendTemperatureAsync(string componentName, CancellationToken cancellationToken)
{
  await SendTemperatureTelemetryAsync(componentName, cancellationToken);

  double maxTemp = _temperatureReadingsDateTimeOffset[componentName].Values.Max<double>();
  if (maxTemp > _maxTemp[componentName])
  {
    _maxTemp[componentName] = maxTemp;
    await UpdateMaxTemperatureSinceLastRebootAsync(componentName, cancellationToken);
  }
}

private async Task SendTemperatureTelemetryAsync(string componentName, CancellationToken cancellationToken)
{
  const string telemetryName = "temperature";
  double currentTemperature = _temperature[componentName];
  using Message msg = PnpConvention.CreateMessage(telemetryName, currentTemperature, componentName);

  await _deviceClient.SendEventAsync(msg, cancellationToken);

  if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName))
  {
    _temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow, currentTemperature);
  }
  else
  {
    _temperatureReadingsDateTimeOffset.TryAdd(
      componentName,
      new Dictionary<DateTimeOffset, double>
      {
        { DateTimeOffset.UtcNow, currentTemperature },
      });
  }
}

El método UpdateMaxTemperatureSinceLastRebootAsync envía una actualización de la propiedad maxTempSinceLastReboot a IoT Central. Este método usa la clase PnpConvention para crear la revisión:

private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName, CancellationToken cancellationToken)
{
  const string propertyName = "maxTempSinceLastReboot";
  double maxTemp = _maxTemp[componentName];
  TwinCollection reportedProperties = PnpConvention.CreateComponentPropertyPatch(componentName, propertyName, maxTemp);

  await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken);
}

El método TargetTemperatureUpdateCallbackAsync controla la actualización de la propiedad de escritura de la temperatura objetivo desde IoT Central. Este método usa la clase PnpConvention para leer el mensaje de actualización de la propiedad y construir la respuesta:

private async Task TargetTemperatureUpdateCallbackAsync(TwinCollection desiredProperties, object userContext)
{
  const string propertyName = "targetTemperature";
  string componentName = (string)userContext;

  bool targetTempUpdateReceived = PnpConvention.TryGetPropertyFromTwin(
    desiredProperties,
    propertyName,
    out double targetTemperature,
    componentName);
  if (!targetTempUpdateReceived)
  {
      return;
  }

  TwinCollection pendingReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse(
      componentName,
      propertyName,
      targetTemperature,
      (int)StatusCode.InProgress,
      desiredProperties.Version);

  await _deviceClient.UpdateReportedPropertiesAsync(pendingReportedProperty);

  // Update Temperature in 2 steps
  double step = (targetTemperature - _temperature[componentName]) / 2d;
  for (int i = 1; i <= 2; i++)
  {
      _temperature[componentName] = Math.Round(_temperature[componentName] + step, 1);
      await Task.Delay(6 * 1000);
  }

  TwinCollection completedReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse(
      componentName,
      propertyName,
      _temperature[componentName],
      (int)StatusCode.Completed,
      desiredProperties.Version,
      "Successfully updated target temperature");

  await _deviceClient.UpdateReportedPropertiesAsync(completedReportedProperty);
}

El método HandleMaxMinReportCommand controla los comandos de los componentes a los que se llama desde IoT Central:

private Task<MethodResponse> HandleMaxMinReportCommand(MethodRequest request, object userContext)
{
    try
    {
        string componentName = (string)userContext;
        DateTime sinceInUtc = JsonConvert.DeserializeObject<DateTime>(request.DataAsJson);
        var sinceInDateTimeOffset = new DateTimeOffset(sinceInUtc);

        if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName))
        {

            Dictionary<DateTimeOffset, double> allReadings = _temperatureReadingsDateTimeOffset[componentName];
            Dictionary<DateTimeOffset, double> filteredReadings = allReadings.Where(i => i.Key > sinceInDateTimeOffset)
                .ToDictionary(i => i.Key, i => i.Value);

            if (filteredReadings != null && filteredReadings.Any())
            {
                var report = new
                {
                    maxTemp = filteredReadings.Values.Max<double>(),
                    minTemp = filteredReadings.Values.Min<double>(),
                    avgTemp = filteredReadings.Values.Average(),
                    startTime = filteredReadings.Keys.Min(),
                    endTime = filteredReadings.Keys.Max(),
                };

                byte[] responsePayload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));
                return Task.FromResult(new MethodResponse(responsePayload, (int)StatusCode.Completed));
            }

            return Task.FromResult(new MethodResponse((int)StatusCode.NotFound));
        }

        return Task.FromResult(new MethodResponse((int)StatusCode.NotFound));
    }
    catch (JsonReaderException ex)
    {
        // ...
    }
}

Obtención de información sobre la conexión

Al ejecutar la aplicación de dispositivo de ejemplo más adelante en este tutorial, necesitará los siguientes valores de configuración:

  • Ámbito de identificador: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo. Anote el valor de Ámbito de id.
  • Clave principal del grupo: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo > SAS-IoT-Devices (Dispositivos SAS-IoT). Tome nota del valor de Primary key (Clave principal) de la firma de acceso compartido.

Use Azure Cloud Shell para generar una clave de dispositivo a partir de la clave principal que ha recuperado:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

Anote la clave de dispositivo generada, ya que la usará más adelante en este tutorial.

Nota

Para ejecutar este ejemplo, no es necesario registrar el dispositivo de antemano en la aplicación de IoT Central. En el ejemplo se usa la funcionalidad de IoT Central para registrar automáticamente los dispositivos cuando se conectan por primera vez.

Ejecución del código

Nota:

Configure TemperatureController como proyecto de inicio antes de ejecutar el código.

Ejecución de la aplicación de ejemplo en Visual Studio

  1. En Explorador de soluciones, seleccione el archivo de proyecto PnpDeviceSamples > TemperatureController.

  2. Vaya a Proyecto > Propiedades del controlador de temperatura > Depurar. Después, agregue las siguientes variables de entorno al proyecto:

    Nombre Value
    IOTHUB_DEVICE_SECURITY_TYPE DPS
    IOTHUB_DEVICE_DPS_ENDPOINT global.azure-devices-provisioning.net
    IOTHUB_DEVICE_DPS_ID_SCOPE El valor del ámbito del identificador que anotó anteriormente.
    IOTHUB_DEVICE_DPS_DEVICE_ID sample-device-01
    IOTHUB_DEVICE_DPS_DEVICE_KEY El valor de la clave de dispositivo generado que anotó anteriormente.

Ahora puede ejecutar y depurar el ejemplo en Visual Studio.

La salida siguiente muestra el registro del dispositivo y la conexión a IoT Central. El ejemplo empieza a enviar telemetría:

[03/31/2021 14:43:17]info: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Press Control+C to quit the sample.
[03/31/2021 14:43:17]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set up the device client.
[03/31/2021 14:43:18]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Initializing via DPS
[03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler for 'reboot' command.
[03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Connection status change registered - status=Connected, reason=Connection_Ok.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler for "getMaxMinReport" command.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler to receive 'targetTemperature' updates.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component = 'deviceInformation', properties update is complete.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - { "serialNumber": "SR-123456" } is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat1", { "temperature": 34.2 } in °C.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", { "maxTempSinceLastReboot": 34.2 } in °C is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat2", { "temperature": 25.1 } in °C.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat2", { "maxTempSinceLastReboot": 25.1 } in °C is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - {"workingSet":31412} in KB.

Como operador de la aplicación de Azure IoT Central, puede:

  • Ver la telemetría enviada por los dos componentes del termostato en la página Overview (Información general):

    Captura de pantalla que muestra la página de información general del dispositivo.

  • Consulte las propiedades del dispositivo en la página About (Acerca de). En esta página se muestran las propiedades del componente de información del dispositivo y los dos componentes del termostato:

    Captura de pantalla que muestra la vista de propiedades del dispositivo.

Personalización de la plantilla de dispositivo

Como desarrollador de soluciones, puede personalizar la plantilla de dispositivo que IoT Central creó automáticamente al conectar el dispositivo de control de la temperatura.

Para agregar una propiedad de nube para almacenar el nombre de cliente asociado al dispositivo:

  1. En la aplicación de IoT Central, vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. En el modelo de controlador de temperatura, seleccione +Agregar funcionalidad.

  3. Indique el Nombre del cliente como el Nombre para mostrar, seleccione Propiedad en la nube como Tipo de funcionalidad, expanda la entrada y elija Cadena como el Esquema. Después, seleccione Guardar.

Para personalizar cómo se muestran los comandos Get Max-Min report cen la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para getMaxMinReport (thermostat1), reemplace Get Max-Min report por Get thermostat1 status report.

  3. Para getMaxMinReport (thermostat2), reemplace Get Max-Min report por Get thermostat2 status report.

  4. Seleccione Guardar.

Para personalizar cómo se muestran las propiedades de escritura Target Temperature en la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para targetTemperature (thermostat1) , reemplace Target Temperature por Target Temperature (1) .

  3. Para targetTemperature (thermostat2) , reemplace Target Temperature por Target Temperature (2) .

  4. Seleccione Guardar.

Los componentes del termostato del modelo Thermostat Controller (Controlador del termostato) incluyen la propiedad grabableTarget Temperature; la plantilla de dispositivo incluye la propiedad en la nube Customer Name. Cree una vista que pueda usar el operador para editar estas propiedades:

  1. Seleccione Views (Vistas) y el icono Editing device and cloud data (Edición de los datos del dispositivo y de la nube).

  2. Escriba Properties (Propiedades) como nombre del formulario.

  3. Seleccione las propiedades Target Temperature (1), Target Temperature (2) y Customer Name. Después, seleccione Add section (Agregar sección).

  4. Guarde los cambios.

Captura de pantalla que muestra una vista para actualizar los valores de propiedad.

Publicación de la plantilla de dispositivo

Para que un operador pueda ver y usar las personalizaciones realizadas, debe publicar la plantilla de dispositivo.

Seleccione Publish (Publicar) en la plantilla del dispositivo Thermostat. En el panel Publicar esta plantilla de dispositivo en la aplicación, seleccione Publicar.

Ahora los operadores podrán usar la vista Properties (Propiedades) para actualizar los valores de propiedad y llamar a comandos denominados Get thermostat1 status report y Get thermostat2 status report en la página de comandos del dispositivo:

  • Actualice los valores de la propiedad grabable en la página Properties (Propiedades):

    Captura de pantalla que muestra la actualización de las propiedades del dispositivo.

  • Llame al comando desde la página Commands (Comandos). Si ejecuta el comando de informe de estado, seleccione una fecha y hora para el parámetro Desde antes de la ejecución del comando:

    Captura de pantalla que muestra la llamada a un comando.

    Captura de pantalla que muestra una respuesta de comando.

Puede ver cómo responde el dispositivo a los comandos y las actualizaciones de propiedades:

[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Command: Received - component="thermostat2", generating max, min and avg temperature report since 31/03/2021 06:00:00.
[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Command: component="thermostat2", MaxMinReport since 31/03/2021 06:00:00: maxTemp=36.4, minTemp=36.4, avgTemp=36.4, startTime=31/03/2021 14:46:33, endTime=31/03/2021 14:46:55

...

[03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Received - component="thermostat1", { "targetTemperature": 67°C }.
[03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is InProgress.
[03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is Completed
[03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat1", { "temperature": 67 } in °C.

Examinar el código

Prerrequisitos

Para completar los pasos de este artículo, necesitará los siguientes recursos:

Revisión del código

En la copia del SDK de Microsoft Azure IoT para Java que descargó anteriormente, abra azure-iot-sdk-java/iothub/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/device/TemperatureController.java en un editor de texto.

El ejemplo implementa el modelo de lenguaje de definición de gemelo digital del Controlador de temperatura de varios componentes.

Al ejecutar el ejemplo para conectarse a IoT Central, usa Device Provisioning Service (DPS) para registrar el dispositivo y generar una cadena de conexión. En el ejemplo se recupera la información de conexión de DPS que necesita del entorno de línea de comandos.

El método main realiza las acciones siguientes:

  • Llama a initializeAndProvisionDevice para establecer el identificador del modelo dtmi:com:example:TemperatureController;2, usar DPS para aprovisionar y registrar el dispositivo, crear una instancia de initializeAndProvisionDevice y conectarse a la aplicación IoT Central. IoT Central usa el identificador de modelo para identificar o generar la plantilla de dispositivo específico. Para más información, consulte Asignación de un dispositivo a una plantilla de dispositivo.
  • Crea controladores de comandos para los comandos getMaxMinReport y reboot.
  • Crea controladores de actualización de propiedades para las propiedades targetTemperature de escritura.
  • Envía los valores iniciales de las propiedades en la interfaz Información de dispositivo y las propiedades de memoria del dispositivo y número de serie.
  • Inicia un subproceso para enviar telemetría de temperatura de los dos termostatos y actualiza la propiedad maxTempSinceLastReboot cada cinco segundos.
public static void main(String[] args) throws Exception {

  // ...
  
  switch (deviceSecurityType.toLowerCase())
  {
    case "dps":
    {
      if (validateArgsForDpsFlow())
      {
        initializeAndProvisionDevice();
        break;
      }
      throw new IllegalArgumentException("Required environment variables are not set for DPS flow, please recheck your environment.");
    }
    case "connectionstring":
    {
      // ...
    }
    default:
    {
      // ...
    }
  }
  
  deviceClient.subscribeToMethods(new MethodCallback(), null);
  
  deviceClient.subscribeToDesiredPropertiesAsync(
  {
  (twin, context) ->
      TwinCollection desiredProperties = twin.getDesiredProperties();
      for (String desiredPropertyKey : desiredProperties.keySet())
      {
          TargetTemperatureUpdateCallback.onPropertyChanged(new Property(desiredPropertyKey, desiredProperties.get(desiredPropertyKey)), null);
      }
  },
  null,
  (exception, context) ->
  {
      if (exception == null)
      {
          log.info("Successfully subscribed to desired properties. Getting initial state");
          deviceClient.getTwinAsync(
              (twin, getTwinException, getTwinContext) ->
              {
                  log.info("Initial twin state received");
                  log.info(twin.toString());
              },
              null);
      }
      else
      {
          log.info("Failed to subscribe to desired properties. Error code {}", exception.getStatusCode());
          System.exit(-1);
      }
  },
  null);

  updateDeviceInformation();
  sendDeviceMemory();
  sendDeviceSerialNumber();
  
  final AtomicBoolean temperatureReset = new AtomicBoolean(true);
  maxTemperature.put(THERMOSTAT_1, 0.0d);
  maxTemperature.put(THERMOSTAT_2, 0.0d);
  
  new Thread(new Runnable() {
    @SneakyThrows({InterruptedException.class, IOException.class})
    @Override
    public void run() {
      while (true) {
        if (temperatureReset.get()) {
          // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.
          temperature.put(THERMOSTAT_1, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue());
          temperature.put(THERMOSTAT_2, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue());
        }

        sendTemperatureReading(THERMOSTAT_1);
        sendTemperatureReading(THERMOSTAT_2);

        temperatureReset.set(temperature.get(THERMOSTAT_1) == 0 && temperature.get(THERMOSTAT_2) == 0);
        Thread.sleep(5 * 1000);
      }
    }
  }).start();
}

El método initializeAndProvisionDevice muestra cómo el dispositivo usa DPS para registrarse y conectarse a IoT Central. La carga incluye el identificador modelo que IoT Central usa para asignar el dispositivo a una plantilla de dispositivo:

private static void initializeAndProvisionDevice() throws Exception {
  SecurityProviderSymmetricKey securityClientSymmetricKey = new SecurityProviderSymmetricKey(deviceSymmetricKey.getBytes(), registrationId);
  ProvisioningDeviceClient provisioningDeviceClient;
  ProvisioningStatus provisioningStatus = new ProvisioningStatus();

  provisioningDeviceClient = ProvisioningDeviceClient.create(globalEndpoint, scopeId, provisioningProtocol, securityClientSymmetricKey);

  AdditionalData additionalData = new AdditionalData();
  additionalData.setProvisioningPayload(com.microsoft.azure.sdk.iot.provisioning.device.plugandplay.PnpHelper.createDpsPayload(MODEL_ID));

  ProvisioningDeviceClientRegistrationResult registrationResult = provisioningDeviceClient.registerDeviceSync(additionalData);

    ClientOptions options = ClientOptions.builder().modelId(MODEL_ID).build();
    if (registrationResult.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) {
        System.out.println("IotHUb Uri : " + registrationResult.getIothubUri());
        System.out.println("Device ID : " + registrationResult.getDeviceId());
        String iotHubUri = registrationResult.getIothubUri();
        String deviceId = registrationResult.getDeviceId();
        log.debug("Opening the device client.");
        deviceClient = new DeviceClient(iotHubUri, deviceId, securityClientSymmetricKey, IotHubClientProtocol.MQTT, options);
        deviceClient.open(true);
    }
}

El método sendTemperatureTelemetry muestra cómo el dispositivo envía la telemetría de temperatura de un componente a IoT Central. Este método usa la clase PnpConvention para crear el mensaje:

  private static void sendTemperatureTelemetry(String componentName) {
    String telemetryName = "temperature";
    double currentTemperature = temperature.get(componentName);

    Message message = PnpConvention.createIotHubMessageUtf8(telemetryName, currentTemperature, componentName);
    deviceClient.sendEventAsync(message, new MessageIotHubEventCallback(), message);

    // Add the current temperature entry to the list of temperature readings.
    Map<Date, Double> currentReadings;
    if (temperatureReadings.containsKey(componentName)) {
      currentReadings = temperatureReadings.get(componentName);
    } else {
      currentReadings = new HashMap<>();
    }
    currentReadings.put(new Date(), currentTemperature);
    temperatureReadings.put(componentName, currentReadings);
  }

El método updateMaxTemperatureSinceLastReboot envía una actualización de la propiedad maxTempSinceLastReboot desde un componente a IoT Central. Este método usa la clase PnpConvention para crear la revisión:

private static void updateMaxTemperatureSinceLastReboot(String componentName) throws IOException {
  String propertyName = "maxTempSinceLastReboot";
  double maxTemp = maxTemperature.get(componentName);

  TwinCollection reportedProperty = PnpConvention.createComponentPropertyPatch(propertyName, maxTemp, componentName);
  deviceClient.updateReportedPropertiesAsync(reportedProperty, sendReportedPropertiesResponseCallback, null);
  log.debug("Property: Update - {\"{}\": {}°C} is {}.", propertyName, maxTemp, StatusCode.COMPLETED);
}

La clase TargetTemperatureUpdateCallback contiene el método onPropertyChanged para controlar las actualizaciones de propiedades de escritura a un componente desde IoT Central. Este método usa la clase PnpConvention para crear la respuesta:

private static class TargetTemperatureUpdateCallback
{
    final static String propertyName = "targetTemperature";
    @SneakyThrows(InterruptedException.class)
    public static void onPropertyChanged(Property property, Object context) {
        String componentName = (String) context;
        if (property.getKey().equalsIgnoreCase(componentName)) {
            double targetTemperature = (double) ((TwinCollection) property.getValue()).get(propertyName);
            log.debug("Property: Received - component=\"{}\", {\"{}\": {}°C}.", componentName, propertyName, targetTemperature);
            TwinCollection pendingPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
                    propertyName,
                    targetTemperature,
                    componentName,
                    StatusCode.IN_PROGRESS.value,
                    property.getVersion().longValue(),
                    null);
            deviceClient.updateReportedPropertiesAsync(pendingPropertyPatch, sendReportedPropertiesResponseCallback, null);
            log.debug("Property: Update - component=\"{}\", {\"{}\": {}°C} is {}", componentName, propertyName, targetTemperature, StatusCode.IN_PROGRESS);
            // Update temperature in 2 steps
            double step = (targetTemperature - temperature.get(componentName)) / 2;
            for (int i = 1; i <=2; i++) {
                temperature.put(componentName, BigDecimal.valueOf(temperature.get(componentName) + step).setScale(1, RoundingMode.HALF_UP).doubleValue());
                Thread.sleep(5 * 1000);
            }
            TwinCollection completedPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
                    propertyName,
                    temperature.get(componentName),
                    componentName,
                    StatusCode.COMPLETED.value,
                    property.getVersion().longValue(),
                    "Successfully updated target temperature.");
            deviceClient.updateReportedPropertiesAsync(completedPropertyPatch, sendReportedPropertiesResponseCallback, null);
            log.debug("Property: Update - {\"{}\": {}°C} is {}", propertyName, temperature.get(componentName), StatusCode.COMPLETED);
        } else {
            log.debug("Property: Received an unrecognized property update from service.");
        }
    }
}

La clase MethodCallback contiene el método onMethodInvoked para controlar los comandos del componente a los que se llama desde IoT Central:

private static class MethodCallback implements com.microsoft.azure.sdk.iot.device.twin.MethodCallback
{
    final String reboot = "reboot";
    final String getMaxMinReport1 = "thermostat1*getMaxMinReport";
    final String getMaxMinReport2 = "thermostat2*getMaxMinReport";
    @SneakyThrows(InterruptedException.class)
    @Override
    public DirectMethodResponse onMethodInvoked(String methodName, DirectMethodPayload methodData, Object context) {
        String jsonRequest = methodData.getPayload(String.class);
        switch (methodName) {
            case reboot:
                int delay = getCommandRequestValue(jsonRequest, Integer.class);
                log.debug("Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {} seconds).", delay);
                Thread.sleep(delay * 1000L);
                temperature.put(THERMOSTAT_1, 0.0d);
                temperature.put(THERMOSTAT_2, 0.0d);
                maxTemperature.put(THERMOSTAT_1, 0.0d);
                maxTemperature.put(THERMOSTAT_2, 0.0d);
                temperatureReadings.clear();
                return new DirectMethodResponse(StatusCode.COMPLETED.value, null);
            case getMaxMinReport1:
            case getMaxMinReport2:
                String[] words = methodName.split("\\*");
                String componentName = words[0];
                if (temperatureReadings.containsKey(componentName)) {
                    Date since = getCommandRequestValue(jsonRequest, Date.class);
                    log.debug("Command: Received - component=\"{}\", generating min, max, avg temperature report since {}", componentName, since);
                    Map<Date, Double> allReadings = temperatureReadings.get(componentName);
                    Map<Date, Double> filteredReadings = allReadings.entrySet().stream()
                            .filter(map -> map.getKey().after(since))
                            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
                    if (!filteredReadings.isEmpty()) {
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
                        double maxTemp = Collections.max(filteredReadings.values());
                        double minTemp = Collections.min(filteredReadings.values());
                        double avgTemp = filteredReadings.values().stream().mapToDouble(Double::doubleValue).average().orElse(Double.NaN);
                        String startTime =  sdf.format(Collections.min(filteredReadings.keySet()));
                        String endTime =  sdf.format(Collections.max(filteredReadings.keySet()));
                        String responsePayload = String.format(
                                "{\"maxTemp\": %.1f, \"minTemp\": %.1f, \"avgTemp\": %.1f, \"startTime\": \"%s\", \"endTime\": \"%s\"}",
                                maxTemp,
                                minTemp,
                                avgTemp,
                                startTime,
                                endTime);
                        log.debug("Command: MaxMinReport since {}: \"maxTemp\": {}°C, \"minTemp\": {}°C, \"avgTemp\": {}°C, \"startTime\": {}, \"endTime\": {}",
                                since,
                                maxTemp,
                                minTemp,
                                avgTemp,
                                startTime,
                                endTime);
                        return new DirectMethodResponse(StatusCode.COMPLETED.value, responsePayload);
                    }
                    log.debug("Command: component=\"{}\", no relevant readings found since {}, cannot generate any report.", componentName, since);
                    return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
                }
                log.debug("Command: component=\"{}\", no temperature readings sent yet, cannot generate any report.", componentName);
                return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
            default:
                log.debug("Command: command=\"{}\" is not implemented, no action taken.", methodName);
                return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
        }
    }
}

Obtención de información sobre la conexión

Al ejecutar la aplicación de dispositivo de ejemplo más adelante en este tutorial, necesitará los siguientes valores de configuración:

  • Ámbito de identificador: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo. Anote el valor de Ámbito de id.
  • Clave principal del grupo: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo > SAS-IoT-Devices (Dispositivos SAS-IoT). Tome nota del valor de Primary key (Clave principal) de la firma de acceso compartido.

Use Azure Cloud Shell para generar una clave de dispositivo a partir de la clave principal que ha recuperado:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

Anote la clave de dispositivo generada, ya que la usará más adelante en este tutorial.

Nota

Para ejecutar este ejemplo, no es necesario registrar el dispositivo de antemano en la aplicación de IoT Central. En el ejemplo se usa la funcionalidad de IoT Central para registrar automáticamente los dispositivos cuando se conectan por primera vez.

En Windows, vaya a la carpeta raíz del repositorio del SDK de Azure IoT para Java que ha descargado.

Ejecute el siguiente comando para compilar la aplicación de ejemplo:

mvn install -T 2C -DskipTests

Ejecución del código

Para ejecutar la aplicación de ejemplo, abra un entorno de línea de comandos y vaya a la carpeta azure-iot-sdk-java/iothub/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample que contiene la carpeta src con el archivo de ejemplo TemperatureController.java.

Establezca las variables de entorno para configurar el ejemplo. El siguiente fragmento de código muestra cómo establecer las variables de entorno en un símbolo del sistema de Windows. Si una un shell de bash, reemplace los comandos set por los comandos export:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

Ejecución del ejemplo:

mvn exec:java -Dexec.mainClass="samples.com.microsoft.azure.sdk.iot.device.TemperatureController"

La salida siguiente muestra el registro del dispositivo y la conexión a IoT Central. El ejemplo empieza a enviar telemetría:

2021-03-30 15:33:25.138 DEBUG TemperatureController:123 - Initialize the device client.
Waiting for Provisioning Service to register
Waiting for Provisioning Service to register
IotHUb Uri : iotc-60a.....azure-devices.net
Device ID : sample-device-01
2021-03-30 15:33:38.294 DEBUG TemperatureController:247 - Opening the device client.
2021-03-30 15:33:38.307 INFO  ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true
2021-03-30 15:33:38.321 INFO  ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true
2021-03-30 15:33:38.427 DEBUG MqttIotHubConnection:274 - Opening MQTT connection...
2021-03-30 15:33:38.427 DEBUG Mqtt:123 - Sending MQTT CONNECT packet...
2021-03-30 15:33:44.628 DEBUG Mqtt:126 - Sent MQTT CONNECT packet was acknowledged
2021-03-30 15:33:44.630 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/#
2021-03-30 15:33:44.731 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/# was acknowledged
2021-03-30 15:33:44.733 DEBUG MqttIotHubConnection:279 - MQTT connection opened successfully
2021-03-30 15:33:44.733 DEBUG IotHubTransport:302 - The connection to the IoT Hub has been established
2021-03-30 15:33:44.734 INFO  IotHubTransport:1429 - Updating transport status to new status CONNECTED with reason CONNECTION_OK
2021-03-30 15:33:44.735 DEBUG IotHubTransport:1439 - Invoking connection status callbacks with new status details
2021-03-30 15:33:44.739 DEBUG IotHubTransport:394 - Client connection opened successfully
2021-03-30 15:33:44.740 INFO  DeviceClient:438 - Device client opened successfully
2021-03-30 15:33:44.740 DEBUG TemperatureController:152 - Set handler for "reboot" command.
2021-03-30 15:33:44.742 DEBUG TemperatureController:153 - Set handler for "getMaxMinReport" command.
2021-03-30 15:33:44.774 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] )
2021-03-30 15:33:44.774 DEBUG TemperatureController:156 - Set handler to receive "targetTemperature" updates.
2021-03-30 15:33:44.775 INFO  IotHubTransport:1344 - Sending message ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] )
2021-03-30 15:33:44.779 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/methods/POST/#
2021-03-30 15:33:44.793 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.794 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [eeee4444-ff55-6666-77aa-888888bbbbbb] Message Id [ffff5555-aa66-7777-88bb-999999cccccc] Request Id [0] Device Operation Type [DEVICE_OPERATION_TWIN_GET_REQUEST] )
2021-03-30 15:33:44.819 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa6666-bb77-8888-99cc-000000dddddd] Message Id [aaaa0000-bb11-2222-33cc-444444dddddd] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.881 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic $iothub/methods/POST/# was acknowledged
2021-03-30 15:33:44.882 INFO  IotHubTransport:1344 - Sending message ( Message details: Correlation Id [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.882 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/twin/res/#
2021-03-30 15:33:44.893 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Message Id [cccc2222-dd33-4444-55ee-666666ffffff] Request Id [1] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.904 DEBUG TemperatureController:423 - Property: Update - component = "deviceInformation" is COMPLETED.
2021-03-30 15:33:44.915 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Message Id [eeee4444-ff55-6666-77aa-888888bbbbbb] )
2021-03-30 15:33:44.915 DEBUG TemperatureController:434 - Telemetry: Sent - {"workingSet": 1024.0KiB }
2021-03-30 15:33:44.915 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [ffff5555-aa66-7777-88bb-999999cccccc] Message Id [aaaa6666-bb77-8888-99cc-000000dddddd] Request Id [2] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.916 DEBUG TemperatureController:442 - Property: Update - {"serialNumber": SR-123456} is COMPLETED
2021-03-30 15:33:44.927 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] )
2021-03-30 15:33:44.927 DEBUG TemperatureController:461 - Telemetry: Sent - {"temperature": 5.8°C} with message Id bbbb1111-cc22-3333-44dd-555555eeeeee.

Como operador de la aplicación de Azure IoT Central, puede:

  • Ver la telemetría enviada por los dos componentes del termostato en la página Overview (Información general):

    Captura de pantalla que muestra la página de información general del dispositivo.

  • Consulte las propiedades del dispositivo en la página About (Acerca de). En esta página se muestran las propiedades del componente de información del dispositivo y los dos componentes del termostato:

    Captura de pantalla que muestra la vista de propiedades del dispositivo.

Personalización de la plantilla de dispositivo

Como desarrollador de soluciones, puede personalizar la plantilla de dispositivo que IoT Central creó automáticamente al conectar el dispositivo de control de la temperatura.

Para agregar una propiedad de nube para almacenar el nombre de cliente asociado al dispositivo:

  1. En la aplicación de IoT Central, vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. En el modelo de controlador de temperatura, seleccione +Agregar funcionalidad.

  3. Indique el Nombre del cliente como el Nombre para mostrar, seleccione Propiedad en la nube como Tipo de funcionalidad, expanda la entrada y elija Cadena como el Esquema. Después, seleccione Guardar.

Para personalizar cómo se muestran los comandos Get Max-Min report cen la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para getMaxMinReport (thermostat1), reemplace Get Max-Min report por Get thermostat1 status report.

  3. Para getMaxMinReport (thermostat2), reemplace Get Max-Min report por Get thermostat2 status report.

  4. Seleccione Guardar.

Para personalizar cómo se muestran las propiedades de escritura Target Temperature en la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para targetTemperature (thermostat1) , reemplace Target Temperature por Target Temperature (1) .

  3. Para targetTemperature (thermostat2) , reemplace Target Temperature por Target Temperature (2) .

  4. Seleccione Guardar.

Los componentes del termostato del modelo Thermostat Controller (Controlador del termostato) incluyen la propiedad grabableTarget Temperature; la plantilla de dispositivo incluye la propiedad en la nube Customer Name. Cree una vista que pueda usar el operador para editar estas propiedades:

  1. Seleccione Views (Vistas) y el icono Editing device and cloud data (Edición de los datos del dispositivo y de la nube).

  2. Escriba Properties (Propiedades) como nombre del formulario.

  3. Seleccione las propiedades Target Temperature (1), Target Temperature (2) y Customer Name. Después, seleccione Add section (Agregar sección).

  4. Guarde los cambios.

Captura de pantalla que muestra una vista para actualizar los valores de propiedad.

Publicación de la plantilla de dispositivo

Para que un operador pueda ver y usar las personalizaciones realizadas, debe publicar la plantilla de dispositivo.

Seleccione Publish (Publicar) en la plantilla del dispositivo Thermostat. En el panel Publicar esta plantilla de dispositivo en la aplicación, seleccione Publicar.

Ahora los operadores podrán usar la vista Properties (Propiedades) para actualizar los valores de propiedad y llamar a comandos denominados Get thermostat1 status report y Get thermostat2 status report en la página de comandos del dispositivo:

  • Actualice los valores de la propiedad grabable en la página Properties (Propiedades):

    Captura de pantalla que muestra la actualización de las propiedades del dispositivo.

  • Llame al comando desde la página Commands (Comandos). Si ejecuta el comando de informe de estado, seleccione una fecha y hora para el parámetro Desde antes de la ejecución del comando:

    Captura de pantalla que muestra la llamada a un comando.

    Captura de pantalla que muestra una respuesta de comando.

Puede ver cómo responde el dispositivo a los comandos y las actualizaciones de propiedades:

2021-03-30 15:43:57.133 DEBUG TemperatureController:309 - Command: Received - component="thermostat1", generating min, max, avg temperature report since Tue Mar 30 06:00:00 BST 2021
2021-03-30 15:43:57.153 DEBUG TemperatureController:332 - Command: MaxMinReport since Tue Mar 30 06:00:00 BST 2021: "maxTemp": 35.6°C, "minTemp": 35.6°C, "avgTemp": 35.6°C, "startTime": 2021-03-30T15:43:41Z, "endTime": 2021-03-30T15:43:56Z
2021-03-30 15:43:57.394 DEBUG TemperatureController:502 - Command - Response from IoT Hub: command name=null, status=OK_EMPTY


...

2021-03-30 15:48:47.808 DEBUG TemperatureController:372 - Property: Received - component="thermostat2", {"targetTemperature": 67.0°C}.
2021-03-30 15:48:47.837 DEBUG TemperatureController:382 - Property: Update - component="thermostat2", {"targetTemperature": 67.0°C} is IN_PROGRESS

Examinar el código

Prerrequisitos

Para completar los pasos de este artículo, necesitará los siguientes recursos:

  • Una máquina de desarrollo con la versión 6 de Node.js o una posterior instalada. Puede ejecutar node --version en la línea de comandos para comprobar la versión. En las instrucciones de este tutorial se da por hecho que se ejecuta el comando node en el símbolo del sistema de Windows. No obstante, puede usar Node.js en muchos otros sistemas operativos.

  • Una copia local del repositorio de GitHub de SDK de IoT de Microsoft Azure para Node.js que contiene el código de ejemplo. Use este vínculo para descargar una copia del repositorio: Descargar el archivo ZIP. Después, descomprima el archivo en una ubicación conveniente en la máquina local.

Revisión del código

En la copia del SDK de Microsoft Azure IoT para Node.js que descargó anteriormente, abra el archivo azure-iot-sdk-node/device/samples/javascript/pnp_temperature_controller.js en un editor de texto.

El ejemplo implementa el modelo de lenguaje de definición de gemelo digital del Controlador de temperatura de varios componentes.

Al ejecutar el ejemplo para conectarse a IoT Central, usa Device Provisioning Service (DPS) para registrar el dispositivo y generar una cadena de conexión. En el ejemplo se recupera la información de conexión de DPS que necesita del entorno de línea de comandos.

El método main realiza las acciones siguientes:

  • Crea un objeto client y establece el identificador del modelo dtmi:com:example:TemperatureController;2 antes de abrir la conexión. IoT Central usa el identificador de modelo para identificar o generar la plantilla de dispositivo específico. Para más información, consulte Asignación de un dispositivo a una plantilla de dispositivo.
  • Crea controladores para tres comandos.
  • Inicia un bucle para que cada componente de termostato envíe los datos de telemetría de la temperatura cada 5 segundos.
  • Inicia un bucle para que el componente predeterminado envíe los datos de telemetría del tamaño del espacio de trabajo cada 6 segundos.
  • Envía la propiedad maxTempSinceLastReboot para cada componente del termostato.
  • Envía las propiedades de información del dispositivo.
  • Crea controladores de propiedades grabables para los tres componentes.
async function main() {
  // ...

  // fromConnectionString must specify a transport, coming from any transport package.
  const client = Client.fromConnectionString(deviceConnectionString, Protocol);
  console.log('Connecting using connection string: ' + deviceConnectionString);
  let resultTwin;

  try {
    // Add the modelId here
    await client.setOptions(modelIdObject);
    await client.open();
    console.log('Enabling the commands on the client');
    client.onDeviceMethod(commandNameGetMaxMinReport1, commandHandler);
    client.onDeviceMethod(commandNameGetMaxMinReport2, commandHandler);
    client.onDeviceMethod(commandNameReboot, commandHandler);

    // Send Telemetry after some interval
    let index1 = 0;
    let index2 = 0;
    let index3 = 0;
    intervalToken1 = setInterval(() => {
      const data = JSON.stringify(thermostat1.updateSensor().getCurrentTemperatureObject());
      sendTelemetry(client, data, index1, thermostat1ComponentName).catch((err) => console.log('error ', err.toString()));
      index1 += 1;
    }, 5000);

    intervalToken2 = setInterval(() => {
      const data = JSON.stringify(thermostat2.updateSensor().getCurrentTemperatureObject());
      sendTelemetry(client, data, index2, thermostat2ComponentName).catch((err) => console.log('error ', err.toString()));
      index2 += 1;
    }, 5500);


    intervalToken3 = setInterval(() => {
      const data = JSON.stringify({ workingset: 1 + (Math.random() * 90) });
      sendTelemetry(client, data, index3, null).catch((err) => console.log('error ', err.toString()));
      index3 += 1;
    }, 6000);

    // attach a standard input exit listener
    exitListener(client);

    try {
      resultTwin = await client.getTwin();
      // Only report readable properties
      const patchRoot = helperCreateReportedPropertiesPatch({ serialNumber: serialNumber }, null);
      const patchThermostat1Info = helperCreateReportedPropertiesPatch({
        maxTempSinceLastReboot: thermostat1.getMaxTemperatureValue(),
      }, thermostat1ComponentName);

      const patchThermostat2Info = helperCreateReportedPropertiesPatch({
        maxTempSinceLastReboot: thermostat2.getMaxTemperatureValue(),
      }, thermostat2ComponentName);

      const patchDeviceInfo = helperCreateReportedPropertiesPatch({
        manufacturer: 'Contoso Device Corporation',
        model: 'Contoso 47-turbo',
        swVersion: '10.89',
        osName: 'Contoso_OS',
        processorArchitecture: 'Contoso_x86',
        processorManufacturer: 'Contoso Industries',
        totalStorage: 65000,
        totalMemory: 640,
      }, deviceInfoComponentName);

      // the below things can only happen once the twin is there
      updateComponentReportedProperties(resultTwin, patchRoot, null);
      updateComponentReportedProperties(resultTwin, patchThermostat1Info, thermostat1ComponentName);
      updateComponentReportedProperties(resultTwin, patchThermostat2Info, thermostat2ComponentName);
      updateComponentReportedProperties(resultTwin, patchDeviceInfo, deviceInfoComponentName);
      desiredPropertyPatchListener(resultTwin, [thermostat1ComponentName, thermostat2ComponentName, deviceInfoComponentName]);
    } catch (err) {
      console.error('could not retrieve twin or report twin properties\n' + err.toString());
    }
  } catch (err) {
    console.error('could not connect Plug and Play client or could not attach interval function for telemetry\n' + err.toString());
  }
}

La función provisionDevice muestra cómo el dispositivo usa DPS para registrarse y conectarse a IoT Central. La carga incluye el identificador modelo que IoT Central usa para asignar el dispositivo a una plantilla de dispositivo:

async function provisionDevice(payload) {
  var provSecurityClient = new SymmetricKeySecurityClient(registrationId, symmetricKey);
  var provisioningClient = ProvisioningDeviceClient.create(provisioningHost, idScope, new ProvProtocol(), provSecurityClient);

  if (payload) {
    provisioningClient.setProvisioningPayload(payload);
  }

  try {
    let result = await provisioningClient.register();
    deviceConnectionString = 'HostName=' + result.assignedHub + ';DeviceId=' + result.deviceId + ';SharedAccessKey=' + symmetricKey;
    console.log('registration succeeded');
    console.log('assigned hub=' + result.assignedHub);
    console.log('deviceId=' + result.deviceId);
    console.log('payload=' + JSON.stringify(result.payload));
  } catch (err) {
    console.error("error registering device: " + err.toString());
  }
}

La función sendTelemetry muestra cómo el dispositivo envía la telemetría de temperatura a IoT Central. Para la telemetría de los componentes, agrega una propiedad denominada $.sub con el nombre de componente:

async function sendTelemetry(deviceClient, data, index, componentName) {
  if componentName) {
    console.log('Sending telemetry message %d from component: %s ', index, componentName);
  } else {
    console.log('Sending telemetry message %d from root interface', index);
  }
  const msg = new Message(data);
  if (componentName) {
    msg.properties.add(messageSubjectProperty, componentName);
  }
  msg.contentType = 'application/json';
  msg.contentEncoding = 'utf-8';
  await deviceClient.sendEvent(msg);
}

El método main utiliza un método auxiliar llamado helperCreateReportedPropertiesPatch para crear mensajes de actualización de propiedades. Este método usa un parámetro opcional para especificar el componente que envía la propiedad:

const helperCreateReportedPropertiesPatch = (propertiesToReport, componentName) => {
  let patch;
  if (!!(componentName)) {
    patch = { };
    propertiesToReport.__t = 'c';
    patch[componentName] = propertiesToReport;
  } else {
    patch = { };
    patch = propertiesToReport;
  }
  if (!!(componentName)) {
    console.log('The following properties will be updated for component: ' + componentName);
  } else {
    console.log('The following properties will be updated for root interface.');
  }
  console.log(patch);
  return patch;
};

El método main usa el siguiente método para controlar las actualizaciones que se realizan en las propiedades grabables desde IoT Central. Observe la forma en que el método compila la respuesta con la versión y el código de estado:

const desiredPropertyPatchListener = (deviceTwin, componentNames) => {
  deviceTwin.on('properties.desired', (delta) => {
    console.log('Received an update for device with value: ' + JSON.stringify(delta));
    Object.entries(delta).forEach(([key, values]) => {
      const version = delta.$version;
      if (!!(componentNames) && componentNames.includes(key)) { // then it is a component we are expecting
        const componentName = key;
        const patchForComponents = { [componentName]: {} };
        Object.entries(values).forEach(([propertyName, propertyValue]) => {
          if (propertyName !== '__t' && propertyName !== '$version') {
            console.log('Will update property: ' + propertyName + ' to value: ' + propertyValue + ' of component: ' + componentName);
            const propertyContent = { value: propertyValue };
            propertyContent.ac = 200;
            propertyContent.ad = 'Successfully executed patch';
            propertyContent.av = version;
            patchForComponents[componentName][propertyName] = propertyContent;
          }
        });
        updateComponentReportedProperties(deviceTwin, patchForComponents, componentName);
      }
      else if  (key !== '$version') { // individual property for root
        const patchForRoot = { };
        console.log('Will update property: ' + key + ' to value: ' + values + ' for root');
        const propertyContent = { value: values };
        propertyContent.ac = 200;
        propertyContent.ad = 'Successfully executed patch';
        propertyContent.av = version;
        patchForRoot[key] = propertyContent;
        updateComponentReportedProperties(deviceTwin, patchForRoot, null);
      }
    });
  });
};

El método main utiliza los siguientes métodos para controlar los comandos desde IoT Central:

const commandHandler = async (request, response) => {
  helperLogCommandRequest(request);
  switch (request.methodName) {
  case commandNameGetMaxMinReport1: {
    await sendCommandResponse(request, response, 200, thermostat1.getMaxMinReportObject());
    break;
  }
  case commandNameGetMaxMinReport2: {
    await sendCommandResponse(request, response, 200, thermostat2.getMaxMinReportObject());
    break;
  }
  case commandNameReboot: {
    await sendCommandResponse(request, response, 200, 'reboot response');
    break;
  }
  default:
    await sendCommandResponse(request, response, 404, 'unknown method');
    break;
  }
};

const sendCommandResponse = async (request, response, status, payload) => {
  try {
    await response.send(status, payload);
    console.log('Response to method: ' + request.methodName + ' sent successfully.' );
  } catch (err) {
    console.error('An error occurred when sending a method response:\n' + err.toString());
  }
};

Obtención de información sobre la conexión

Al ejecutar la aplicación de dispositivo de ejemplo más adelante en este tutorial, necesitará los siguientes valores de configuración:

  • Ámbito de identificador: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo. Anote el valor de Ámbito de id.
  • Clave principal del grupo: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo > SAS-IoT-Devices (Dispositivos SAS-IoT). Tome nota del valor de Primary key (Clave principal) de la firma de acceso compartido.

Use Azure Cloud Shell para generar una clave de dispositivo a partir de la clave principal que ha recuperado:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

Anote la clave de dispositivo generada, ya que la usará más adelante en este tutorial.

Nota

Para ejecutar este ejemplo, no es necesario registrar el dispositivo de antemano en la aplicación de IoT Central. En el ejemplo se usa la funcionalidad de IoT Central para registrar automáticamente los dispositivos cuando se conectan por primera vez.

Ejecución del código

Para ejecutar la aplicación de ejemplo, abra un entorno de línea de comandos y vaya a la carpeta azure-iot-sdk-node/device/samples/javascript, que contiene el archivo de ejemplo pnp_temperature_controller.js.

Establezca las variables de entorno para configurar el ejemplo. El siguiente fragmento de código muestra cómo establecer las variables de entorno en un símbolo del sistema de Windows. Si una un shell de bash, reemplace los comandos set por los comandos export:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

Instale los paquetes necesarios:

npm install

Ejecución del ejemplo:

node pnp_temperature_controller.js

La salida siguiente muestra el registro del dispositivo y la conexión a IoT Central. Luego, el ejemplo envía la propiedad maxTempSinceLastReboot de los dos componentes del termostato antes de empezar a enviar datos de telemetría:

registration succeeded
assigned hub=iotc-....azure-devices.net
deviceId=sample-device-01
payload=undefined
Connecting using connection string: HostName=iotc-....azure-devices.net;DeviceId=sample-device-01;SharedAccessKey=qdv...IpAo=
Enabling the commands on the client
Please enter q or Q to exit sample.
The following properties will be updated for root interface.
{ serialNumber: 'alwinexlepaho8329' }
The following properties will be updated for component: thermostat1
{ thermostat1: { maxTempSinceLastReboot: 1.5902294191855972, __t: 'c' } }
The following properties will be updated for component: thermostat2
{ thermostat2: { maxTempSinceLastReboot: 16.181771928614545, __t: 'c' } }
The following properties will be updated for component: deviceInformation
{ deviceInformation:
   { manufacturer: 'Contoso Device Corporation',
     model: 'Contoso 47-turbo',
     swVersion: '10.89',
     osName: 'Contoso_OS',
     processorArchitecture: 'Contoso_x86',
     processorManufacturer: 'Contoso Industries',
     totalStorage: 65000,
     totalMemory: 640,
     __t: 'c' } }
executed sample
Received an update for device with value: {"$version":1}
Properties have been reported for component: thermostat1
Properties have been reported for component: thermostat2
Properties have been reported for component: deviceInformation
Properties have been reported for root interface.
Sending telemetry message 0 from component: thermostat1 
Sending telemetry message 0 from component: thermostat2 
Sending telemetry message 0 from root interface

Como operador de la aplicación de Azure IoT Central, puede:

  • Ver la telemetría enviada por los dos componentes del termostato en la página Overview (Información general):

    Captura de pantalla que muestra la página de información general del dispositivo.

  • Consulte las propiedades del dispositivo en la página About (Acerca de). En esta página se muestran las propiedades del componente de información del dispositivo y los dos componentes del termostato:

    Captura de pantalla que muestra la vista de propiedades del dispositivo.

Personalización de la plantilla de dispositivo

Como desarrollador de soluciones, puede personalizar la plantilla de dispositivo que IoT Central creó automáticamente al conectar el dispositivo de control de la temperatura.

Para agregar una propiedad de nube para almacenar el nombre de cliente asociado al dispositivo:

  1. En la aplicación de IoT Central, vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. En el modelo de controlador de temperatura, seleccione +Agregar funcionalidad.

  3. Indique el Nombre del cliente como el Nombre para mostrar, seleccione Propiedad en la nube como Tipo de funcionalidad, expanda la entrada y elija Cadena como el Esquema. Después, seleccione Guardar.

Para personalizar cómo se muestran los comandos Get Max-Min report cen la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para getMaxMinReport (thermostat1), reemplace Get Max-Min report por Get thermostat1 status report.

  3. Para getMaxMinReport (thermostat2), reemplace Get Max-Min report por Get thermostat2 status report.

  4. Seleccione Guardar.

Para personalizar cómo se muestran las propiedades de escritura Target Temperature en la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para targetTemperature (thermostat1) , reemplace Target Temperature por Target Temperature (1) .

  3. Para targetTemperature (thermostat2) , reemplace Target Temperature por Target Temperature (2) .

  4. Seleccione Guardar.

Los componentes del termostato del modelo Thermostat Controller (Controlador del termostato) incluyen la propiedad grabableTarget Temperature; la plantilla de dispositivo incluye la propiedad en la nube Customer Name. Cree una vista que pueda usar el operador para editar estas propiedades:

  1. Seleccione Views (Vistas) y el icono Editing device and cloud data (Edición de los datos del dispositivo y de la nube).

  2. Escriba Properties (Propiedades) como nombre del formulario.

  3. Seleccione las propiedades Target Temperature (1), Target Temperature (2) y Customer Name. Después, seleccione Add section (Agregar sección).

  4. Guarde los cambios.

Captura de pantalla que muestra una vista para actualizar los valores de propiedad.

Publicación de la plantilla de dispositivo

Para que un operador pueda ver y usar las personalizaciones realizadas, debe publicar la plantilla de dispositivo.

Seleccione Publish (Publicar) en la plantilla del dispositivo Thermostat. En el panel Publicar esta plantilla de dispositivo en la aplicación, seleccione Publicar.

Ahora los operadores podrán usar la vista Properties (Propiedades) para actualizar los valores de propiedad y llamar a comandos denominados Get thermostat1 status report y Get thermostat2 status report en la página de comandos del dispositivo:

  • Actualice los valores de la propiedad grabable en la página Properties (Propiedades):

    Captura de pantalla que muestra la actualización de las propiedades del dispositivo.

  • Llame al comando desde la página Commands (Comandos). Si ejecuta el comando de informe de estado, seleccione una fecha y hora para el parámetro Desde antes de la ejecución del comando:

    Captura de pantalla que muestra la llamada a un comando.

    Captura de pantalla que muestra una respuesta de comando.

Puede ver cómo responde el dispositivo a los comandos y las actualizaciones de propiedades. El comando getMaxMinReport está en el thermostat2 componente, mientras que el comando reboot está en el componente predeterminado. La propiedad grabable targetTemperature se estableció para el componente thermostat2:

Received command request for command name: thermostat2*getMaxMinReport
The command request payload is:
2021-03-26T06:00:00.000Z
Response to method: thermostat2*getMaxMinReport sent successfully.

...

Received command request for command name: reboot
The command request payload is:
10
Response to method: reboot sent successfully.

...

Received an update for device with value: {"thermostat2":{"targetTemperature":76,"__t":"c"},"$version":2}
Will update property: targetTemperature to value: 76 of component: thermostat2
Properties have been reported for component: thermostat2

Examinar el código

Prerrequisitos

Para completar los pasos de este artículo, necesitará los siguientes recursos:

  • Una máquina de desarrollo con Python instalado. Compruebe el SDK de Python de Azure IoT para ver los requisitos actuales de la versión de Python. Puede ejecutar python --version en la línea de comandos para comprobar la versión. Python está disponible para una amplia variedad de sistemas operativos. En las instrucciones de este tutorial se da por hecho que se ejecuta el comando python en el símbolo del sistema de Windows.

  • Una copia local del repositorio de GitHub de SDK de IoT de Microsoft Azure para Python que contiene el código de ejemplo. Use este vínculo para descargar una copia del repositorio: Descargar el archivo ZIP. Después, descomprima el archivo en una ubicación conveniente en la máquina local.

Revisión del código

En la copia del SDK de Microsoft Azure IoT para Python que descargó anteriormente, abra el archivo azure-iot-sdk-python/samples/pnp/temp_controller_with_thermostats.py en un editor de texto.

El ejemplo implementa el modelo de lenguaje de definición de gemelo digital del Controlador de temperatura de varios componentes.

Al ejecutar el ejemplo para conectarse a IoT Central, usa Device Provisioning Service (DPS) para registrar el dispositivo y generar una cadena de conexión. En el ejemplo se recupera la información de conexión de DPS que necesita del entorno de línea de comandos.

La función main:

  • Usa DPS para aprovisionar el dispositivo. La información de aprovisionamiento incluye el identificador de modelo. IoT Central usa el identificador de modelo para identificar o generar la plantilla de dispositivo específico. Para más información, consulte Asignación de un dispositivo a una plantilla de dispositivo.
  • Crea un objeto Device_client y establece el identificador del modelo dtmi:com:example:TemperatureController;2 antes de abrir la conexión.
  • Envía los valores de propiedad iniciales a IoT Central. Utiliza pnp_helper para crear las revisiones.
  • Crea agentes de escucha para los comandos getMaxMinReport y reboot. Cada componente de termostato tiene su propio comando getMaxMinReport.
  • Crea un agente de escucha de propiedad para realizar escuchas de las actualizaciones de las propiedades de escritura.
  • Inicia un bucle para enviar telemetría de temperatura desde los dos componentes del termostato y la telemetría del conjunto de trabajo desde el componente predeterminado cada 8 segundos.
async def main():
    switch = os.getenv("IOTHUB_DEVICE_SECURITY_TYPE")
    if switch == "DPS":
        provisioning_host = (
            os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            if os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            else "global.azure-devices-provisioning.net"
        )
        id_scope = os.getenv("IOTHUB_DEVICE_DPS_ID_SCOPE")
        registration_id = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_ID")
        symmetric_key = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_KEY")

        registration_result = await provision_device(
            provisioning_host, id_scope, registration_id, symmetric_key, model_id
        )

        if registration_result.status == "assigned":
            print("Device was assigned")
            print(registration_result.registration_state.assigned_hub)
            print(registration_result.registration_state.device_id)
            device_client = IoTHubDeviceClient.create_from_symmetric_key(
                symmetric_key=symmetric_key,
                hostname=registration_result.registration_state.assigned_hub,
                device_id=registration_result.registration_state.device_id,
                product_info=model_id,
            )
        else:
            raise RuntimeError(
                "Could not provision device. Aborting Plug and Play device connection."
            )

    elif switch == "connectionString":
        # ...

    # Connect the client.
    await device_client.connect()

    ################################################
    # Update readable properties from various components

    properties_root = pnp_helper.create_reported_properties(serialNumber=serial_number)
    properties_thermostat1 = pnp_helper.create_reported_properties(
        thermostat_1_component_name, maxTempSinceLastReboot=98.34
    )
    properties_thermostat2 = pnp_helper.create_reported_properties(
        thermostat_2_component_name, maxTempSinceLastReboot=48.92
    )
    properties_device_info = pnp_helper.create_reported_properties(
        device_information_component_name,
        swVersion="5.5",
        manufacturer="Contoso Device Corporation",
        model="Contoso 4762B-turbo",
        osName="Mac Os",
        processorArchitecture="x86-64",
        processorManufacturer="Intel",
        totalStorage=1024,
        totalMemory=32,
    )

    property_updates = asyncio.gather(
        device_client.patch_twin_reported_properties(properties_root),
        device_client.patch_twin_reported_properties(properties_thermostat1),
        device_client.patch_twin_reported_properties(properties_thermostat2),
        device_client.patch_twin_reported_properties(properties_device_info),
    )

    ################################################
    # Get all the listeners running
    print("Listening for command requests and property updates")

    global THERMOSTAT_1
    global THERMOSTAT_2
    THERMOSTAT_1 = Thermostat(thermostat_1_component_name, 10)
    THERMOSTAT_2 = Thermostat(thermostat_2_component_name, 10)

    listeners = asyncio.gather(
        execute_command_listener(
            device_client, method_name="reboot", user_command_handler=reboot_handler
        ),
        execute_command_listener(
            device_client,
            thermostat_1_component_name,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_command_listener(
            device_client,
            thermostat_2_component_name,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_property_listener(device_client),
    )

    ################################################
    # Function to send telemetry every 8 seconds

    async def send_telemetry():
        print("Sending telemetry from various components")

        while True:
            curr_temp_ext = random.randrange(10, 50)
            THERMOSTAT_1.record(curr_temp_ext)

            temperature_msg1 = {"temperature": curr_temp_ext}
            await send_telemetry_from_temp_controller(
                device_client, temperature_msg1, thermostat_1_component_name
            )

            curr_temp_int = random.randrange(10, 50)  # Current temperature in Celsius
            THERMOSTAT_2.record(curr_temp_int)

            temperature_msg2 = {"temperature": curr_temp_int}

            await send_telemetry_from_temp_controller(
                device_client, temperature_msg2, thermostat_2_component_name
            )

            workingset_msg3 = {"workingSet": random.randrange(1, 100)}
            await send_telemetry_from_temp_controller(device_client, workingset_msg3)

    send_telemetry_task = asyncio.ensure_future(send_telemetry())

    # ...

La función provision_device usa DPS para aprovisionar el dispositivo y registrarlo con IoT Central. La función incluye el identificador del modelo de dispositivo, que IoT Central usa para asignar un dispositivo a una plantilla de dispositivo en la carga útil de aprovisionamiento:

async def provision_device(provisioning_host, id_scope, registration_id, symmetric_key, model_id):
    provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(
        provisioning_host=provisioning_host,
        registration_id=registration_id,
        id_scope=id_scope,
        symmetric_key=symmetric_key,
    )

    provisioning_device_client.provisioning_payload = {"modelId": model_id}
    return await provisioning_device_client.register()

La función execute_command_listener controla las solicitudes de comando, ejecuta la función max_min_handler cuando el dispositivo recibe el comando getMaxMinReport para los componentes del termostato y la función reboot_handler cuando el dispositivo recibe el comando reboot. Usa el módulo pnp_helper para generar la respuesta:

async def execute_command_listener(
    device_client,
    component_name=None,
    method_name=None,
    user_command_handler=None,
    create_user_response_handler=None,
):
    while True:
        if component_name and method_name:
            command_name = component_name + "*" + method_name
        elif method_name:
            command_name = method_name
        else:
            command_name = None

        command_request = await device_client.receive_method_request(command_name)
        print("Command request received with payload")
        values = command_request.payload
        print(values)

        if user_command_handler:
            await user_command_handler(values)
        else:
            print("No handler provided to execute")

        (response_status, response_payload) = pnp_helper.create_response_payload_with_status(
            command_request, method_name, create_user_response=create_user_response_handler
        )

        command_response = MethodResponse.create_from_method_request(
            command_request, response_status, response_payload
        )

        try:
            await device_client.send_method_response(command_response)
        except Exception:
            print("responding to the {command} command failed".format(command=method_name))

async def execute_property_listener controla las actualizaciones de las propiedades de escritura como targetTemperature para los componentes del termostato y genera la respuesta JSON. Usa el módulo pnp_helper para generar la respuesta:

async def execute_property_listener(device_client):
    while True:
        patch = await device_client.receive_twin_desired_properties_patch()  # blocking call
        print(patch)
        properties_dict = pnp_helper.create_reported_properties_from_desired(patch)

        await device_client.patch_twin_reported_properties(properties_dict)

La función send_telemetry_from_temp_controller envía los mensajes de telemetría de los componentes del termostato a IoT Central. Usa el módulo pnp_helper para crear los mensajes:

async def send_telemetry_from_temp_controller(device_client, telemetry_msg, component_name=None):
    msg = pnp_helper.create_telemetry(telemetry_msg, component_name)
    await device_client.send_message(msg)
    print("Sent message")
    print(msg)
    await asyncio.sleep(5)

Obtención de información sobre la conexión

Al ejecutar la aplicación de dispositivo de ejemplo más adelante en este tutorial, necesitará los siguientes valores de configuración:

  • Ámbito de identificador: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo. Anote el valor de Ámbito de id.
  • Clave principal del grupo: En la aplicación de IoT Central, vaya a Permisos > Grupos de conexión del dispositivo > SAS-IoT-Devices (Dispositivos SAS-IoT). Tome nota del valor de Primary key (Clave principal) de la firma de acceso compartido.

Use Azure Cloud Shell para generar una clave de dispositivo a partir de la clave principal que ha recuperado:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

Anote la clave de dispositivo generada, ya que la usará más adelante en este tutorial.

Nota

Para ejecutar este ejemplo, no es necesario registrar el dispositivo de antemano en la aplicación de IoT Central. En el ejemplo se usa la funcionalidad de IoT Central para registrar automáticamente los dispositivos cuando se conectan por primera vez.

Ejecución del código

Para ejecutar la aplicación de ejemplo, abra un entorno de línea de comandos y vaya a la carpeta azure-iot-sdk-python-2/samples/pnp que contiene el archivo de ejemplo temp_controller_with_thermostats.py.

Establezca las variables de entorno para configurar el ejemplo. El siguiente fragmento de código muestra cómo establecer las variables de entorno en un símbolo del sistema de Windows. Si una un shell de bash, reemplace los comandos set por los comandos export:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

Instale los paquetes necesarios:

pip install azure-iot-device

Ejecución del ejemplo:

python temp_controller_with_thermostats.py

La salida siguiente muestra el registro del dispositivo y la conexión a IoT Central. El ejemplo envía las propiedades maxTempSinceLastReboot de los dos componentes del termostato antes de comenzar a enviar telemetría:

Device was assigned
iotc-60a.....azure-devices.net
sample-device-01
Updating pnp properties for root interface
{'serialNumber': 'alohomora'}
Updating pnp properties for thermostat1
{'thermostat1': {'maxTempSinceLastReboot': 98.34, '__t': 'c'}}
Updating pnp properties for thermostat2
{'thermostat2': {'maxTempSinceLastReboot': 48.92, '__t': 'c'}}
Updating pnp properties for deviceInformation
{'deviceInformation': {'swVersion': '5.5', 'manufacturer': 'Contoso Device Corporation', 'model': 'Contoso 4762B-turbo', 'osName': 'Mac Os', 'processorArchitecture': 'x86-64', 'processorManufacturer': 'Intel', 'totalStorage': 1024, 'totalMemory': 32, '__t': 'c'}}
Listening for command requests and property updates
Press Q to quit
Sending telemetry from various components
Sent message
{"temperature": 27}
Sent message
{"temperature": 17}
Sent message
{"workingSet": 13}

Como operador de la aplicación de Azure IoT Central, puede:

  • Ver la telemetría enviada por los dos componentes del termostato en la página Overview (Información general):

    Captura de pantalla que muestra la página de información general del dispositivo.

  • Consulte las propiedades del dispositivo en la página About (Acerca de). En esta página se muestran las propiedades del componente de información del dispositivo y los dos componentes del termostato:

    Captura de pantalla que muestra la vista de propiedades del dispositivo.

Personalización de la plantilla de dispositivo

Como desarrollador de soluciones, puede personalizar la plantilla de dispositivo que IoT Central creó automáticamente al conectar el dispositivo de control de la temperatura.

Para agregar una propiedad de nube para almacenar el nombre de cliente asociado al dispositivo:

  1. En la aplicación de IoT Central, vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. En el modelo de controlador de temperatura, seleccione +Agregar funcionalidad.

  3. Indique el Nombre del cliente como el Nombre para mostrar, seleccione Propiedad en la nube como Tipo de funcionalidad, expanda la entrada y elija Cadena como el Esquema. Después, seleccione Guardar.

Para personalizar cómo se muestran los comandos Get Max-Min report cen la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para getMaxMinReport (thermostat1), reemplace Get Max-Min report por Get thermostat1 status report.

  3. Para getMaxMinReport (thermostat2), reemplace Get Max-Min report por Get thermostat2 status report.

  4. Seleccione Guardar.

Para personalizar cómo se muestran las propiedades de escritura Target Temperature en la aplicación de IoT Central:

  1. Vaya a la plantilla del dispositivo Temperature Controller (Controlador de temperatura) en la página Device templates (Plantillas de dispositivo).

  2. Para targetTemperature (thermostat1) , reemplace Target Temperature por Target Temperature (1) .

  3. Para targetTemperature (thermostat2) , reemplace Target Temperature por Target Temperature (2) .

  4. Seleccione Guardar.

Los componentes del termostato del modelo Thermostat Controller (Controlador del termostato) incluyen la propiedad grabableTarget Temperature; la plantilla de dispositivo incluye la propiedad en la nube Customer Name. Cree una vista que pueda usar el operador para editar estas propiedades:

  1. Seleccione Views (Vistas) y el icono Editing device and cloud data (Edición de los datos del dispositivo y de la nube).

  2. Escriba Properties (Propiedades) como nombre del formulario.

  3. Seleccione las propiedades Target Temperature (1), Target Temperature (2) y Customer Name. Después, seleccione Add section (Agregar sección).

  4. Guarde los cambios.

Captura de pantalla que muestra una vista para actualizar los valores de propiedad.

Publicación de la plantilla de dispositivo

Para que un operador pueda ver y usar las personalizaciones realizadas, debe publicar la plantilla de dispositivo.

Seleccione Publish (Publicar) en la plantilla del dispositivo Thermostat. En el panel Publicar esta plantilla de dispositivo en la aplicación, seleccione Publicar.

Ahora los operadores podrán usar la vista Properties (Propiedades) para actualizar los valores de propiedad y llamar a comandos denominados Get thermostat1 status report y Get thermostat2 status report en la página de comandos del dispositivo:

  • Actualice los valores de la propiedad grabable en la página Properties (Propiedades):

    Captura de pantalla que muestra la actualización de las propiedades del dispositivo.

  • Llame al comando desde la página Commands (Comandos). Si ejecuta el comando de informe de estado, seleccione una fecha y hora para el parámetro Desde antes de la ejecución del comando:

    Captura de pantalla que muestra la llamada a un comando.

    Captura de pantalla que muestra una respuesta de comando.

Puede ver cómo responde el dispositivo a los comandos y las actualizaciones de propiedades:

{'thermostat1': {'targetTemperature': 67, '__t': 'c'}, '$version': 2}
the data in the desired properties patch was: {'thermostat1': {'targetTemperature': 67, '__t': 'c'}, '$version': 2}
Values received are :-
{'targetTemperature': 67, '__t': 'c'}
Sent message

...

Command request received with payload
2021-03-31T05:00:00.000Z
Will return the max, min and average temperature from the specified time 2021-03-31T05:00:00.000Z to the current time
Done generating
{"avgTemp": 4.0, "endTime": "2021-03-31T12:29:48.322427", "maxTemp": 18, "minTemp": null, "startTime": "2021-03-31T12:28:28.322381"}

Visualización de datos sin procesar

Puede usar la vista Datos sin procesar para examinar los datos sin procesar que el dispositivo envía a IoT Central:

Captura de pantalla en la que se muestra la vista de datos sin procesar.

En esta vista, puede seleccionar las columnas que se van a mostrar y establecer el intervalo de tiempo que desea ver. La columna Datos no modelados muestra datos del dispositivo que no coinciden con ninguna de las definiciones de telemetría o propiedades de la plantilla de dispositivo.

Limpieza de recursos

Si no tiene previsto realizar otro inicios rápidos o tutoriales de IoT Central, puede eliminar la aplicación de IoT Central:

  1. En la aplicación de IoT Central, vaya a Aplicación > Administración.
  2. Seleccione Delete (Eliminar) y confirme la acción.

Pasos siguientes

Si prefiere continuar con el conjunto de tutoriales de IoT Central y obtener más información sobre la creación de una solución de IoT Central, consulte: