4.4. Implementing standard Object
This section describes implementation of a standard Object defined in OMA LwM2M Registry. If you are interested in implementing your own Objects, please jump to Custom LwM2M objects section or prepare an Object definition in XML on your own.
As an example, we are going to implement Time Object with ID 3333 in version 1.0. It is one of the simplest Objects defined in OMA LwM2M Registry, which allows to understand basics of operations on Objects.
Note
For conformance with the LwM2M specification, the Device Object must be implemented. We are implementing Time Object instead for simplicity, because it contains less resources.
This objects contains definition of three resources. They are presented in the table below with their most important attributes.
ID |
Name |
Operations |
Mandatory |
Type |
Description |
---|---|---|---|---|---|
5506 |
Current Time |
RW |
Mandatory |
Time |
Unix Time. A signed integer representing the number of seconds since Jan 1st, 1970 in the UTC time zone. |
5507 |
Fractional Time |
RW |
Optional |
Float |
Fractional part of the time when sub-second precision is used (e.g., 0.23 for 230 ms). |
5750 |
Application Type |
RW |
Optional |
String |
The application type of the sensor or actuator as a string depending on the use case. |
ID - number used to identify the particular Resource. Different Objects may use the same Resource IDs for different purposes.
Operations - RW indicates, that Resource is Readable and Writable.
Mandatory - not all Resources defined for standard object must be implemented to be compliant with specification. In this case only the Current Time resource is mandatory.
Although Current Time and Fractional Time resources are writable, we will not focus on setting system time and not implement this operation for these two resources.
4.4.1. Implementing the Object
Generating base source code
To generate layout of Object’s implementation, we will use the anjay_codegen.py
script, which is bundled with Anjay library. Without going into details, which
are described in Tools section, we may just call
./tools/lwm2m_object_registry.py --get-xml 3333 -v 1.0 | ./tools/anjay_codegen.py -i - -o time_object.c
from the Anjay root directory. This command downloads Object’s definition from
OMA LwM2M registry and converts it to source code with a lot of TODOs. Now we
are going to replace them with actual code.
Keeping Instance and Object state
The actual data of a LwM2M Object sits in its Instances and Resources of that Instance, so we must have at least one Instance to operate on some real data.
The state of our Time Object Instance will be placed in time_instance_t
struct. The only thing we must keep there is Instance ID (iid) and a value of
Application Type Resource, because for Current Time Resource we will be using a
system clock source directly, whenever a read handler is called
Note that there is also a second array for keeping backup of Application Type - this will be required for implementation of transactions. We will back to it at the end of this tutorial.
There is also the time_object_t
structure, but we do not need to keep
anything related to the entire Time Object, so we can leave the default
implementation, which just keeps the Object definition and the list of its
instances.
typedef struct time_instance_struct {
anjay_iid_t iid;
char application_type[64];
char application_type_backup[64];
} time_instance_t;
typedef struct time_object_struct {
const anjay_dm_object_def_t *def;
AVS_LIST(time_instance_t) instances;
} time_object_t;
Initializing, releasing and resetting the instance
Next we have to implement init_instance()
and release_instance()
functions. These functions are used during creation and deletion of instances,
performed by LwM2M Server for example.
In this case, all we have to do is to initialize Application Type with some
value. Since it is a C-string, it is enough to set the first byte to \0
.
static int init_instance(time_instance_t *inst, anjay_iid_t iid) {
assert(iid != ANJAY_ID_INVALID);
inst->iid = iid;
inst->application_type[0] = '\0';
return 0;
}
If you decide to allocate the memory for the Application Type dynamically
instead of using fixed-size buffers, then it should be freed in
release_instance()
function. In this case, release_instance()
may do
nothing and the default implementation can be left.
The next function to implement is instance_reset()
, which should reset
the Instance to its default state, which means the empty Application Type in our
case.
static int instance_reset(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr,
anjay_iid_t iid) {
(void) anjay;
time_object_t *obj = get_obj(obj_ptr);
time_instance_t *inst = find_instance(obj, iid);
assert(inst);
inst->application_type[0] = '\0';
return 0;
}
We can also disable the presence of one of the Resources in the
list_resources()
function. It is done by changing
ANJAY_DM_RES_PRESENT
to ANJAY_DM_RES_ABSENT
in the
anjay_dm_emit_res()
call. This change will simplify implementation of Read
handler and Observe/Notifications support in the next section.
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_CURRENT_TIME, ANJAY_DM_RES_RW,
ANJAY_DM_RES_PRESENT);
anjay_dm_emit_res(ctx, RID_FRACTIONAL_TIME, ANJAY_DM_RES_RW,
ANJAY_DM_RES_ABSENT);
anjay_dm_emit_res(ctx, RID_APPLICATION_TYPE, ANJAY_DM_RES_RW,
ANJAY_DM_RES_PRESENT);
return 0;
}
Note
Using -r
command line option in anjay_codegen.py
you can generate
Object’s stub with specified Resources only.
Read and Write handlers
Now we are ready to implement resource_read()
and resource_write()
handlers. These handlers will be called every time LwM2M Server performs Read
or Write operation.
We may use avs_time_real_now()
to get the current time.
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;
time_object_t *obj = get_obj(obj_ptr);
time_instance_t *inst = find_instance(obj, iid);
assert(inst);
switch (rid) {
case RID_CURRENT_TIME: {
assert(riid == ANJAY_ID_INVALID);
int64_t timestamp;
if (avs_time_real_to_scalar(×tamp, AVS_TIME_S,
avs_time_real_now())) {
return -1;
}
return anjay_ret_i64(ctx, timestamp);
}
case RID_APPLICATION_TYPE:
assert(riid == ANJAY_ID_INVALID);
return anjay_ret_string(ctx, inst->application_type);
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
As you remember, we do not want to set the system time, so Write operation is allowed only on Application Type resource.
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;
time_object_t *obj = get_obj(obj_ptr);
time_instance_t *inst = find_instance(obj, iid);
assert(inst);
switch (rid) {
case RID_APPLICATION_TYPE:
assert(riid == ANJAY_ID_INVALID);
return anjay_get_string(ctx, inst->application_type,
sizeof(inst->application_type));
default:
return ANJAY_ERR_METHOD_NOT_ALLOWED;
}
}
Initialization of the Object
There is one function left to implement to have the basic functionality:
time_object_create()
. By default, there is no Object Instance created, so no
data could be read unless LwM2M Server creates it. However, we are able to
add an Instance right now, by calling add_instance()
.
const anjay_dm_object_def_t **time_object_create(void) {
time_object_t *obj = (time_object_t *) avs_calloc(1, sizeof(time_object_t));
if (!obj) {
return NULL;
}
obj->def = &OBJ_DEF;
time_instance_t *inst = add_instance(obj, 0);
if (inst) {
strcpy(inst->application_type, "Clock 0");
} else {
avs_free(obj);
return NULL;
}
return &obj->def;
}
Since we do not allocate memory for anything else during object creation, we may
leave the default implementation of time_object_release()
, which will remove
the created instance.
Registering the Object in Anjay
The last things to do is to create header file for implemented object, register
it in Anjay and update CMakeLists.txt
file.
#ifndef TIME_OBJECT_H
#define TIME_OBJECT_H
#include <anjay/dm.h>
const anjay_dm_object_def_t **time_object_create(void);
void time_object_release(const anjay_dm_object_def_t **def);
#endif // TIME_OBJECT_H
int main(int argc, char *argv[]) {
if (argc != 2) {
avs_log(tutorial, ERROR, "usage: %s ENDPOINT_NAME", argv[0]);
return -1;
}
const anjay_configuration_t CONFIG = {
.endpoint_name = argv[1],
.in_buffer_size = 4000,
.out_buffer_size = 4000,
.msg_cache_size = 4000
};
anjay_t *anjay = anjay_new(&CONFIG);
if (!anjay) {
avs_log(tutorial, ERROR, "Could not create Anjay object");
return -1;
}
int result = 0;
// Setup necessary objects
if (setup_security_object(anjay) || setup_server_object(anjay)) {
result = -1;
}
const anjay_dm_object_def_t **time_object = NULL;
if (!result) {
time_object = time_object_create();
if (time_object) {
result = anjay_register_object(anjay, time_object);
} else {
result = -1;
}
}
if (!result) {
result = anjay_event_loop_run(
anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));
}
anjay_delete(anjay);
time_object_release(time_object);
return result;
}
cmake_minimum_required(VERSION 3.1)
project(anjay-bc-object-implementation C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_EXTENSIONS OFF)
add_compile_options(-Wall -Wextra)
find_package(anjay REQUIRED)
add_executable(${PROJECT_NAME}
src/main.c
src/time_object.h
src/time_object.c)
target_link_libraries(${PROJECT_NAME} PRIVATE anjay)
Now the client is ready to be built and connected to LwM2M Server, allowing it to read the Time object.
Important
Custom objects are not automatically managed by Anjay. Remember to release created object after deleting the Anjay object.
4.4.2. Supporting transactional writes
Consider the following scenario: LwM2M Server tries to write to two or more resources at once. The write on Application Type will probably succeed, but we are sure, that in this case write on the Current Time will fail. Without supporting transactions, the entire Write operation will fail, but the Application Type resource will be changed.
By default, transaction handlers are set to anjay_dm_transaction_NOOP
and do nothing. To properly support Writes on the object implemented in this
tutorial, it is enough to implement only two handlers: transaction_begin
,
which makes a backup of Application Type value and transaction_rollback
,
which reverts Application Type to its initial value (before Write is performed).
This is why we need application_type_backup
array.
int transaction_begin(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr) {
(void) anjay;
time_object_t *obj = get_obj(obj_ptr);
time_instance_t *element;
AVS_LIST_FOREACH(element, obj->instances) {
strcpy(element->application_type_backup, element->application_type);
}
return 0;
}
int transaction_rollback(anjay_t *anjay,
const anjay_dm_object_def_t *const *obj_ptr) {
(void) anjay;
time_object_t *obj = get_obj(obj_ptr);
time_instance_t *element;
AVS_LIST_FOREACH(element, obj->instances) {
strcpy(element->application_type, element->application_type_backup);
}
return 0;
}
static const anjay_dm_object_def_t OBJ_DEF = {
.oid = 3333,
.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,
.transaction_begin = transaction_begin,
.transaction_validate = anjay_dm_transaction_NOOP,
.transaction_commit = anjay_dm_transaction_NOOP,
.transaction_rollback = transaction_rollback
}
};
Note
Complete code of this example can be found in examples/tutorial/BC-ObjectImplementation subdirectory of main Anjay project repository.