8.5. Anjay Object stub generator
8.5.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.
8.5.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.
8.5.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.
8.5.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_tobject definition contains a member calledinstanceswhich represents a list of instancesAVS_LIST(some_object_instance_t).Instances are identified by their ID set in
iidfield ofsome_object_name_instance_tstructure.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_createhandler.add_instancefunction allocates memory for a new instance, initializes the instance and appends it to theinstanceslist.Previously allocated instance can be removed by the server by means of
instance_removehandler.release_instancefunction cleans up the instance and then the memory is deallocated.Each handler (apart from
instance_createandinstance_remove) takinganjay_iid_t iidas an argument utilizes auxiliaryfind_instancefunction to get the pointer to the instance.All allocated instances are deallocated in
some_object_name_object_releasefunction.
8.5.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_tobject definition contains a member calledinstanceswhich is an array of 10some_object_instance_telements.Instances are identified by
iidused as their index in theinstancesarray, meaning thatfind_instance-like function is not needed.The server cannot create and remove instances, so
instance_createandinstance_removehandlers are not implemented.
8.5.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.
8.5.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.
8.5.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