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 nameelement1
,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 byCREATE_MODEL_INSTANCE
macro;DESIRED_PROPERTIES_AS_JSON
is aconst 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 inBEGIN_NAMESPACE
macro.modelName
- The model name, as defined with theDEFINE_MODEL
macro.serializerIncludePropertyPath
- an optional bool argument. Default value:false
. If set totrue
it instructs the serializer to include the full property path (including the property name) in the resulting JSON. If set tofalse
, 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_Create
ModelName
have to be disposed of by calling IoTHubDeviceTwin_Destroy
ModelName
.
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_Create
ModelName
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_Create
ModelName
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*
.