3.4. Notifications support

3.4.1. Overview

Some resources, like sensor readings, change over time. To track these changes, the LwM2M Server can use the Observe operation. This allows it to receive notifications whenever a value changes or meets certain conditions.

See also

For more details about the Observe operation and observation attributes, refer to the LwM2M TS: Core specification.

3.4.2. Notify the library about changes

The LwM2M Server can observe changes in resources using the Observe operation. When resource values change independently of LwM2M operations (such as Write or Create), you must inform the Anjay Lite library manually by calling the anj_core_data_model_changed() function:

void anj_core_data_model_changed(anj_t *anj,
                                 const anj_uri_path_t *path,
                                 anj_core_change_type_t change_type);

Call this function with the correct change_type based on what changed:

  • Use ANJ_CORE_CHANGE_TYPE_VALUE_CHANGED when a resource or resource instance value has been updated.

  • Use ANJ_CORE_CHANGE_TYPE_ADDED or ANJ_CORE_CHANGE_TYPE_DELETED when an object instance or resource instance has been added or removed.

    Note

    If the removed entity was being observed, its observation will be automatically removed.

Once anj_core_data_model_changed() is called, the library will respond to the change during subsequent calls to anj_core_step(). If the affected resource is under observation and the configured conditions are met, a Notify message will be prepared and eventually sent to the LwM2M Server.

Important

anj_core_data_model_changed() is not only used to support notifications, but is also essential for other mechanisms, including:

  • Triggering an Update message that includes a list of all objects and their instances when the set of registered object instances changes.

  • Reading certain resources that manage registration state, such as the Lifetime, Mute Send, or Notification Storing resources of the Server object.

Note

The number of concurrent observations and the number of entities with observation attributes are limited by the configuration options: ANJ_OBSERVE_MAX_OBSERVATIONS_NUMBER and ANJ_OBSERVE_MAX_WRITE_ATTRIBUTES_NUMBER.

3.4.3. Example: Add notifications to a Temperature object

This example shows how to add notification support to a Temperature object. You’ll use the anj_core_data_model_changed() function to inform the library when resource values change.

3.4.3.1. Update resource values

The update_sensor_value() function updates three resources periodically: Sensor Value, Min Measured Value, and Max Measured Value.

Call anj_core_data_model_changed() for each resource whose value changes:

void update_sensor_value(anj_t *anj, const anj_dm_obj_t *obj) {
    (void) obj;

    temp_obj_ctx_t *ctx = get_ctx();

    double prev_temp_value = ctx->sensor_value;
    ctx->sensor_value = next_temperature_with_limit(ctx->sensor_value, 0.2);

    if (prev_temp_value != ctx->sensor_value) {
        anj_core_data_model_changed(anj,
                                    &ANJ_MAKE_RESOURCE_PATH(TEMPERATURE_OID, 0,
                                                            RID_SENSOR_VALUE),
                                    ANJ_CORE_CHANGE_TYPE_VALUE_CHANGED);
    }
    if (ctx->sensor_value < ctx->min_sensor_value) {
        ctx->min_sensor_value = ctx->sensor_value;
        anj_core_data_model_changed(
                anj,
                &ANJ_MAKE_RESOURCE_PATH(TEMPERATURE_OID, 0,
                                        RID_MIN_MEASURED_VALUE),
                ANJ_CORE_CHANGE_TYPE_VALUE_CHANGED);
    }
    if (ctx->sensor_value > ctx->max_sensor_value) {
        ctx->max_sensor_value = ctx->sensor_value;
        anj_core_data_model_changed(
                anj,
                &ANJ_MAKE_RESOURCE_PATH(TEMPERATURE_OID, 0,
                                        RID_MAX_MEASURED_VALUE),
                ANJ_CORE_CHANGE_TYPE_VALUE_CHANGED);
    }
}

If any of these resources are observed by the LwM2M Server, the library will send a Notify message when the value changes.

Note

Do not call anj_core_data_model_changed() when the change is directly triggered by an LwM2M operation (e.g., Write or Create). In such cases, all required actions are handled internally by the library.

Warning

It’s crucial to call anj_core_data_model_changed() only after ensuring that the subsequent res_read() call will return the updated resource value. In this simple example, since res_read() just returns a value from memory, the function can be called right after assigning new values to fields in temp_obj_ctx_t. Anjay Lite may perform a read immediately during a call to anj_core_data_model_changed(), and may continue reading during future anj_core_step() calls.

This behavior differs from Anjay 3, where anjay_notify_changed() and anjay_notify_instances_changed() can be invoked at any time, as long as the new resource values are available before the next call to anjay_event_loop_run().

3.4.3.2. Handle execute operations

The Reset Min and Max Measured Values resource is implemented using res_execute(). Although it is triggered by an LwM2M command, it changes other resource values, so you must still notify the library:

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) iid;
    (void) execute_arg;
    (void) execute_arg_len;

    temp_obj_ctx_t *temp_obj_ctx = get_ctx();

    switch (rid) {
    case RID_RESET_MIN_MAX_MEASURED_VALUES: {
        temp_obj_ctx->min_sensor_value = temp_obj_ctx->sensor_value;
        temp_obj_ctx->max_sensor_value = temp_obj_ctx->sensor_value;

        anj_core_data_model_changed(
                anj,
                &ANJ_MAKE_RESOURCE_PATH(TEMPERATURE_OID, 0,
                                        RID_MIN_MEASURED_VALUE),
                ANJ_CORE_CHANGE_TYPE_VALUE_CHANGED);
        anj_core_data_model_changed(
                anj,
                &ANJ_MAKE_RESOURCE_PATH(TEMPERATURE_OID, 0,
                                        RID_MAX_MEASURED_VALUE),
                ANJ_CORE_CHANGE_TYPE_VALUE_CHANGED);
        return 0;
    }
    default:
        break;
    }

    return ANJ_DM_ERR_NOT_FOUND;
}

Note

It is not required to check whether the value actually changed before calling anj_core_data_model_changed(). For simplicity, this check is skipped in the res_execute() callback. However, doing so helps avoid unnecessary Notify messages—especially for resources that are observed without any conditions (like thresholds). In these cases, the library does not remember the last value it sent, so it may send unnecessary notifications unless your application filters them out.

3.4.3.3. Update function declaration

Update the update_sensor_value() function to include the anj_t *anj parameter, since it is required by anj_core_data_model_changed():

/**
 * @brief Updates the sensor value and adjusts min/max tracked values.
 *
 * Simulates a new temperature reading for the given object by applying a small
 * random fluctuation to the current value. Also updates the minimum and maximum
 * recorded values based on the new reading.
 *
 * @param anj Pointer to the Anjay Lite instance.
 * @param obj Pointer to the Temperature Object.
 */
void update_sensor_value(anj_t *anj, const anj_dm_obj_t *obj);

3.4.3.4. Call the function in the main loop

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

    while (true) {
        anj_core_step(&anj);
        update_sensor_value(&anj, get_temperature_obj());
        usleep(50 * 1000);
    }
    return 0;
}

That’s it. Your application now supports Observe/Notify for dynamic resources like temperature readings.