5.11. IPSO objects implementation
5.11.1. Introduction
IPSO (Internet Protocol for Smart Objects) objects are a collection of LwM2M objects that can be used to expose some common features of many IoT devices, like sensors, buttons, actuators or control switches. Using predefined objects for these purposes enables higher interoperability of applications, i.e. all devices that have a temperature sensor can report the readings in a standardized way, making it possible to easily process such measurements from different, nonhomogeneous devices on the cloud.
In practice, IPSO objects are most importantly a convenient way to report sensor data over LwM2M. All IPSO objects of aforementioned certain kinds share common set of resources, and thanks to that these objects in a large part can be easily preimplemented.
Anjay provides a ready-to-use implementation of:
basic (i.e. scalar) sensor objects (e.g. Temperature Object or Pressure Object),
three-axis sensor objects (e.g. Accelerometer Object or Magnetometer Object),
Push Button Object.
User’s only responsibility is to retrieve those values from actual sensors and supply them to Anjay, making it very easy to implement LwM2M devices with sensor support.
The API is declared in include_public/anjay/ipso_objects.h
and
include_public/anjay/ipso_objects_v2.h
headers. To use them, enable them
first by either defining ANJAY_WITH_MODULE_IPSO_OBJECTS
and/or
ANJAY_WITH_MODULE_IPSO_OBJECTS_V2
in the Anjay’s configuration files, or, if
using CMake, enabling WITH_MODULE_ipso_objects
and/or
WITH_MODULE_ipso_objects_v2
options.
Important
The APIs for basic and 3D IPSO sensors objects explained in this tutorial
are new, experimental variants declared in
include_public/anjay/ipso_objects_v2.h
.
5.11.2. Supported objects
Implementation of IPSO objects in Anjay supports objects that have the following set of resources:
Basic (scalar) sensor objects |
|||
---|---|---|---|
Resource ID |
Resource Name |
Must be supported by object |
|
5601 |
Min Measured Value |
no |
|
5602 |
Max Measured Value |
no |
|
5603 |
Min Range Value |
no |
|
5604 |
Max Range Value |
no |
|
5605 |
Reset Min and Max Measured Values |
no |
|
5700 |
Sensor Value |
yes |
|
5701 |
Sensor Units |
no |
Three-axis sensor objects |
|||
---|---|---|---|
Resource ID |
Resource Name |
Must be supported by object |
|
5508 |
Min X Value |
no |
|
5509 |
Max X Value |
no |
|
5510 |
Min Y Value |
no |
|
5511 |
Max Y Value |
no |
|
5512 |
Min Z Value |
no |
|
5513 |
Max Z Value |
no |
|
5603 |
Min Range Value |
no |
|
5604 |
Max Range Value |
no |
|
5605 |
Reset Min and Max Measured Values |
no |
|
5701 |
Sensor Units |
no |
|
5702 |
X Value |
yes |
|
5703 |
Y Value |
no |
|
5704 |
Z Value |
no |
As of December 13th, 2023, objects registered by IPSO Alliance that meet these requirements are: 3300 (Generic Sensor), 3301 (Illuminance), 3303 (Temperature), 3304 (Humidity), 3313 (Accelerometer), 3314 (Magnetometer), 3315 (Barometer), 3316 (Voltage), 3317 (Current), 3318 (Frequency), 3319 (Depth), 3320 (Percentage), 3321 (Altitude), 3322 (Load), 3323 (Pressure), 3324 (Loudness), 3325 (Concentration), 3326 (Acidity), 3327 (Conductivity), 3328 (Power), 3329 (Power Factor), 3330 (Distance), 3334 (Gyrometer), 3345 (Multiple Axis Joystick), 3346 (Rate).
Additionally, object 3347 (Push Button) is supported with a separate API.
5.11.3. Usage example
This tutorial builds up on the Installing mandatory Objects tutorial which contains an implementation of a minimal, but complete LwM2M client.
Note
Complete code of this example can be found in examples/tutorial/AT-IpsoObjects subdirectory of main Anjay project repository.
In this example we’ll implement a simple application that simulates a few thermometers, accelerometers and buttons.
5.11.3.1. Installing objects and instances
To setup an IPSO object, you must install it first using one of the following methods:
For sensors, the API accepts Object ID, object version and maximum number of instances that’ll be installed later. For button, the Object ID and version is defined upfront.
Important
It’s important to set appropriate object version number. Without configuring it a LwM2M server may fail to interpret resources that were added in newer versions of an object. Such an example is Gyrometer Object, which has the “Reset Min and Max Measured Values” resource available only since version 1.1.
In this example all enabled resources are available in version 1.0 of these
objects, to which passing NULL
defaults to.
After installing objects, instances of these objects can be added using following APIs:
For basic and 3D sensors, these methods accept an initial value of the sensor and a structure that provides metadata about each instance: anjay_ipso_v2_basic_sensor_meta_t and anjay_ipso_v2_3d_sensor_meta_t, respectively.
These structs are used to configure unit, reported minimum and maximum values that can be measured by a sensor, and presence of optional Y and Z axis in case of 3D objects.
In our example, let’s define some macros and necessary metadata structs first:
#define TEMPERATURE_OBJ_OID 3303
#define ACCELEROMETER_OBJ_OID 3313
#define THERMOMETER_COUNT 3
#define ACCELEROMETER_COUNT 2
#define BUTTON_COUNT 4
static const anjay_ipso_v2_basic_sensor_meta_t thermometer_meta = {
.unit = "Cel",
.min_max_measured_value_present = true,
.min_range_value = -20.0,
.max_range_value = 120.0
};
static const anjay_ipso_v2_3d_sensor_meta_t accelerometer_meta = {
.unit = "m/s2",
.min_range_value = -20.0,
.max_range_value = 20.0,
.y_axis_present = true,
.z_axis_present = true
};
Note
It’s a good practice to report values using units defined in SenML Units Registry, the up to date list can be found here.
Then, let’s introduce some helper methods that will install our sensor objects and add all instances upfront:
static int setup_temperature_object(anjay_t *anjay) {
if (anjay_ipso_v2_basic_sensor_install(anjay, TEMPERATURE_OBJ_OID, NULL,
THERMOMETER_COUNT)) {
return -1;
}
for (anjay_iid_t iid = 0; iid < THERMOMETER_COUNT; iid++) {
if (anjay_ipso_v2_basic_sensor_instance_add(
anjay, TEMPERATURE_OBJ_OID, iid, 20.0, &thermometer_meta)) {
return -1;
}
}
return 0;
}
static int setup_accelerometer_object(anjay_t *anjay) {
if (anjay_ipso_v2_3d_sensor_install(anjay, ACCELEROMETER_OBJ_OID, NULL,
ACCELEROMETER_COUNT)) {
return -1;
}
for (anjay_iid_t iid = 0; iid < ACCELEROMETER_COUNT; iid++) {
anjay_ipso_v2_3d_sensor_value_t initial_value = {
.x = 0.0,
.y = 0.0,
.z = 0.0
};
if (anjay_ipso_v2_3d_sensor_instance_add(anjay, ACCELEROMETER_OBJ_OID,
iid, &initial_value,
&accelerometer_meta)) {
return -1;
}
}
return 0;
}
static int setup_button_object(anjay_t *anjay) {
if (anjay_ipso_button_install(anjay, BUTTON_COUNT)) {
return -1;
}
for (anjay_iid_t iid = 0; iid < BUTTON_COUNT; iid++) {
if (anjay_ipso_button_instance_add(anjay, iid, "")) {
return -1;
}
}
return 0;
}
Finally, let’s call these methods in initialization code, in main()
method:
int main(int argc, char *argv[]) {
// ...
if (setup_security_object(anjay) || setup_server_object(anjay)
|| setup_temperature_object(anjay)
|| setup_accelerometer_object(anjay)
|| setup_button_object(anjay)) {
result = -1;
}
// ...
}
5.11.3.2. Updating values
To update reported value of a sensor, use one of following methods:
Important
Keep in mind that a LwM2M Server is allowed to configure resource observations with attributes that require the client to report the data very frequently or when some threshold value is exceeded, even for a very short moment. If you want to ensure that server is notified of every change of resource value that could meet such conditions, you must update the value very frequently.
Important
These methods (as all methods in Anjay’s public API) cannot be called from
an interrupt. In case ANJAY_WITH_THREAD_SAFETY
is disabled Anjay APIs
are not safe to call from other contexts than method which runs event loop
and avs_sched
tasks, while if ANJAY_WITH_THREAD_SAFETY
is enabled
calling such methods will attempt to lock a mutex from an interrupt which
also is wrong.
If your application retrieves new sensor values and/or button state changes in an interrupt, you must find a way to pass these values to a non-interrupt execution context.
In our example we’re simulating values of these sensors, so let’s add some utility methods first:
static double get_random_in_range(double min, double max) {
return min + (max - min) * rand() / RAND_MAX;
}
static double get_thermometer_value(void) {
return get_random_in_range(thermometer_meta.min_range_value,
thermometer_meta.max_range_value);
}
static anjay_ipso_v2_3d_sensor_value_t get_accelerometer_value(void) {
return (anjay_ipso_v2_3d_sensor_value_t) {
.x = get_random_in_range(accelerometer_meta.min_range_value,
accelerometer_meta.max_range_value),
.y = get_random_in_range(accelerometer_meta.min_range_value,
accelerometer_meta.max_range_value),
.z = get_random_in_range(accelerometer_meta.min_range_value,
accelerometer_meta.max_range_value)
};
}
static bool get_button_state(void) {
return rand() % 2 == 0;
}
Then, let’s implement a scheduler task that will update all sensors. The task schedules itself to run every second:
static void update_sensor_values(avs_sched_t *sched, const void *anjay_ptr) {
anjay_t *anjay = *(anjay_t *const *) anjay_ptr;
for (anjay_iid_t iid = 0; iid < THERMOMETER_COUNT; iid++) {
(void) anjay_ipso_v2_basic_sensor_value_update(
anjay, TEMPERATURE_OBJ_OID, iid, get_thermometer_value());
}
for (anjay_iid_t iid = 0; iid < ACCELEROMETER_COUNT; iid++) {
anjay_ipso_v2_3d_sensor_value_t value = get_accelerometer_value();
(void) anjay_ipso_v2_3d_sensor_value_update(
anjay, ACCELEROMETER_OBJ_OID, iid, &value);
}
for (anjay_iid_t iid = 0; iid < BUTTON_COUNT; iid++) {
(void) anjay_ipso_button_update(anjay, iid, get_button_state());
}
AVS_SCHED_DELAYED(sched, NULL, avs_time_duration_from_scalar(1, AVS_TIME_S),
update_sensor_values, &anjay, sizeof(anjay));
}
Lastly, let’s call this method once before entering event loop. From that moment the task will keep running infinitely.
int main(int argc, char *argv[]) {
// ...
if (!result) {
update_sensor_values(anjay_get_scheduler(anjay), &anjay);
result = anjay_event_loop_run(
anjay, avs_time_duration_from_scalar(1, AVS_TIME_S));
}
// ...
}
5.11.3.3. Removing instances
In case you need to change the set of instances of installed IPSO objects, those instances can be removed using following methods:
In our example instance set doesn’t change. All objects and instances are
automatically deleted when anjay_delete()
is called.