最近在做智能穿戴设备的项目,需要将一些状态数据集合传输回APP端,由于数据集合稍大,如果原封不动地将集合传输过去,功耗/速度都达不到要求,于是本猿跟同事讨论了下,决定对数据进行压缩,再进行传输。
又因为数据量仅仅是"稍大",而且"稍大"的概念,也只是单片机级的"稍大",所以也不打算引入开源的压缩库来实现,仅仅是选取了最基础入门的两种压缩算法,来进行分析与实现。
没错,这两种算法分别是游程编码 & 哈夫曼编码, 下面是我对这两种方案的分析:
一. 哈夫曼编码压缩方案
如果你已经忘记啥是哈夫曼树了, 先看下陈皓大神的文章, 回忆一下哈!
压缩流程简介
- 格式为小端格式,从低位向高位遍历,录入 / 获取状态数据;
- 粗略估计状态模式的出现频率,建立哈夫曼二叉树,算出每种状态模式类别的哈夫曼编码
- 固件端建立状态哈夫曼表,根据表录入状态数据
- apk端,建立二叉树结构,逐位获取数据,在二叉树上生成查询路径,得到相应的哈夫曼编码数据
- 每个字节可存四个状态, 16个字节组成一个状态包。
构建哈夫曼树
预估下状态模式的出现频率,可生成表如下:
模式类别 | 出现频率 | 备注 |
---|---|---|
出现状态A 1次 | 30 | 2分钟 |
出现状态A 5次 | 15 | 10分钟 |
出现状态A 15次 | 10 | 30分钟 |
出现状态A 30次 | 5 | 60分钟 |
出现状态B 1次 | 29 | 2分钟 |
出现状态B 5次 | 14 | 10分钟 |
出现状态B 15次 | 9 | 30分钟 |
出现状态B 30次 | 4 | 60分钟 |
出现状态C 1次 | 31 | 2分钟 |
出现状态C 5次 | 16 | 10分钟 |
出现状态C 15次 | 13 | 30分钟 |
出现状态C 30次 | 6 | 60分钟 |
出现状态C 150次 | 3 | 300分钟 |
出现标记位 | 2 | 结束位 |
注:频率值越高,出现频率越大
根据出现频率,可构建哈夫曼树如下:
通过该树,便可以得出每种状态模式类别的哈弗曼编码,如下:
状态模式类别定义
模式类别 | 哈夫曼编码 | 备注 |
---|---|---|
出现状态A 1次 | 110 | 2分钟 重复5次压缩 |
出现状态A 5次 | 000 | 10分钟 重复3次压缩 |
出现状态A 15次 | 0110 | 30分钟 重复2次压缩 |
出现状态A 30次 | 01001 | 60分钟 不压缩 |
出现状态B 1次 | 101 | 2分钟 |
出现状态B 5次 | 1001 | 10分钟 |
出现状态B 15次 | 0101 | 30分钟 |
出现状态B 30次 | 01000 | 60分钟 |
出现状态C 1次 | 111 | 2分钟 |
出现状态C 5次 | 001 | 10分钟 |
出现状态C 15次 | 1000 | 30分钟 |
出现状态C 30次 | 01111 | 60分钟 重复5次压缩 |
出现状态C 150次 | 011101 | 300分钟 |
出现标记位 | 011100 | 结束位 |
举个栗子
出现状态A 1次: | XX | XX | X 0 | 11 |
出现状态A 2次: | XX | 01 | 10 | 11 |
出现状态A 3次: | 11 | 01 | 10 | 11 | + | XX | XX | XX | X 0 |
出现状态A 4次: | 11 | 01 | 10 | 11 | + | XX | XX | 01 | 10 |
出现状态A 5次: | XX | XX | X 0 | 00 |
出现状态A 6次: | XX | 01 | 10 | 00 |
出现状态A 7次: | 11 | 01 | 10 | 00 | + | XX | XX | XX | X 0 |
出现状态A 8次: | 11 | 01 | 10 | 00 | + | XX | XX | 01 | 10 |
出现状态A 9次: | 11 | 01 | 10 | 00 | + | X 0 | 11 | 01 | 10 |
出现状态A 10次: | XX | 00 | 00 | 00 |
...
出现状态A 15次: | XX | XX | 01 | 10 |
...
出现状态A 30次: | XX | X 1 | 00 | 10 |
...
出现状态A 35次: | 00 | 01 | 00 | 10 |
...
出现状态A 60次: | 01 | 01 | 00 | 10 | + | 00 | 11 | 10 | 10 |
注:最后出现状态A 60次,末尾要加结束位。
实现流程
- malloc并初始化状态数据, 每个字节默认数据值为FF;
- 建立huffman表,保存每种状态模式类别的哈夫曼编码、需做压缩时的重复次数以及哈夫曼编码的位数;
- 从sensor中获取状态模式,查询huffman表,获取相应的压缩重复次数以及哈夫曼编码的位数;
- 通过获取到的哈夫曼编码的位数N,向上遍历N个bit,与获取到的状态模式进行比较;
- 如果相等,重复次数加1, 否则,直接写入;
- 如果重复次数达到huffman表中获取的压缩重复次数时,则进行压缩;
- 进行压缩后,需递归判断是否满足下一层级的压缩要求,如果是,则继续压缩;
- 重复6,7,直至获取完所有状态数据;
- 将数据传给apk,free掉压缩状态数据.
实现细节
- 开始压缩第一个状态数据时(即package_idx和byte_idx均为0时),直接写入;
- 状态数据编码位数不确定,导致bit_idx, byte_idx的增加不确定;
- 写入所有状态数据后,需在末尾处加入结束位;
- 判断是否需要进行重复压缩时,需注意package与package间,byte与byte间的边界处理;
二. 游程编码压缩方案
压缩流程简介
- 格式为小端格式,两个bit作为基本单位,从低位向高位遍历,录入 / 获取状态数据;
- 如果连续出现8个相同的状态,则进行压缩;
- 将8个相同的状态,转化为标记位压缩格式;
- 每个字节可存四个状态, 16个字节组成一个状态包。
状态类别定义
状态类别 | 两位二进制编码 |
---|---|
状态A | 00 |
状态B | 01 |
状态C | 10 |
标记位 | 11 |
标记位说明
如果出现在存储字节的最高单位,则表明该字节无需继续读取, 直接读下一个字节;
-
如果不出现在存储字节的最高单位;
- 该标记位的下一个单位, 是重复状态值
- 不需要继续解析该字节,转而解析下一个字节,获取状态值的重复次数
如果连续出现两个标记位,说明可以结束获取状态数据了
举个栗子
出现状态A 1次: | XX | 11 | 11 | 00 |
出现状态A 2次: | 11 | 11 | 00 | 00 |
出现状态A 3次: | 11 | 00 | 00 | 00 |
出现状态A 4次: | 00 | 00 | 00 | 00 |
出现状态A 5次: | 00 | 00 | 00 | 00 | + | XX | 11 | 11 | 00 |
出现状态A 6次: | 00 | 00 | 00 | 00 | + | 11 | 11 | 00 | 00 |
出现状态A 7次: | 00 | 00 | 00 | 00 | + | 11 | 00 | 00 | 00 |
出现状态A 8次: | xx | xx | 00 | 11 | + | 00 | 00 | 10 | 00 |
为什么是8次才压缩?
我们可以用4次来举栗子哈:
出现状态A 1次: | XX | 11 | 11 | 00 |
出现状态A 2次: | 11 | 11 | 00 | 00 |
出现状态A 3次: | 11 | 00 | 00 | 00 |
出现状态A 4次: | XX | XX | 00 | 11 | + | 00 | 00 | 01 | 00 |
在这个时候,如果用户任性,不出现状态A了,突然出现状态B了, 会怎样咧?
出现状态B 1次: | XX | XX | 00 | 11 | + | 00 | 00 | 01 | 00 | + | XX | 11 | 11 | 01 |
立马就要用到第三个字节了,
即是如果用户出现状态A 4次,完了立马就切换状态, 我们就需要用3个字节来存储,
而如果采用8次的话, 依然是2个字节;
而如果采用超过8次再进行压缩的话,
当数据重复超过8次时, 也需要用3个字节存储,
而8次的方法,依然是2个字节;
当数据重复8次后,出现不同的状态值时,
不管是采取哪种方式,均需要3个字节存储;
由上可得, 8次可以作为一个临界点,用其作为压缩起点,可达最优。
实现流程
- malloc并初始化压缩状态数据, 每个字节默认数据值为FF;
- 验证请求类型,并根据请求类型, 计算需要上传的状态数据量;
- 检测与处理状态数据空包的情况;
- 初始化第一个状态包,依次从sensor中将获取到的状态数据,压缩存入包中;
- 如包满,则动态初始化下一个状态包,继续4操作,直至数据获取完毕;
- 将数据传给apk,free掉压缩数据
注: 压缩过程中记录下当前游标位置 / 当前字节、位的索引 以及当前游标字节的类型(是否为压缩字节),以判断是直接插入状态数据,还是数据已经满足重复条件,须进行压缩转换存储;
实现细节
- package与package之间, byte与byte之间的边界处理
- 判断是否上传昨天数据
- 未push完睡眠数据,package满与byte满的处理问题
- 是否记录上传位置
- 从sensor中获取具体的状态数据
- 重复状态数据跨越三个字节时的压缩处理, 更甚的是, 跨三个字节,而且还跨包的处理;
- 根据实际情况动态malloc package,节省内存空间
三. 方案分析与选择
实现难度: 固件端两种方案的实现难度差不多, 而app端需要建立哈夫曼树结构, 来进行路径选择,解析哈夫曼编码;
空间角度: 固件端需要建立一张哈夫曼表,虽然也不是很大,但...单片机,你懂的...
压缩效果: 在小数据量的压缩情景下,两种方案的压缩效果差别不大,网上有篇文章 ,将数据先用游程编码压一遍,再用哈夫曼编码压一遍,效果肯定更优,但貌似没啥必要~~
综上所述, 我们最后选择用游程编码方案来进行实现。
四. 源码解析
俗话说, Talk is cheap, show me the code!
注: 因为"你懂的"的原因,我把部分变量名改了下,可能会造成可读性上的困扰,请见谅哈~~
#define STATE_PACKAGE_MAX_LEN 16
#define STATE_PACKAGE_MAX_NUM 25
#define STATE_BYTE_MAX_BIT 3
typedef struct
{
uint8_t value;
bool is_compress_byte;
} StateByte;
typedef struct
{
StateByte *data;
uint8_t byte_idx;
uint8_t bit_idx;
} StatePackage;
typedef struct
{
StatePackage package[STATE_PACKAGE_MAX_NUM];
uint8_t len;
} StateDetail;
typedef enum
{
REQ_STATE_DTAIL_TODAY = 0,
REQ_STATE_DTAIL_YESTERDAY = 1,
NEVER_REQ_STATE_DTAIL = 2,
REQ_STATE_DTAIL_ERR = 3,
} req_state_dtail_state;
typedef enum
{
LIGHT_STATE = 0x00,
DEEP_STATE = 0x01,
AKE_STATE = 0x02,
STATE_FLAG_STATE = 0x03,
STATE_ERR_STATE = 0x04,
} state_mode_state_t;
typedef struct
{
bool is_pos_record_state;
bool is_state;
bool is_ake_type;
bool is_state_type;
} GsensorState;
typedef struct
{
StateByte *now_byte;
StateByte *last_byte;
StateByte *last_two_byte;
StateByte *cur_byte;
uint8_t cur_bit_idx;
uint8_t repeat_num;
uint16_t package_idx;
state_mode_state_t comp_mode;
} CompressDtail;
static StateDetail state_dtail;
static bool is_state_dtail_activate;
static uint16_t upload_start_state_idx;
static req_state_dtail_state cur_req_state_dtail_state;
extern unsigned char Rec_Status[720];
extern short rec_page;
static uint16_t calculate_cur_state_idx(void)
{
uint8_t cur_rtc_time[7];
get_rtc_time(cur_rtc_time, 7);
uint16_t cur_hour = cur_rtc_time[3];
uint16_t cur_min = cur_rtc_time[4];
return (cur_hour * 60 + cur_min) / 2;
}
static void init_state_package(StatePackage *package)
{
if (package->data != NULL) vPortFree(package->data);
package->data = (StateByte *)pvPortMalloc(sizeof(StateByte) * state_PACKAGE_MAX_LEN);
if (package->data == NULL) return;
for(int i = 0; i < state_PACKAGE_MAX_LEN; ++i) {
package->data[i].value = 0xFF;
package->data[i].is_compress_byte = false;
}
package->byte_idx = 0;
package->bit_idx = 0;
++state_dtail.len;
}
void init_state_dtail(void)
{
upload_start_state_idx = 0;
cur_req_state_dtail_state = NEVER_REQ_state_DTAIL;
is_state_dtail_activate = false;
state_dtail.len = 0;
for (int i = 0; i < state_PACKAGE_MAX_NUM; ++i) {
state_dtail.package[i].data = NULL;
}
}
static inline bool is_data_in_low_addr(short is_get_today_data)
{
return ( rec_page ^ is_get_today_data );
}
static GsensorState get_sensor_state_state(uint16_t pos, short is_get_today_data)
{
uint8_t bit_idx = is_data_in_low_addr(is_get_today_data) ? 3 : 7;
GsensorState state;
state.is_pos_record_state = (bool)( (Rec_Status[pos] >> (bit_idx--) ) & 1);
state.is_state = (bool)( (Rec_Status[pos] >> (bit_idx--) ) & 1);
state.is_ake_type = (bool)( (Rec_Status[pos] >> (bit_idx--) ) & 1);
state.is_state_type = (bool)( (Rec_Status[pos] >> (bit_idx) ) & 1);
return state;
}
static inline bool is_push_success(uint16_t res)
{
return (res != 0xFFFF);
}
static inline bool is_push_next_day_data(short is_get_today_data)
{
if (is_get_today_data == 1){
return false;
}
else {
return true;
}
}
static state_mode_state_t get_state_mode(uint16_t pos, short is_get_today_data)
{
GsensorState state = get_sensor_state(pos, is_get_today_data);
if (!state.is_pos_record_state || !state.is_state) return AKE_STATE;
if ( state.is_state && state.is_state_type == 1) return LIGHT_STATE;
if ( state.is_state && state.is_state_type == 0) return DEEP_STATE;
return state_ERR_STATE;
}
static StateByte *get_last_byte(StatePackage *now_package, uint16_t package_idx)
{
StateByte *last_byte = NULL;
if (now_package->byte_idx == 0){
if (package_idx <= 0) return NULL;
StatePackage *last_package = &state_dtail.package[package_idx - 1];
uint8_t last_byte_idx = last_package->byte_idx;
last_byte = &last_package->data[last_byte_idx];
}
else {
last_byte = &now_package->data[now_package->byte_idx - 1];
}
return last_byte;
}
static StateByte *get_last_two_byte(StatePackage *now_package, uint16_t package_idx)
{
StatePackage *last_package = NULL;
StateByte *last_two_byte = NULL;
uint8_t last_two_byte_idx = 0;
if (now_package->byte_idx <= 1){
if (package_idx <= 0) return NULL;
last_package = &state_dtail.package[package_idx - 1];
if (now_package->byte_idx == 0) {
last_two_byte_idx = last_package->byte_idx - 1;
}
else {
last_two_byte_idx = last_package->byte_idx;
}
last_two_byte = &last_package->data[last_two_byte_idx];
}
else {
last_two_byte = &now_package->data[now_package->byte_idx - 2];
}
return last_two_byte;
}
static inline bool is_package_full(uint8_t byte_idx)
{
return (byte_idx >= state_PACKAGE_MAX_LEN);
}
static void assign_state_mode(StateByte *byte, uint8_t cur_bit_idx, state_mode_state_t mode)
{
byte->value &= (~(0x03 << (cur_bit_idx * 2))); //clear
byte->value |= (mode << (cur_bit_idx * 2)); //assignment
}
static void compress_seq_data(StateByte *now_byte, StatePackage *package, state_mode_state_t mode)
{
if (package->bit_idx >= 4) {
if ( is_package_full(++package->byte_idx) ) return;
now_byte = &package->data[package->byte_idx];
package->bit_idx = 0;
}
assign_state_mode(now_byte, package->bit_idx, mode);
++package->bit_idx;
}
static inline bool is_last_byte_allow_repeat(CompressDtail *dtail)
{
return ( dtail->cur_byte == dtail->now_byte && dtail->last_byte != NULL
&& !dtail->last_byte->is_compress_byte);
}
static inline bool is_last_two_byte_allow_repeat(CompressDtail *dtail)
{
return ( dtail->cur_byte == dtail->last_byte && dtail->last_two_byte !=NULL
&& !dtail->last_two_byte->is_compress_byte);
}
static StateByte *get_last_byte2chk_repeat_data(CompressDtail *dtail)
{
if ( is_last_byte_allow_repeat(dtail) ) {
return dtail->last_byte;
}
else if( is_last_two_byte_allow_repeat(dtail) ) {
return dtail->last_two_byte;
}
else {
return NULL;
}
}
static void convert2compress_byte(StateByte *last_byte, StateByte *now_byte,
state_mode_state_t mode, StatePackage *package)
{
last_byte->value = (mode << 2) | 0x03;
now_byte->value &= 0x00;
now_byte->value |= 0x08;
last_byte->is_compress_byte = true;
now_byte->is_compress_byte = true;
package->bit_idx = 0;
}
static void reset_used_byte(StateByte *byte, StatePackage *package)
{
byte->value |= 0xFF;
package->bit_idx = 0;
package->byte_idx--;
}
static void convert2comp_byte4last_byte(CompressDtail *dtail)
{
assign_state_mode(dtail->last_two_byte, dtail->cur_bit_idx, state_FLAG_STATE);
assign_state_mode(dtail->last_two_byte, (dtail->cur_bit_idx + 1), dtail->comp_mode);
dtail->last_byte->value &= 0x00;
dtail->last_byte->value |= 0x08;
dtail->last_two_byte->is_compress_byte = true;
dtail->last_byte->is_compress_byte = true;
}
static void compress_repeat_data(CompressDtail *dtail, StatePackage *package)
{
MZ_UI_ASSERT(dtail->cur_bit_idx <= 3, UI_SENSOR_DEBUG);
if (dtail->cur_byte == dtail->last_byte && dtail->cur_bit_idx == 0) {
convert2compress_byte(dtail->last_byte, dtail->now_byte, dtail->comp_mode, package);
}
else if(dtail->cur_byte == dtail->last_two_byte) {
if (dtail->cur_bit_idx < 3) {
convert2comp_byte4last_byte(dtail);
reset_used_byte(dtail->now_byte, package);
}
else {
assign_state_mode(dtail->last_byte, dtail->cur_bit_idx, state_FLAG_STATE);
convert2compress_byte(dtail->last_byte, dtail->now_byte, dtail->comp_mode, package);
}
}
}
static inline uint8_t get_cur_bit_mode(StateByte *byte, uint8_t cur_bit_idx)
{
return (byte->value >> (cur_bit_idx * 2) & 0x03);
}
static bool operate_compressed_byte(CompressDtail *comp_dtail, StatePackage *package)
{
for(int i = 0; i < state_BYTE_MAX_BIT; ++i) {
uint8_t cur_bit_mode = get_cur_bit_mode(comp_dtail->last_byte, i);
if (cur_bit_mode != state_FLAG_STATE) continue;
cur_bit_mode = get_cur_bit_mode(comp_dtail->last_byte, i + 1);
if (comp_dtail->comp_mode == cur_bit_mode && comp_dtail->now_byte->value < 0xFF) {
comp_dtail->now_byte->value += 1;
break;
}
else {
if ( is_package_full(++package->byte_idx) ) return false;
comp_dtail->now_byte = &package->data[package->byte_idx];
compress_seq_data(comp_dtail->now_byte, package, comp_dtail->comp_mode);
}
}
return true;
}
static bool is_cur_mode_repeat(CompressDtail *comp_dtail, StatePackage *package)
{
while(1) {
if (comp_dtail->cur_bit_idx == 0){
comp_dtail->cur_byte = get_last_byte2chk_repeat_data(comp_dtail);
if (comp_dtail->cur_byte == NULL) return false;
comp_dtail->cur_bit_idx = 3;
}
else {
comp_dtail->cur_bit_idx--;
}
uint8_t cur_bit_mode = get_cur_bit_mode(comp_dtail->cur_byte, comp_dtail->cur_bit_idx);
if(comp_dtail->comp_mode != cur_bit_mode) {
return false;
}
else if (++comp_dtail->repeat_num == 8) {
break;
}
}
return true;
}
static inline bool is_compress_seq_data(CompressDtail *comp_dtail, StatePackage *package)
{
return comp_dtail->last_byte == NULL || comp_dtail->last_byte->is_compress_byte
|| !is_cur_mode_repeat(comp_dtail, package);
}
static bool compress_data_byRLC(StatePackage *package, uint16_t package_idx, state_mode_state_t cur_mode)
{
CompressDtail comp_dtail;
comp_dtail.now_byte = &package->data[package->byte_idx];
comp_dtail.last_byte = get_last_byte(package, package_idx);
comp_dtail.last_two_byte = get_last_two_byte(package, package_idx);
comp_dtail.cur_byte = comp_dtail.now_byte;
comp_dtail.cur_bit_idx = package->bit_idx;
comp_dtail.package_idx = package_idx;
comp_dtail.comp_mode = cur_mode;
comp_dtail.repeat_num = 1;
if (comp_dtail.last_byte != NULL && comp_dtail.now_byte->is_compress_byte) {
return operate_compressed_byte(&comp_dtail, package);
}
if ( is_compress_seq_data(&comp_dtail, package) ) {
compress_seq_data(comp_dtail.now_byte, package, cur_mode);
}
else {
compress_repeat_data(&comp_dtail, package);
}
return true;
}
static uint16_t push_data2package(uint16_t package_idx, uint16_t begin, uint16_t end, short is_get_today_data)
{
StatePackage *package = &state_dtail.package[package_idx];
uint16_t cursor = end;
while ( package->byte_idx < state_PACKAGE_MAX_LEN && cursor > begin ) {
state_mode_state_t cur_mode = get_state_mode(--cursor, is_get_today_data);
if ( cur_mode == state_ERR_STATE ) {
return 0xFFFF;
}
compress_data_byRLC(package, package_idx, cur_mode);
}
return cursor;
}
static inline bool is_push_end(uint16_t cur_idx)
{
return (cur_idx <= upload_start_state_idx);
}
static bool is_push_continually(uint16_t cur_idx, uint16_t *upload_end_state_idx, short *is_get_today_data)
{
if ( !is_push_end(cur_idx) ) {
*upload_end_state_idx = cur_idx;
}
else {
if ( !is_push_next_day_data(*is_get_today_data) ) return false;
*is_get_today_data = 1;
*upload_end_state_idx = calculate_cur_state_idx();
upload_start_state_idx = 0;
}
return true;
}
static inline bool is_record_start_idx(uint16_t upload_end_state_idx)
{
return ( (cur_req_state_dtail_state == REQ_STATE_DTAIL_TODAY
|| cur_req_state_dtail_state == REQ_STATE_DTAIL_YESTERDAY)
&& upload_end_state_idx < 720);
}
static bool push_data2state_dtail(uint16_t upload_end_state_idx, short is_get_today_data)
{
uint16_t package_idx = 0, res = 0;
uint16_t cur_end_idx = upload_end_state_idx;
init_state_package(&state_dtail.package[package_idx]);
while( package_idx < state_PACKAGE_MAX_NUM ) {
if ( is_package_full(state_dtail.package[package_idx].byte_idx) ) {
state_dtail.package[package_idx].byte_idx--;
init_state_package(&state_dtail.package[++package_idx]);
}
res = push_data2package(package_idx, upload_start_state_idx,
cur_end_idx, is_get_today_data);
if ( !is_push_success(res) ) return false;
if ( !is_push_continually(res, &cur_end_idx, &is_get_today_data) ) break;
}
if ( is_record_start_idx(upload_end_state_idx) ) {
upload_start_state_idx = upload_end_state_idx;
}
return true;
}
static inline bool is_upload_type_valid(void)
{
return (cur_req_state_dtail_state != REQ_state_DTAIL_ERR);
}
static void init_upload_type(uint16_t *upload_end_state_idx, short *is_get_today_data)
{
switch(cur_req_state_dtail_state) {
case NEVER_REQ_STATE_DTAIL:
*upload_end_state_idx = 720;
*is_get_today_data = 0;
upload_start_state_idx = 0;
break;
case REQ_STATE_DTAIL_TODAY:
MZ_UI_ASSERT(upload_start_state_idx > 0, UI_SENSOR_DEBUG);
*upload_end_state_idx = calculate_cur_state_idx();
*is_get_today_data = 1;
break;
case REQ_STATE_DTAIL_YESTERDAY:
MZ_UI_ASSERT(upload_start_state_idx > 0, UI_SENSOR_DEBUG);
*upload_end_state_idx = 720;
*is_get_today_data = 0;
break;
default:
break;
}
}
StateDetail *get_state_dtail(void)
{
if ( is_state_dtail_activate ) return &state_dtail;
uint16_t upload_end_state_idx;
short is_get_today_data;
if (!is_upload_type_valid() ) return NULL;
init_upload_type(&upload_end_state_idx, &is_get_today_data);
if ( is_push_end(upload_end_state_idx) && is_get_today_data) return NULL;
bool is_success = push_data2state_dtail(upload_end_state_idx, is_get_today_data);
if (!is_success) return NULL;
cur_req_state_dtail_state = REQ_STATE_DTAIL_TODAY;
is_state_dtail_activate = true;
return &state_dtail;
}
void destroy_state_dtail(void)
{
state_dtail.len = 0;
is_state_dtail_activate = false;
for ( int i = 0; i < state_dtail.len; ++i ) {
if (state_dtail.package[i].data != NULL) {
vPortFree(state_dtail.package[i].data);
}
}
}