.. 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. Send method =========== The **Send** operation allows a LwM2M Client to transmit data to the LwM2M Server without receiving an explicit request from the Server. To create a **Send** message, the client populates the ``anj_send_request_t`` structure with the desired resource records. These represent the data to be sent. Once the structure is prepared, the client invokes the ``anj_send_new_request()`` function to register the message for transmission. The payload can contain values from multiple resources, even if they belong to different objects, or represent time-series data for a given resource. .. note:: Calling ``anj_send_new_request()`` does *not* send the message immediately. Anjay Lite queues the request and sends it only when: - A registration session with the LwM2M Server is active. - There is no other ongoing CoAP exchange in progress (e.g., a Server request, Update message, or Notification) .. note:: The **Send** operation requires either `SenML CBOR` or `LwM2M CBOR` support to be enabled in Anjay Lite. Choose the format based on your application's requirements. - `SenML CBOR` supports timestamps, which is useful for time-series data. - `LwM2M CBOR` cannot encode identical resource paths, but it typically produces more compact messages and is recommended when minimizing payload size is important. Example ------- .. note:: Code related to this tutorial can be found under `examples/tutorial/BC-Send` in the Anjay Lite source directory and is based on `examples/tutorial/BC-BasicObjectImplementation` example. First, set the queue size and enable **Send** operation (``ANJ_WITH_LWM2M_SEND``) in `CMakeLists.txt`: .. highlight:: cmake .. snippet-source:: examples/tutorial/BC-Send/CMakeLists.txt :emphasize-lines: 8-9 cmake_minimum_required(VERSION 3.6.0) project(anjay_lite_bc_send C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_EXTENSIONS OFF) set(ANJ_WITH_LWM2M_SEND ON) set(ANJ_LWM2M_SEND_QUEUE_SIZE 1) This setting determines how many Send messages Anjay Lite can queue at the same time. Next, we make a few modifications to the loop in which we call ``anj_core_step()``: .. highlight:: c .. snippet-source:: examples/tutorial/BC-Send/src/main.c anj_res_value_t value; uint64_t next_read_time = anj_time_now() + 1000; uint16_t send_id = 0; anj_io_out_entry_t records[MAX_RECORDS]; fin_handler_data_t data = { 0 }; while (true) { anj_core_step(&anj); update_sensor_value(get_temperature_obj()); usleep(50 * 1000); if (next_read_time < anj_time_now()) { next_read_time = anj_time_now() + 1000; if (data.record_idx < MAX_RECORDS) { if (anj_dm_res_read(&anj, &ANJ_MAKE_RESOURCE_PATH(3303, 0, 5700), &value)) { log(L_ERROR, "Failed to read resource"); } else { records[data.record_idx].path = ANJ_MAKE_RESOURCE_PATH(3303, 0, 5700); records[data.record_idx].type = ANJ_DATA_TYPE_DOUBLE; records[data.record_idx].value = value; records[data.record_idx].timestamp = (double) anj_time_real_now() / 1000; data.record_idx++; } } else { log(L_WARNING, "Records array full, abort send operation ID: " "%u", send_id); if (anj_send_abort(&anj, send_id)) { log(L_ERROR, "Failed to abort send operation"); } else { data.record_idx = 0; data.send_in_progress = false; } } } if (data.record_idx >= RECORDS_CNT_SEND_TRIGGER && !data.send_in_progress) { data.records_cnt = data.record_idx; data.records = records; data.send_in_progress = true; /* Record list full, request send */ anj_send_request_t send_req = { .finished_handler = send_finished_handler, .data = (void *) &data, .content_format = ANJ_SEND_CONTENT_FORMAT_SENML_CBOR, .records_cnt = data.records_cnt, .records = records }; if (anj_send_new_request(&anj, &send_req, &send_id)) { log(L_ERROR, "Failed to request new send"); data.send_in_progress = false; } } } **How it works** Here’s what each key variable does: - ``anj_res_value_t value``: holds the latest value read from the resource. - ``uint64_t next_read_time``: defines when the next resource read should happen. It’s updated every time we try to read the resource. - ``uint16_t send_id``: stores the current **Send** operation’s ID. You will need this value only if you want to abort the operation by calling ``anj_send_abort``. - ``anj_io_out_entry_t records[MAX_RECORDS]``: stores the list of values to be sent. - ``fin_handler_data_t data`` tracks metadata that you want to process after the **Send** operation completes. The data structure looks like this: .. highlight:: c .. snippet-source:: examples/tutorial/BC-Send/src/main.c typedef struct fin_handler_data { size_t records_cnt; size_t record_idx; anj_io_out_entry_t *records; bool send_in_progress; } fin_handler_data_t; Gathering the data for the Send message ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Once per second, we attempt to call ``anj_dm_res_read`` to read the ``/3303/0/5700`` resource. If the read is successful, we create a new entry in the records array with: - the resource path - the data type - the current value - a timestamp We use ``data.record_idx`` to track the next free slot in the array and increase it after each successful read. .. highlight:: c .. snippet-source:: examples/tutorial/BC-Send/src/main.c records[data.record_idx].path = ANJ_MAKE_RESOURCE_PATH(3303, 0, 5700); records[data.record_idx].type = ANJ_DATA_TYPE_DOUBLE; records[data.record_idx].value = value; records[data.record_idx].timestamp = (double) anj_time_real_now() / 1000; data.record_idx++; .. note:: If a timestamp is not required, you may omit setting this field in the record. .. note:: The values we store in the ``records`` array may be gathered directly from the sensor object omiting the ``anj_dm_res_read`` call. Preparing the Send message ^^^^^^^^^^^^^^^^^^^^^^^^^^ When ``data.record_idx`` reaches or exceeds ``RECORDS_CNT_SEND_TRIGGER``, it means we’ve gathered enough data to send. Start by updating the ``data`` structure: .. highlight:: c .. snippet-source:: examples/tutorial/BC-Send/src/main.c data.records_cnt = data.record_idx; data.records = records; data.send_in_progress = true; The ``send_in_progress`` flag indicates that a **Send** message is currently in progress. .. note:: If ``ANJ_LWM2M_SEND_QUEUE_SIZE`` is set to ``1``, only one **Send** request can be active at a time. To support more simultaneous operations, increase this setting. The ``record_idx`` and ``records`` values are stored in the ``data`` structure so they can later be cleaned up once the send completes. The ``data`` structure is passed to the callback function that is invoked after the **Send** message has been processed. Now we create a Send request: .. highlight:: c .. snippet-source:: examples/tutorial/BC-Send/src/main.c /* Record list full, request send */ anj_send_request_t send_req = { .finished_handler = send_finished_handler, .data = (void *) &data, .content_format = ANJ_SEND_CONTENT_FORMAT_SENML_CBOR, .records_cnt = data.records_cnt, .records = records }; We configure the following fields in the request structure: - ``finished_handler``: a callback function that will be called after the **Send** operation completes. - ``data``: a pointer to the user-defined structure passed to the callback. - ``content_format``: specifies the encoding formatv. - ``records_cnt`` and ``records``: define the number of records and a pointer to the array containing them. .. note:: The ``records`` array passed in ``anj_send_request_t`` is not copied internally. Its contents must remain unchanged and valid until the **Send** operation completes. Schedule the send ^^^^^^^^^^^^^^^^^ Once the request is ready, pass it to ``anj_send_new_request()``. If the function succeeds: - A new **Send** message is queued, - ``send_id`` stores its ID, which you can use later to cancel the **Send** operation if needed, - Anjay Lite will process the request during the subsequent ``anj_core_step()`` calls. Send mesage completion ^^^^^^^^^^^^^^^^^^^^^^ Once Anjay Lite finishes processing the Send request, it calls the handler function provided in the request to notify that the operation has completed: .. highlight:: c .. snippet-source:: examples/tutorial/BC-Send/src/main.c static void send_finished_handler(anj_t *anjay, uint16_t send_id, int result, void *data_) { (void) anjay; (void) send_id; (void) result; assert(data_); fin_handler_data_t *data = (fin_handler_data_t *) data_; /* move the records not yet processed to the begining of the array */ memmove(data->records, data->records + data->records_cnt, (MAX_RECORDS - data->records_cnt) * sizeof(anj_io_out_entry_t)); data->record_idx = data->record_idx - data->records_cnt; data->send_in_progress = false; } The logic inside this function can be adjusted to suit your application needs. What this handler does: - Clear the ``send_in_progress`` flag to indicate readiness for the next Send operation. - Shifts any remaining unsent records to the front of the ``records`` array using ``memmove()`` to free up space for new data. That’s it! Your client is now ready to send data using the LwM2M **Send** method in Anjay Lite.