文章目錄
- 前言
- 一、數據流架構
- 二、具體實現講解
- 1.命令解析層
- 2.配置處理層
前言
最近工作中遇到一個需求,在開發一個產品時,需要通過app去配置產品的一些功能開關、參數等,然後在這個同時,還可以通過電腦端應用去配置產品的功能開關、參數等,然後它們配置的flash有的是相同的,有的是不同的。另外這些參數配置,還可以通過用户手動組合鍵觸發去修改。
另外,app和電腦端應用的協議在同系列產品中是共用的,但是配置的flash存儲方式在產品中又不能固定下來。
當我想實現app、電腦應用的協議的模塊化時,我又沒法繞過flash配置,除非我app用一套固定的flash存儲結構,電腦應用也用一套固定的flash存儲結構。嗯,這是我之前的想法。
後來就想啊想。想法就從生活中來了。
我app的協議是固定的,每條協議應該配置什麼是固定的,那麼我就給app一套獨立的flash抽象結構,這好比一種語言,比如假設這是“英語”
我電腦應用端的協議也是固定的,每條協議應該配置什麼是固定的,那麼我就給電腦應用端一套獨立的flash抽象結構,這也好比一種語言,比如假設這是“法語”
ok,我實際用户的業務邏輯中的存儲配置是另一套,而且每個產品項目可能不同,假設這可能是中文,可能是粵語…
那麼我只需要在app協議的框架中,抽象出一個翻譯器,讓用户自己實現從中文轉換為英文,或從粵語轉換成英文。電腦端應用也是如此。
這樣我就無需關注或統一具體的業務邏輯底層的flash配置,而實現了將app、電腦應用、實際底層存儲三者之間統一起來。而且對app和電腦應用端的協議解析框架實現瞭解耦。
簡直完美!
開講,代碼隱去具體實現細節,只講解框架思路哈!
一、數據流架構
二、具體實現講解
1.命令解析層
命令解析層將數據流解析出來,輸出完整的一幀及長度,具體的實現細節可以參考我的一套開源模塊b_protocol_core,極其好用,我起碼在十幾個大大小小的不同格式協議的項目中應用了,兼容性相對還闊以。這裏就不細講了。總之就是獲取到完整的一幀數據及長度。
然後,丟到配置處理層去解析具體的命令
2.配置處理層
就我目前的項目需求而言,設計的命令處理函數原型如下
// 主配置數據結構,統一當前協議要操作的存儲數據及其格式,由具體協議而定。
typedef struct {
// 設備基礎信息
uint8_t mac[6];
uint16_t hardware_version;
uint16_t firmware_major;
uint16_t firmware_minor;
char device_name[21];
uint8_t par1;
uint8_t par2;
....
} flash_raw_data_t;
// 命令處理函數類型
typedef result_t (*cmd_handler_t)(flash_raw_data_t *p_flash,
void* cmd_data, uint16_t len,
uint8_t* response, uint16_t* resp_len);
// 命令處理項
typedef struct {
uint8_t main_cmd;
cmd_handler_t handler;
const char* cmd_name;
} cmd_entry_t;
// 框架入口函數
extern result_t cmd_framework_encode(uint8_t* data, uint16_t len);
將我們通過命令解析層解析出的一幀完整數據,交付給cmd_framework_encode函數進行處理,這個函數就負責翻譯存儲數據及命令分發匹配,匹配到的命令就按照協議統一的flash數據格式處理,處理之後在將存儲數據翻譯回去。
嗯,flash翻譯操作的思路大概是讀改寫。
我們來看具體的實現
首先三個弱函數
// 弱定義的數據發送接口
__weak result_t data_sender(uint8_t* data, uint16_t len)
{
// 用户需要實現此函數來發送數據,用於響應數據發送
return GS_SUCCESS;
}
// 弱定義的配置數據加載接口
__weak uint8_t flash_data_load(uint8_t profile_id, flash_raw_data_t *p_flash)
{
// 用户需要實現此函數來從實際存儲加載數據到flash_raw_data_t結構體
//由用户根據自己項目實際實現,所以可以很靈活..
p_flash->hardware_version = HW_VERSION;
p_flash->firmware_major = SW_VERSION;
p_flash->par1 = user_flash.parxxx;//user_flash及具體的flash存儲數據。
return GS_SUCCESS;
}
// 弱定義的配置數據保存接口
__weak uint8_t flash_data_save(uint8_t profile_id, flash_raw_data_t *p_flash)
{
// 用户需要實現此函數來保存gs_flash_raw_data_t結構體數據到實際存儲
user_flash.parxxx = p_flash->par1;
return GS_SUCCESS;
}
上面三個弱函數框架本身並不關心,完全交給用户實現。
flash_data_load就好比把用户業務邏輯的存儲格式翻譯成當前協議框架的存儲格式。相當於一個翻譯官,也是這個框架思路的核心思想。
同理,flash_data_save就是把協議框架存儲格式翻譯成用户業務邏輯的具體存儲格式。同樣相當於一個翻譯官。
一個英譯漢,一個漢譯英。
一箇中國人不會英語,一個英國人不會中文。怎麼讓他們兩個交流呢?那就是引入一個翻譯啦。是不是,所以敲代碼也來自與生活,哈哈。
//命令表,綁定命令和具體的執行回調函數。
static const cmd_entry_t cmd_table[] = {
{MAIN_CMD_MISC, misc_device_info_opt, "MISC_DEVICE_INFO"},
....
};
result_t cmd_framework_encode(uint8_t* data, uint16_t len)
{
frame_header_t *header = (frame_header_t *)data;
uint8_t main_cmd = header->main_cmd & 0x7F; // 取主命令, 假如最高位1/0代表設置/查詢
uint8_t profile_id = header->profile_id ;//支持多套參數修改
result_t result = ERR_UNSUPPORTED_CMD;
// 查找命令處理函數
for (uint16_t i = 0; i < sizeof(cmd_table)/sizeof(cmd_entry_t); i++){
if (cmd_table[i].main_cmd == main_cmd) {
uint8_t response[64];
uint16_t resp_len = 0;
printf("%s\r\n",gs_cmd_table[i].cmd_name);
//申請一個flash空間,可以堆分配也可以棧分配。如果結構體內容比較大,堆應該安全一些,如果用堆在flash_data_save之後記得釋放。
flash_raw_data_t flash_data;
//把用户的flash存儲格式通過翻譯器,翻譯成協議統一的存儲格式
flash_data_load(profile_id, &flash_data);
//匹配到命令,調用綁定的命令處理函數
//通過協議統一的存儲格式去處理flash數據。
result = gs_cmd_table[i].handler(
&flash_data,
data,len,
response, &resp_len
);
if(result == GS_SUCCESS) {
//處理成功之後,比如有修改配置,則把flash數據再翻譯回用户的存儲格式
flash_data_save(profile_id, p_flash_data);
}else{
printf("%s err %d",cmd_table[i].cmd_name, result);
}
// 發送響應數據
if (result == GS_SUCCESS && resp_len > 0) {
result_t send_result = data_sender(response, resp_len);
if (send_result != GS_SUCCESS) {
result = send_result;
}
}
break;
}
}
return result;
}
再拿個具體的命令講解,這個是查詢flash數據。修改也是類似的操作。只要根據命令去修改p_flash的內容即可。
// 雜項配置 - 設備基本信息 (0x00)
result_t misc_device_info_opt(flash_raw_data_t *p_flash,
void* cmd_data, uint16_t len,
uint8_t* response, uint16_t* resp_len)
{
cmd_device_info_t *cmd_info = (cmd_device_info_t *)cmd_data;
cmd_device_info_t *resp_info= (cmd_device_info_t *)response;
memcpy(cmd_info , resp_info, sizeof(cmd_device_info_t ))
// 填充設備信息
resp_info->hardware_version = p_flash->hardware_version;
resp_info->firmware_major = p_flash->firmware_major;
memcpy(resp_info->device_name, p_flash->device_name, sizeof(p_flash->device_name));
// 計算校驗和
response->checksum = check_sum(response, sizeof(cmd_device_info_t ) - 1);
*resp_len = resp_header->length;
return GS_SUCCESS;
}