..
   Copyright 2023-2025 AVSystem <avsystem@avsystem.com>
   AVSystem Anjay Lite LwM2M SDK
   All rights reserved.

   Licensed under AVSystem Anjay Lite LwM2M Client SDK - Non-Commercial License.
   See the attached LICENSE file for details.

Multi-Instance Object Dynamic
=============================

Overview
--------

This guide explains how to implement a **multi-instance Object** with a dynamic
set of Instances, as defined in
`OMA LwM2M Registry <https://www.openmobilealliance.org/specifications/registries/objects>`_
**with dynamic set of Instances**.

As an example, we are going to implement
`Temperature Object with ID 3303 in version 1.1 <https://raw.githubusercontent.com/OpenMobileAlliance/lwm2m-registry/prod/version_history/3303-1_1.xml>`_ by extending the code developed in `Multi-instance Object Implementation <../AT-MultiInstanceObject.html>`_.
This document highlights only the additional steps and modifications needed.

Implement the Object
--------------------

.. note::
   Code related to this tutorial can be found under `examples/tutorial/AT-MultiInstanceObjectDynamic`
   in the Anjay Lite source directory and is based on `examples/tutorial/AT-MultiInstanceObject`
   example.

.. note::
    To generate code stubs for multi-instance objects with dynamic instance management, use the ``anjay_codegen.py`` script with the ``-ni`` and ``-di`` flags:

    .. code-block:: bash

        ./tools/anjay_codegen.py -i temperature_obj.xml -o temperature_obj.c -ni 5 -di
 
    For details, see the :ref:`Dynamic multiple instance object generation<multi-dynamic-instance-generator>` section.

Although the set of Object Instances is dynamic, you must still define the
maximum number of instances using ``TEMP_OBJ_NUMBER_OF_INSTANCES``.

Object and Object Instances State
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Since the number of instances is now dynamic, to support transactional operations,
cache entire Object Instances instead of individual Resources.

.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/temperature_obj.c
    :emphasize-lines: 12, 14

    typedef struct {
        anj_iid_t iid;
        double sensor_value;
        double min_sensor_value;
        double max_sensor_value;
        char application_type[TEMP_OBJ_APPL_TYPE_MAX_SIZE];
    } temp_obj_inst_t;

    typedef struct {
        anj_dm_obj_t obj;
        anj_dm_obj_inst_t insts[TEMP_OBJ_NUMBER_OF_INSTANCES];
        anj_dm_obj_inst_t insts_cached[TEMP_OBJ_NUMBER_OF_INSTANCES];
        temp_obj_inst_t temp_insts[TEMP_OBJ_NUMBER_OF_INSTANCES];
        temp_obj_inst_t temp_insts_cached[TEMP_OBJ_NUMBER_OF_INSTANCES];
    } temp_obj_ctx_t;


Create and Delete Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^

To support server-side Instance management, implement the ``inst_create`` and
``inst_delete`` handlers.

.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/temperature_obj.c
    :emphasize-lines: 2-3

    static const anj_dm_handlers_t TEMP_OBJ_HANDLERS = {
        .inst_create = inst_create,
        .inst_delete = inst_delete,
        .res_read = res_read,
        .res_write = res_write,
        .res_execute = res_execute,
        .transaction_begin = transaction_begin,
        .transaction_validate = transaction_validate,
        .transaction_end = transaction_end,
    };


Anjay Lite requires the instances to be sorted in ascending ``iid`` order in the
``anj_dm_obj_inst_t::insts``


.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/temperature_obj.c

    // classic bubble sort for keeping the IID in the ascending order
    static void sort_instances(temp_obj_ctx_t *ctx) {
        for (uint16_t i = 0; i < TEMP_OBJ_NUMBER_OF_INSTANCES - 1; i++) {
            for (uint16_t j = i + 1; j < TEMP_OBJ_NUMBER_OF_INSTANCES; j++) {
                if (ctx->temp_insts[i].iid > ctx->temp_insts[j].iid) {
                    // swap temp_insts
                    temp_obj_inst_t tmp_temp = ctx->temp_insts[i];
                    ctx->temp_insts[i] = ctx->temp_insts[j];
                    ctx->temp_insts[j] = tmp_temp;

                    // swap insts
                    anj_dm_obj_inst_t tmp_inst = ctx->insts[i];
                    ctx->insts[i] = ctx->insts[j];
                    ctx->insts[j] = tmp_inst;
                }
            }
        }
    }

    static int inst_create(anj_t *anj, const anj_dm_obj_t *obj, anj_iid_t iid) {
        (void) anj;
        assert(iid != ANJ_ID_INVALID);
        temp_obj_ctx_t *ctx = get_ctx();

        // find an unitialized instance and use it
        bool found = false;
        for (uint16_t idx = 0; idx < TEMP_OBJ_NUMBER_OF_INSTANCES; idx++) {
            if (ctx->temp_insts[idx].iid == ANJ_ID_INVALID) {
                ctx->temp_insts[idx].iid = iid;
                ctx->insts[idx].iid = iid;
                found = true;
                break;
            }
        }
        if (!found) {
            // no free instance found
            return -1;
        }
        sort_instances(ctx);
        return 0;
    }

    static int inst_delete(anj_t *anj, const anj_dm_obj_t *obj, anj_iid_t iid) {
        (void) anj;
        temp_obj_ctx_t *ctx = get_ctx();
        for (uint16_t idx = 0; idx < TEMP_OBJ_NUMBER_OF_INSTANCES; idx++) {
            if (ctx->temp_insts[idx].iid == iid) {
                ctx->insts[idx].iid = ANJ_ID_INVALID;
                ctx->temp_insts[idx].iid = ANJ_ID_INVALID;
                sort_instances(ctx);
                return 0;
            }
        }
        return ANJ_DM_ERR_NOT_FOUND;
    }


If the **Create** operation does not specify an `iid`, Anjay Lite assigns one and
passes it to the `inst_create handler`.


Object definition and Initialization
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Begin by defining a static `temperature_obj` structure that holds the Object metadata:

.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/temperature_obj.c

    static temp_obj_ctx_t temperature_obj = {
        .obj = {
            .oid = 3303,
            .version = "1.1",
            .handlers = &TEMP_OBJ_HANDLERS,
            .max_inst_count = TEMP_OBJ_NUMBER_OF_INSTANCES
        }
    };

Next, create an initialization function to populate the `insts` and `temp_insts` arrays:

.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/temperature_obj.c

    void temperature_obj_init(void) {
        // initialize the object with 0 instances
        for (int i = 0; i < TEMP_OBJ_NUMBER_OF_INSTANCES; i++) {
            temperature_obj.insts[i].res_count = TEMPERATURE_RESOURCES_COUNT;
            temperature_obj.insts[i].resources = RES;
            temperature_obj.insts[i].iid = ANJ_ID_INVALID;
            temperature_obj.temp_insts[i].iid = ANJ_ID_INVALID;
        }

        temperature_obj.obj.insts = temperature_obj.insts;

        temp_obj_inst_t *inst;
        // initilize 1st instance
        inst = &temperature_obj.temp_insts[0];
        temperature_obj.insts[0].iid = 1;
        inst->iid = 1;
        snprintf(inst->application_type, sizeof(inst->application_type),
                "Sensor_1");
        inst->sensor_value = 10.0;
        inst->min_sensor_value = 10.0;
        inst->max_sensor_value = 10.0;

        // initialize 2nd instance
        inst = &temperature_obj.temp_insts[1];
        temperature_obj.insts[1].iid = 2;
        inst->iid = 2;
        snprintf(inst->application_type, sizeof(inst->application_type),
                "Sensor_2");
        inst->sensor_value = 20.0;
        inst->min_sensor_value = 20.0;
        inst->max_sensor_value = 20.0;
    }

Call the initialization function from `main()` before registering the Object with
the Anjay Lite Data Model:

.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/main.c

    int main(int argc, char *argv[]) {
        // ...

        temperature_obj_init();
        if (anj_dm_add_obj(&anj, get_temperature_obj())) {
            log(L_ERROR, "install_temperature_object error");
            return -1;
        }

        // ...
    }


Support transactional Writes
----------------------------

To ensure consistent behavior during transactions, you must cache the complete
Object context — not just the writable Resources. This includes both the `insts`
and `temp_insts` arrays:

.. highlight:: c
.. snippet-source:: examples/tutorial/AT-MultiInstanceObjectDynamic/src/temperature_obj.c
    :emphasize-lines: 6-7, 25-27

    static int transaction_begin(anj_t *anj, const anj_dm_obj_t *obj) {
        (void) anj;
        (void) obj;

        temp_obj_ctx_t *ctx = get_ctx();
        memcpy(ctx->insts_cached, ctx->insts, sizeof(ctx->insts));
        memcpy(ctx->temp_insts_cached, ctx->temp_insts, sizeof(ctx->temp_insts));
        return 0;
    }

    static int transaction_validate(anj_t *anj, const anj_dm_obj_t *obj) {
        (void) anj;
        (void) obj;
        // Perform validation of the object
        return 0;
    }

    static void transaction_end(anj_t *anj, const anj_dm_obj_t *obj, int result) {
        (void) anj;
        (void) obj;

        if (result) {
            // restore cached data
            temp_obj_ctx_t *ctx = get_ctx();
            memcpy(ctx->insts, ctx->insts_cached, sizeof(ctx->insts));
            memcpy(ctx->temp_insts, ctx->temp_insts_cached,
                sizeof(ctx->temp_insts));
        }
    }