代码¶
Warning
以下代码应基于发布代码中的代码,可能已更新。
offline_sensing.h¶
/**
* @file offline_sensing.h
* @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
* @brief 离线传感模块 - 高频批量传感器数据采集和存储
* @version 1.0
* @date 2025-01-XX
* @copyright Copyright (c) 2025
*
* @details
* 该模块提供离线传感器数据采集,可配置:
* - 采样频率(默认:100 Hz,高于在线传感)
* - 采样时长(默认:10秒)
* - 内存缓冲区存储(启用/禁用)
* - SD卡存储(启用/禁用)
* - 采样完成后MQTT报告(启用/禁用)
*
* 功能特性:
* - 基于高精度定时器的采样(ESP定时器)
* - 在内存缓冲区和/或SD卡中存储数据
* - 采样完成后自动MQTT报告
* - 线程安全操作
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "node_acc_adxl355.h"
#include "tiny_measurement_config.h"
#ifdef __cplusplus
extern "C"
{
#endif
/* ============================================================================
* CONFIGURATION STRUCTURE
* ============================================================================ */
/**
* @brief 离线传感配置
*/
typedef struct
{
float sampling_frequency_hz; ///< 采样频率(Hz,默认:100.0)
float sampling_duration_sec; ///< 采样时长(秒,默认:10.0)
bool enable_memory; ///< 启用内存缓冲区存储(默认:true)
bool enable_sd; ///< 启用SD卡存储(默认:true)
bool enable_mqtt_report; ///< 采样完成后启用MQTT报告(默认:true)
const char *sd_file_path; ///< SD卡文件路径(默认:NULL自动生成)
const char *mqtt_report_topic; ///< MQTT报告主题(默认:NULL使用默认)
} offline_sensing_config_t;
/**
* @brief 离线传感样本数据结构
*/
typedef struct
{
float x; ///< X轴加速度(g)
float y; ///< Y轴加速度(g)
float z; ///< Z轴加速度(g)
float temp; ///< 温度(°C)
uint64_t timestamp_us; ///< 时间戳(微秒)
} offline_sensing_sample_t;
/**
* @brief 离线传感会话报告
*/
typedef struct
{
uint32_t total_samples; ///< 采集的样本总数
float actual_frequency_hz; ///< 实际达到的采样频率
float duration_sec; ///< 实际时长
bool memory_storage_success; ///< 内存存储成功状态
bool sd_storage_success; ///< SD卡存储成功状态
char sd_file_path[256]; ///< SD卡文件路径(如果已保存)
uint64_t start_timestamp_us; ///< 开始时间戳
uint64_t end_timestamp_us; ///< 结束时间戳
} offline_sensing_report_t;
/* ============================================================================
* FUNCTION DECLARATIONS
* ============================================================================ */
/**
* @brief 初始化离线传感模块
* @param config 配置结构(NULL使用默认配置)
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_init(const offline_sensing_config_t *config);
/**
* @brief 为离线传感设置传感器句柄
* @param handle ADXL355传感器句柄
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_set_sensor_handle(adxl355_handle_t *handle);
/**
* @brief 启动离线传感会话
* @return 成功返回ESP_OK,失败返回错误码
* @note 此函数阻塞直到采样完成
*/
esp_err_t offline_sensing_start(void);
/**
* @brief 停止离线传感会话(如果正在运行)
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_stop(void);
/**
* @brief 检查离线传感是否正在运行
* @param is_running 输出参数,true表示正在运行
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_is_running(bool *is_running);
/**
* @brief 获取最后一次采样报告
* @param report 输出参数,报告
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_get_report(offline_sensing_report_t *report);
/**
* @brief 获取内存缓冲区数据(如果启用)
* @param samples 样本的输出缓冲区
* @param max_samples 要检索的最大样本数
* @param actual_samples 输出参数,实际检索的样本数
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_get_memory_data(offline_sensing_sample_t *samples,
uint32_t max_samples,
uint32_t *actual_samples);
/**
* @brief 清除内存缓冲区
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_clear_memory(void);
/**
* @brief 反初始化离线传感模块
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t offline_sensing_deinit(void);
#ifdef __cplusplus
}
#endif
offline_sensing.c¶
关键实现细节¶
定时器回调¶
static void sampling_timer_callback(void *arg)
{
if (!s_is_running || s_adxl355_handle == NULL)
{
return;
}
adxl355_accelerations_t accel;
float temperature;
esp_err_t accel_ret = adxl355_read_accelerations(s_adxl355_handle, &accel);
esp_err_t temp_ret = adxl355_read_temperature(s_adxl355_handle, &temperature);
if (accel_ret == ESP_OK && temp_ret == ESP_OK)
{
uint64_t timestamp = esp_timer_get_time();
// 如果启用,存储在内存缓冲区
if (s_config.enable_memory && s_memory_buffer != NULL)
{
if (xSemaphoreTake(s_memory_mutex, pdMS_TO_TICKS(10)) == pdTRUE)
{
if (s_memory_buffer_index < s_memory_buffer_size)
{
s_memory_buffer[s_memory_buffer_index].x = accel.x;
s_memory_buffer[s_memory_buffer_index].y = accel.y;
s_memory_buffer[s_memory_buffer_index].z = accel.z;
s_memory_buffer[s_memory_buffer_index].temp = temperature;
s_memory_buffer[s_memory_buffer_index].timestamp_us = timestamp;
s_memory_buffer_index++;
}
xSemaphoreGive(s_memory_mutex);
}
}
s_total_samples++;
}
}
SD卡存储¶
static esp_err_t save_to_sd_card(const offline_sensing_sample_t *samples, uint32_t count)
{
// 生成带时间戳和参数的文件路径
char file_path[256];
if (s_config.sd_file_path != NULL)
{
snprintf(file_path, sizeof(file_path), "%s/%s", MOUNT_POINT, s_config.sd_file_path);
}
else
{
// 自动生成文件名
struct tm timeinfo;
localtime_r(&s_start_calendar_time, &timeinfo);
int freq_int = (int)(s_config.sampling_frequency_hz + 0.5f);
int dur_int = (int)(s_config.sampling_duration_sec + 0.5f);
snprintf(file_path, sizeof(file_path),
"%s/%04d%02d%02d%02d%02d%02d_F%04d_D%04d.csv",
MOUNT_POINT,
timeinfo.tm_year + 1900,
timeinfo.tm_mon + 1,
timeinfo.tm_mday,
timeinfo.tm_hour,
timeinfo.tm_min,
timeinfo.tm_sec,
freq_int,
dur_int);
}
// 打开文件进行写入
FILE *f = fopen(file_path, "w");
if (f == NULL)
{
ESP_LOGE(TAG, "打开文件写入失败: %s", file_path);
return ESP_FAIL;
}
// 写入CSV头
fprintf(f, "timestamp_us,x,y,z,temp\n");
// 写入样本
for (uint32_t i = 0; i < count; i++)
{
fprintf(f, "%llu,%.6f,%.6f,%.6f,%.2f\n",
(unsigned long long)samples[i].timestamp_us,
samples[i].x, samples[i].y, samples[i].z, samples[i].temp);
}
fclose(f);
ESP_LOGI(TAG, "SD: %u 样本 -> %s", count, file_path);
return ESP_OK;
}
启动函数¶
esp_err_t offline_sensing_start(void)
{
if (!s_is_initialized)
{
ESP_LOGE(TAG, "离线传感未初始化");
return ESP_ERR_INVALID_STATE;
}
if (s_is_running)
{
ESP_LOGW(TAG, "离线传感已在运行");
return ESP_ERR_INVALID_STATE;
}
// 重置状态
s_total_samples = 0;
s_memory_buffer_index = 0;
s_start_timestamp = esp_timer_get_time();
s_start_calendar_time = time(NULL);
// 计算定时器周期
uint64_t period_us = (uint64_t)(1000000.0f / s_config.sampling_frequency_hz);
// 创建定时器
esp_timer_create_args_t timer_args = {
.callback = sampling_timer_callback,
.arg = NULL,
.name = "offline_sampling_timer"
};
esp_err_t ret = esp_timer_create(&timer_args, &s_sampling_timer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "创建定时器失败: %s", esp_err_to_name(ret));
return ret;
}
s_is_running = true;
// 立即执行第一次采样
sampling_timer_callback(NULL);
// 启动周期定时器
ret = esp_timer_start_periodic(s_sampling_timer, period_us);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "启动定时器失败: %s", esp_err_to_name(ret));
esp_timer_delete(s_sampling_timer);
s_sampling_timer = NULL;
s_is_running = false;
return ret;
}
ESP_LOGI(TAG, "离线传感已启动: %.2f Hz,持续 %.2f 秒",
s_config.sampling_frequency_hz, s_config.sampling_duration_sec);
// 等待采样时长(阻塞)
uint32_t duration_ms = (uint32_t)(s_config.sampling_duration_sec * 1000.0f);
vTaskDelay(pdMS_TO_TICKS(duration_ms));
// 停止采样
offline_sensing_stop();
// 处理并保存数据
s_end_timestamp = esp_timer_get_time();
float actual_duration = (s_end_timestamp - s_start_timestamp) / 1000000.0f;
float actual_frequency = s_total_samples / actual_duration;
// 准备报告
s_last_report.total_samples = s_total_samples;
s_last_report.actual_frequency_hz = actual_frequency;
s_last_report.duration_sec = actual_duration;
s_last_report.start_timestamp_us = s_start_timestamp;
s_last_report.end_timestamp_us = s_end_timestamp;
// 如果启用,保存到SD卡
if (s_config.enable_sd && s_total_samples > 0)
{
if (s_config.enable_memory && s_memory_buffer != NULL)
{
s_last_report.sd_storage_success = (save_to_sd_card(s_memory_buffer,
s_memory_buffer_index) == ESP_OK);
}
}
s_last_report.memory_storage_success = (s_config.enable_memory && s_memory_buffer != NULL);
// 如果启用,发送MQTT报告
if (s_config.enable_mqtt_report)
{
send_mqtt_report(&s_last_report);
}
ESP_LOGI(TAG, "完成: %u 样本,%.2f Hz,%.2f 秒",
s_total_samples, s_config.sampling_frequency_hz, s_config.sampling_duration_sec);
return ESP_OK;
}
使用示例¶
#include "offline_sensing.h"
#include "node_acc_adxl355.h"
// 初始化传感器
adxl355_handle_t adxl355_handle;
adxl355_init(&adxl355_handle, ADXL355_RANGE_2G, ADXL355_ODR_1000);
// 设置传感器句柄
offline_sensing_set_sensor_handle(&adxl355_handle);
// 配置离线传感
offline_sensing_config_t config = {
.sampling_frequency_hz = 100.0f,
.sampling_duration_sec = 10.0f,
.enable_memory = true,
.enable_sd = true,
.enable_mqtt_report = true,
.sd_file_path = NULL, // 自动生成文件名
.mqtt_report_topic = NULL // 使用默认主题
};
// 初始化
offline_sensing_init(&config);
// 启动传感(阻塞直到完成)
offline_sensing_start();
// 获取报告
offline_sensing_report_t report;
offline_sensing_get_report(&report);
ESP_LOGI(TAG, "样本: %u,频率: %.2f Hz,时长: %.2f 秒",
report.total_samples, report.actual_frequency_hz, report.duration_sec);
// 获取内存数据(可选)
offline_sensing_sample_t samples[1000];
uint32_t actual_samples;
offline_sensing_get_memory_data(samples, 1000, &actual_samples);
// 清理
offline_sensing_deinit();