azure-iot-sdk-c/serializer/devdoc/serializer_apis.md

16 KiB

SERIALIZER APIs v5

Overview

The SERIALIZER APIs allows developers to quickly and easily define models for their devices directly as code, while supporting the required features for modeling devices (including multiple models and multiple devices within the same application).

SERIALIZER description macro

Example

In the header file:

#include "serializer.h"
BEGIN_NAMESPACE(MyFunkyTV);
DECLARE_STRUCT(MenuType,
    int, source,
    double, brightness
    );

DECLARE_MODEL(FunkyTV,
    WITH_DATA(int, screenSize),
    WITH_DATA(bool, hasEthernet),
    WITH_DATA(MenuType, tvMenu),
    WITH_ACTION(LostSignal, int, source, int, resolution)
);

DECLARE_MODEL(AnotherDevice, 
    ...
);

END_NAMESPACE(MyFunkyTV);

BEGIN_NAMESPACE(schemaNamespace)

This macro marks the start of a section that declares the model elements (like complex types, etc.). Declarations are typically placed in header files, so that they can be shared between translation units.

END_NAMESPACE(schemaNamespace)

This macro marks the end of a section that declares the model elements.

DECLARE_STRUCT(structTypeName, field1Type, field1Name, ...)

This macro allows declaring a struct (complex) type for a model.

Arguments:

  • structTypeName - specifies the struct type name
  • (fieldXType, fieldXName) - The type and the name for the field X of the struct type. A struct type can have any number of fields from 1 to 61 (inclusive). At least one field must be defined.

Example:

DECLARE_STRUCT(MenuType,
   int, source,
   double, brightness
   );

DECLARE_MODEL(modelName, element1, ...)

This macro allows declaring a model that can be later used to instantiate a device. Arguments:

  • modelName - specifies the model name
  • element1, element2, ... - a model element (can be a property and action).
  • A property is described in a model by using the WITH_DATA.
  • An action is described in a model by using the WITH_ACTION macro.

Example:

DECLARE_MODEL(FunkyTV,
    ...
);

WITH_DATA(propertyType, propertyName)

The WITH_DATA macro allows declaring a model property in a model. A property can be serialized by using the SERIALIZE macro.

Arguments:

  • propertyType - specifies the property type. Can be any of the following types:
    • int
    • double
    • float
    • long
    • int8_t
    • uint8_t
    • int16_t
    • int32_t
    • int64_t
    • bool
    • ascii_char_ptr
    • EDM_DATE_TIME_OFFSET
    • EDM_GUID
    • EDM_BINARY
    • Any struct type previously introduced by another DECLARE_STRUCT.
  • propertyName - specifies the property name
DECLARE_MODEL(FunkyTV,
    WITH_DATA(int, screenSize),
    WITH_DATA(bool, hasEthernet),
    ...
    );

WITH_REPORTED_PROPERTY

WITH_REPORTED_PROPERTY(propertyType, propertyName)

WITH_REPORTED_PROPERTY macro allows declaring a reported property (in the context of DeviceTwin). Reported properties have the same type as properties declared by WITH_DATA macro.

WITH_DESIRED_PROPERTY

WITH_DESIRED_PROPERTY(propertyType, propertyname [,onDesiredProperty])

WITH_DESIRED_PROPERTY macro declares a desired property. This is a DeviceTwin notion. Desired properties have the same types as regular properties declared using WITH_DATA macro. Additionally, desired properties can have an optional 3rd argument, which is a function name. The function will be called when the desired property is received. The function receives a pointer to the encompassing model where the desired property is declared.

Example

DECLARE_MODEL(Car,
    WITH_DESIRED_PROPERTY(int, softwareVersion),
    WITH_DESIRED_PROPERTY(ascii_char_ptr, firmwareVersionAsString),
    WITH_DESIRED_PROPERTY(int, maxSpeed, OnMaxSpeed)
    ...
    );

void OnMaxSpeed(void* v)
{
    Car* car = v;
    printf("maxSpeed has been received and it is %d\n", car->maxSpeed);
}

WITH_ACTION(actionName, arg1Type, arg1Name, ...)

The WITH_ACTION macro allows declaring a model action. Once the action is declared, it will have to be complemented by a C function that defines the action. The C function prototype is the following:

Arguments:

  • actionName - specifies the action name.
  • argXtype, argXName - defines the type and name for the Xth argument of the action. The type can be any of the primitive types or a struct type.
DECLARE_MODEL(FunkyTV,
    ...
    WITH_ACTION(channelChange, ascii_char_ptr, Property1),
    ...
);

The following is the C function definition:

```c
EXECUTE_COMMAND_RESULT actionName(modelName* model, arg1Type arg1Name, arg2Type arg2Name) /*more arguments can follow if more are declared in `WITH_ACTION` */
{
    
}

WITH_METHOD(methodName, arg1Type, arg1, arg2Type, arg2, ...)

WITH_METHOD introduces a Device Method in the model. WITH_METHOD is similar to WITH_ACTION: it will result in a user-supplied C function being called. The main difference is in return value (Methods return a number and a JSON value, Actions return values of an enum).

Arguments:

  • methodName - specifies the method name.
  • argXtype, argXName - defines the type and name for the Xth argument of the method. The type can be any of the primitive types or a struct type.
DECLARE_MODEL(FunkyTV,
    ...
    WITH_METHOD(channelChange, ascii_char_ptr, Property1),
    ...
);

The following is the C function definition of a method:

METHODRETURN_HANDLE methodName(modelName* model, arg1Type arg1, arg2Type arg2)

METHODRETURN_HANDLE is an abstract type around the following data:

  • int statusCode; /*the result of the method call*/
  • char* jsonValue; /*the JSON value to be returned, can be NULL. */

GET_MODEL_HANDLE(schemaNamespace, modelName)

The GET_MODEL_HANDLE macro returns the model handle for the model with type modelName inside the namespace called schemaNamespace.

Acting on models

SERIALIZE(destination, destinationSize, property1, ...)

This macro produces the JSON serialized representation of the properties.

Arguments:

  • destination - pointer to an unsigned char* that will receive the serialized data.
  • destinationSize - pointer to a size_t that gets written with the size in bytes of the serialized data
  • property1, property2, ... - a list of property values to send. The order in which the properties appear in the list does not matter, all values will be sent together.

Returns:

  • CODEFIRST_OK on success
  • Any other value on failure
...
DECLARE_MODEL(FunkyTV,
    WITH_DATA(int, screenSize),
    WITH_DATA(bool, hasEthernet),
    ...
);
...

int main(int argc, char** argv)
{
...
    FunkyTV* funkyTV = CREATE_MODEL_INSTANCE(MyFunkyTV, FunkyTV);
    unsigned char* destination; size_t destinationSize;
    funkyTV->hasEthernet = false;
    funkyTV->screenSize = 42;
    SERIALIZE(&destination, &destinationSize, funkyTV->hasEthernet, funkyTV->screenSize);
    printf("serialized data is %*.*s\r\n",(int)destinationSize, (int)destinationSize,  (char*)destination);
...
}

SERIALIZE_REPORTED_PROPERTIES

SERIALIZE_REPORTED_PROPERTIES(destination, destinationSize, reportedProperty1, ...)

This macro produces the JSON serialized representation of the reported properties. The arguments can be individual properties or a complete model.

Arguments:

  • destination - pointer to an unsigned char* that will receive the serialized reported properties.
  • destinationSize - pointer to a size_t that gets written with the size in bytes of the serialized reported properties.
  • property1, property2, ... - a list of reported properties to send. The order in which the reported properties appear in the list does not matter, all values will be sent together. If the reported property argument is a complete model, then only the reported properties in that model will be serialized.

Returns:

  • CODEFIRST_OK on success
  • Any other value on failure

Example

...
DECLARE_MODEL(FunkyTV,
    WITH_REPORTED_PROPERTY(int, screenSize),
    WITH_REPORTED_PROPERTY(bool, hasEthernet),
    ...
);
...

int main(int argc, char** argv)
{
...
    FunkyTV* funkyTV = CREATE_MODEL_INSTANCE(MyFunkyTV, FunkyTV);
    unsigned char* destination; size_t destinationSize;
    funkyTV->hasEthernet = false;
    funkyTV->screenSize = 42;
    SERIALIZE_REPORTED_PROPERTIES(&destination, &destinationSize, funkyTV->hasEthernet, funkyTV->screenSize);
    printf("serialized reported properties \n%*.*s\r\n",(int)destinationSize, (int)destinationSize,  (char*)destination);
...
}

EXECUTE_COMMAND

Any action that is declared in a model must also have an implementation as a C function.

The C function arguments must be:

  • First argument must be of the type pointer to device data (i.e. FunkyTV*).
  • Following arguments must match the arguments declared in the model action.

The macro EXECUTE_COMMAND(device, commandBuffer) shall execute the command indicated in the commandBuffer for the device.

DECLARE_MODEL(MyFunkyTV,
    ...
    WITH_ACTION(changeChannel, ascii_char_ptr, Property1)
    );
...

void changeChannel(FunkyTV* device, ascii_char_ptr Property1)
{
    printf("Changing Channel to channel %s\r\n", Property1);
}

#define COMMAND_TEXT "\"Name\":\"changeChannel\",\"Parameters\":\"FabrikamTV\""

int main(void)
{
    ...
    EXECUTE_COMMAND(funkyTv, COMMAND_TEXT);
    ...
} 

INGEST_DESIRED_PROPERTIES

INGEST_DESIRED_PROPERTIES(modelInstance, DESIRED_PROPERTIES_AS_JSON)

INGEST_DESIRED_PROPERTIES shall populate desired properties with values from the JSON.

Arguments:

  • modelInstance is a previous model instance created by CREATE_MODEL_INSTANCE macro;
  • DESIRED_PROPERTIES_AS_JSON is a const char* pointer pointing to a null terminated string.

Returns: INGEST_DESIRED_PROPERTIES returns CODEFIRST_OK if success, any other error indicates failure.

Example:

DECLARE_MODEL(Car,
    WITH_DESIRED_PROPERTY(int, softwareVersion),
    WITH_DESIRED_PROPERTY(ascii_char_ptr, firmwareVersionAsString)
    ...
    );

int main(void)
{

	Car* car = CREATE_MODEL_INSTANCE(MyFunkyCarNamespace, Car);
    ...
    INGEST_DESIRED_PROPERTIES(car, "{\"softwareVersion\":3, \"firmwareVersionAsString\":\"the firmware 0.1\"}");
    ...
}




## SERIALIZER APIs

### serializer_init

SERIALIZER_RESULT serializer_init(const char* overrideSchemaNamespace)


An optional API used to pass `overrideSchemaNamespace`. If `serializer_init` is not called then `overrideSchemaNamespace`
shall be assumed to be `NULL`.

__Arguments:__
-	`overrideSchemaNamespace` - An override schema namespace to use for all models. Optional, can be `NULL`. 
If `schemaNamespace` is not `NULL`, its value shall be used instead of the namespace defined for each model 
by using the DECLARE_XXX macro.

__Returns:__
-	`SERIALIZER_OK` on success
-	Any other error on failure

Example:
```c
int main(int argc, char** argv)
{
    ...
    if (serializer_init(NULL) != SERIALIZER_OK)
	{
		/* error */
    }
    else
    {   
	...
    }
}

serializer_deinit

void serializer_deinit(void)

Deinitializes the serializer library. An empty function preserved for compatibility purposes. Example:

int main(int argc, char** argv)
{
    ...
    (void)serializer_init(NULL);
    ...
    serializer_deinit();
}

CREATE_MODEL_INSTANCE

void* CREATE_MODEL_INSTANCE(schemaNamespace, modelName, serializerIncludePropertyPath)

Initializes a model instance that has the model identified by the schema Namespace and Model Name.

Arguments:

  • schemaNamespace - The schema namespace as specified in BEGIN_NAMESPACE macro.
  • modelName - The model name, as defined with the DEFINE_MODEL macro.
  • serializerIncludePropertyPath - an optional bool argument. Default value: false. If set to true it instructs the serializer to include the full property path (including the property name) in the resulting JSON. If set to false, the property path (and name) will not appear in the resulting JSON.

Returns:

  • A pointer to a structure of type modelName
  • NULL in case of an error.

Example:

DECLARE_MODEL(MyFunkyTV,
    WITH_DATA(int, screenSize),
    WITH_DATA(bool, hasEthernet),
    ...
    );
int main(int argc, char** argv)
{

	FunkyTV* funkyTV = CREATE_MODEL_INSTANCE(MyFunkyTV, FunkyTV);
    ...
    funkyTV->hasEthernet = false;
    funkyTV->screenSize = 42;
    unsigned char* destination; size_t destinationSize;
    SERIALIZE(&destination, &destinationSize, funkyTV->hasEthernet, funkyTV->screenSize);
    printf("serialization = %*.*s", (int)destinationSize, (int)destinationSize, destination);
}

DESTROY_MODEL_INSTANCE

DESTROY_MODEL_INSTANCE(instance)

Frees any resources associated with the model instance.

Arguments:

  • instance - A previously created instance with CREATE_MODEL_INSTANCE.

SERIALIZER_DEVICETWIN

In order to better support Device Twin features by providing a stronger cohesion, serializer has been enhanced with a layer specially crafted for Device Twin. The new functionality is accesibile by #include "serializer_devicetwin.h".

DECLARE_MODEL becomes DECLARE_DEVICETWIN_MODEL. All the semantics are preserved. Model instances are created by a C function, and not a by CREATE_MODEL_INSTANCE macro.

Example:

#include "serializer_devicetwin.h"

BEGIN_NAMESPACE(Contoso);
DECLARE_DEVICETWIN_MODEL(Car,
    WITH_DESIRED_PROPERTY(CarSettings, settings)
);
END_NAMESPACE(Contoso);
...

REGISTER_SCHEMA_NAMESPACE(Contoso);
Car* car = IoTHubDeviceTwin_CreateCar(iotHubClientHandle);
car->settings = ...;

Before using the model name, register the namespace by REGISTER_SCHEMA_NAMESPACE. REGISTER_SCHEMA_NAMESPACE takes as argument the namespace name.

The above code declares a DEVICETWIN model. The model declaration brings in IoTHubDeviceTwin_CreateCar - a C function that creates a model instance (in this example, Car).

At the time of calling IoTHubDeviceTwin_CreateCar(iotHubClientHandle) the device twin callback is routed to an internal function that will automatically update the desired properties.

The model instances created by IoTHubDeviceTwin_CreateModelName have to be disposed of by calling IoTHubDeviceTwin_DestroyModelName.

DECLARE_DEVICETWIN_MODEL

DECLARE_DEVICETWIN_MODEL(modelName,... )

DECLARE_DEVICETWIN_MODEL defines a DEVICETWIN model. DEVICETWIN models are automatically linked with an IOTHUB_CLIENT_HANDLE or IOTHUB_CLIENT_LL_HANDLE for the purpose of receiving desired properties.

SERIALIZER_REGISTER_NAMESPACE

SERIALIZER_REGISTER_NAMESPACE(name)

SERIALIZER_REGISTER_NAMESPACE registers the constituents of a namespace.

IoTHubDeviceTwin_CreateModelName

IoTHubDeviceTwin_CreateModelName(IOTHUB_CLIENT_HANDLE handle);

IoTHubDeviceTwin_CreateModelName creates a ModelName model instance. It also links this model instance with a IoTHubClient handle for the purpose of receiving desired properties.

IoTHubDeviceTwin_DestroyModelName

IoTHubDeviceTwin_DestroyModelName(ModelName* model);

IoTHubDeviceTwin_DestroyModelName frees all used resources by a model instance of type ModelName. It also unregisters the DeviceTwin callback.

IoTHubDeviceTwin_LL_CreateModelName

IoTHubDeviceTwin_LL_CreateModelName(IOTHUB_CLIENT_LL_HANDLE handle);

IoTHubDeviceTwin_LL_CreateModelName creates a ModelName model instance. It also links this model instance with a IoTHubClient_LL handle for the purpose of receiving desired properties.

IoTHubDeviceTwin_LL_DestroyModelName

IoTHubDeviceTwin_LL_DestroyModelName(ModelName* model);

IoTHubDeviceTwin_LL_DestroyModelName frees all used resources by a model instance of type ModelName. It also unregisters the DeviceTwin callback.

IoTHubDeviceTwin_LL_SendReportedStateModelName

IoTHubDeviceTwin_LL_SendReportedState*ModelName*(name* model, IOTHUB_CLIENT_REPORTED_STATE_CALLBACK deviceTwinCallback, void* context)

IoTHubDeviceTwin_LL_SendReportedState*ModelName* sends the complee reported state for a model instance. The model instance needs to have been created by IoTHubDeviceTwin_LL_Create*ModelName*.

IoTHubDeviceTwin_SendReportedStateModelName

IoTHubDeviceTwin_SendReportedState*ModelName*(name* model, IOTHUB_CLIENT_REPORTED_STATE_CALLBACK deviceTwinCallback, void* context)

IoTHubDeviceTwin_SendReportedState*ModelName* sends the complete reported state for a model instance. The model instance needs to have been created by IoTHubDeviceTwin_Create*ModelName*.