4.5. Multi-Instance Object
4.5.1. Overview
This guide explains how to implement a multi-instance Object as defined in OMA LwM2M Registry.
In LwM2M, some Objects may have multiple Instances. These Instances can be dynamically created, such as those used to manage server connections or represent installed software packages.
As an example, we are going to implement Temperature Object with ID 3303 in version 1.1 by extending the code presented in the Basic Object Implementation. Only the required differences are described here.
4.5.2. Implement the Object
Note
Code related to this tutorial can be found under examples/tutorial/AT-MultiInstanceObject in the Anjay Lite source directory and is based on examples/tutorial/BC-BasicObjectImplementation example.
Note
To generate code stubs for a multi-instance object, use the anjay_codegen.py
script with the -ni
option:
./tools/anjay_codegen.py -i temperature_obj.xml -o temperature_obj.c -ni 5
For details, see the Multiple instance object generation section.
4.5.2.1. Object and Object Instances State
To store the state of each Object Instance separately, define two data structures:
One for the individual Object Instance with Instance ID and Resources values
One for the Object definition and its Instances
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];
char application_type_cached[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];
temp_obj_inst_t temp_insts[TEMP_OBJ_NUMBER_OF_INSTANCES];
} temp_obj_ctx_t;
Next, add a helper function to retrieve the instance based on the iid (Instance ID):
static temp_obj_inst_t *get_temp_inst(anj_iid_t iid) {
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) {
return &ctx->temp_insts[idx];
}
}
return NULL;
}
4.5.2.2. Read, write and execute Handlers
Each handler receives an anj_iid_t iid parameter, which now must be used to access the correct instance.
static int res_read(anj_t *anj,
const anj_dm_obj_t *obj,
anj_iid_t iid,
anj_rid_t rid,
anj_riid_t riid,
anj_res_value_t *out_value) {
(void) anj;
(void) obj;
(void) riid;
temp_obj_inst_t *temp_inst = get_temp_inst(iid);
assert(temp_inst);
switch (rid) {
case RID_SENSOR_VALUE:
out_value->double_value = temp_inst->sensor_value;
break;
//...
}
static int res_write(anj_t *anj,
const anj_dm_obj_t *obj,
anj_iid_t iid,
anj_rid_t rid,
anj_riid_t riid,
const anj_res_value_t *value) {
(void) anj;
(void) obj;
(void) riid;
temp_obj_inst_t *temp_inst = get_temp_inst(iid);
assert(temp_inst);
switch (rid) {
case RID_APPLICATION_TYPE:
return anj_dm_write_string_chunked(value,
temp_inst->application_type,
TEMP_OBJ_APPL_TYPE_MAX_SIZE, NULL);
break;
//...
}
static int res_execute(anj_t *anj,
const anj_dm_obj_t *obj,
anj_iid_t iid,
anj_rid_t rid,
const char *execute_arg,
size_t execute_arg_len) {
(void) anj;
(void) obj;
(void) execute_arg;
(void) execute_arg_len;
temp_obj_inst_t *temp_inst = get_temp_inst(iid);
assert(temp_inst);
switch (rid) {
case RID_RESET_MIN_MAX_MEASURED_VALUES: {
temp_inst->min_sensor_value = temp_inst->sensor_value;
temp_inst->max_sensor_value = temp_inst->sensor_value;
return 0;
}
//...
}
4.5.2.3. Object definition and Initialization
First, define the anj_dm_obj_inst_t array for the Object Instances:
static anj_dm_obj_inst_t INSTS[TEMP_OBJ_NUMBER_OF_INSTANCES] = {
{
.iid = 1,
.res_count = TEMPERATURE_RESOURCES_COUNT,
.resources = RES
},
{
.iid = 2,
.res_count = TEMPERATURE_RESOURCES_COUNT,
.resources = RES
}
};
Then add a static initialization of the Object, including its Instances:
static temp_obj_ctx_t temperature_obj = {
.obj = {
.oid = 3303,
.version = "1.1",
.insts = INSTS,
.handlers = &TEMP_OBJ_HANDLERS,
.max_inst_count = TEMP_OBJ_NUMBER_OF_INSTANCES
},
.temp_insts[0].iid = 1,
.temp_insts[0].application_type = "Sensor_1",
.temp_insts[0].sensor_value = 10.0,
.temp_insts[0].min_sensor_value = 10.0,
.temp_insts[0].max_sensor_value = 10.0,
.temp_insts[1].iid = 2,
.temp_insts[1].application_type = "Sensor_2",
.temp_insts[1].sensor_value = 20.0,
.temp_insts[1].min_sensor_value = 20.0,
.temp_insts[1].max_sensor_value = 20.0
};
4.5.3. Support transactional Writes
Transactions are performed on an Object level, not an Object Instance Level. This means you need to back up the state of all writable resources in all Object Instances.
static int transaction_begin(anj_t *anj, const anj_dm_obj_t *obj) {
(void) anj;
(void) obj;
temp_obj_ctx_t *ctx = get_ctx();
for (int i = 0; i < TEMP_OBJ_NUMBER_OF_INSTANCES; i++) {
temp_obj_inst_t *temp_inst = &ctx->temp_insts[i];
memcpy(temp_inst->application_type_cached, temp_inst->application_type,
TEMP_OBJ_APPL_TYPE_MAX_SIZE);
}
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();
for (int i = 0; i < TEMP_OBJ_NUMBER_OF_INSTANCES; i++) {
temp_obj_inst_t *temp_inst = &ctx->temp_insts[i];
memcpy(temp_inst->application_type,
temp_inst->application_type_cached,
TEMP_OBJ_APPL_TYPE_MAX_SIZE);
}
}
}