Files
2025-12-03 11:12:34 +08:00

483 lines
17 KiB
C

#include "app_config.h"
#include "app_task.h"
#include "system/timer.h"
#include "app_main.h"
#include "system/includes.h"
#include "key_event_deal.h"
#include "timestamp/timestamp.h"
#include "health_manager/health_manager.h"
#define LOG_TAG_CONST SPORT_HEALTH_MANAGE
#define LOG_TAG "[SHM_SLEEP]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
#ifdef SUPPORT_MS_EXTENSIONS
#pragma bss_seg(".health_manager.data.bss")
#pragma data_seg(".health_manager.data")
#pragma const_seg(".health_manager.text.const")
#pragma code_seg(".health_manager.text")
#endif
#if (TCFG_SPORT_HEALTH_ENABLE&&TCFG_SPORT_HEALTH_SLEEP)
//睡眠记录配置
#define HEALTH_FILE_TYPE F_TYPE_SLEEP
#define HEALTH_FILE_VERSION (0) //协议约定
#define HEALTH_FILE_INTERVAL (0Xff) //协议约定
#define TCFG_SPORT_HEALTH_AWAKE_MAX_LEN 30 //30min的清醒数据将被过滤
//测试数据配置
#define TCFG_SPORT_HEALTH_SLEEP_TEST 0
#define TCFG_SPORT_HEALTH_SLEEP_TEST_YEAR 2024
#define TCFG_SPORT_HEALTH_SLEEP_TEST_MONTH 9
#define TCFG_SPORT_HEALTH_SLEEP_TEST_DAY 7
#define TCFG_SPORT_HEALTH_SLEEP_TARGET_MIN (8*60*60)//7h
#pragma pack(1)//
struct sleep_storage_data {
u8 stage;
u8 sleep_min;//min
};
#pragma pack()//
static void motion_sleep_data_test_get(sleep_data *out);
/* ------------------------------------------------------------------------------------*/
/**
* @brief motion_sleep_data_get_switch 睡眠数据选择接口
*
* @param out 数据
*
* @return
*/
/* ------------------------------------------------------------------------------------*/
static int motion_sleep_data_get_switch(sleep_data *out)
{
//使用模拟测试数据
#if TCFG_SPORT_HEALTH_SLEEP_TEST
motion_sleep_data_test_get(out);
#else
//使用JL算法数据
//若当前处于睡眠,则拒绝数据获取
if (!algo_motion_sleep_is_terminated()) {
return false;
}
//获取数据
algo_motion_sleep_get(out);
#endif
return true;
}
/* ------------------------------------------------------------------------------------*/
/**
* @brief sport_health_sleep_data_stroage 睡眠数据存储
*
* @return
*/
/* ------------------------------------------------------------------------------------*/
static int sport_health_sleep_data_stroage()
{
sleep_data out = {0};
//获取睡眠数据
if (!motion_sleep_data_get_switch(&out)) {
//需要忽略本次数据
return SHM_ERR_OK;
}
//没有数据,或者只有清醒数据,忽略
if ((out.blocks == 0) || ((out.blocks == 1) && (out.chart[0].stage == SLEEP_STAGE_AWAKE))) {
return SHM_ERR_OK;
}
//获取系统时间
struct sys_time time;
rtc_read_time(&time);
#if TCFG_SPORT_HEALTH_SLEEP_TEST //测试使用
struct sys_time _time = {
.year = TCFG_SPORT_HEALTH_SLEEP_TEST_YEAR,
.month = TCFG_SPORT_HEALTH_SLEEP_TEST_MONTH,
.day = TCFG_SPORT_HEALTH_SLEEP_TEST_DAY + 1,
.hour = 22,
.min = 30,
};
memcpy(&time, &_time, sizeof(struct sys_time));
#endif
//获取文件句柄
int file_len = 0;
int swap_days = 0;
void *fp = sport_health_file_open_by_time(HEALTH_FILE_TYPE, time.year, time.month, time.day);
#if TCFG_SPORT_HEALTH_SLEEP_TEST//测试使用
if (fp) {
sport_health_file_delete(fp);
sport_health_file_close(fp);
fp = NULL;
}
#endif
if (!fp) {
//若句柄不存在,则新建文件
struct health_file_total_head file_head = {
.type = HEALTH_FILE_TYPE,
.year = time.year,
.month = time.month,
.day = time.day,
.crc = 0,
.interval = HEALTH_FILE_INTERVAL,
.version = HEALTH_FILE_VERSION,
.reserve = 0,
};
sport_health_common_swapX((u8 *)&file_head.year, (u8 *)&file_head.year, 2);
fp = sport_health_file_open(HEALTH_FILE_TYPE, 0);
file_len = sport_health_file_write(fp, (u8 *)&file_head, file_len, sizeof(struct health_file_total_head));
} else {
//否则续写
file_len = sport_health_file_get_len(fp);
}
u8 valid_point_num = 0;
u8 data_head_flag = 0;
struct health_file_data_head data_head = {0};
for (int i = 0; i < out.blocks - 1; i++) {
//数据开头的清醒数据不需要记录
if ((i == 0) && (out.chart[i].stage == SLEEP_STAGE_AWAKE)) {
continue;
}
//写入数据包头
if (!data_head_flag) {
struct sys_time start_time;
timestamp_utc_sec_2_mytime(out.chart[i].timestamp, &start_time);
data_head .hour = start_time.hour;
data_head .min = start_time.min;
data_head .len = 0xffff;
file_len = sport_health_file_write(fp, (u8 *)&data_head, file_len, sizeof(struct health_file_data_head));
data_head_flag = 1;
}
//按格式写入数据到文件
int block_len = out.chart[i + 1].timestamp - out.chart[i].timestamp;
struct sys_time block_time_begin;
timestamp_utc_sec_2_mytime(out.chart[i].timestamp, &block_time_begin);
struct sys_time block_time_end;
timestamp_utc_sec_2_mytime(out.chart[i + 1].timestamp, &block_time_end);
struct sleep_storage_data sleep_block = {
.stage = (out.chart[i].stage == SLEEP_STAGE_AWAKE) ? 0xff : out.chart[i].stage,
};
log_debug("[%d:%d]~[%d %d]", block_time_begin.hour, block_time_begin.min, block_time_end.hour, block_time_end.min);
if (block_time_end.hour * 60 + block_time_end.min < block_time_begin.hour * 60 + block_time_begin.min) {
//存在跨天数据,则在0点分段写入,方便绘图
swap_days = 1;
int sleep_min = 24 * 60 - (block_time_begin.hour * 60 + block_time_begin.min);
do {
//睡眠时间超过255分钟时,需要分段写入
if (sleep_min > 255) {
sleep_block.sleep_min = 255;
log_debug("[1]index:%d stage:%d len:%d", valid_point_num, sleep_block.stage, sleep_block.sleep_min);
file_len = sport_health_file_write(fp, (u8 *)&sleep_block, file_len, sizeof(struct sleep_storage_data));
valid_point_num++;
sleep_min -= 255;
} else {
sleep_block.sleep_min = sleep_min;
log_debug("[2]index:%d stage:%d len:%d", valid_point_num, sleep_block.stage, sleep_block.sleep_min);
file_len = sport_health_file_write(fp, (u8 *)&sleep_block, file_len, sizeof(struct sleep_storage_data));
valid_point_num++;
}
} while (sleep_min > 255);
sleep_min = block_time_end.hour * 60 + block_time_end.min;
do {
//睡眠时间超过255分钟时,需要分段写入
if (sleep_min > 255) {
sleep_block.sleep_min = 255;
log_debug("[3]index:%d stage:%d len:%d", valid_point_num, sleep_block.stage, sleep_block.sleep_min);
file_len = sport_health_file_write(fp, (u8 *)&sleep_block, file_len, sizeof(struct sleep_storage_data));
valid_point_num++;
sleep_min -= 255;
} else {
sleep_block.sleep_min = sleep_min;
log_debug("[4]index:%d stage:%d len:%d", valid_point_num, sleep_block.stage, sleep_block.sleep_min);
file_len = sport_health_file_write(fp, (u8 *)&sleep_block, file_len, sizeof(struct sleep_storage_data));
valid_point_num++;
}
} while (sleep_min > 255);
} else { //正常写入
int sleep_min = (out.chart[i + 1].timestamp - out.chart[i].timestamp + 30) / 60;
do {
//睡眠时间超过255分钟时,需要分段写入
if (sleep_min > 255) {
sleep_block.sleep_min = 255;
log_debug("[5]index:%d stage:%d len:%d", valid_point_num, sleep_block.stage, sleep_block.sleep_min);
file_len = sport_health_file_write(fp, (u8 *)&sleep_block, file_len, sizeof(struct sleep_storage_data));
valid_point_num++;
sleep_min -= 255;
} else {
sleep_block.sleep_min = sleep_min;
log_debug("[6]index:%d stage:%d len:%d", valid_point_num, sleep_block.stage, sleep_block.sleep_min);
file_len = sport_health_file_write(fp, (u8 *)&sleep_block, file_len, sizeof(struct sleep_storage_data));
valid_point_num++;
}
} while (sleep_min > 255);
}
}
//记录数据长度
data_head.len = valid_point_num * 2;
sport_health_common_swapX((u8 *) & (data_head.len), (u8 *) & (data_head.len), 2);
sport_health_file_update(fp, (u8 *)&data_head, sizeof(struct health_file_total_head), sizeof(struct health_file_data_head));
//更新文件头的crc和跨天信息
struct health_file_total_head re_file_head;
sport_health_file_read(fp, (u8 *)& re_file_head, 0, sizeof(struct health_file_total_head));
for (int i = 0; i < 16; i++) {
//用reserve区记录分段信息
re_file_head.reserve |= (swap_days << re_file_head.crc);
//CRC做强制校验,有差异即可
re_file_head.crc++;
}
sport_health_file_update(fp, (u8 *)&re_file_head, 0, sizeof(struct health_file_total_head));
//关闭文件,结束记录
sport_health_file_close(fp);
return SHM_ERR_OK;
}
/* ------------------------------------------------------------------------------------*/
/**
* @brief sport_health_sleep_rec_data_get 获取记录数据
*
* @param value 句柄
*
* @return
*/
/* ------------------------------------------------------------------------------------*/
static int sport_health_sleep_rec_data_get(struct sleep_data_analysis *value)
{
sleep_data *out = (sleep_data *)value->data;
//获取日期
struct sys_time time;
#if 0//只读当天
rtc_read_time(&time);
#else//由句柄指定日期
memcpy(&time, &value->file_time, sizeof(struct sys_time));
#endif
//打开文件
void *fp = sport_health_file_open_by_time(HEALTH_FILE_TYPE, time.year, time.month, time.day);
if (!fp) {
out->blocks = 0;
//文件不存在
return -SHM_ERR_FILE_NOT_FIND;
}
/* int block_idx = 0; */
int file_total = sport_health_file_get_len(fp);
int file_offset = 0;
//读取文件头
struct health_file_total_head total_head;
sport_health_file_read(fp, (u8 *)&total_head, 0, sizeof(struct health_file_total_head));
sport_health_common_swapX((u8 *)&total_head.year, (u8 *)&total_head.year, 2);
file_offset += sizeof(struct health_file_total_head);
struct sys_time start_time;
u32 timestamp_start;
u32 timestamp_block;
//分段读睡眠数据
int sleep_data_cnt = 0;
do {
//数据包头
struct health_file_data_head data_head;
sport_health_file_read(fp, (u8 *)&data_head, file_offset, sizeof(struct health_file_data_head));
file_offset += sizeof(struct health_file_data_head);
start_time.year = total_head.year;
start_time.month = total_head.month;
start_time.day = total_head.day;
start_time.hour = data_head.hour;
start_time.min = data_head.min;
start_time.sec = 0;
timestamp_start = timestamp_mytime_2_utc_sec(&start_time);
//数据跨天
if (total_head.reserve & BIT(sleep_data_cnt)) {
timestamp_start -= 24 * 60 * 60;
}
timestamp_block = timestamp_start;
u16 blocks = data_head.len;
//异常数据退出
if (blocks == 0xffff) {
break;
}
sport_health_common_swapX((u8 *)&blocks, (u8 *)&blocks, 2);
u8 *rbuf = zalloc(blocks);
sport_health_file_read(fp, rbuf, file_offset, blocks);
file_offset += blocks;
struct sleep_storage_data *p = (struct sleep_storage_data *)rbuf;
//读取该段数据
for (int i = 0; i < blocks / 2; i++) {
//记录睡眠的状态和起始时间戳
p->stage = (p->stage == 0xff) ? SLEEP_STAGE_AWAKE : p->stage;
out->chart[out->blocks].stage = p->stage;
out->chart[out->blocks].timestamp = timestamp_block;
#if 0 //debug使用
struct sys_time block_t;
timestamp_utc_sec_2_mytime(out->chart[out->blocks].timestamp, &block_t);
printf("[SLEEP_TEST_READ]%d STA:%d [%4d-%2d-%2d-%2d:%2d:%2d]", out->blocks, out->chart[out->blocks].stage,
block_t.year, block_t.month, block_t.day, block_t.hour, block_t.min, block_t.sec);
#endif//TCFG_SPORT_HEALTH_SLEEP_TEST
//sw下一段睡眠
timestamp_block += p->sleep_min * 60;
out->blocks++;
p++;
}
//清醒作为包尾,用特殊值标记,跟清醒数据区分
out->chart[out->blocks].stage = SLEEP_STAGE_BLOCK_END;
out->chart[out->blocks].timestamp = timestamp_block;
out->blocks++;
free(rbuf);
} while (file_offset < file_total);
sport_health_file_close(fp);
return SHM_ERR_OK;
}
/* ------------------------------------------------------------------------------------*/
/**
* @brief sport_health_sleep_data_analysis 解析睡眠数据
*
* @param value 句柄
*
* @return
*/
/* ------------------------------------------------------------------------------------*/
static int sport_health_sleep_data_analysis(struct sleep_data_analysis *value)
{
sleep_data *data = (sleep_data *)value->data;
for (int i = 0; i < data->blocks - 1; i++) {
int time_len = data->chart[i + 1].timestamp - data->chart[i].timestamp;
//统计各类型睡眠的时长
if (data->chart[i].stage == SLEEP_STAGE_DEEP) {
value->deep_min += time_len;
} else if (data->chart[i].stage == SLEEP_STAGE_LIGHT) {
value->light_min += time_len;
} else if (data->chart[i].stage == SLEEP_STAGE_REM) {
value->rem_min += time_len;
} else if (data->chart[i].stage == SLEEP_STAGE_AWAKE) {
//可能包含有较长的清醒数据,根据项目需求进行过滤
#if TCFG_SPORT_HEALTH_AWAKE_MAX_LEN
time_len = time_len > TCFG_SPORT_HEALTH_AWAKE_MAX_LEN ? 0 : time_len;
#endif
value->awake_min += time_len;
} else {
time_len = 0;
}
value->total_min += time_len;
}
return SHM_ERR_OK;
}
static int shm_sleep_io_crtl(int cmd, void *priv)
{
int ret = SHM_ERR_OK;
switch (cmd) {
case SHM_CMD_SAVE_SINGLE:
ret = sport_health_sleep_data_stroage();
break;
default:
/* ret = -SHE_ERR_MOD_NO_THIS_CMD; */
break;
}
return ret;
}
static int shm_sleep_get_value(int type, void *priv)
{
int ret = SHM_ERR_OK;
switch (type) {
case SHM_GET_TYPE_INFO:
break;
case SHM_GET_TYPE_INFO_STORAGE:
//get date from file
ret = sport_health_sleep_rec_data_get((struct sleep_data_analysis *)priv);
break;
case SHM_GET_TYPE_DATA_ANALYSIS:
ret = sport_health_sleep_data_analysis((struct sleep_data_analysis *)priv);
break;
default:
ret = -SHM_ERR_MOD_NO_THIS_TYPE;
break;
}
return ret;
}
REGISTER_SPORT_HEALTH_MODULE(sleep)
{
.module = SHM_MOD_SLEEP,
.io_ctrl = shm_sleep_io_crtl,
.get_value = shm_sleep_get_value,
};
/*****************************************************************************************************/
#if TCFG_SPORT_HEALTH_SLEEP_TEST
/* ------------------------------------------------------------------------------------*/
/**
* @brief motion_sleep_data_test_get 模拟数据
*
* @param out
*/
/* ------------------------------------------------------------------------------------*/
static void motion_sleep_data_test_get(sleep_data *out)
{
struct sys_time time = {
.year = TCFG_SPORT_HEALTH_SLEEP_TEST_YEAR,
.month = TCFG_SPORT_HEALTH_SLEEP_TEST_MONTH,
.day = TCFG_SPORT_HEALTH_SLEEP_TEST_DAY,
.hour = 22,
.min = 30,
};
struct sys_time block_t;
int timestamp_tmp = timestamp_mytime_2_utc_sec(&time);
int sleep_time = 0;
out->blocks = 0;
do {
out->chart[out->blocks].stage = rand32() % 3;
out->chart[out->blocks].timestamp = timestamp_tmp;
timestamp_utc_sec_2_mytime(out->chart[out->blocks].timestamp, &block_t);
int len = rand32() % 3000 + 10 * 60;
printf("[SLEEP_TEST_GET]%d STA:%d [%4d-%2d-%2d-%2d:%2d:%2d]len:%d \n", out->blocks, out->chart[out->blocks].stage,
block_t.year, block_t.month, block_t.day, block_t.hour, block_t.min, block_t.sec, len);
timestamp_tmp += len;
sleep_time += len;
out->blocks++;
} while (sleep_time < TCFG_SPORT_HEALTH_SLEEP_TARGET_MIN);
}
static sleep_data_analysis value = {
.file_time.year = TCFG_SPORT_HEALTH_SLEEP_TEST_YEAR,
.file_time.month = TCFG_SPORT_HEALTH_SLEEP_TEST_MONTH,
.file_time.day = TCFG_SPORT_HEALTH_SLEEP_TEST_DAY + 1,
.file_time.hour = 22,
.file_time.min = 30,
.file_time.blocks = 0,
};
int sleep_data_test()
{
sport_health_sleep_data_stroage();
sport_health_sleep_rec_data_get(&value);
return 0;
}
#endif//TCFG_SPORT_HEALTH_SLEEP_TEST
#endif// SPORT_HEALTH_MANAGE_SLEEP_ENABLE