Azure IoT SDK per dispositivi C: altre informazioni sul serializzatoreAzure IoT device SDK for C – more about serializer

Il primo articolo di questa serie ha introdotto Azure IoT SDK per dispositivi per C. L'articolo successivo fornisce una descrizione più dettagliata di IoTHubClient.The first article in this series introduced the Azure IoT device SDK for C. The next article provided a more detailed description of the IoTHubClient. Questo articolo completa l'illustrazione dell'SDK fornendo una descrizione più dettagliata del componente restante, la libreria serializer.This article completes coverage of the SDK by providing a more detailed description of the remaining component: the serializer library.

L'articolo introduttivo ha descritto come usare la libreria serializer per inviare eventi e ricevere messaggi dall'hub IoT.The introductory article described how to use the serializer library to send events to and receive messages from IoT Hub. Questo articolo estende la discussione, fornendo una spiegazione più completa della modellazione dei dati con il linguaggio macro serializer .In this article, we extend that discussion by providing a more complete explanation of how to model your data with the serializer macro language. Include anche altri dettagli su come vengono serializzati i messaggi da parte della libreria e, in alcuni casi, come è possibile controllare il comportamento della serializzazione.The article also includes more detail about how the library serializes messages (and in some cases how you can control the serialization behavior). Verranno descritti anche alcuni parametri modificabili che determinano le dimensioni dei modelli creati.We'll also describe some parameters you can modify that determine the size of the models you create.

L'articolo si conclude rivedendo alcuni argomenti trattati negli articoli precedenti, quale la gestione dei messaggi e delle proprietà.Finally, the article revisits some topics covered in previous articles such as message and property handling. Come si scoprirà, alcune funzionalità hanno lo stesso comportamento quando si usa sia la libreria serializer che la libreria IoTHubClient.As we'll find out, those features work in the same way using the serializer library as they do with the IoTHubClient library.

Tutto ciò che viene descritto in questo articolo si basa sugli esempi dell'SDK per serializer .Everything described in this article is based on the serializer SDK samples. Se si vuole seguire la procedura, vedere le applicazioni simplesample_amqp e simplesample_http incluse in Azure IoT SDK per dispositivi per C.If you want to follow along, see the simplesample_amqp and simplesample_http applications included in the Azure IoT device SDK for C.

È possibile trovare il repository GitHub di Azure IoT SDK per dispositivi per C e visualizzare i dettagli dell'API nelle informazioni di riferimento per l'API C.You can find the Azure IoT device SDK for C GitHub repository and view details of the API in the C API reference.

Linguaggio di modellazioneThe modeling language

L'articolo introduttivo di questa serie ha presentato il linguaggio di modellazione di Azure IoT SDK per dispositivi per C tramite l'esempio fornito nell'applicazione simplesample_amqp:The introductory article in this series introduced the Azure IoT device SDK for C modeling language through the example provided in the simplesample_amqp application:

BEGIN_NAMESPACE(WeatherStation);

DECLARE_MODEL(ContosoAnemometer,
WITH_DATA(ascii_char_ptr, DeviceId),
WITH_DATA(double, WindSpeed),
WITH_ACTION(TurnFanOn),
WITH_ACTION(TurnFanOff),
WITH_ACTION(SetAirResistance, int, Position)
);

END_NAMESPACE(WeatherStation);

Come si può notare, il linguaggio di modellazione si basa su macro C.As you can see, the modeling language is based on C macros. Si inizia sempre la definizione con BEGIN_NAMESPACE e si termina sempre con END_NAMESPACE.You always begin your definition with BEGIN_NAMESPACE and always end with END_NAMESPACE. È normale denominare lo spazio dei nomi in base alla propria società o, come in questo caso, in base al progetto su cui si sta lavorando.It's common to name the namespace for your company or, as in this example, the project that you're working on.

Nello spazio dei nomi sono incluse le definizioni dei modelli.What goes inside the namespace are model definitions. In questo caso è presente un singolo modello per un anemometro.In this case, there is a single model for an anemometer. Al modello, come detto, può essere assegnato un nome qualsiasi, ma in genere si usa un nome che faccia riferimento al dispositivo o al tipo di dati che si vuole scambiare con l'hub IoT.Once again, the model can be named anything, but typically this is named for the device or type of data you want to exchange with IoT Hub.

I modelli contengono una definizione degli eventi che è possibile inserire nell'hub IoT (dati), oltre ai messaggi che si possono ricevere dall'hub IoT (azioni).Models contain a definition of the events you can ingress to IoT Hub (the data) as well as the messages you can receive from IoT Hub (the actions). Come si può vedere dall'esempio, gli eventi hanno un tipo e un nome, mentre le azioni hanno un nome e parametri facoltativi, ognuno con un tipo.As you can see from the example, events have a type and a name; actions have a name and optional parameters (each with a type).

In questo esempio non vengono illustrati i tipi di dati aggiuntivi supportati dall'SDK,What’s not demonstrated in this sample are additional data types that are supported by the SDK. che saranno trattati in seguito.We'll cover that next.

Nota

L'hub IoT fa riferimento ai dati inviati da un dispositivo come eventi, mentre il linguaggio di modellazione li definisce dati, usando WITH_DATA.IoT Hub refers to the data a device sends to it as events, while the modeling language refers to it as data (defined using WITH_DATA). In modo analogo, l'hub IoT fa riferimento ai dati inviati ai dispositivi come messaggi, mentre il linguaggio di modellazione li definisce azioni, usando WITH_ACTION.Likewise, IoT Hub refers to the data you send to devices as messages, while the modeling language refers to it as actions (defined using WITH_ACTION). Tenere presente che questi termini possono essere usati in modo intercambiabile in questo articolo.Be aware that these terms may be used interchangeably in this article.

Tipi di dati supportatiSupported data types

I tipi di dati seguenti sono supportati nei modelli creati con la libreria serializer .The following data types are supported in models created with the serializer library:

TipoType DescrizioneDescription
doubledouble Numero a virgola mobile a precisione doppiadouble precision floating point number
intint Intero a 32 bit32 bit integer
floatfloat Numero a virgola mobile a precisione singolasingle precision floating point number
longlong Intero lungolong integer
int8_tint8_t Intero a 8 bit8 bit integer
int16_tint16_t Intero a 16 bit16 bit integer
int32_tint32_t Intero a 32 bit32 bit integer
int64_tint64_t Intero a 64 bit64 bit integer
boolbool booleanboolean
ascii_char_ptrascii_char_ptr Stringa ASCIIASCII string
EDM_DATE_TIME_OFFSETEDM_DATE_TIME_OFFSET Offset data/oradate time offset
EDM_GUIDEDM_GUID GUIDGUID
EDM_BINARYEDM_BINARY binarybinary
DECLARE_STRUCTDECLARE_STRUCT Tipo di dati complessocomplex data type

Si inizierà con l'ultimo tipo di dati.Let’s start with the last data type. DECLARE_STRUCT consente di definire i tipi di dati complessi, che sono raggruppamenti degli altri tipi primitivi.The DECLARE_STRUCT allows you to define complex data types, which are groupings of the other primitive types. Questi raggruppamenti consentono di definire un modello simile al seguente:These groupings allow us to define a model that looks like this:

DECLARE_STRUCT(TestType,
double, aDouble,
int, aInt,
float, aFloat,
long, aLong,
int8_t, aInt8,
uint8_t, auInt8,
int16_t, aInt16,
int32_t, aInt32,
int64_t, aInt64,
bool, aBool,
ascii_char_ptr, aAsciiCharPtr,
EDM_DATE_TIME_OFFSET, aDateTimeOffset,
EDM_GUID, aGuid,
EDM_BINARY, aBinary
);

DECLARE_MODEL(TestModel,
WITH_DATA(TestType, Test)
);

Il modello contiene un singolo evento dati di tipo TestType.Our model contains a single data event of type TestType. TestType è un tipo complesso che include diversi membri, i quali dimostrano collettivamente i tipi primitivi supportati dal linguaggio di modellazione serializer.TestType is a complex type that includes several members, which collectively demonstrate the primitive types supported by the serializer modeling language.

Con un modello come questo è possibile scrivere codice per l'invio di dati all'hub IoT con l'aspetto seguente:With a model like this, we can write code to send data to IoT Hub that appears as follows:

TestModel* testModel = CREATE_MODEL_INSTANCE(MyThermostat, TestModel);

testModel->Test.aDouble = 1.1;
testModel->Test.aInt = 2;
testModel->Test.aFloat = 3.0f;
testModel->Test.aLong = 4;
testModel->Test.aInt8 = 5;
testModel->Test.auInt8 = 6;
testModel->Test.aInt16 = 7;
testModel->Test.aInt32 = 8;
testModel->Test.aInt64 = 9;
testModel->Test.aBool = true;
testModel->Test.aAsciiCharPtr = "ascii string 1";

time_t now;
time(&now);
testModel->Test.aDateTimeOffset = GetDateTimeOffset(now);

EDM_GUID guid = { { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F } };
testModel->Test.aGuid = guid;

unsigned char binaryArray[3] = { 0x01, 0x02, 0x03 };
EDM_BINARY binaryData = { sizeof(binaryArray), &binaryArray };
testModel->Test.aBinary = binaryData;

SendAsync(iotHubClientHandle, (const void*)&(testModel->Test));

Sostanzialmente, si assegna un valore a ogni membro della struttura Test e quindi si chiama SendAsync per inviare l'evento dati Test nel cloud.Basically, we’re assigning a value to every member of the Test structure and then calling SendAsync to send the Test data event to the cloud. SendAsync è una funzione helper che invia un singolo evento dati all'hub IoT:SendAsync is a helper function that sends a single data event to IoT Hub:

void SendAsync(IOTHUB_CLIENT_LL_HANDLE iotHubClientHandle, const void *dataEvent)
{
    unsigned char* destination;
    size_t destinationSize;
    if (SERIALIZE(&destination, &destinationSize, *(const unsigned char*)dataEvent) ==
    {
        // null terminate the string
        char* destinationAsString = (char*)malloc(destinationSize + 1);
        if (destinationAsString != NULL)
        {
            memcpy(destinationAsString, destination, destinationSize);
            destinationAsString[destinationSize] = '\0';
            IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromString(destinationAsString);
            if (messageHandle != NULL)
            {
                IoTHubClient_SendEventAsync(iotHubClientHandle, messageHandle, sendCallback, (void*)0);

                IoTHubMessage_Destroy(messageHandle);
            }
            free(destinationAsString);
        }
        free(destination);
    }
}

Questa funzione serializza l'evento dati specificato e lo invia all'hub IoT tramite IoTHubClient_SendEventAsync.This function serializes the given data event and sends it to IoT Hub using IoTHubClient_SendEventAsync. È lo stesso codice descritto negli articoli precedenti,SendAsync incapsula la logica in una funzione pratica.This is the same code discussed in previous articles (SendAsync encapsulates the logic into a convenient function).

Un'altra funzione helper usata nel codice precedente è GetDateTimeOffset.One other helper function used in the previous code is GetDateTimeOffset. Questa funzione trasforma l'ora specificata in un valore di tipo EDM_DATE_TIME_OFFSET:This function transforms the given time into a value of type EDM_DATE_TIME_OFFSET:

EDM_DATE_TIME_OFFSET GetDateTimeOffset(time_t time)
{
    struct tm newTime;
    gmtime_s(&newTime, &time);
    EDM_DATE_TIME_OFFSET dateTimeOffset;
    dateTimeOffset.dateTime = newTime;
    dateTimeOffset.fractionalSecond = 0;
    dateTimeOffset.hasFractionalSecond = 0;
    dateTimeOffset.hasTimeZone = 0;
    dateTimeOffset.timeZoneHour = 0;
    dateTimeOffset.timeZoneMinute = 0;
    return dateTimeOffset;
}

Se si esegue questo codice, viene inviato il messaggio seguente all'hub IoT:If you run this code, the following message is sent to IoT Hub:

{"aDouble":1.100000000000000, "aInt":2, "aFloat":3.000000, "aLong":4, "aInt8":5, "auInt8":6, "aInt16":7, "aInt32":8, "aInt64":9, "aBool":true, "aAsciiCharPtr":"ascii string 1", "aDateTimeOffset":"2015-09-14T21:18:21Z", "aGuid":"00010203-0405-0607-0809-0A0B0C0D0E0F", "aBinary":"AQID"}

Si noti che la serializzazione è in JSON, il formato generato dalla libreria serializer .Note that the serialization is JSON, which is the format generated by the serializer library. Si noti anche che ogni membro dell'oggetto JSON serializzato corrisponde ai membri di TestType definiti nel modello.Also note that each member of the serialized JSON object matches the members of the TestType that we defined in our model. Anche i valori corrispondono esattamente a quelli usati nel codice.The values also exactly match those used in the code. Notare tuttavia che i dati binari sono codificati in Base 64: "AQID" è la codifica Base 64 di {0x01, 0x02, 0x03}.However, note that the binary data is base64-encoded: "AQID" is the base64 encoding of {0x01, 0x02, 0x03}.

Questo esempio dimostra il vantaggio che deriva dall'uso della libreria serializer che abilita l'invio di codice JSON nel cloud, senza doversi occupare esplicitamente della serializzazione nell'applicazione.This example demonstrates the advantage of using the serializer library -- it enables us to send JSON to the cloud, without having to explicitly deal with serialization in our application. Tutto ciò di cui ci si deve occupare è l'impostazione dei valori degli eventi dati nel modello e la successiva chiamata di API semplici per inviare tali eventi nel cloud.All we have to worry about is setting the values of the data events in our model and then calling simple APIs to send those events to the cloud.

Con queste informazioni è possibile definire modelli che includono la gamma di tipi di dati supportati, compresi i tipi complessi. Volendo, si potrebbero anche includere tipi complessi in altri tipi complessi.With this information, we can define models that include the range of supported data types, including complex types (we could even include complex types within other complex types). Il codice JSON generato dall'esempio precedente solleva tuttavia un punto importante.However, he serialized JSON generated by the example above brings up an important point. Come si inviano dati con la libreria serializer determina esattamente come viene formato JSON.How we send data with the serializer library determines exactly how the JSON is formed. Di seguito viene trattato questo punto specifico.That particular point is what we'll cover next.

Altre informazioni sulla serializzazioneMore about serialization

La sezione precedente evidenzia un esempio dell'output generato dalla libreria serializer .The previous section highlights an example of the output generated by the serializer library. Questa sezione descrive in che modo la libreria serializza i dati e come si può controllare questo comportamento tramite le API di serializzazione.In this section, we'll explain how the library serializes data and how you can control that behavior using the serialization APIs.

Per procedere nella discussione sulla serializzazione, si utilizzerà un nuovo modello basato su un termostato.In order to advance the discussion on serialization, we'll work with a new model based on a thermostat. Prima di tutto verrà fornito un insieme di informazioni di base sullo scenario che si proverà a usare.First, let's provide some background on the scenario we're trying to address.

Si vuole creare il modello di un termostato che misuri la temperatura e l'umidità.We want to model a thermostat that measures temperature and humidity. I singoli dati saranno inviati all'hub IoT in modo diverso.Each piece of data is going to be sent to IoT Hub differently. Per impostazione predefinita, il termostato immette un evento temperatura ogni 2 minuti e un evento umidità ogni 15 minuti.By default, the thermostat ingresses a temperature event once every 2 minutes; a humidity event is ingressed once every 15 minutes. Con l'evento immesso dovrà essere incluso un timestamp che indichi l'ora in cui è stata misurata la temperatura o l'umidità corrispondente.When either event is ingressed, it must include a timestamp that shows the time that the corresponding temperature or humidity was measured.

Dato lo scenario, verranno illustrati due diversi metodi per modellare i dati e verrà spiegato l'effetto della modellazione sull'output serializzato.Given this scenario, we'll demonstrate two different ways to model the data, and we'll explain the effect that modeling has on the serialized output.

Modello 1Model 1

Ecco la prima versione di un modello che supporta lo scenario precedente:Here's the first version of a model that supports the previous scenario:

BEGIN_NAMESPACE(Contoso);

DECLARE_STRUCT(TemperatureEvent,
int, Temperature,
EDM_DATE_TIME_OFFSET, Time);

DECLARE_STRUCT(HumidityEvent,
int, Humidity,
EDM_DATE_TIME_OFFSET, Time);

DECLARE_MODEL(Thermostat,
WITH_DATA(TemperatureEvent, Temperature),
WITH_DATA(HumidityEvent, Humidity)
);

END_NAMESPACE(Contoso);

Il modello include due eventi dati: Temperature e Humidity.Note that the model includes two data events: Temperature and Humidity. A differenza degli esempi precedenti, il tipo di ogni evento è una struttura definita tramite DECLARE_STRUCT.Unlike previous examples, the type of each event is a structure defined using DECLARE_STRUCT. TemperatureEvent include una misura della temperatura e un timestamp. HumidityEvent contiene una misura dell'umidità e un timestamp.TemperatureEvent includes a temperature measurement and a timestamp; HumidityEvent contains a humidity measurement and a timestamp. Questo modello offre un modo naturale per modellare i dati per lo scenario descritto sopra.This model gives us a natural way to model the data for the scenario described above. Quando si invia un evento nel cloud, si invia una coppia temperatura/timestamp o umidità/timestamp.When we send an event to the cloud, we'll either send a temperature/timestamp or a humidity/timestamp pair.

È possibile inviare un evento temperatura nel cloud usando codice come il seguente:We can send a temperature event to the cloud using code such as the following:

time_t now;
time(&now);
thermostat->Temperature.Temperature = 75;
thermostat->Temperature.Time = GetDateTimeOffset(now);

unsigned char* destination;
size_t destinationSize;
if (SERIALIZE(&destination, &destinationSize, thermostat->Temperature) == IOT_AGENT_OK)
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
}

Nel codice di esempio si useranno valori hardcoded per temperatura e umidità, ma si immagini di recuperare effettivamente questi valori mediante il campionamento dei sensori corrispondenti nel termostato.We'll use hard-coded values for temperature and humidity in the sample code, but imagine that we’re actually retrieving these values by sampling the corresponding sensors on the thermostat.

Il codice precedente usa la funzione helper GetDateTimeOffset introdotta in precedenza.The code above uses the GetDateTimeOffset helper that was introduced previously. Per motivi che diventeranno più chiari in seguito, il codice separa in modo esplicito l'attività di serializzazione e di invio dell'evento.For reasons that will become clear later, this code explicitly separates the task of serializing and sending the event. Il codice precedente serializza l'evento temperatura in un buffer.The previous code serializes the temperature event into a buffer. Quindi sendMessage, una funzione helper inclusa in simplesample_amqp, invia l'evento all'hub IoT:Then, sendMessage is a helper function (included in simplesample_amqp) that sends the event to IoT Hub:

static void sendMessage(IOTHUB_CLIENT_HANDLE iotHubClientHandle, const unsigned char* buffer, size_t size)
{
    static unsigned int messageTrackingId;
    IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromByteArray(buffer, size);
    if (messageHandle != NULL)
    {
        IoTHubClient_SendEventAsync(iotHubClientHandle, messageHandle, sendCallback, (void*)(uintptr_t)messageTrackingId);

        IoTHubMessage_Destroy(messageHandle);
    }
    free((void*)buffer);
}

Questo codice è un subset della funzione helper SendAsync descritta nella sezione precedente, che quindi non verrà illustrata di nuovo.This code is a subset of the SendAsync helper described in the previous section, so we won’t go over it again here.

Quando si esegue il codice precedente per inviare l'evento temperatura, questo formato serializzato dell'evento viene inviato all'hub IoT:When we run the previous code to send the Temperature event, this serialized form of the event is sent to IoT Hub:

{"Temperature":75, "Time":"2015-09-17T18:45:56Z"}

Viene inviato un evento temperatura di tipo TemperatureEvent la cui struttura contiene un membro Temperature e Time.We're sending a temperature which is of type TemperatureEvent and that struct contains a Temperature and Time member. Questa condizione viene riflessa direttamente dei dati serializzati.This is directly reflected in the serialized data.

In modo analogo è possibile inviare un evento umidità con questo codice:Similarly, we can send a humidity event with this code:

thermostat->Humidity.Humidity = 45;
thermostat->Humidity.Time = GetDateTimeOffset(now);
if (SERIALIZE(&destination, &destinationSize, thermostat->Humidity) == IOT_AGENT_OK)
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
}

Il formato serializzato inviato all'hub IoT è simile al seguente:The serialized form that’s sent to IoT Hub appears as follows:

{"Humidity":45, "Time":"2015-09-17T18:45:56Z"}

Anche in questo caso, è un comportamento previsto.Again, this is as expected.

Con questo modello si può immaginare come sia facile aggiungere altri eventi.With this model, you can imagine how additional events can easily be added. Si possono definire altre strutture con DECLARE_STRUCT e includere l'evento corrispondente nel modello tramite WITH_DATA.You define more structures using DECLARE_STRUCT, and include the corresponding event in the model using WITH_DATA.

Ora si modificherà il modello in modo da includere gli stessi dati, ma con una struttura diversa.Now, let’s modify the model so that it includes the same data but with a different structure.

Modello 2Model 2

Considerare questo modello come alternativa a quello precedente:Consider this alternative model to the one above:

DECLARE_MODEL(Thermostat,
WITH_DATA(int, Temperature),
WITH_DATA(int, Humidity),
WITH_DATA(EDM_DATE_TIME_OFFSET, Time)
);

In questo caso sono state eliminate le macro DECLARE_STRUCT e sono stati semplicemente definiti gli elementi di dati dello scenario usando tipi semplici del linguaggio di modellazione.In this case we've eliminated the DECLARE_STRUCT macros and are simply defining the data items from our scenario using simple types from the modeling language.

Per il momento si ignorerà l'evento Time .Just for the moment let’s ignore the Time event. Ecco quindi il codice per l'immissione dell'evento Temperature:With that aside, here’s the code to ingress Temperature:

time_t now;
time(&now);
thermostat->Temperature = 75;

unsigned char* destination;
size_t destinationSize;
if (SERIALIZE(&destination, &destinationSize, thermostat->Temperature) == IOT_AGENT_OK)
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
}

Questo codice invia l'evento serializzato seguente all'hub IoT:This code sends the following serialized event to IoT Hub:

{"Temperature":75}

Il codice per inviare l'evento Humidity è simile al seguente:And the code for sending the Humidity event appears as follows:

thermostat->Humidity = 45;
if (SERIALIZE(&destination, &destinationSize, thermostat->Humidity) == IOT_AGENT_OK)
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
}

Questo codice invia queste informazioni all'hub IoT:This code sends this to IoT Hub:

{"Humidity":45}

Di nuovo nulla di insolito fino a questo punto.So far there are still no surprises. Si modificherà ora la modalità d'uso della macro SERIALIZE.Now let's change how we use the SERIALIZE macro.

La macro SERIALIZE può accettare più eventi dati come argomenti.The SERIALIZE macro can take multiple data events as arguments. In questo modo si possono serializzare insieme gli eventi Temperature e Humidity e inviarli all'hub IoT con una sola chiamata:This enables us to serialize the Temperature and Humidity event together and send them to IoT Hub in one call:

if (SERIALIZE(&destination, &destinationSize, thermostat->Temperature, thermostat->Humidity) == IOT_AGENT_OK)
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
}

Si potrebbe supporre che il risultato di questo codice sia l'invio di due eventi dati all'hub IoT:You might guess that the result of this code is that two data events are sent to IoT Hub:

[[

{"Temperature":75},{"Temperature":75},

{"Humidity":45}{"Humidity":45}

]]

In altre parole, si potrebbe ritenere che questo codice corrisponda all'invio di Temperature e Humidity separatamenteIn other words, you might expect that this code is the same as sending Temperature and Humidity separately. e che passare entrambi gli eventi a SERIALIZE nella stessa chiamata sia solo questione di praticità.It’s just a convenience to pass both events to SERIALIZE in the same call. Non è però così.However, that’s not the case. Al contrario, il codice precedente invia questo singolo evento dati all'hub IoT:Instead, the code above sends this single data event to IoT Hub:

{"Temperature":75, "Humidity":45}{"Temperature":75, "Humidity":45}

Può sembrare strano perché il modello definisce Temperature e Humidity come due eventi distinti:This may seem strange because our model defines Temperature and Humidity as two separate events:

DECLARE_MODEL(Thermostat,
WITH_DATA(int, Temperature),
WITH_DATA(int, Humidity),
WITH_DATA(EDM_DATE_TIME_OFFSET, Time)
);

Più precisamente, questi eventi non sono stati modellati con Temperature e Humidity nella stessa struttura:More to the point, we didn’t model these events where Temperature and Humidity are in the same structure:

DECLARE_STRUCT(TemperatureAndHumidityEvent,
int, Temperature,
int, Humidity,
);

DECLARE_MODEL(Thermostat,
WITH_DATA(TemperatureAndHumidityEvent, TemperatureAndHumidity),
);

Se fosse stato usato questo modello, sarebbe stato più facile capire in che modo Temperature e Humidity sarebbero inviati nello stesso messaggio serializzato.If we used this model, it would be easier to understand how Temperature and Humidity would be sent in the same serialized message. Potrebbe tuttavia non essere chiaro perché funziona in questo modo quando si passano entrambi gli eventi dati a SERIALIZE con il modello n. 2.However it may not be clear why it works that way when you pass both data events to SERIALIZE using model 2.

Questo comportamento è più facile da comprendere se si conoscono i presupposti della libreria serializer .This behavior is easier to understand if you know the assumptions that the serializer library is making. Per chiarire questo aspetto, si riesaminerà il modello:To make sense of this let’s go back to our model:

DECLARE_MODEL(Thermostat,
WITH_DATA(int, Temperature),
WITH_DATA(int, Humidity),
WITH_DATA(EDM_DATE_TIME_OFFSET, Time)
);

Pensare al modello in termini di "orientato a oggetti".Think of this model in object-oriented terms. In questo caso si modella un dispositivo fisico (Thermostat), che include attributi come Temperature e Humidity.In this case we’re modeling a physical device (a thermostat) and that device includes attributes like Temperature and Humidity.

Si può inviare l'intero stato del modello con un codice come il seguente:We can send the entire state of our model with code such as the following:

if (SERIALIZE(&destination, &destinationSize, thermostat->Temperature, thermostat->Humidity, thermostat->Time) == IOT_AGENT_OK)
{
    sendMessage(iotHubClientHandle, destination, destinationSize);
}

Presupponendo che i valori di Temperature, Humidity e Time siano impostati, si vedrà un evento come questo inviato all'hub IoT:Assuming the values of Temperature, Humidity and Time are set, we would see an event like this sent to IoT Hub:

{"Temperature":75, "Humidity":45, "Time":"2015-09-17T18:45:56Z"}

A volte è consigliabile inviare solo alcune proprietà del modello nel cloud. Questo è vero soprattutto se il modello contiene un numero elevato di eventi dati.Sometimes you may only want to send some properties of the model to the cloud (this is especially true if your model contains a large number of data events). È utile inviare solo un subset di eventi dati, come nell'esempio precedente:It’s useful to send only a subset of data events, such as in our earlier example:

{"Temperature":75, "Time":"2015-09-17T18:45:56Z"}

Questo genera esattamente lo stesso evento serializzato come se si fosse definito TemperatureEvent con un membro Temperature e un membro Time, come è stato fatto nel modello n. 1.This generates exactly the same serialized event as if we had defined a TemperatureEvent with a Temperature and Time member, just as we did with model 1. In questo caso è stato possibile generare esattamente lo stesso evento serializzato usando un modello diverso (modello n. 2), perché la chiamata a SERIALIZE è stata eseguita in un modo diverso.In this case we were able to generate exactly the same serialized event by using a different model (model 2) because we called SERIALIZE in a different way.

Il punto importante è che se si passano più eventi dati a SERIALIZE , presuppone che ogni evento sia una proprietà in un singolo oggetto JSON.The important point is that if you pass multiple data events to SERIALIZE, then it assumes each event is a property in a single JSON object.

L'approccio migliore dipende dall'utente e da come viene considerato il modello.The best approach depends on you and how you think about your model. Se si inviano "eventi" nel cloud e ogni evento contiene un set di proprietà definito, è più appropriato il primo approccio.If you’re sending "events" to the cloud and each event contains a defined set of properties, then the first approach makes a lot of sense. In tal caso, usare DECLARE_STRUCT per definire la struttura di ogni evento e quindi includere gli eventi nel modello usando la macro WITH_DATA.In that case you would use DECLARE_STRUCT to define the structure of each event and then include them in your model with the WITH_DATA macro. Inviare quindi ogni evento come illustrato sopra nel primo esempio.Then you send each event as we did in the first example above. Con questo approccio si passerà solo un singolo evento dati a SERIALIZER.In this approach you would only pass a single data event to SERIALIZER.

Se si considera il modello come se fosse orientato a oggetti, potrebbe essere più appropriato il secondo approccio.If you think about your model in an object-oriented fashion, then the second approach may suit you. In questo caso, gli elementi definiti con WITH_DATA diventano le "proprietà" dell'oggetto.In this case, the elements defined using WITH_DATA are the "properties" of your object. Si passerà il subset di eventi che si preferisce a SERIALIZE , a seconda del livello di stato degli "oggetti" che si vuole inviare nel cloud.You pass whatever subset of events to SERIALIZE that you like, depending on how much of your "object’s" state you want to send to the cloud.

Nessun approccio è giusto o sbagliato.Nether approach is right or wrong. Tenere solo presente il funzionamento della libreria serializer e scegliere l'approccio di modellazione che meglio soddisfa le proprie esigenze.Just be aware of how the serializer library works, and pick the modeling approach that best fits your needs.

Gestione dei messaggiMessage handling

Fino ad ora si è discusso solo dell'invio di eventi all'hub IoT, ma non è stata considerata la ricezione dei messaggi.So far this article has only discussed sending events to IoT Hub, and hasn't addressed receiving messages. Il motivo è che quello che occorre sapere sulla ricezione dei messaggi è stato ampiamente illustrato in un articolo precedente.The reason for this is that what we need to know about receiving messages has largely been covered in an earlier article. Tenere presente, come detto in quell'articolo, che i messaggi vengono elaborati registrando una funzione di richiamata dei messaggi:Recall from that article that you process messages by registering a message callback function:

IoTHubClient_SetMessageCallback(iotHubClientHandle, IoTHubMessage, myWeather)

Si scrive quindi la funzione di callback che viene richiamata alla ricezione di un messaggio:You then write the callback function that’s invoked when a message is received:

static IOTHUBMESSAGE_DISPOSITION_RESULT IoTHubMessage(IOTHUB_MESSAGE_HANDLE message, void* userContextCallback)
{
    IOTHUBMESSAGE_DISPOSITION_RESULT result;
    const unsigned char* buffer;
    size_t size;
    if (IoTHubMessage_GetByteArray(message, &buffer, &size) != IOTHUB_MESSAGE_OK)
    {
        printf("unable to IoTHubMessage_GetByteArray\r\n");
        result = EXECUTE_COMMAND_ERROR;
    }
    else
    {
        /*buffer is not zero terminated*/
        char* temp = malloc(size + 1);
        if (temp == NULL)
        {
            printf("failed to malloc\r\n");
            result = EXECUTE_COMMAND_ERROR;
        }
        else
        {
            memcpy(temp, buffer, size);
            temp[size] = '\0';
            EXECUTE_COMMAND_RESULT executeCommandResult = EXECUTE_COMMAND(userContextCallback, temp);
            result =
                (executeCommandResult == EXECUTE_COMMAND_ERROR) ? IOTHUBMESSAGE_ABANDONED :
                (executeCommandResult == EXECUTE_COMMAND_SUCCESS) ? IOTHUBMESSAGE_ACCEPTED :
                IOTHUBMESSAGE_REJECTED;
            free(temp);
        }
    }
    return result;
}

Questa implementazione di IoTHubMessage chiama la funzione specifica per ogni azione del modello.This implementation of IoTHubMessage calls the specific function for each action in your model. Ad esempio, se il modello definisce questa azione:For example, if your model defines this action:

WITH_ACTION(SetAirResistance, int, Position)

È necessario definire una funzione con questa firma.You must define a function with this signature:

EXECUTE_COMMAND_RESULT SetAirResistance(ContosoAnemometer* device, int Position)
{
    (void)device;
    (void)printf("Setting Air Resistance Position to %d.\r\n", Position);
    return EXECUTE_COMMAND_SUCCESS;
}

SetAirResistance .SetAirResistance is then called when that message is sent to your device.

Non è stato invece ancora illustrato qual è l'aspetto della versione serializzata del messaggio.What we haven't explained yet is what the serialized version of message looks like. In altre parole, se si vuole inviare un messaggio SetAirResistance al dispositivo, quale sarà il suo aspetto?In other words, if you want to send a SetAirResistance message to your device, what does that look like?

Se si invia un messaggio a un dispositivo, si dovrà usare Azure IoT service SDK.If you're sending a message to a device, you would do so through the Azure IoT service SDK. È comunque necessario sapere quale stringa inviare per richiamare un'azione particolare.You still need to know what string to send to invoke a particular action. Il formato generale per l'invio di un messaggio è simile al seguente:The general format for sending a message appears as follows:

{"Name" : "", "Parameters" : "" }

Se invia un oggetto JSON serializzato con due proprietà: Name è il nome dell'azione, ovvero il messaggio, e Parameters contiene i parametri dell'azione.You're sending a serialized JSON object with two properties: Name is the name of the action (message) and Parameters contains the parameters of that action.

Ad esempio, per richiamare SetAirResistance è possibile inviare questo messaggio a un dispositivo:For example, to invoke SetAirResistance you can send this message to a device:

{"Name" : "SetAirResistance", "Parameters" : { "Position" : 5 }}

Il nome dell'azione deve corrispondere esattamente a un'azione definita nel modello.The action name must exactly match an action defined in your model. Anche i nomi dei parametri devono corrispondere.The parameter names must match as well. Tenere presente anche la distinzione tra maiuscole e minuscole.Also note case sensitivity. Name e Parameters sono sempre in maiuscolo.Name and Parameters are always uppercase. Assicurarsi di rispettare la corrispondenza maiuscole/minuscole per i nomi di azioni e i parametri del modello.Make sure to match the case of your action name and parameters in your model. In questo esempio, il nome dell'azione è "SetAirResistance" e non "setairresistance".In this example, the action name is "SetAirResistance" and not "setairresistance".

Le altre due azioni TurnFanOn e TurnFanOff possono essere richiamate inviando i messaggi seguenti a un dispositivo:The two other actions TurnFanOn and TurnFanOff can be invoked by sending these messages to a device:

{"Name" : "TurnFanOn", "Parameters" : {}}
{"Name" : "TurnFanOff", "Parameters" : {}}

Questo descrive tutto ciò che è necessario sapere quando si inviano eventi e si ricevono messaggi con la libreria serializer .This section described everything you need to know when sending events and receiving messages with the serializer library. Prima di proseguire, si esamineranno alcuni parametri che è possibile configurare per controllare le dimensioni del modello.Before moving on, let's cover some parameters you can configure that control how large your model is.

Configurazione delle macroMacro configuration

Se si usa la libreria Serializer , una parte importante dell'SDK che è opportuno tenere presente si trova nella libreria azure-c-shared-utility.If you’re using the Serializer library an important part of the SDK to be aware of is found in the azure-c-shared-utility library. Se è stato clonato il repository Azure-iot-sdk-c da GitHub usando l'opzione ricorsiva, la libreria delle utilità condivise è disponibile qui:If you have cloned the Azure-iot-sdk-c repository from GitHub using the --recursive option, then you will find this shared utility library here:

.\\c-utility

Se la libreria non è stata clonata, è possibile trovarla qui.If you have not cloned the library, you can find it here.

Nella libreria delle utilità condivise, si troverà la seguente cartella:Within the shared utility library, you will find the following folder:

azure-c-shared-utility\\macro\_utils\_h\_generator.

Questa cartella contiene una soluzione di Visual Studio chiamata macro_utils_h_generator.sln:This folder contains a Visual Studio solution called macro_utils_h_generator.sln:

Il programma in questa soluzione genera il file macro_utils.h.The program in this solution generates the macro_utils.h file. L'SDK include un file macro_utils.h predefinito.There’s a default macro_utils.h file included with the SDK. Questa soluzione consente tuttavia di modificare alcuni parametri e quindi di ricreare il file di intestazione in base a questi parametri.This solution allows you to modify some parameters and then recreate the header file based on these parameters.

I due parametri chiave da considerare sono nArithmetic e nMacroParameters, definiti in queste due righe del file macro_utils.tt:The two key parameters to be concerned with are nArithmetic and nMacroParameters which are defined in these two lines found in macro_utils.tt:

<#int nArithmetic=1024;#>
<#int nMacroParameters=124;/*127 parameters in one macro definition in C99 in chapter 5.2.4.1 Translation limits*/#>

Questi valori sono i parametri predefiniti inclusi nell'SDK.These values are the default parameters included with the SDK. Ecco il significato di ogni parametro:Each parameter has the following meaning:

  • nMacroParameters: controlla la quantità di parametri che possono essere inclusi in una definizione di macro DECLARE_MODEL.nMacroParameters – Controls how many parameters you can have in one DECLARE_MODEL macro definition.
  • nArithmetic: controlla il numero totale di membri consentiti in un modello.nArithmetic – Controls the total number of members allowed in a model.

Il motivo per cui questi parametri sono importanti è il fatto che controllano le dimensioni massime del modello.The reason these parameters are important is because they control how large your model can be. Considerare ad esempio questa definizione di modello:For example, consider this model definition:

DECLARE_MODEL(MyModel,
WITH_DATA(int, MyData)
);

Come già accennato, DECLARE_MODEL è semplicemente una macro C.As mentioned previously, DECLARE_MODEL is just a C macro. I nomi del modello e l'istruzione WITH_DATA (un'altra macro) sono parametri di DECLARE_MODEL.The names of the model and the WITH_DATA statement (yet another macro) are parameters of DECLARE_MODEL. nMacroParameters definisce il numero di parametri che può essere incluso in DECLARE_MODELLO.nMacroParameters defines how many parameters can be included in DECLARE_MODEL. In realtà definisce la quantità di eventi dati e dichiarazioni di azione supportata.Effectively, this defines how many data event and action declarations you can have. Quindi, il limite predefinito di 124 significa che è possibile definire un modello con una combinazione di circa 60 azioni ed eventi dati.As such, with the default limit of 124 this means that you can define a model with a combination of about 60 actions and data events. Se si prova a superare questo limite, verranno visualizzati errori del compilatore simili al seguente:If you try to exceed this limit, you'll receive compiler errors that look similar to this:

Il parametro nArithmetic riguarda più il funzionamento interno del linguaggio macro che l'applicazione.The nArithmetic parameter is more about the internal workings of the macro language than your application. Controlla il numero totale dei membri che si possono usare nel modello, incluse le macro DECLARE_STRUCT.It controls the total number of members you can have in your model, including DECLARE_STRUCT macros. Se iniziano a essere visualizzati errori del compilatore come questo, provare ad aumentare il valore di nArithmetic:If you start seeing compiler errors such as this, then you should try increasing nArithmetic:

Per modificare questi parametri, modificare i valori nel file macro_utils.tt, ricompilare la soluzione macro_utils_h_generator.sln ed eseguire il programma compilato.If you want to change these parameters, modify the values in the macro_utils.tt file, recompile the macro_utils_h_generator.sln solution, and run the compiled program. In questo caso, verrà generato un nuovo file macro_utils.h che sarà inserito nella directory .\common\inc.When you do so, a new macro_utils.h file is generated and placed in the .\common\inc directory.

Per usare la nuova versione di macro_utils.h, rimuovere il pacchetto NuGet serializer dalla soluzione e al suo posto includere il progetto di Visual Studio serializer.In order to use the new version of macro_utils.h, remove the serializer NuGet package from your solution and in its place include the serializer Visual Studio project. In questo modo si consente la compilazione del codice in base al codice sorgente della libreria serializer,This enables your code to compile against the source code of the serializer library. incluso il file macro_utils.h aggiornato.This includes the updated macro_utils.h. Se si vuole procedere in questo modo per simplesample_amqp, iniziare rimuovendo il pacchetto NuGet della libreria serializer dalla soluzione:If you want to do this for simplesample_amqp, start by removing the NuGet package for the serializer library from the solution:

Aggiungere quindi questo progetto alla soluzione Visual Studio:Then add this project to your Visual Studio solution:

.\c\serializer\build\windows\serializer.vcxproj.\c\serializer\build\windows\serializer.vcxproj

Al termine, la soluzione dovrebbe avere un aspetto analogo al seguente:When you're done, your solution should look like this:

A questo punto quando si compila la soluzione, il file macro_utils.h aggiornato viene incluso nel file binario.Now when you compile your solution, the updated macro_utils.h is included in your binary.

Se si aumentano molto questi valori, potrebbero essere superati i limiti del compilatore.Note that increasing these values high enough can exceed compiler limits. A questo proposito, nMacroParameters è il parametro principale a cui prestare attenzione.To this point, the nMacroParameters is the main parameter with which to be concerned. La specifica C99 definisce che è consentito un minimo di 127 parametri in una definizione di macro.The C99 spec specifies that a minimum of 127 parameters are allowed in a macro definition. Il compilatore Microsoft segue esattamente la specifica e ha un limite di 127, quindi non si potrà aumentare nMacroParameters oltre il valore predefinito.The Microsoft compiler follows the spec exactly (and has a limit of 127), so you won't be able to increase nMacroParameters beyond the default. Altri compilatori potrebbero consentirlo, ad esempio il compilatore GNU supporta un limite più alto.Other compilers might allow you to do so (for example, the GNU compiler supports a higher limit).

A questo punto è stato discusso praticamente tutto ciò che occorre sapere sulla scrittura del codice con la libreria serializer .So far we've covered just about everything you need to know about how to write code with the serializer library. Prima di concludere, ecco di seguito alcuni argomenti degli articoli precedenti che potrebbero risultare interessanti.Before concluding, let's revisit some topics from previous articles that you may be wondering about.

API di livello inferioreThe lower-level APIs

L'applicazione di esempio illustrata in questo articolo è simplesample_amqp.The sample application on which this article focused is simplesample_amqp. Questo esempio usa le API di livello superiore (non "LL") per inviare eventi e ricevere messaggi.This sample uses the higher-level (the non-"LL") APIs to send events and receive messages. Se si usano queste API, viene eseguito un thread in background che si occupa dell'invio degli eventi e della ricezione dei messaggi.If you use these APIs, a background thread runs which takes care of both sending events and receiving messages. Queste API (LL) di livello inferiore possono tuttavia essere usate per eliminare il thread in background e assumere il controllo esplicito dei tempi di invio di eventi e ricezione dei messaggi dal cloud.However, you can use the lower-level (LL) APIs to eliminate this background thread and take explicit control over when you send events or receive messages from the cloud.

Come descritto in un articolo precedenteè disponibile un set di funzioni costituito dalle API di livello superiore:As described in a previous article, there is a set of functions that consists of the higher-level APIs:

  • IoTHubClient_CreateFromConnectionStringIoTHubClient_CreateFromConnectionString
  • IoTHubClient_SendEventAsyncIoTHubClient_SendEventAsync
  • IoTHubClient_SetMessageCallbackIoTHubClient_SetMessageCallback
  • IoTHubClient_DestroyIoTHubClient_Destroy

Queste API sono illustrate in simplesample_amqp.These APIs are demonstrated in simplesample_amqp.

È disponibile anche set analogo di API di livello inferiore.There is also an analogous set of lower-level APIs.

  • IoTHubClient_LL_CreateFromConnectionStringIoTHubClient_LL_CreateFromConnectionString
  • IoTHubClient_LL_SendEventAsyncIoTHubClient_LL_SendEventAsync
  • IoTHubClient_LL_SetMessageCallbackIoTHubClient_LL_SetMessageCallback
  • IoTHubClient_LL_DestroyIoTHubClient_LL_Destroy

Tenere presente che le API di livello inferiore funzionano esattamente come descritto negli articoli precedenti.Note that the lower-level APIs work exactly the same way as described in the previous articles. È possibile usare il primo set di API se si vuole avere un thread in background che gestisce l'invio degli eventi e la ricezione dei messaggi.You can use the first set of APIs if you want a background thread to handle sending events and receiving messages. Usare il secondo set di API se si vuole avere il controllo esplicito dei tempi di invio e ricezione dei dati dall'hub IoT.You use the second set of APIs if you want explicit control over when you send and receive data from IoT Hub. Entrambi i set di API funzionano correttamente con la libreria serializer .Either set of APIs work equally well with the serializer library.

Per un esempio dell'uso delle API di livello inferiore con la libreria serializer, vedere l'applicazione simplesample_http.For an example of how the lower-level APIs are used with the serializer library, see the simplesample_http application.

Argomenti aggiuntiviAdditional topics

Alcuni altri argomenti che vale la pena citare riguardano la gestione delle proprietà, l'uso di credenziali del dispositivo alternative e le opzioni di configurazione.A few other topics worth mentioning again are property handling, using alternate device credentials, and configuration options. Ecco tutti gli argomenti illustrati in un articolo precedente.These are all topics covered in a previous article. L'aspetto principale in questo caso è che tutte queste funzionalità hanno lo stesso comportamento quando si usa sia la libreria serializer che la libreria IoTHubClient.The main point is that all of these features work in the same way with the serializer library as they do with the IoTHubClient library. Ad esempio, per associare proprietà a un evento dal modello, usare IoTHubMessage_Properties e Map_AddorUpdate come descritto in precedenza:For example, if you want to attach properties to an event from your model, you use IoTHubMessage_Properties and Map_AddorUpdate, the same way as described previously:

MAP_HANDLE propMap = IoTHubMessage_Properties(message.messageHandle);
sprintf_s(propText, sizeof(propText), "%d", i);
Map_AddOrUpdate(propMap, "SequenceNumber", propText);

Non ha importanza se l'evento è stato generato dalla libreria serializer o creato manualmente con la libreria IoTHubClient.Whether the event was generated from the serializer library or created manually using the IoTHubClient library does not matter.

Per quanto riguarda le credenziali del dispositivo alternative, IoTHubClient_LL_Create funziona altrettanto bene di IoTHubClient_CreateFromConnectionString per l'allocazione di un IOTHUB_CLIENT_HANDLE.For the alternate device credentials, using IoTHubClient_LL_Create works just as well as IoTHubClient_CreateFromConnectionString for allocating an IOTHUB_CLIENT_HANDLE.

Infine, se si usa la libreria serializer, è possibile impostare le opzioni di configurazione con IoTHubClient_LL_SetOption esattamente come è stato fatto con la libreria IoTHubClient.Finally, if you're using the serializer library, you can set configuration options with IoTHubClient_LL_SetOption just as you did when using the IoTHubClient library.

Una funzionalità esclusiva nella libreria serializer riguarda le API di inizializzazione.A feature that is unique to the serializer library are the initialization APIs. Prima di iniziare a utilizzare la libreria, è necessario chiamare serializer_init:Before you can start working with the library, you must call serializer_init:

serializer_init(NULL);

Questa operazione viene eseguita subito prima di chiamare IoTHubClient_CreateFromConnectionString.This is done just before you call IoTHubClient_CreateFromConnectionString.

In modo analogo, una volta che si è terminato di utilizzare la libreria, l'ultima chiamata da eseguire sarà serializer_deinit:Similarly, when you're done working with the library, the last call you’ll make is to serializer_deinit:

serializer_deinit();

Per il resto, tutte le altre funzionalità elencate sopra hanno lo stesso comportamento sia nella libreria serializer che nella libreria IoTHubClient.Otherwise, all of the other features listed above work the same in the serializer library as they do in the IoTHubClient library. Per altre informazioni su uno qualsiasi di questi argomenti, vedere l' articolo precedente di questa serie.For more information about any of these topics, see the previous article in this series.

Passaggi successiviNext steps

Questo articolo descrive in dettaglio gli aspetti univoci della libreria serializer inclusa in Azure IoT SDK per dispositivi per C. Con le informazioni fornite si dovrebbe avere una buona conoscenza di come usare i modelli per inviare eventi e ricevere messaggi dall'hub IoT.This article describes in detail the unique aspects of the serializer library contained in the Azure IoT device SDK for C. With the information provided you should have a good understanding of how to use models to send events and receive messages from IoT Hub.

Questo articolo conclude anche la serie in tre parti relativa allo sviluppo di applicazioni con Azure IoT SDK per dispositivi per C. Le informazioni dovrebbero essere sufficienti non solo per iniziare, ma anche per avere una conoscenza approfondita del funzionamento delle API.This also concludes the three-part series on how to develop applications with the Azure IoT device SDK for C. This should be enough information to not only get you started but give you a thorough understanding of how the APIs work. Per altre informazioni, nell'SDK sono disponibili alcuni esempi non illustrati in questo articolo.For additional information, there are a few samples in the SDK not covered here. Anche la documentazione dell'SDK è una risorsa molto utile per altre informazioni.Otherwise, the SDK documentation is a good resource for additional information.

Per altre informazioni sullo sviluppo dell'hub IoT, vedere gli Azure IoT SDK.To learn more about developing for IoT Hub, see the Azure IoT SDKs.

Per altre informazioni sulle funzionalità dell'hub IoT, vedere:To further explore the capabilities of IoT Hub, see: