基于 游程编码 与 哈夫曼编码 的状态数据压缩分析与实现

最近在做智能穿戴设备的项目,需要将一些状态数据集合传输回APP端,由于数据集合稍大,如果原封不动地将集合传输过去,功耗/速度都达不到要求,于是本猿跟同事讨论了下,决定对数据进行压缩,再进行传输。

又因为数据量仅仅是"稍大",而且"稍大"的概念,也只是单片机级的"稍大",所以也不打算引入开源的压缩库来实现,仅仅是选取了最基础入门的两种压缩算法,来进行分析与实现。

没错,这两种算法分别是游程编码 & 哈夫曼编码, 下面是我对这两种方案的分析:

一. 哈夫曼编码压缩方案


如果你已经忘记啥是哈夫曼树了, 先看下陈皓大神的文章, 回忆一下哈!

压缩流程简介

  1. 格式为小端格式,从低位向高位遍历,录入 / 获取状态数据;
  2. 粗略估计状态模式的出现频率,建立哈夫曼二叉树,算出每种状态模式类别的哈夫曼编码
  3. 固件端建立状态哈夫曼表,根据表录入状态数据
  4. apk端,建立二叉树结构,逐位获取数据,在二叉树上生成查询路径,得到相应的哈夫曼编码数据
  5. 每个字节可存四个状态, 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次,末尾要加结束位。

实现流程

  1. malloc并初始化状态数据, 每个字节默认数据值为FF;
  2. 建立huffman表,保存每种状态模式类别的哈夫曼编码、需做压缩时的重复次数以及哈夫曼编码的位数;
  3. 从sensor中获取状态模式,查询huffman表,获取相应的压缩重复次数以及哈夫曼编码的位数;
  4. 通过获取到的哈夫曼编码的位数N,向上遍历N个bit,与获取到的状态模式进行比较;
  5. 如果相等,重复次数加1, 否则,直接写入;
  6. 如果重复次数达到huffman表中获取的压缩重复次数时,则进行压缩;
  7. 进行压缩后,需递归判断是否满足下一层级的压缩要求,如果是,则继续压缩;
  8. 重复6,7,直至获取完所有状态数据;
  9. 将数据传给apk,free掉压缩状态数据.

实现细节

  1. 开始压缩第一个状态数据时(即package_idx和byte_idx均为0时),直接写入;
  2. 状态数据编码位数不确定,导致bit_idx, byte_idx的增加不确定;
  3. 写入所有状态数据后,需在末尾处加入结束位;
  4. 判断是否需要进行重复压缩时,需注意package与package间,byte与byte间的边界处理;

二. 游程编码压缩方案


压缩流程简介

  1. 格式为小端格式,两个bit作为基本单位,从低位向高位遍历,录入 / 获取状态数据;
  2. 如果连续出现8个相同的状态,则进行压缩;
  3. 将8个相同的状态,转化为标记位压缩格式;
  4. 每个字节可存四个状态, 16个字节组成一个状态包。

状态类别定义

状态类别 两位二进制编码
状态A 00
状态B 01
状态C 10
标记位 11

标记位说明

  1. 如果出现在存储字节的最高单位,则表明该字节无需继续读取, 直接读下一个字节;

  2. 如果不出现在存储字节的最高单位;

    • 该标记位的下一个单位, 是重复状态值
    • 不需要继续解析该字节,转而解析下一个字节,获取状态值的重复次数
  3. 如果连续出现两个标记位,说明可以结束获取状态数据了

举个栗子

出现状态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次可以作为一个临界点,用其作为压缩起点,可达最优。

实现流程

  1. malloc并初始化压缩状态数据, 每个字节默认数据值为FF;
  2. 验证请求类型,并根据请求类型, 计算需要上传的状态数据量;
  3. 检测与处理状态数据空包的情况;
  4. 初始化第一个状态包,依次从sensor中将获取到的状态数据,压缩存入包中;
  5. 如包满,则动态初始化下一个状态包,继续4操作,直至数据获取完毕;
  6. 将数据传给apk,free掉压缩数据

注: 压缩过程中记录下当前游标位置 / 当前字节、位的索引 以及当前游标字节的类型(是否为压缩字节),以判断是直接插入状态数据,还是数据已经满足重复条件,须进行压缩转换存储;

实现细节

  1. package与package之间, byte与byte之间的边界处理
  2. 判断是否上传昨天数据
  3. 未push完睡眠数据,package满与byte满的处理问题
  4. 是否记录上传位置
  5. 从sensor中获取具体的状态数据
  6. 重复状态数据跨越三个字节时的压缩处理, 更甚的是, 跨三个字节,而且还跨包的处理;
  7. 根据实际情况动态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);
        }
    }
}

推荐阅读更多精彩内容