Skip to content

CODE

Warning

The following code should be based on the code in the release code, which may have been updated.

sensing_command.h

/**
 * @file sensing_command.h
 * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
 * @brief MQTT remote control command handler for sensing modules
 * @version 1.0
 * @date 2025-12-17
 * @copyright Copyright (c) 2025
 *
 * @details
 * This module handles MQTT commands for controlling online and offline sensing.
 * Command format: SENSE,ONLINE or SENSE,OFFLINE with parameters
 *
 * Supported commands:
 * - SENSE,ONLINE,F=frequency,D=duration
 * - SENSE,ONLINE,STOP
 * - SENSE,ONLINE,STATUS
 * - SENSE,OFFLINE,F=frequency,D=duration
 * - SENSE,OFFLINE,F=frequency,D=duration,DL=delay_seconds
 * - SENSE,OFFLINE,F=frequency,D=duration,TIME=YYMMDDHHMMSS
 * - SENSE,OFFLINE,STOP
 * - SENSE,OFFLINE,STATUS
 */

#pragma once

#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "node_acc_adxl355.h"

#ifdef __cplusplus
extern "C"
{
#endif

/* ============================================================================
 * FUNCTION DECLARATIONS
 * ============================================================================ */

/**
 * @brief Process MQTT sensing command
 * @param command Command string (e.g., "SENSE,ONLINE,F=20,D=60")
 * @param command_len Length of command string
 * @return ESP_OK on success, error code on failure
 * @note This function parses and executes the command, then publishes response via MQTT
 */
esp_err_t sensing_command_process(const char *command, int command_len);

/**
 * @brief Initialize sensing command handler
 * @return ESP_OK on success, error code on failure
 * @note Must be called after MQTT is initialized
 */
esp_err_t sensing_command_init(void);

/**
 * @brief Set sensor handle for command handler
 * @param handle ADXL355 sensor handle
 * @return ESP_OK on success, error code on failure
 * @note Must be called before processing commands
 */
esp_err_t sensing_command_set_sensor_handle(adxl355_handle_t *handle);

#ifdef __cplusplus
}
#endif

sensing_command.c

/**
 * @file sensing_command.c
 * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
 * @brief MQTT remote control command handler implementation
 * @version 1.0
 * @date 2025-12-17
 * @copyright Copyright (c) 2025
 *
 */

#include "sensing_command.h"
#include "online_sensing.h"
#include "offline_sensing.h"
#include "node_mqtt.h"
#include "node_acc_adxl355.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* ============================================================================
 * PRIVATE DEFINITIONS
 * ============================================================================ */

static const char *TAG = "SensingCmd";

/* ============================================================================
 * PRIVATE VARIABLES
 * ============================================================================ */

static adxl355_handle_t *s_sensor_handle = NULL;
static TaskHandle_t s_offline_sensing_task_handle = NULL;
static TaskHandle_t s_online_stop_task_handle = NULL;
static TaskHandle_t s_command_task_handle = NULL;
static QueueHandle_t s_command_queue = NULL;

/* ============================================================================
 * COMMAND QUEUE STRUCTURE
 * ============================================================================ */

#define COMMAND_QUEUE_SIZE 5
#define MAX_COMMAND_LENGTH 256

typedef struct
{
    char command[MAX_COMMAND_LENGTH];
    int command_len;
} command_queue_item_t;

/* ============================================================================
 * TASK PARAMETER STRUCTURES
 * ============================================================================ */

/**
 * @brief Parameters for offline sensing task with delay
 */
typedef struct
{
    offline_sensing_config_t config;
    int delay_ms;
} offline_sensing_task_params_t;

/* ============================================================================
 * PRIVATE FUNCTION DECLARATIONS
 * ============================================================================ */

static esp_err_t parse_float_param(const char *str, const char *prefix, float *value);
static esp_err_t parse_int_param(const char *str, const char *prefix, int *value);
static esp_err_t parse_time_param(const char *str, const char *prefix, time_t *timestamp);
static esp_err_t send_mqtt_response(const char *response);
static void offline_sensing_task_wrapper(void *pvParameters);
static void online_sensing_stop_task(void *pvParameters);
static void command_processing_task(void *pvParameters);
static esp_err_t handle_online_command(const char *cmd);
static esp_err_t handle_offline_command(const char *cmd);

/* ============================================================================
 * PRIVATE FUNCTION IMPLEMENTATIONS
 * ============================================================================ */

/**
 * @brief Parse float parameter from command string
 */
static esp_err_t parse_float_param(const char *str, const char *prefix, float *value)
{
    char search_str[32];
    snprintf(search_str, sizeof(search_str), "%s=", prefix);
    const char *pos = strstr(str, search_str);
    if (pos == NULL)
    {
        return ESP_ERR_NOT_FOUND;
    }

    pos += strlen(search_str);

    // Find the end of the value (comma or end of string)
    const char *end = strchr(pos, ',');
    if (end == NULL)
    {
        end = pos + strlen(pos);
    }

    // Extract value string
    char value_str[64];
    size_t len = end - pos;
    if (len >= sizeof(value_str))
    {
        len = sizeof(value_str) - 1;
    }
    strncpy(value_str, pos, len);
    value_str[len] = '\0';

    // Parse float
    char *parse_end;
    *value = strtof(value_str, &parse_end);

    // Check if parsing was successful
    if (parse_end == value_str || *parse_end != '\0')
    {
        return ESP_ERR_INVALID_ARG;
    }

    return ESP_OK;
}

/**
 * @brief Parse int parameter from command string
 */
static esp_err_t parse_int_param(const char *str, const char *prefix, int *value)
{
    char search_str[32];
    snprintf(search_str, sizeof(search_str), "%s=", prefix);
    const char *pos = strstr(str, search_str);
    if (pos == NULL)
    {
        return ESP_ERR_NOT_FOUND;
    }

    pos += strlen(search_str);

    // Find the end of the value (comma or end of string)
    const char *end = strchr(pos, ',');
    if (end == NULL)
    {
        end = pos + strlen(pos);
    }

    // Extract value string
    char value_str[64];
    size_t len = end - pos;
    if (len >= sizeof(value_str))
    {
        len = sizeof(value_str) - 1;
    }
    strncpy(value_str, pos, len);
    value_str[len] = '\0';

    // Parse int
    char *parse_end;
    *value = (int)strtol(value_str, &parse_end, 10);

    // Check if parsing was successful
    if (parse_end == value_str || *parse_end != '\0')
    {
        return ESP_ERR_INVALID_ARG;
    }

    return ESP_OK;
}

/**
 * @brief Parse TIME parameter (YYMMDDHHMMSS format) and convert to Unix timestamp
 */
static esp_err_t parse_time_param(const char *str, const char *prefix, time_t *timestamp)
{
    char search_str[32];
    snprintf(search_str, sizeof(search_str), "%s=", prefix);
    const char *pos = strstr(str, search_str);
    if (pos == NULL)
    {
        return ESP_ERR_NOT_FOUND;
    }

    pos += strlen(search_str);

    // Find the end of the value (comma or end of string)
    const char *end = strchr(pos, ',');
    if (end == NULL)
    {
        end = pos + strlen(pos);
    }

    // Extract value string (should be exactly 12 digits)
    size_t len = end - pos;
    if (len != 12)
    {
        return ESP_ERR_INVALID_ARG;
    }

    char time_str[13];
    strncpy(time_str, pos, 12);
    time_str[12] = '\0';

    // Parse components
    int yy, mm, dd, hh, min, ss;
    if (sscanf(time_str, "%2d%2d%2d%2d%2d%2d", &yy, &mm, &dd, &hh, &min, &ss) != 6)
    {
        return ESP_ERR_INVALID_ARG;
    }

    // Validate ranges
    if (mm < 1 || mm > 12 || dd < 1 || dd > 31 || hh > 23 || min > 59 || ss > 59)
    {
        return ESP_ERR_INVALID_ARG;
    }

    // Convert to full year (YY: 00-99 -> 2000-2099)
    int year = 2000 + yy;

    // Create tm structure
    struct tm timeinfo = {0};
    timeinfo.tm_year = year - 1900;
    timeinfo.tm_mon = mm - 1;
    timeinfo.tm_mday = dd;
    timeinfo.tm_hour = hh;
    timeinfo.tm_min = min;
    timeinfo.tm_sec = ss;
    timeinfo.tm_isdst = -1; // Let system determine DST

    // Convert to Unix timestamp
    *timestamp = mktime(&timeinfo);
    if (*timestamp == -1)
    {
        return ESP_ERR_INVALID_ARG;
    }

    return ESP_OK;
}

/**
 * @brief Send MQTT response message
 * @note Optimized to minimize stack usage - uses minimal logging
 */
static esp_err_t send_mqtt_response(const char *response)
{
    if (!s_is_mqtt_connected || s_mqtt_client == NULL)
    {
        return ESP_ERR_INVALID_STATE;
    }

    // Publish to the same topic (or a response topic)
    // esp_mqtt_client_publish is non-blocking and should not need much stack
    int ret = esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLISH_TOPIC, 
                                       response, strlen(response), 1, 0);
    if (ret < 0)
    {
        return ESP_FAIL;
    }

    return ESP_OK;
}

/**
 * @brief Task wrapper for offline sensing (to avoid blocking)
 * @note Handles delay before starting sensing
 */
static void offline_sensing_task_wrapper(void *pvParameters)
{
    offline_sensing_task_params_t *params = (offline_sensing_task_params_t *)pvParameters;

    // Wait for delay if specified (moved here to avoid blocking command handler)
    if (params->delay_ms > 0)
    {
        int delay_sec = params->delay_ms / 1000;
        int remaining_ms = params->delay_ms % 1000;

        ESP_LOGI(TAG, "Countdown: %d sec (start in %d sec)", delay_sec, delay_sec);

        // Countdown display with dynamic interval
        int remaining_sec = delay_sec;
        while (remaining_sec > 0)
        {
            // Determine interval based on remaining time (not initial delay)
            int interval_sec = 1;  // Default: 1 second
            if (remaining_sec > 300)      // > 5 minutes: show every 30 seconds
            {
                interval_sec = 30;
            }
            else if (remaining_sec > 60)   // > 1 minute: show every 10 seconds
            {
                interval_sec = 10;
            }
            else if (remaining_sec > 10)  // > 10 seconds: show every 5 seconds
            {
                interval_sec = 5;
            }
            else                          // <= 10 seconds: show every 1 second
            {
                interval_sec = 1;
            }

            int wait_sec = (remaining_sec >= interval_sec) ? interval_sec : remaining_sec;
            vTaskDelay(pdMS_TO_TICKS(wait_sec * 1000));
            remaining_sec -= wait_sec;

            if (remaining_sec > 0)
            {
                ESP_LOGI(TAG, "Countdown: %d sec", remaining_sec);
            }
        }

        // Wait for remaining milliseconds
        if (remaining_ms > 0)
        {
            vTaskDelay(pdMS_TO_TICKS(remaining_ms));
        }

        ESP_LOGI(TAG, "Countdown: 0 sec - Starting now");
    }

    ESP_LOGI(TAG, "OFFLINE: F=%.2f Hz, D=%.2f sec", 
             params->config.sampling_frequency_hz, params->config.sampling_duration_sec);

    // Initialize offline sensing
    esp_err_t ret = offline_sensing_init(&params->config);
    if (ret != ESP_OK)
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,ERROR,OFFLINE,INIT_FAILED");
        send_mqtt_response(resp);
        free(params);
        s_offline_sensing_task_handle = NULL;
        vTaskDelete(NULL);
        return;
    }

    // Set sensor handle
    ret = offline_sensing_set_sensor_handle(s_sensor_handle);
    if (ret != ESP_OK)
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,ERROR,OFFLINE,SET_HANDLE_FAILED");
        send_mqtt_response(resp);
        offline_sensing_deinit();
        free(params);
        s_offline_sensing_task_handle = NULL;
        vTaskDelete(NULL);
        return;
    }

    // Start offline sensing (this will block until complete)
    ret = offline_sensing_start();

    // Get report
    offline_sensing_report_t report;
    if (ret == ESP_OK)
    {
        offline_sensing_get_report(&report);
        char resp[256];
        // Use configured values (not actual measured values) for FREQ and DUR
        snprintf(resp, sizeof(resp), 
                "SENSE,OK,OFFLINE,SAMPLES=%lu,FREQ=%.2f,DUR=%.2f,SD=%s",
                (unsigned long)report.total_samples, 
                params->config.sampling_frequency_hz,  // Use configured frequency
                params->config.sampling_duration_sec,   // Use configured duration
                report.sd_storage_success ? "OK" : "FAIL");
        send_mqtt_response(resp);
    }
    else
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,ERROR,OFFLINE,START_FAILED");
        send_mqtt_response(resp);
    }

    // Cleanup
    offline_sensing_deinit();
    free(params);
    s_offline_sensing_task_handle = NULL;
    vTaskDelete(NULL);
}

/**
 * @brief Task to stop online sensing after specified duration
 * @note Optimized to minimize stack usage
 */
static void online_sensing_stop_task(void *pvParameters)
{
    float duration_sec = *(float *)pvParameters;
    free(pvParameters);

    // Wait for the specified duration
    vTaskDelay(pdMS_TO_TICKS((uint32_t)(duration_sec * 1000)));

    // Stop online sensing (simple operation, should not need much stack)
    esp_err_t ret = online_sensing_stop();

    // Send response using static buffer to minimize stack usage
    static char resp[128];
    if (ret == ESP_OK)
    {
        snprintf(resp, sizeof(resp), "SENSE,OK,ONLINE,AUTO_STOPPED");
    }
    else
    {
        snprintf(resp, sizeof(resp), "SENSE,ERROR,ONLINE,AUTO_STOP_FAILED");
    }
    send_mqtt_response(resp);

    s_online_stop_task_handle = NULL;
    vTaskDelete(NULL);
}

/**
 * @brief Handle ONLINE sensing commands
 */
static esp_err_t handle_online_command(const char *cmd)
{
    // Check for STOP command
    if (strstr(cmd, "STOP") != NULL)
    {
        // Cancel auto-stop task if running
        if (s_online_stop_task_handle != NULL)
        {
            vTaskDelete(s_online_stop_task_handle);
            s_online_stop_task_handle = NULL;
        }

        esp_err_t ret = online_sensing_stop();
        if (ret == ESP_OK)
        {
            send_mqtt_response("SENSE,OK,ONLINE,STOPPED");
        }
        else
        {
            send_mqtt_response("SENSE,ERROR,ONLINE,STOP_FAILED");
        }
        return ret;
    }

    // Check for STATUS command
    if (strstr(cmd, "STATUS") != NULL)
    {
        bool is_running = false;
        online_sensing_is_running(&is_running);
        online_sensing_config_t config;
        online_sensing_get_config(&config);

        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,STATUS,ONLINE,RUNNING=%s,F=%.2f",
                 is_running ? "YES" : "NO", config.sampling_frequency_hz);
        send_mqtt_response(resp);
        return ESP_OK;
    }

    // Parse F and D parameters
    float freq = 0.0f;
    float duration = 0.0f;

    if (parse_float_param(cmd, "F", &freq) != ESP_OK)
    {
        send_mqtt_response("SENSE,ERROR,ONLINE,INVALID_FREQ");
        return ESP_ERR_INVALID_ARG;
    }

    if (parse_float_param(cmd, "D", &duration) != ESP_OK)
    {
        duration = 0.0f; // Default: continuous
    }

    // Validate frequency
    if (freq <= 0.0f || freq > 300.0f)
    {
        send_mqtt_response("SENSE,ERROR,ONLINE,INVALID_FREQ");
        return ESP_ERR_INVALID_ARG;
    }

    // Stop if already running
    bool is_running = false;
    online_sensing_is_running(&is_running);
    if (is_running)
    {
        online_sensing_stop();
        vTaskDelay(pdMS_TO_TICKS(100)); // Give it time to stop
    }

    // Update frequency
    online_sensing_set_frequency(freq);

    // Start online sensing
    esp_err_t ret = online_sensing_start();
    if (ret == ESP_OK)
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,OK,ONLINE,F=%.2f,D=%.2f", freq, duration);
        send_mqtt_response(resp);

        // If duration is specified and > 0, schedule auto-stop
        if (duration > 0.0f)
        {
            // Cancel existing stop task if any
            if (s_online_stop_task_handle != NULL)
            {
                vTaskDelete(s_online_stop_task_handle);
                s_online_stop_task_handle = NULL;
            }

            // Allocate memory for duration parameter
            float *duration_ptr = malloc(sizeof(float));
            if (duration_ptr != NULL)
            {
                *duration_ptr = duration;

                // Create task to stop after duration
                // Note: online_sensing_stop() is simple (just stops timer), but MQTT publish may need some stack
                // Using 3072 bytes should be sufficient (reduced from 6144 after optimization)
                BaseType_t task_ret = xTaskCreate(
                    online_sensing_stop_task,
                    "online_stop",
                    3072, // Stack size (3KB should be enough after optimization)
                    duration_ptr,
                    5, // Priority
                    &s_online_stop_task_handle);

                if (task_ret != pdPASS)
                {
                    free(duration_ptr);
                    ESP_LOGW(TAG, "Failed to create auto-stop task, online sensing will run continuously");
                }
                else
                {
                    ESP_LOGI(TAG, "Online sensing will auto-stop after %.2f seconds", duration);
                }
            }
            else
            {
                ESP_LOGW(TAG, "Failed to allocate memory for auto-stop task");
            }
        }
    }
    else
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,ERROR,ONLINE,START_FAILED");
        send_mqtt_response(resp);
    }

    return ret;
}

/**
 * @brief Handle OFFLINE sensing commands
 */
static esp_err_t handle_offline_command(const char *cmd)
{
    // Check for STOP command
    if (strstr(cmd, "STOP") != NULL)
    {
        bool is_running = false;
        offline_sensing_is_running(&is_running);
        if (is_running)
        {
            esp_err_t ret = offline_sensing_stop();
            if (ret == ESP_OK)
            {
                send_mqtt_response("SENSE,OK,OFFLINE,STOPPED");
            }
            else
            {
                send_mqtt_response("SENSE,ERROR,OFFLINE,STOP_FAILED");
            }
            return ret;
        }
        else
        {
            send_mqtt_response("SENSE,ERROR,OFFLINE,NOT_RUNNING");
            return ESP_ERR_INVALID_STATE;
        }
    }

    // Check for STATUS command
    if (strstr(cmd, "STATUS") != NULL)
    {
        bool is_running = false;
        offline_sensing_is_running(&is_running);

        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,STATUS,OFFLINE,RUNNING=%s",
                 is_running ? "YES" : "NO");
        send_mqtt_response(resp);
        return ESP_OK;
    }

    // Check if already running
    bool is_running = false;
    offline_sensing_is_running(&is_running);
    if (is_running)
    {
        send_mqtt_response("SENSE,ERROR,OFFLINE,ALREADY_RUNNING");
        return ESP_ERR_INVALID_STATE;
    }

    // Parse parameters
    float freq = 0.0f;
    float duration = 0.0f;
    int delay_sec = 0;
    time_t target_time = 0;
    bool has_delay = false;
    bool has_time = false;

    if (parse_float_param(cmd, "F", &freq) != ESP_OK)
    {
        send_mqtt_response("SENSE,ERROR,OFFLINE,INVALID_FREQ");
        return ESP_ERR_INVALID_ARG;
    }

    if (parse_float_param(cmd, "D", &duration) != ESP_OK)
    {
        send_mqtt_response("SENSE,ERROR,OFFLINE,INVALID_DURATION");
        return ESP_ERR_INVALID_ARG;
    }

    // Validate parameters (must match offline_sensing.c limits: 1-4000 Hz)
    // Note: Maximum frequency of 4000 Hz is limited by ADXL355 sensor's maximum output data rate (ODR)
    if (freq <= 0.0f || freq > 4000.0f)
    {
        send_mqtt_response("SENSE,ERROR,OFFLINE,INVALID_FREQ");
        return ESP_ERR_INVALID_ARG;
    }

    if (duration <= 0.0f)
    {
        send_mqtt_response("SENSE,ERROR,OFFLINE,INVALID_DURATION");
        return ESP_ERR_INVALID_ARG;
    }

    // Check for delay or time parameter
    if (parse_int_param(cmd, "DL", &delay_sec) == ESP_OK)
    {
        has_delay = true;
        if (delay_sec < 0)
        {
            send_mqtt_response("SENSE,ERROR,OFFLINE,INVALID_DELAY");
            return ESP_ERR_INVALID_ARG;
        }
    }
    else if (parse_time_param(cmd, "TIME", &target_time) == ESP_OK)
    {
        has_time = true;
        time_t now = time(NULL);
        if (target_time <= now)
        {
            send_mqtt_response("SENSE,ERROR,OFFLINE,TIME_PAST");
            return ESP_ERR_INVALID_ARG;
        }
    }

    // Calculate actual delay
    int actual_delay_ms = 0;
    if (has_time)
    {
        time_t now = time(NULL);
        actual_delay_ms = (int)((target_time - now) * 1000);
        if (actual_delay_ms < 0)
        {
            send_mqtt_response("SENSE,ERROR,OFFLINE,TIME_PAST");
            return ESP_ERR_INVALID_ARG;
        }
    }
    else if (has_delay)
    {
        actual_delay_ms = delay_sec * 1000;
    }

    // Create configuration
    offline_sensing_config_t config = {
        .sampling_frequency_hz = freq,
        .sampling_duration_sec = duration,
        .enable_memory = true,
        .enable_sd = true,
        .enable_mqtt_report = true,
        .sd_file_path = NULL,
        .mqtt_report_topic = NULL
    };

    // Pre-check memory availability before creating task
    // This allows immediate feedback if memory is insufficient
    if (config.enable_memory)
    {
        // Calculate required buffer size (same as in offline_sensing_init)
        uint32_t buffer_size = (uint32_t)(freq * duration) + 100; // Add margin
        size_t required_bytes = buffer_size * sizeof(offline_sensing_sample_t); // Each sample is ~24 bytes

        // Check PSRAM availability (we use PSRAM for large buffers)
        size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
        if (psram_free < required_bytes)
        {
            char resp[256];
            snprintf(resp, sizeof(resp), 
                    "SENSE,ERROR,OFFLINE,INSUFFICIENT_MEMORY,NEED=%zu,AVAILABLE=%zu",
                    required_bytes, psram_free);
            send_mqtt_response(resp);
            ESP_LOGE(TAG, "Insufficient PSRAM: need %zu bytes, available %zu bytes", 
                     required_bytes, psram_free);
            return ESP_ERR_NO_MEM;
        }

        ESP_LOGI(TAG, "Memory check passed: need %zu bytes, available %zu bytes (PSRAM)",
                 required_bytes, psram_free);
    }

    // Allocate parameters for task (includes config and delay)
    offline_sensing_task_params_t *task_params = malloc(sizeof(offline_sensing_task_params_t));
    if (task_params == NULL)
    {
        send_mqtt_response("SENSE,ERROR,OFFLINE,MEMORY_FAILED");
        return ESP_ERR_NO_MEM;
    }
    memcpy(&task_params->config, &config, sizeof(offline_sensing_config_t));
    task_params->delay_ms = actual_delay_ms;

    // Create task for offline sensing (non-blocking, delay handled in task)
    BaseType_t ret = xTaskCreate(
        offline_sensing_task_wrapper,
        "offline_sensing_cmd",
        8192, // Stack size
        task_params,
        5, // Priority
        &s_offline_sensing_task_handle);

    if (ret != pdPASS)
    {
        free(task_params);
        send_mqtt_response("SENSE,ERROR,OFFLINE,TASK_CREATE_FAILED");
        return ESP_FAIL;
    }

    // Send acknowledgment (delay is handled in task, so we don't wait here)
    char resp[128];
    if (has_time)
    {
        snprintf(resp, sizeof(resp), "SENSE,OK,OFFLINE,F=%.2f,D=%.2f,TIME=%lld,DELAY=%d",
                 freq, duration, (long long)target_time, actual_delay_ms / 1000);
    }
    else if (has_delay)
    {
        snprintf(resp, sizeof(resp), "SENSE,OK,OFFLINE,F=%.2f,D=%.2f,DL=%d",
                 freq, duration, delay_sec);
    }
    else
    {
        snprintf(resp, sizeof(resp), "SENSE,OK,OFFLINE,F=%.2f,D=%.2f",
                 freq, duration);
    }
    send_mqtt_response(resp);

    return ESP_OK;
}

/* ============================================================================
 * PUBLIC FUNCTION IMPLEMENTATIONS
 * ============================================================================ */

esp_err_t sensing_command_init(void)
{
    // Create command queue
    s_command_queue = xQueueCreate(COMMAND_QUEUE_SIZE, sizeof(command_queue_item_t));
    if (s_command_queue == NULL)
    {
        ESP_LOGE(TAG, "Failed to create command queue");
        return ESP_ERR_NO_MEM;
    }

    // Create command processing task
    BaseType_t ret = xTaskCreate(
        command_processing_task,
        "cmd_processor",
        4096, // Stack size (increased for command processing)
        NULL,
        5, // Priority
        &s_command_task_handle);

    if (ret != pdPASS)
    {
        ESP_LOGE(TAG, "Failed to create command processing task");
        vQueueDelete(s_command_queue);
        s_command_queue = NULL;
        return ESP_FAIL;
    }

    ESP_LOGI(TAG, "Sensing command handler initialized");
    return ESP_OK;
}

/**
 * @brief Command processing task (runs in separate task with larger stack)
 */
static void command_processing_task(void *pvParameters)
{
    command_queue_item_t item;

    ESP_LOGI(TAG, "Command processing task started");

    while (1)
    {
        // Wait for command from queue
        if (xQueueReceive(s_command_queue, &item, portMAX_DELAY) == pdTRUE)
        {
            // Check sensor handle first
            if (s_sensor_handle == NULL)
            {
                send_mqtt_response("SENSE,ERROR,SENSOR_NOT_INITIALIZED");
                ESP_LOGE(TAG, "Sensor handle not set, cannot process command");
                continue;
            }

            // Trim whitespace
            int len = item.command_len;
            while (len > 0 && (item.command[len-1] == ' ' || item.command[len-1] == '\t' || 
                              item.command[len-1] == '\r' || item.command[len-1] == '\n'))
            {
                item.command[--len] = '\0';
            }

            // Check for empty command
            if (len == 0)
            {
                send_mqtt_response("SENSE,ERROR,EMPTY_COMMAND");
                continue;
            }

            ESP_LOGD(TAG, "Processing command: %s", item.command);

            // Check if it's a SENSE command
            if (strncmp(item.command, "SENSE,", 6) != 0)
            {
                // Not a sensing command, ignore
                continue;
            }

            const char *cmd_part = item.command + 6; // Skip "SENSE,"

            // Check for empty command after SENSE,
            if (strlen(cmd_part) == 0)
            {
                send_mqtt_response("SENSE,ERROR,INVALID_COMMAND");
                continue;
            }

            // Check for ONLINE or OFFLINE
            if (strncmp(cmd_part, "ONLINE,", 7) == 0)
            {
                handle_online_command(cmd_part + 7);
            }
            else if (strncmp(cmd_part, "OFFLINE,", 8) == 0)
            {
                handle_offline_command(cmd_part + 8);
            }
            else
            {
                send_mqtt_response("SENSE,ERROR,UNKNOWN_COMMAND");
            }
        }
    }
}

/**
 * @brief Process MQTT sensing command (queues command for processing)
 */
esp_err_t sensing_command_process(const char *command, int command_len)
{
    if (command == NULL || command_len <= 0)
    {
        return ESP_ERR_INVALID_ARG;
    }

    if (s_command_queue == NULL)
    {
        ESP_LOGE(TAG, "Command queue not initialized");
        return ESP_ERR_INVALID_STATE;
    }

    // Prepare queue item
    command_queue_item_t item;
    int len = command_len < MAX_COMMAND_LENGTH - 1 ? command_len : MAX_COMMAND_LENGTH - 1;
    strncpy(item.command, command, len);
    item.command[len] = '\0';
    item.command_len = len;

    // Send to queue (non-blocking, drop if queue is full)
    if (xQueueSend(s_command_queue, &item, 0) != pdTRUE)
    {
        ESP_LOGW(TAG, "Command queue full, dropping command");
        return ESP_ERR_NO_MEM;
    }

    return ESP_OK;
}

/**
 * @brief Set sensor handle (must be called before processing commands)
 */
esp_err_t sensing_command_set_sensor_handle(adxl355_handle_t *handle)
{
    if (handle == NULL)
    {
        return ESP_ERR_INVALID_ARG;
    }

    s_sensor_handle = handle;
    ESP_LOGI(TAG, "Sensor handle set");
    return ESP_OK;
}

KEY IMPLEMENTATIONS

The implementation includes:

Key Functions

  1. Command Processing
  2. sensing_command_process(): Main entry point for command processing
  3. command_processing_task(): FreeRTOS task that processes commands from queue
  4. handle_online_command(): Handles ONLINE sensing commands
  5. handle_offline_command(): Handles OFFLINE sensing commands

  6. Parameter Parsing

  7. parse_float_param(): Extracts float parameters from command string
  8. parse_int_param(): Extracts integer parameters from command string
  9. parse_time_param(): Parses TIME parameter in YYMMDDHHMMSS format

  10. Task Management

  11. offline_sensing_task_wrapper(): Wrapper task for offline sensing with delay support
  12. online_sensing_stop_task(): Task to auto-stop online sensing after duration

  13. Response Handling

  14. send_mqtt_response(): Publishes command response via MQTT

Command Queue Structure

#define COMMAND_QUEUE_SIZE 5
#define MAX_COMMAND_LENGTH 256

typedef struct
{
    char command[MAX_COMMAND_LENGTH];
    int command_len;
} command_queue_item_t;

Initialization Flow

esp_err_t sensing_command_init(void)
{
    // Create command queue
    s_command_queue = xQueueCreate(COMMAND_QUEUE_SIZE, sizeof(command_queue_item_t));

    // Create command processing task
    BaseType_t ret = xTaskCreate(
        command_processing_task,
        "cmd_processor",
        4096, // Stack size
        NULL,
        5, // Priority
        &s_command_task_handle);

    return ret == pdPASS ? ESP_OK : ESP_FAIL;
}

Command Processing Flow

  1. Command received via MQTT callback
  2. Command queued for processing
  3. Command processing task dequeues and validates
  4. Command parsed and routed to appropriate handler
  5. Response published via MQTT

Parameter Parsing Example

static esp_err_t parse_float_param(const char *str, const char *prefix, float *value)
{
    char search_str[32];
    snprintf(search_str, sizeof(search_str), "%s=", prefix);
    const char *pos = strstr(str, search_str);
    if (pos == NULL)
    {
        return ESP_ERR_NOT_FOUND;
    }

    pos += strlen(search_str);
    const char *end = strchr(pos, ',');
    if (end == NULL)
    {
        end = pos + strlen(pos);
    }

    char value_str[64];
    size_t len = end - pos;
    if (len >= sizeof(value_str))
    {
        len = sizeof(value_str) - 1;
    }
    strncpy(value_str, pos, len);
    value_str[len] = '\0';

    char *parse_end;
    *value = strtof(value_str, &parse_end);

    if (parse_end == value_str || *parse_end != '\0')
    {
        return ESP_ERR_INVALID_ARG;
    }

    return ESP_OK;
}

Online Command Handler

static esp_err_t handle_online_command(const char *cmd)
{
    // Check for STOP command
    if (strstr(cmd, "STOP") != NULL)
    {
        // Cancel auto-stop task if running
        if (s_online_stop_task_handle != NULL)
        {
            vTaskDelete(s_online_stop_task_handle);
            s_online_stop_task_handle = NULL;
        }

        esp_err_t ret = online_sensing_stop();
        if (ret == ESP_OK)
        {
            send_mqtt_response("SENSE,OK,ONLINE,STOPPED");
        }
        else
        {
            send_mqtt_response("SENSE,ERROR,ONLINE,STOP_FAILED");
        }
        return ret;
    }

    // Check for STATUS command
    if (strstr(cmd, "STATUS") != NULL)
    {
        bool is_running = false;
        online_sensing_is_running(&is_running);
        online_sensing_config_t config;
        online_sensing_get_config(&config);

        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,STATUS,ONLINE,RUNNING=%s,F=%.2f",
                 is_running ? "YES" : "NO", config.sampling_frequency_hz);
        send_mqtt_response(resp);
        return ESP_OK;
    }

    // Parse F and D parameters
    float freq = 0.0f;
    float duration = 0.0f;

    if (parse_float_param(cmd, "F", &freq) != ESP_OK)
    {
        send_mqtt_response("SENSE,ERROR,ONLINE,INVALID_FREQ");
        return ESP_ERR_INVALID_ARG;
    }

    if (parse_float_param(cmd, "D", &duration) != ESP_OK)
    {
        duration = 0.0f; // Default: continuous
    }

    // Validate frequency
    if (freq <= 0.0f || freq > 300.0f)
    {
        send_mqtt_response("SENSE,ERROR,ONLINE,INVALID_FREQ");
        return ESP_ERR_INVALID_ARG;
    }

    // Stop if already running
    bool is_running = false;
    online_sensing_is_running(&is_running);
    if (is_running)
    {
        online_sensing_stop();
        vTaskDelay(pdMS_TO_TICKS(100)); // Give it time to stop
    }

    // Update frequency
    online_sensing_set_frequency(freq);

    // Start online sensing
    esp_err_t ret = online_sensing_start();
    if (ret == ESP_OK)
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,OK,ONLINE,F=%.2f,D=%.2f", freq, duration);
        send_mqtt_response(resp);

        // If duration is specified and > 0, schedule auto-stop
        if (duration > 0.0f)
        {
            // Cancel existing stop task if any
            if (s_online_stop_task_handle != NULL)
            {
                vTaskDelete(s_online_stop_task_handle);
                s_online_stop_task_handle = NULL;
            }

            // Allocate memory for duration parameter
            float *duration_ptr = malloc(sizeof(float));
            if (duration_ptr != NULL)
            {
                *duration_ptr = duration;

                // Create task to stop after duration
                // Note: online_sensing_stop() is simple (just stops timer), but MQTT publish may need some stack
                // Using 3072 bytes should be sufficient (reduced from 6144 after optimization)
                BaseType_t task_ret = xTaskCreate(
                    online_sensing_stop_task,
                    "online_stop",
                    3072, // Stack size (3KB should be enough after optimization)
                    duration_ptr,
                    5, // Priority
                    &s_online_stop_task_handle);

                if (task_ret != pdPASS)
                {
                    free(duration_ptr);
                    ESP_LOGW(TAG, "Failed to create auto-stop task, online sensing will run continuously");
                }
                else
                {
                    ESP_LOGI(TAG, "Online sensing will auto-stop after %.2f seconds", duration);
                }
            }
            else
            {
                ESP_LOGW(TAG, "Failed to allocate memory for auto-stop task");
            }
        }
    }
    else
    {
        char resp[128];
        snprintf(resp, sizeof(resp), "SENSE,ERROR,ONLINE,START_FAILED");
        send_mqtt_response(resp);
    }

    return ret;
}

Offline Command Handler

static esp_err_t handle_offline_command(const char *cmd)
{
    // Check for STOP command
    if (strstr(cmd, "STOP") != NULL)
    {
        bool is_running = false;
        offline_sensing_is_running(&is_running);
        if (is_running)
        {
            esp_err_t ret = offline_sensing_stop();
            send_mqtt_response(ret == ESP_OK ? 
                "SENSE,OK,OFFLINE,STOPPED" : 
                "SENSE,ERROR,OFFLINE,STOP_FAILED");
            return ret;
        }
        // ... (error handling)
    }

    // Parse parameters (F, D, DL, TIME)
    // ... (implementation details)

    // Create task for offline sensing with delay support
    // ... (task creation)
}

Usage Example

// Initialize command handler
esp_err_t ret = sensing_command_init();
if (ret != ESP_OK)
{
    ESP_LOGE(TAG, "Failed to initialize command handler");
    return;
}

// Set sensor handle
ret = sensing_command_set_sensor_handle(&adxl355_handle);
if (ret != ESP_OK)
{
    ESP_LOGE(TAG, "Failed to set sensor handle");
    return;
}

// In MQTT message callback
void mqtt_message_callback(esp_mqtt_event_handle_t event)
{
    if (strncmp(event->data, "SENSE,", 6) == 0)
    {
        sensing_command_process(event->data, event->data_len);
    }
}