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:

some_object.xml
<?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:

some_object.c
/**
 * 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 called instances which represents a list of instances AVS_LIST(some_object_instance_t).

  • Instances are identified by their ID set in iid field of some_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 the instances 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 and instance_remove) taking anjay_iid_t iid as an argument utilizes auxiliary find_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:

some_object.c
/**
 * 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 called instances which is an array of 10 some_object_instance_t elements.

  • Instances are identified by iid used as their index in the instances array, meaning that find_instance-like function is not needed.

  • The server cannot create and remove instances, so instance_create and instance_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