Zelta/Documentation

Examples

Complete examples for common embedded Linux scenarios.

Basic Linux Application

A simple update checker for any Linux system:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include "zelta.h"

// Current firmware version
#define CURRENT_VERSION "1.0.0"
#define FIRMWARE_PATH "/var/lib/zelta/firmware.bin"

// HTTP response buffer
struct response_buffer {
    char *data;
    size_t size;
};

static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    struct response_buffer *buf = userdata;
    size_t total = size * nmemb;
    
    char *new_data = realloc(buf->data, buf->size + total + 1);
    if (!new_data) return 0;
    
    buf->data = new_data;
    memcpy(buf->data + buf->size, ptr, total);
    buf->size += total;
    buf->data[buf->size] = '\0';
    
    return total;
}

// HTTP client implementation
static int http_request(const char *url, const char *method, 
                       const char *body, const char *api_key,
                       char **response, size_t *response_len) {
    CURL *curl = curl_easy_init();
    if (!curl) return -1;
    
    struct response_buffer buf = {0};
    struct curl_slist *headers = NULL;
    
    // Set headers
    char auth_header[256];
    snprintf(auth_header, sizeof(auth_header), "X-API-Key: %s", api_key);
    headers = curl_slist_append(headers, auth_header);
    headers = curl_slist_append(headers, "Content-Type: application/json");
    
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
    
    if (strcmp(method, "POST") == 0) {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
    }
    
    CURLcode res = curl_easy_perform(curl);
    
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    
    if (res != CURLE_OK) {
        free(buf.data);
        return -1;
    }
    
    *response = buf.data;
    *response_len = buf.size;
    return 0;
}

// File download with progress
static int download_firmware(const char *url, const char *path) {
    CURL *curl = curl_easy_init();
    if (!curl) return -1;
    
    FILE *fp = fopen(path, "wb");
    if (!fp) {
        curl_easy_cleanup(curl);
        return -1;
    }
    
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    
    CURLcode res = curl_easy_perform(curl);
    
    fclose(fp);
    curl_easy_cleanup(curl);
    
    return (res == CURLE_OK) ? 0 : -1;
}

int main(int argc, char *argv[]) {
    const char *api_key = getenv("ZELTA_API_KEY");
    const char *product_id = getenv("ZELTA_PRODUCT_ID");
    
    if (!api_key || !product_id) {
        fprintf(stderr, "Set ZELTA_API_KEY and ZELTA_PRODUCT_ID\n");
        return 1;
    }
    
    // Initialize Zelta
    zelta_config_t config = {
        .server_url = "https://api.zeltasoft.com",
        .api_key = api_key,
        .product_id = product_id,
        .current_version = CURRENT_VERSION,
        .http_request = http_request,
    };
    
    zelta_ctx_t *ctx = zelta_init(&config);
    if (!ctx) {
        fprintf(stderr, "Failed to initialize Zelta\n");
        return 1;
    }
    
    // Check for updates
    printf("Checking for updates...\n");
    
    zelta_update_info_t update;
    int result = zelta_check_update(ctx, &update);
    
    if (result == ZELTA_UPDATE_AVAILABLE) {
        printf("Update available: %s -> %s\n", 
               CURRENT_VERSION, update.version);
        printf("Size: %zu bytes\n", update.size);
        printf("Release notes: %s\n", update.release_notes);
        
        // Download
        printf("Downloading...\n");
        if (download_firmware(update.download_url, FIRMWARE_PATH) == 0) {
            printf("Downloaded to %s\n", FIRMWARE_PATH);
            
            // Verify signature
            if (zelta_verify_firmware(ctx, FIRMWARE_PATH, 
                                      update.signature) == ZELTA_OK) {
                printf("Signature verified!\n");
                
                // Report success
                zelta_report_status(ctx, update.version, 
                                   ZELTA_STATUS_DOWNLOADED, NULL);
            } else {
                fprintf(stderr, "Signature verification failed!\n");
                zelta_report_status(ctx, update.version,
                                   ZELTA_STATUS_FAILED, 
                                   "Signature verification failed");
            }
        }
    } else if (result == ZELTA_NO_UPDATE) {
        printf("Already on latest version\n");
    } else {
        fprintf(stderr, "Update check failed\n");
    }
    
    zelta_cleanup(ctx);
    return 0;
}

Raspberry Pi Example

Complete example for Raspberry Pi with GPIO status LED:

#include <stdio.h>
#include <signal.h>
#include <wiringPi.h>
#include "zelta.h"

#define LED_PIN 17
#define BUTTON_PIN 27

static volatile int running = 1;

void signal_handler(int sig) {
    running = 0;
}

void blink_led(int times, int delay_ms) {
    for (int i = 0; i < times; i++) {
        digitalWrite(LED_PIN, HIGH);
        delay(delay_ms);
        digitalWrite(LED_PIN, LOW);
        delay(delay_ms);
    }
}

void update_progress_callback(int percent, void *userdata) {
    printf("Download progress: %d%%\n", percent);
    
    // Blink LED to show activity
    if (percent % 10 == 0) {
        digitalWrite(LED_PIN, HIGH);
        delay(50);
        digitalWrite(LED_PIN, LOW);
    }
}

int main() {
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    // Initialize GPIO
    wiringPiSetupGpio();
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT);
    pullUpDnControl(BUTTON_PIN, PUD_UP);
    
    // Initialize Zelta
    zelta_ctx_t *ctx = zelta_init_from_file("/etc/zelta/zelta.conf");
    if (!ctx) {
        fprintf(stderr, "Failed to initialize\n");
        return 1;
    }
    
    zelta_set_progress_callback(ctx, update_progress_callback, NULL);
    
    // Startup indication
    blink_led(3, 100);
    
    printf("Zelta OTA client running\n");
    printf("Press button to check for updates\n");
    
    time_t last_check = 0;
    const int check_interval = 3600; // 1 hour
    
    while (running) {
        time_t now = time(NULL);
        
        // Check on button press or interval
        int button_pressed = (digitalRead(BUTTON_PIN) == LOW);
        int interval_elapsed = (now - last_check) >= check_interval;
        
        if (button_pressed || interval_elapsed) {
            printf("Checking for updates...\n");
            digitalWrite(LED_PIN, HIGH);
            
            zelta_update_info_t update;
            int result = zelta_check_update(ctx, &update);
            
            if (result == ZELTA_UPDATE_AVAILABLE) {
                printf("Update available: %s\n", update.version);
                blink_led(5, 200); // Indicate update available
                
                // Auto-download
                if (zelta_download_update(ctx, &update, 
                                          "/tmp/firmware.bin") == ZELTA_OK) {
                    printf("Update downloaded, reboot to apply\n");
                    blink_led(10, 100); // Indicate ready
                }
            } else {
                blink_led(1, 500); // Single blink = no update
            }
            
            digitalWrite(LED_PIN, LOW);
            last_check = now;
            
            // Debounce button
            if (button_pressed) {
                delay(500);
            }
        }
        
        delay(100);
    }
    
    printf("Shutting down\n");
    digitalWrite(LED_PIN, LOW);
    zelta_cleanup(ctx);
    
    return 0;
}

ESP-IDF Integration (ESP32)

For ESP32 with ESP-IDF:

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "nvs_flash.h"
#include "zelta.h"

static const char *TAG = "zelta_ota";

// HTTP client for ESP-IDF
static int esp_http_request(const char *url, const char *method,
                           const char *body, const char *api_key,
                           char **response, size_t *response_len) {
    esp_http_client_config_t config = {
        .url = url,
        .method = (strcmp(method, "POST") == 0) ? 
                  HTTP_METHOD_POST : HTTP_METHOD_GET,
    };
    
    esp_http_client_handle_t client = esp_http_client_init(&config);
    
    // Set headers
    esp_http_client_set_header(client, "X-API-Key", api_key);
    esp_http_client_set_header(client, "Content-Type", "application/json");
    
    if (body) {
        esp_http_client_set_post_field(client, body, strlen(body));
    }
    
    esp_err_t err = esp_http_client_perform(client);
    
    if (err == ESP_OK) {
        int content_length = esp_http_client_get_content_length(client);
        *response = malloc(content_length + 1);
        esp_http_client_read(client, *response, content_length);
        (*response)[content_length] = '\0';
        *response_len = content_length;
    }
    
    esp_http_client_cleanup(client);
    return (err == ESP_OK) ? 0 : -1;
}

void ota_task(void *pvParameter) {
    ESP_LOGI(TAG, "Starting OTA task");
    
    // Get current version from app description
    const esp_app_desc_t *app_desc = esp_app_get_description();
    
    zelta_config_t config = {
        .server_url = CONFIG_ZELTA_SERVER_URL,
        .api_key = CONFIG_ZELTA_API_KEY,
        .product_id = CONFIG_ZELTA_PRODUCT_ID,
        .current_version = app_desc->version,
        .http_request = esp_http_request,
    };
    
    zelta_ctx_t *ctx = zelta_init(&config);
    
    while (1) {
        ESP_LOGI(TAG, "Checking for updates...");
        
        zelta_update_info_t update;
        int result = zelta_check_update(ctx, &update);
        
        if (result == ZELTA_UPDATE_AVAILABLE) {
            ESP_LOGI(TAG, "Update available: %s", update.version);
            
            // Use ESP-IDF OTA APIs
            esp_ota_handle_t ota_handle;
            const esp_partition_t *update_partition = 
                esp_ota_get_next_update_partition(NULL);
            
            esp_err_t err = esp_ota_begin(update_partition, 
                                          OTA_SIZE_UNKNOWN, &ota_handle);
            if (err == ESP_OK) {
                // Download and write firmware
                // ... (implement chunked download)
                
                err = esp_ota_end(ota_handle);
                if (err == ESP_OK) {
                    esp_ota_set_boot_partition(update_partition);
                    ESP_LOGI(TAG, "Update complete, rebooting...");
                    esp_restart();
                }
            }
        }
        
        // Check every hour
        vTaskDelay(pdMS_TO_TICKS(3600000));
    }
}

void app_main(void) {
    nvs_flash_init();
    esp_netif_init();
    esp_event_loop_create_default();
    
    // Connect to WiFi first...
    
    xTaskCreate(&ota_task, "ota_task", 8192, NULL, 5, NULL);
}

Daemon with D-Bus Interface

Full-featured daemon for desktop Linux:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include "zelta.h"

static zelta_ctx_t *g_ctx = NULL;
static sd_bus *g_bus = NULL;
static sd_event *g_event = NULL;

// D-Bus method: CheckUpdate
static int method_check_update(sd_bus_message *m, void *userdata, 
                               sd_bus_error *ret_error) {
    zelta_update_info_t update;
    int result = zelta_check_update(g_ctx, &update);
    
    if (result == ZELTA_UPDATE_AVAILABLE) {
        return sd_bus_reply_method_return(m, "ssb", 
            update.version, update.release_notes, 1);
    } else {
        return sd_bus_reply_method_return(m, "ssb", "", "", 0);
    }
}

// D-Bus method: ApplyUpdate
static int method_apply_update(sd_bus_message *m, void *userdata,
                               sd_bus_error *ret_error) {
    const char *version;
    sd_bus_message_read(m, "s", &version);
    
    // Trigger update application
    // ...
    
    return sd_bus_reply_method_return(m, "b", 1);
}

// D-Bus interface
static const sd_bus_vtable zelta_vtable[] = {
    SD_BUS_VTABLE_START(0),
    SD_BUS_METHOD("CheckUpdate", "", "ssb", method_check_update, 0),
    SD_BUS_METHOD("ApplyUpdate", "s", "b", method_apply_update, 0),
    SD_BUS_SIGNAL("UpdateAvailable", "ss", 0),
    SD_BUS_SIGNAL("UpdateProgress", "i", 0),
    SD_BUS_VTABLE_END
};

int main() {
    int r;
    
    // Initialize Zelta
    g_ctx = zelta_init_from_file("/etc/zelta/zelta.conf");
    
    // Connect to system bus
    r = sd_bus_open_system(&g_bus);
    if (r < 0) {
        fprintf(stderr, "Failed to connect to bus: %s\n", strerror(-r));
        return 1;
    }
    
    // Register D-Bus object
    r = sd_bus_add_object_vtable(g_bus, NULL,
                                 "/dev/zelta/Updater",
                                 "dev.zelta.Updater",
                                 zelta_vtable, NULL);
    
    // Request service name
    r = sd_bus_request_name(g_bus, "dev.zelta.Updater", 0);
    
    // Create event loop
    r = sd_event_default(&g_event);
    r = sd_bus_attach_event(g_bus, g_event, 0);
    
    printf("Zelta D-Bus service running\n");
    
    // Run event loop
    r = sd_event_loop(g_event);
    
    sd_bus_unref(g_bus);
    sd_event_unref(g_event);
    zelta_cleanup(g_ctx);
    
    return 0;
}

CMakeLists.txt

Build configuration for the examples:

cmake_minimum_required(VERSION 3.10)
project(zelta_examples C)

find_package(CURL REQUIRED)
find_package(OpenSSL REQUIRED)

# Basic example
add_executable(basic_example basic_example.c)
target_link_libraries(basic_example zelta ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES})

# Raspberry Pi example (if wiringPi available)
find_library(WIRINGPI_LIB wiringPi)
if(WIRINGPI_LIB)
    add_executable(rpi_example rpi_example.c)
    target_link_libraries(rpi_example zelta ${CURL_LIBRARIES} ${WIRINGPI_LIB})
endif()

# D-Bus daemon (if systemd available)
find_package(PkgConfig)
pkg_check_modules(SYSTEMD libsystemd)
if(SYSTEMD_FOUND)
    add_executable(zelta_daemon daemon_example.c)
    target_link_libraries(zelta_daemon zelta ${SYSTEMD_LIBRARIES})
endif()

Next Steps