7.4. Anjay Object stub generator
7.4.1. Introduction
For easy implementation of custom objects, you can use the ./tools/anjay_codegen.py script. It parses a LwM2M Object Definition XML and generates a skeleton of the LwM2M object code, requiring the user to only fill in actual object logic.
Note
You can use ./tools/lwm2m_object_registry.py script to download the Object Definition XML from OMA LwM2M Object and Resource Registry.
7.4.2. Code generation
This section shows how to generate code for an example object defined in an XML file and covers object templates with both static and dynamic instances.
7.4.2.1. Example object definition
The example object definition is stored in some_object.xml file with the following contents:
<?xml version="1.0" encoding="utf-8"?>
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd">
<Object ObjectType="MODefinition">
<Name>Some Object Name</Name>
<Description1><![CDATA[LwM2M Object description.]]></Description1>
<ObjectID>9999</ObjectID>
<ObjectURN></ObjectURN>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Optional</Mandatory>
<Resources>
<Item ID="0">
<Name>Some String Resource</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>String</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Some description.]]></Description>
</Item>
<Item ID="1">
<Name>Some Integer Resource</Name>
<Operations>RW</Operations>
<MultipleInstances>Single</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Integer</Type>
<RangeEnumeration></RangeEnumeration>
<Units></Units>
<Description><![CDATA[Some description.]]></Description>
</Item>
<Item ID="2">
<Name>Some Boolean Multiple Resource</Name>
<Operations>RW</Operations>
<MultipleInstances>Multiple</MultipleInstances>
<Mandatory>Mandatory</Mandatory>
<Type>Boolean</Type>
<RangeEnumeration/>
<Units></Units>
<Description><![CDATA[Some description.]]></Description>
</Item>
</Resources>
<Description2></Description2>
</Object>
</LWM2M>
We can see that it is a multiple-instance object. Instances of such object can be allocated dynamically on the heap - this solution provides better flexibility but requires additional code related to memory management. Another way is to use a container whose size is fixed and known at compilation time. Both of these approaches are described in the next subsections.
7.4.2.2. Object with dynamically-allocated instances
To create a C language object template with dynamically-allocated instances we execute the following command:
./tools/anjay_codegen.py -i some_object.xml -o some_object.c
The source of the example object looks like this:
/**
* Generated by anjay_codegen.py on 2022-01-24 18:52:32
*
* LwM2M Object: Some Object Name
* ID: 9999, URN: , Optional, Multiple
*
* LwM2M Object description.
*/
#include <assert.h>
#include <stdbool.h>
#include <anjay/anjay.h>
#include <avsystem/commons/avs_defs.h>
#include <avsystem/commons/avs_list.h>
#include <avsystem/commons/avs_memory.h>
/**
* Some String Resource: RW, Single, Mandatory
* type: string, range: N/A, unit: N/A
* Some description.
*/
#define RID_SOME_STRING_RESOURCE 0
/**
* Some Integer Resource: RW, Single, Mandatory
* type: integer, range: N/A, unit: N/A
* Some description.
*/
#define RID_SOME_INTEGER_RESOURCE 1
/**
* Some Boolean Multiple Resource: RW, Multiple, Mandatory
* type: boolean, range: N/A, unit: N/A
* Some description.
*/
#define RID_SOME_BOOLEAN_MULTIPLE_RESOURCE 2
typedef struct some_object_name_instance_struct {
anjay_iid_t iid;
// TODO: instance state
} some_object_name_instance_t;
typedef struct some_object_name_object_struct {
const anjay_dm_object_def_t *def;
AVS_LIST(some_object_name_instance_t) instances;
// TODO: object state
} some_object_name_object_t;
static inline some_object_name_object_t *
get_obj(const anjay_dm_object_def_t *const *obj_ptr) {
assert(obj_ptr);
return AVS_CONTAINER_OF(obj_ptr, some_object_name_object_t, def);
}
static some_object_name_instance_t *find_instance(const some_object_name_object_t *obj,
anjay_iid_t iid) {
AVS_LIST(some_object_name_instance_t) it;
AVS_LIST_FOREACH(it, obj->instances) {
if (it->iid == iid) {
return it;
} else if (it->iid > iid) {
break;
}
}
return NULL;
}
static int list_instances(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_dm_list_ctx_t *ctx) {
(void) anjay;
AVS_LIST(some_object_name_instance_t) it;
AVS_LIST_FOREACH(it, get_obj(obj_ptr)->instances) {
anjay_dm_emit(ctx, it->iid);
}
return 0;
}
static int init_instance(some_object_name_instance_t *inst, anjay_iid_t iid) {
assert(iid != ANJAY_ID_INVALID);
inst->iid = iid;
// TODO: instance init
// TODO: return 0 on success, negative value on failure
return 0;
}
static void release_instance(some_object_name_instance_t *inst) {
// TODO: instance cleanup
(void) inst;
}
static some_object_name_instance_t *
add_instance(some_object_name_object_t *obj, anjay_iid_t iid) {
assert(find_instance(obj, iid) == NULL);
AVS_LIST(some_object_name_instance_t) created =
AVS_LIST_NEW_ELEMENT(some_object_name_instance_t);
if (!created) {
return NULL;
}
int result = init_instance(created, iid);
if (result) {
AVS_LIST_CLEAR(&created);
return NULL;
}
AVS_LIST(some_object_name_instance_t) *ptr;
AVS_LIST_FOREACH_PTR(ptr, &obj->instances) {
if ((*ptr)->iid > created->iid) {
break;
}
}
AVS_LIST_INSERT(ptr, created);
return created;
}
static int instance_create(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
return add_instance(obj, iid) ? 0 : ANJAY_ERR_INTERNAL;
}
static int instance_remove(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
AVS_LIST(some_object_name_instance_t) *it;
AVS_LIST_FOREACH_PTR(it, &obj->instances) {
if ((*it)->iid == iid) {
release_instance(*it);
AVS_LIST_DELETE(it);
return 0;
} else if ((*it)->iid > iid) {
break;
}
}
assert(0);
return ANJAY_ERR_NOT_FOUND;
}
static int instance_reset(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
some_object_name_instance_t *inst = find_instance(obj, iid);
assert(inst);
// TODO: instance reset
return 0;
}
static int list_resources(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_dm_resource_list_ctx_t *ctx) {
(void) anjay;
(void) obj_ptr;
(void) iid;
anjay_dm_emit_res(ctx, RID_SOME_STRING_RESOURCE,
ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);
anjay_dm_emit_res(ctx, RID_SOME_INTEGER_RESOURCE,
ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);
anjay_dm_emit_res(ctx, RID_SOME_BOOLEAN_MULTIPLE_RESOURCE,
ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);
return 0;
}
static int resource_read(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid,
anjay_riid_t riid,
anjay_output_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
some_object_name_instance_t *inst = find_instance(obj, iid);
assert(inst);
switch (rid) {
case RID_SOME_STRING_RESOURCE:
assert(riid == ANJAY_ID_INVALID);
return anjay_ret_string(ctx, ""); // TODO
case RID_SOME_INTEGER_RESOURCE:
assert(riid == ANJAY_ID_INVALID);
return anjay_ret_i32(ctx, 0); // TODO
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:
// TODO: extract Resource Instance
return anjay_ret_bool(ctx, 0); // TODO
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static int resource_write(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid,
anjay_riid_t riid,
anjay_input_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
some_object_name_instance_t *inst = find_instance(obj, iid);
assert(inst);
switch (rid) {
case RID_SOME_STRING_RESOURCE: {
assert(riid == ANJAY_ID_INVALID);
char value[256]; // TODO
return anjay_get_string(ctx, value, sizeof(value)); // TODO
}
case RID_SOME_INTEGER_RESOURCE: {
assert(riid == ANJAY_ID_INVALID);
int32_t value; // TODO
return anjay_get_i32(ctx, &value); // TODO
}
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE: {
// TODO: extract Resource Instance
bool value; // TODO
return anjay_get_bool(ctx, &value); // TODO
}
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static int resource_reset(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
some_object_name_instance_t *inst = find_instance(obj, iid);
assert(inst);
switch (rid) {
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:
return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static int list_resource_instances(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid,
anjay_dm_list_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
some_object_name_instance_t *inst = find_instance(obj, iid);
assert(inst);
switch (rid) {
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:
// anjay_dm_emit(ctx, ...); // TODO
return 0;
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static const anjay_dm_object_def_t OBJ_DEF = {
.oid = 9999,
.handlers = {
.list_instances = list_instances,
.instance_create = instance_create,
.instance_remove = instance_remove,
.instance_reset = instance_reset,
.list_resources = list_resources,
.resource_read = resource_read,
.resource_write = resource_write,
.resource_reset = resource_reset,
.list_resource_instances = list_resource_instances,
// TODO: implement these if transactional write/create is required
.transaction_begin = anjay_dm_transaction_NOOP,
.transaction_validate = anjay_dm_transaction_NOOP,
.transaction_commit = anjay_dm_transaction_NOOP,
.transaction_rollback = anjay_dm_transaction_NOOP,
}
};
const anjay_dm_object_def_t **some_object_name_object_create(void) {
some_object_name_object_t *obj = (some_object_name_object_t *) avs_calloc(1, sizeof(some_object_name_object_t));
if (!obj) {
return NULL;
}
obj->def = &OBJ_DEF;
// TODO: object init
return &obj->def;
}
void some_object_name_object_release(const anjay_dm_object_def_t **def) {
if (def) {
some_object_name_object_t *obj = get_obj(def);
AVS_LIST_CLEAR(&obj->instances) {
release_instance(obj->instances);
}
// TODO: object cleanup
avs_free(obj);
}
}
some_object_name_object_t
object definition contains a member calledinstances
which represents a list of instancesAVS_LIST(some_object_instance_t)
.Instances are identified by their ID set in
iid
field ofsome_object_name_instance_t
structure.To access an instance we have to iterate over all instances and find the one with correct ID.
Instances can be created dynamically by the server using
instance_create
handler.add_instance
function allocates memory for a new instance, initializes the instance and appends it to theinstances
list.Previously allocated instance can be removed by the server by means of
instance_remove
handler.release_instance
function cleans up the instance and then the memory is deallocated.Each handler (apart from
instance_create
andinstance_remove
) takinganjay_iid_t iid
as an argument utilizes auxiliaryfind_instance
function to get the pointer to the instance.All allocated instances are deallocated in
some_object_name_object_release
function.
7.4.2.3. Object with statically-allocated instances
To create a C language object template with fixed 10 instances we use the -n switch:
./tools/anjay_codegen.py -i some_object.xml -o some_object.c -n 10
The resulting code is following:
/**
* Generated by anjay_codegen.py on 2021-10-05 16:11:08
*
* LwM2M Object: Some Object Name
* ID: 9999, URN: , Optional, Multiple
*
* LwM2M Object description.
*/
#include <assert.h>
#include <stdbool.h>
#include <anjay/anjay.h>
#include <avsystem/commons/avs_defs.h>
#include <avsystem/commons/avs_memory.h>
/**
* Some String Resource: RW, Single, Mandatory
* type: string, range: N/A, unit: N/A
* Some description.
*/
#define RID_SOME_STRING_RESOURCE 0
/**
* Some Integer Resource: RW, Single, Mandatory
* type: integer, range: N/A, unit: N/A
* Some description.
*/
#define RID_SOME_INTEGER_RESOURCE 1
/**
* Some Boolean Multiple Resource: RW, Multiple, Mandatory
* type: boolean, range: N/A, unit: N/A
* Some description.
*/
#define RID_SOME_BOOLEAN_MULTIPLE_RESOURCE 2
typedef struct some_object_name_instance_struct {
// TODO: instance state
} some_object_name_instance_t;
typedef struct some_object_name_object_struct {
const anjay_dm_object_def_t *def;
some_object_name_instance_t instances[10];
// TODO: object state
} some_object_name_object_t;
static inline some_object_name_object_t *
get_obj(const anjay_dm_object_def_t *const *obj_ptr) {
assert(obj_ptr);
return AVS_CONTAINER_OF(obj_ptr, some_object_name_object_t, def);
}
static int list_instances(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_dm_list_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
for (anjay_iid_t iid = 0; iid < AVS_ARRAY_SIZE(obj->instances); iid++) {
anjay_dm_emit(ctx, iid);
}
return 0;
}
static int instance_reset(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
assert(iid < AVS_ARRAY_SIZE(obj->instances));
some_object_name_instance_t *inst = &obj->instances[iid];
// TODO: instance reset
// TODO: return 0 on success, negative value on failure
return 0;
}
static int list_resources(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_dm_resource_list_ctx_t *ctx) {
(void) anjay;
(void) obj_ptr;
(void) iid;
anjay_dm_emit_res(ctx, RID_SOME_STRING_RESOURCE,
ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);
anjay_dm_emit_res(ctx, RID_SOME_INTEGER_RESOURCE,
ANJAY_DM_RES_RW, ANJAY_DM_RES_PRESENT);
anjay_dm_emit_res(ctx, RID_SOME_BOOLEAN_MULTIPLE_RESOURCE,
ANJAY_DM_RES_RWM, ANJAY_DM_RES_PRESENT);
return 0;
}
static int resource_read(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid,
anjay_riid_t riid,
anjay_output_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
assert(iid < AVS_ARRAY_SIZE(obj->instances));
some_object_name_instance_t *inst = &obj->instances[iid];
switch (rid) {
case RID_SOME_STRING_RESOURCE:
assert(riid == ANJAY_ID_INVALID);
return anjay_ret_string(ctx, ""); // TODO
case RID_SOME_INTEGER_RESOURCE:
assert(riid == ANJAY_ID_INVALID);
return anjay_ret_i32(ctx, 0); // TODO
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:
// TODO: extract Resource Instance
return anjay_ret_bool(ctx, 0); // TODO
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static int resource_write(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid,
anjay_riid_t riid,
anjay_input_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
assert(iid < AVS_ARRAY_SIZE(obj->instances));
some_object_name_instance_t *inst = &obj->instances[iid];
switch (rid) {
case RID_SOME_STRING_RESOURCE: {
assert(riid == ANJAY_ID_INVALID);
char value[256]; // TODO
return anjay_get_string(ctx, value, sizeof(value)); // TODO
}
case RID_SOME_INTEGER_RESOURCE: {
assert(riid == ANJAY_ID_INVALID);
int32_t value; // TODO
return anjay_get_i32(ctx, &value); // TODO
}
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE: {
// TODO: extract Resource Instance
bool value; // TODO
return anjay_get_bool(ctx, &value); // TODO
}
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static int resource_reset(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
assert(iid < AVS_ARRAY_SIZE(obj->instances));
some_object_name_instance_t *inst = &obj->instances[iid];
switch (rid) {
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:
return ANJAY_ERR_NOT_IMPLEMENTED; // TODO: remove all Resource Instances
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static int list_resource_instances(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid,
anjay_rid_t rid,
anjay_dm_list_ctx_t *ctx) {
(void) anjay;
some_object_name_object_t *obj = get_obj(obj_ptr);
assert(iid < AVS_ARRAY_SIZE(obj->instances));
some_object_name_instance_t *inst = &obj->instances[iid];
switch (rid) {
case RID_SOME_BOOLEAN_MULTIPLE_RESOURCE:
// anjay_dm_emit(ctx, ...); // TODO
return 0;
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
static const anjay_dm_object_def_t OBJ_DEF = {
.oid = 9999,
.handlers = {
.list_instances = list_instances,
.instance_reset = instance_reset,
.list_resources = list_resources,
.resource_read = resource_read,
.resource_write = resource_write,
.resource_reset = resource_reset,
.list_resource_instances = list_resource_instances,
// TODO: implement these if transactional write/create is required
.transaction_begin = anjay_dm_transaction_NOOP,
.transaction_validate = anjay_dm_transaction_NOOP,
.transaction_commit = anjay_dm_transaction_NOOP,
.transaction_rollback = anjay_dm_transaction_NOOP
}
};
const anjay_dm_object_def_t **some_object_name_object_create(void) {
some_object_name_object_t *obj =
(some_object_name_object_t *) avs_calloc(1, sizeof(some_object_name_object_t));
if (!obj) {
return NULL;
}
obj->def = &OBJ_DEF;
// TODO: object init
return &obj->def;
}
void some_object_name_object_release(const anjay_dm_object_def_t **def) {
if (def) {
some_object_name_object_t *obj = get_obj(def);
// TODO: object cleanup
avs_free(obj);
}
}
some_object_name_object_t
object definition contains a member calledinstances
which is an array of 10some_object_instance_t
elements.Instances are identified by
iid
used as their index in theinstances
array, meaning thatfind_instance
-like function is not needed.The server cannot create and remove instances, so
instance_create
andinstance_remove
handlers are not implemented.
7.4.2.4. C++ object templates
The script is capable of generating C++ object templates as well - the -x switch is intended to be used in this case. So, in order to create a C++ object with dynamic instances one has to execute the command:
./tools/anjay_codegen.py -i some_object.xml -o some_object.cpp -x
To create a C++ template of the same object with 10 static instances run:
./tools/anjay_codegen.py -i some_object.xml -o some_object.cpp -n 10 -x
The main difference between the two is that the former approach uses the C++ wrapper of AVS_LIST, and the latter one takes advantage of std::array container.
7.4.3. After generating the object template
Now that the basic object structure is created, one can start thinking about filling in missing parts marked in the code by the TODO comments. Then, to make the object present in the LwM2M Data Model, one shall instantiate it, and finally register it within Anjay.
7.4.4. Additional examples
# list registered LwM2M objects
./tools/lwm2m_object_registry.py --list
# download Object Definition XML for object 3 (Device) to device.xml
./tools/lwm2m_object_registry.py --get-xml 3 > device.xml
# generate object code stub from device.xml
./tools/anjay_codegen.py -i device.xml -o device.c
# download Object Definition XML for object 3 and generate code stub
# without creating an intermediate file
./tools/lwm2m_object_registry.py --get-xml 3 | ./tools/anjay_codegen.py -i - -o device.c
# download Object Definition XML for object 3303 and generate code stub with
# five statically allocated instances without creating an intermediate file
./tools/lwm2m_object_registry.py --get-xml 3303 | ./tools/anjay_codegen.py -i - -o temperature.c -n 5