5.6. Suspending and resuming FOTA Pull downloads

5.6.1. Overview

A firmware download may need to be paused even though the transfer itself has not failed. This may happen when the application reaches a communication budget, leaves an allowed transmission window, or gets an indication from the modem or network stack that connectivity is temporarily unavailable.

The CoAP Downloader provides anj_coap_downloader_suspend() and anj_coap_downloader_resume() for such cases. Suspending a download stops network activity while preserving its progress. Once the application decides that the transfer may continue, it can resume the same download instead of starting it from the beginning.

This tutorial builds on FOTA Pull with CoAP and focuses only on adding application-controlled suspension and resumption to an existing CoAP Pull firmware download.

5.6.2. Project structure

The source files used in this tutorial are located in the examples/tutorial/firmware-update-downloader-suspend-resume directory:

examples/tutorial/firmware-update-downloader-suspend-resume/
├── CMakeLists.txt
└── src
    ├── firmware_update.c (changed)
    ├── firmware_update.h (changed)
    └── main.c (changed)

5.6.3. Example scenario

The example demonstrates a simple application-defined communication budget. The firmware download is suspended after every 100 received data chunks. The application waits for 30 seconds, resumes the transfer and moves the next limit by another 100 chunks.

Note

The number of chunks is used only to keep the example deterministic and easy to run. A chunk delivered by the CoAP Downloader is not necessarily equivalent to a physical, IP, UDP or cellular-network packet. A production application that enforces a real traffic or message budget should normally obtain that information from the modem or networking layer.

5.6.4. Expose download progress to the application

The transfer-window policy is implemented in main.c, while downloader events are handled in firmware_update.c. The Firmware Update module therefore exposes only the information needed by the policy: whether a download is active and how many data chunks have been received.

The chunk counter is advanced whenever the downloader delivers firmware data:

case ANJ_COAP_DOWNLOADER_STATUS_DOWNLOADING: {
    assert(fu->firmware_file != NULL);

    ++fu->received_chunks;
    log(L_INFO,
        "Writing %zu bytes at offset %zu (chunk %zu)",
        data_len,
        fu->offset,
        fu->received_chunks);
    fu->offset += data_len;

main.c accesses the state through small helper functions and does not need access to the CoAP Downloader instance itself:

bool fw_update_is_download_active(void) {
    return firmware_update.download_active;
}

size_t fw_update_get_received_chunk_count(void) {
    return firmware_update.received_chunks;
}

int fw_update_suspend_download(void) {
    return anj_coap_downloader_suspend(&coap_downloader);
}

int fw_update_resume_download(void) {
    return anj_coap_downloader_resume(&coap_downloader);
}

5.6.5. Implement transfer windows

The application keeps the next chunk limit and the time at which a suspended download should be resumed. When the current limit is reached, it requests suspension, schedules resumption 30 seconds later and advances the next limit by 100 chunks.

static void handle_download_window(download_control_t *control) {
    if (!fw_update_is_download_active()) {
        reset_download_control(control);
        return;
    }

    const size_t received_chunks = fw_update_get_received_chunk_count();

    if (!control->is_suspended
            && received_chunks >= control->next_suspend_at_chunk) {
        if (!fw_update_suspend_download()) {
            control->is_suspended = true;
            control->resume_at = anj_time_monotonic_add(
                    anj_time_monotonic_now(),
                    anj_time_duration_new(DOWNLOAD_SUSPEND_SECONDS,
                                        ANJ_TIME_UNIT_S));
            control->next_suspend_at_chunk += DOWNLOAD_CHUNK_LIMIT_STEP;
            log(L_INFO,
                "Downloaded %zu chunks; suspending firmware download for "
                "%d "
                "seconds",
                received_chunks, DOWNLOAD_SUSPEND_SECONDS);
        } else {
            log(L_ERROR, "Failed to suspend firmware download - invalid state");
        }
        return;
    }

    if (control->is_suspended
            && anj_time_monotonic_geq(anj_time_monotonic_now(),
                                    control->resume_at)) {
        if (!fw_update_resume_download()) {
            control->is_suspended = false;
            log(L_INFO, "Resuming firmware download");
        } else {
            log(L_ERROR, "Failed to resume firmware download - invalid state");
        }
    }
}

The policy is evaluated from the main loop together with the normal Anjay Lite and Firmware Update processing:

while (true) {
    anj_core_step(&anj);
    fw_update_process();
    handle_download_window(&download_control);

    struct timespec ts = { 0, 50 * 1000 * 1000 }; // 50 ms
    nanosleep(&ts, NULL);
}

Note

Suspension is asynchronous. Calling anj_coap_downloader_suspend() starts the suspension process, but the downloader may need several calls to anj_coap_downloader_step() before it can be resumed.

5.6.6. Connectivity loss

A communication budget is only one reason to suspend a download. On an embedded device, the modem or network stack may provide a direct indication that connectivity has been lost. If the application knows that communication is not currently possible, it can suspend an active firmware download instead of letting it generate unnecessary traffic attempts.

When connectivity becomes available again, the same application-level event can be used to resume the suspended download. The source of such connectivity information is platform-specific; the suspend/resume API does not require a particular modem, operating system or network-stack integration.

5.6.7. Using suspend/resume together with retry

Suspend/resume and automatic retry address different conditions and can be used together. The retry policy configured for the CoAP Downloader handles transient communication failures automatically, as described in FOTA Pull with CoAP. Suspension is an explicit application decision to stop download activity for a longer or externally defined period.

Suspending a download also stops any pending retry handling. The downloader remains suspended until the application explicitly resumes it. Calling anj_coap_downloader_resume() starts a new download attempt immediately, without waiting for anj_coap_downloader_configuration_t.retry_delay. Afterwards, the configured retry policy continues to apply to subsequent communication failures.