From 3b00e655c83620ace05fb0f980a46b2bce653136 Mon Sep 17 00:00:00 2001 From: luozheng <1732036298@qq.com> Date: Fri, 12 Dec 2025 18:45:46 +0800 Subject: [PATCH] =?utf8?q?fd272:1.=E8=A7=A3=E5=86=B3=E4=B8=8B=E6=8B=89?= =?utf8?q?=E7=95=8C=E9=9D=A2=E8=BF=9E=E6=8E=A5=E7=8A=B6=E6=80=81=E5=9B=BE?= =?utf8?q?=E6=A0=87=EF=BC=8C2.=E6=B7=BB=E5=8A=A0=E6=98=9F=E5=8E=86?= =?utf8?q?=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../src/EphemerisDownloadPresenter.cpp | 9 +- .../nativeui/main/src/PullUpDownView.cpp | 3 + .../msg_center/src/msg_center_customer.cpp | 60 ++-- .../msg_center/src/notify/transmitting.cpp | 177 +++++++++- .../msg_center/src/notify/transmitting.cpp | 1 - .../ble_service/blemoder/bleInstruct.h | 4 + .../ble_service/bleservice/bleEvent.c | 311 +++++++++++++++++- .../ble_service/bleservice/bleEvent.h | 5 +- code/sdk/zhihui/utils/custom_runtime.c | 24 ++ code/sdk/zhihui/utils/custom_runtime.h | 9 + code/sdk/zhihui/utils/custom_utils.h | 8 +- code/sdk/zhihui/utils/custom_utils_at_cmd.c | 31 ++ code/sdk/zhihui/utilscpp/custom_utilscpp.cpp | 249 ++++++++++++++ code/sdk/zhihui/utilscpp/custom_utilscpp.h | 1 + tools/scripts/epo/1765447066.lzma | Bin 0 -> 90463 bytes tools/scripts/epo/EPO_LZMA_TEST.md | 184 +++++++++++ ...14\350\257\201\346\265\201\347\250\213.md" | 198 +++++++++++ ...74\345\274\217\350\257\264\346\230\216.md" | 200 +++++++++++ ...64\351\203\250\347\244\272\344\276\213.md" | 187 +++++++++++ tools/scripts/epo/analyze_lzma.py | 100 ++++++ tools/scripts/epo/create_epo_lzma.py | 206 ++++++++++++ tools/scripts/epo/epo.lzma | Bin 0 -> 154 bytes tools/scripts/epo/epo_decompress.py | 178 ++++++++++ tools/scripts/epo/temp_packed_data.bin | Bin 0 -> 417 bytes 24 files changed, 2095 insertions(+), 50 deletions(-) create mode 100644 tools/scripts/epo/1765447066.lzma create mode 100644 tools/scripts/epo/EPO_LZMA_TEST.md create mode 100644 "tools/scripts/epo/EPO_LZMA_\351\252\214\350\257\201\346\265\201\347\250\213.md" create mode 100644 "tools/scripts/epo/LZMA_\345\244\264\351\203\250\346\240\274\345\274\217\350\257\264\346\230\216.md" create mode 100644 "tools/scripts/epo/LZMA_\345\244\264\351\203\250\347\244\272\344\276\213.md" create mode 100644 tools/scripts/epo/analyze_lzma.py create mode 100644 tools/scripts/epo/create_epo_lzma.py create mode 100644 tools/scripts/epo/epo.lzma create mode 100644 tools/scripts/epo/epo_decompress.py create mode 100644 tools/scripts/epo/temp_packed_data.bin diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/ephemeris_download/src/EphemerisDownloadPresenter.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/ephemeris_download/src/EphemerisDownloadPresenter.cpp index 27272f13e..0e3d28aed 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/ephemeris_download/src/EphemerisDownloadPresenter.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/ephemeris_download/src/EphemerisDownloadPresenter.cpp @@ -12,6 +12,10 @@ #include "NativeRegisterManager.h" #include "wearable_log.h" +#ifdef SUPPORT_BLE_SERVICE +#include "bleEvent.h" +#endif + namespace OHOS { REGIST_SLICE(VIEW_EPHEMERIS_DOWNLOAD, EphemerisDownloadView, EphemerisDownloadPresenter); @@ -43,7 +47,10 @@ bool EphemerisDownloadPresenter::OnClick(UIView &view, //点击下载按键 if (strcmp(view.GetViewId(), DOWNLOAD_ID) == 0) { //进行下载 - EphemerisDownloadModel::GetInstance().DownloadEphemeris(); + // EphemerisDownloadModel::GetInstance().DownloadEphemeris(); + #ifdef SUPPORT_BLE_SERVICE + ble_request_epo_file(); + #endif } //点击返回按键 else if (strcmp(view.GetViewId(), BACK_ID) == 0) { diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/main/src/PullUpDownView.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/main/src/PullUpDownView.cpp index c39394fc2..3bd779ad4 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/main/src/PullUpDownView.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/main/src/PullUpDownView.cpp @@ -136,6 +136,9 @@ namespace OHOS return; } + if (status) + showPhoneConnectedStatus->SetSrc(DROPDOWN_PHONECONNECTED_IMAGE); + else showPhoneConnectedStatus->SetSrc(DROPDOWN_PHONEDISCONNECTED_IMAGE); if (GetConnectedStatusView() != nullptr) diff --git a/code/sdk/application/wearable/fd272/service/msg_center/src/msg_center_customer.cpp b/code/sdk/application/wearable/fd272/service/msg_center/src/msg_center_customer.cpp index f09ea4f8a..1463e8c42 100644 --- a/code/sdk/application/wearable/fd272/service/msg_center/src/msg_center_customer.cpp +++ b/code/sdk/application/wearable/fd272/service/msg_center/src/msg_center_customer.cpp @@ -558,37 +558,37 @@ void MsgCenterNotifybleTransmit(const Topic topic, Request* req) { - #ifdef SUPPORT_BLE_SERVICE - unused(topic); - uint8_t* data=(uint8_t*)req->data; + #ifdef SUPPORT_BLE_SERVICE + unused(topic); + uint8_t* data=(uint8_t*)req->data; + WEARABLE_LOGI(WEARABLE_LOG_MODULE_APP, "MsgCenterNotifybleTransmit:%d",data[0]); + - - if(data[0]==TRANS_FREE) - { - if(transmittingISShow()) - NotificationManager::GetInstance()->StopNotify(); - } - else if (data[0]==IS_NEED_CAT1_OTA) - { - - NotificationManager::GetInstance()->SetNotifyPriority(PRIORITY_ONE); - - // NotificationManager::GetInstance()->ShowNotify(showCat1UpdataNotify, (uint16_t)topic, true); - } - else if (data[0]==FILE_IS_DONE) - { - set_transmittingShow_falg(data[0]); - } - else - { - set_transmittingShow_falg(data[0]); - - NotificationManager::GetInstance()->SetNotifyPriority(PRIORITY_ONE); - - NotificationManager::GetInstance()->ShowNotify(showtransmitNotify, (uint16_t)topic, true); - } - // unused(req); - #endif + if(data[0]==TRANS_FREE) + { + if(transmittingISShow()) + NotificationManager::GetInstance()->StopNotify(); + } + else if (data[0]==IS_NEED_CAT1_OTA) + { + + NotificationManager::GetInstance()->SetNotifyPriority(PRIORITY_ONE); + + // NotificationManager::GetInstance()->ShowNotify(showCat1UpdataNotify, (uint16_t)topic, true); + } + else if (data[0]==FILE_IS_DONE) + { + set_transmittingShow_falg(data[0]); + } + else + { + set_transmittingShow_falg(data[0]); + + NotificationManager::GetInstance()->SetNotifyPriority(PRIORITY_ONE); + + NotificationManager::GetInstance()->ShowNotify(showtransmitNotify, (uint16_t)topic, true); + } + #endif } void MsgCenterNotifyChargePlugProc(const Topic topic, Request* req) diff --git a/code/sdk/application/wearable/fd272/service/msg_center/src/notify/transmitting.cpp b/code/sdk/application/wearable/fd272/service/msg_center/src/notify/transmitting.cpp index 7d3856480..ab3ed1282 100644 --- a/code/sdk/application/wearable/fd272/service/msg_center/src/notify/transmitting.cpp +++ b/code/sdk/application/wearable/fd272/service/msg_center/src/notify/transmitting.cpp @@ -1259,6 +1259,167 @@ namespace OHOS } + + void creat_down_EPO() + { + printf("creat_down_EPO\n"); + if (Title == nullptr) { + Title = new UILabelExt(); + } + Title->SetPosition(158, 51); + Title->Resize(800, 88); + Title->SetViewId("label1"); + Title->SetStyle(STYLE_TEXT_FONT, 30); + + Title->SetText("星历下载中"); + Title->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + Title->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + Title->SetRollSpeed(0); + Title->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + // Add to rootView + UIViewGroup_transmitting->Add(Title); + Title->SetVisible(true); + + + if (label_ == nullptr) { + label_ = new UILabelExt(); + } + label_->SetPosition(60, 281); + label_->Resize(345, 101); + label_->SetStyle(STYLE_TEXT_FONT, 30); + // Set additional property + label_->SetText("正在下载星历,请不要做其他操作或关闭手表"); + label_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + label_->SetTextColor(Color::GetColorFromRGBA(0xAA, 0xAA, 0xAA, 0xff)); + label_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + label_->SetRollSpeed(0); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + // Add to rootView + UIViewGroup_transmitting->Add(label_); + label_->SetVisible(true); + + CreatetransmittingAnimator(); + + UIViewGroup_transmitting->Add(downImageAnimatorView); + } + + void creat_down_EPO_succ() + { + printf("creat_down_EPO_succ\n"); + if (Title == nullptr) { + Title = new UILabelExt(); + } + Title->SetPosition(173, 51); + Title->Resize(800, 88); + Title->SetStyle(STYLE_TEXT_FONT, 30); + + Title->SetText("星历下载成功"); + Title->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + Title->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + Title->SetRollSpeed(0); + Title->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + Title->SetX((466 - Title->GetWidth()) / 2); + + // Add to rootView + UIViewGroup_transmitting->Add(Title); + Title->SetVisible(true); + + + if (label_ == nullptr) { + label_ = new UILabelExt(); + } + label_->SetPosition(60, 281); + label_->Resize(345, 101); + label_->SetViewId("label2"); + label_->SetStyle(STYLE_TEXT_FONT, 30); + + // Set additional property + label_->SetText("星历下载成功,请稍等..."); + label_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + label_->SetTextColor(Color::GetColorFromRGBA(0xAA, 0xAA, 0xAA, 0xff)); + label_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + label_->SetRollSpeed(0); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + if (transmitting_img == nullptr) + { + transmitting_img = new UIImageView(); + } + transmitting_img->SetPosition(193, 150,80, 80); + const char *imagesPath = TRANSMITTING_SUCC_PATH; + transmitting_img->SetSrc(imagesPath); + UIViewGroup_transmitting->Add(transmitting_img); + + uint32_t tick; + osStatus_t retTimer; + if(transmitting_timer!=NULL) + { + osTimerStop(transmitting_timer); + osTimerDelete(transmitting_timer); + transmitting_timer = NULL; + } + transmitting_timer = osTimerNew((osTimerFunc_t)dialtimerCallBack, osTimerPeriodic, NULL, NULL); + tick =3000;// osMs2Tick(1000); + retTimer = osTimerStart(transmitting_timer, tick); + if (retTimer != osOK && transmitting_timer != NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"StartCallTimer osTimerStart failed, ret = %lx!!", retTimer); + osTimerDelete(transmitting_timer); + transmitting_timer = NULL; + return ; + } + + // Add to rootView + UIViewGroup_transmitting->Add(label_); + label_->SetVisible(true); + } + + void creat_down_EPO_fail() + { + printf("creat_down_EPO_fail\n"); + if (Title == nullptr) + { + Title = new UILabelExt(); + } + Title->SetPosition(173, 51); + Title->Resize(800, 88); + Title->SetViewId("label1"); + Title->SetStyle(STYLE_TEXT_FONT, 30); + + // Set additional property + Title->SetText("星历下载失败"); + Title->SetLineBreakMode(UILabel::LINE_BREAK_ADAPT); + Title->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + Title->SetRollSpeed(0); + Title->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + Title->SetX((466 - Title->GetWidth()) / 2); + + // Add to rootView + UIViewGroup_transmitting->Add(Title); + Title->SetVisible(true); + + + if (label_ == nullptr) + { + label_ = new UILabelExt(); + } + label_->SetPosition(60, 281); + label_->Resize(345, 101); + label_->SetStyle(STYLE_TEXT_FONT, 30); + Title->SetText("星历下载失败,请重新下载"); + + + label_->SetLineBreakMode(UILabel::LINE_BREAK_WRAP); + label_->SetTextColor(Color::GetColorFromRGBA(0xAA, 0xAA, 0xAA, 0xff)); + label_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); + label_->SetRollSpeed(0); + label_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); + + UIViewGroup_transmitting->Add(label_); + label_->SetVisible(true); + } bool transmittingISShow(void) { @@ -1280,8 +1441,8 @@ namespace OHOS { UIViewGroup_transmitting->RemoveAll(); } - uapi_dev_runtime_set_transmitting_status(1); - + uapi_dev_runtime_set_transmitting_status(1); + printf("Transmitting_falg:%d",Transmitting_falg); #ifdef SUPPORT_BLE_SERVICE switch (Transmitting_falg) { @@ -1334,6 +1495,15 @@ namespace OHOS case TRANS_BIN_FAIL: creat_down_BIN_fail(); break; + case TRANS_EPO_TRANSMITTING: + creat_down_EPO(); + break; + case TRANS_EPO_SUCC: + creat_down_EPO_succ(); + break; + case TRANS_EPO_FAIL: + creat_down_EPO_fail(); + break; default: break; @@ -1399,7 +1569,8 @@ namespace OHOS && Transmitting_falg!=TRANS_OTA_TRANSMITTING && Transmitting_falg!=TRANS_MP3_TRANSMITTING && Transmitting_falg!=START_CAT1_OTA - && Transmitting_falg!=TRANS_BIN_TRANSMITTING) + && Transmitting_falg!=TRANS_BIN_TRANSMITTING + && Transmitting_falg!=TRANS_EPO_TRANSMITTING) { NotificationManager::GetInstance()->StopNotify(); } diff --git a/code/sdk/application/wearable/n9/service/msg_center/src/notify/transmitting.cpp b/code/sdk/application/wearable/n9/service/msg_center/src/notify/transmitting.cpp index 908585909..ad60d724e 100644 --- a/code/sdk/application/wearable/n9/service/msg_center/src/notify/transmitting.cpp +++ b/code/sdk/application/wearable/n9/service/msg_center/src/notify/transmitting.cpp @@ -65,7 +65,6 @@ namespace OHOS static void CreatetransmittingAnimator(void) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ShutDownAnimatorView::ShowUpShutDownAnimatorView"); imageInfo_dwon = CreateImageInfoArray(ShutDownPaths,SHUTDOWN_IMAGE_ANIMATOR_VIEW_NUM_FRAMES, "bin"); if (downImageAnimatorView == nullptr) diff --git a/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.h b/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.h index 60dfcb1b5..e05c83d27 100644 --- a/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.h +++ b/code/sdk/zhihui/services/ble_service/blemoder/bleInstruct.h @@ -59,6 +59,10 @@ extern "C" IS_NEED_CAT1_OTA, TRANS_FREE, + + TRANS_EPO_TRANSMITTING, + TRANS_EPO_SUCC, + TRANS_EPO_FAIL, } transmitting_type; diff --git a/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.c b/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.c index 392909082..627a5bb65 100644 --- a/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.c +++ b/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.c @@ -20,6 +20,7 @@ extern "C" { #include "wearable_log.h" #include #include +#include #include "stdbool.h" #include "bts_br_gap.h" @@ -129,6 +130,9 @@ ble_read_data* ble_read_timer_p=NULL; #define UP_DATE_FWPKG_PATH UPG_FILE_NAME #endif +//gnss文件目录 +#define GNSS_DATA_DIR "/user/userdata/gnss" + //MP3文件重命名 static errcode_t create_mp3_name(char* path,char*dir_path ,char* file_name) @@ -265,6 +269,34 @@ static errcode_t get_app_file_path(char* input_filename ,uint32_t input_size, ch input_filename, output_file_path); return ERRCODE_SUCC; } + +static errcode_t get_epo_file_path(char* input_filename ,uint32_t input_size, char* output_file_path , uint32_t max_output_size) +{ + if (input_filename == NULL || output_file_path == NULL) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[BLE_EVENT_ERROR] get_epo_file_path: NULL pointer detected - input_filename=%p, output_file_path=%p", + input_filename, output_file_path); + return ERRCODE_FAIL; + } + + if (input_size == 0 || max_output_size == 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[BLE_EVENT_ERROR] get_epo_file_path: Invalid size parameters - input_size=%u, max_output_size=%u", + input_size, max_output_size); + return ERRCODE_FAIL; + } + + uapi_create_dir(GNSS_DATA_DIR); + + int ret = sprintf_s(output_file_path, max_output_size, "%s/%s", GNSS_DATA_DIR, input_filename); + if (ret < 0) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[BLE_EVENT_ERROR] get_epo_file_path: sprintf_s failed - ret=%d, filename='%s', max_size=%u", + ret, input_filename, max_output_size); + return ERRCODE_FAIL; + } + + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[BLE_EVENT_DEBUG] get_epo_file_path: Success - input='%s', output='%s'", + input_filename, output_file_path); + return ERRCODE_SUCC; +} static errcode_t get_custom_dial_file_path(uint16_t id,char *filename,uint32_t input_size, char* output_file_path , uint32_t max_output_size) { if( id >=5 || output_file_path == NULL || filename == NULL) @@ -471,6 +503,7 @@ errcode_t sendBleData_zhihui(uint8_t *buf,uint16_t len) #ifdef BLE_LOG WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"bigDataBufState:%d and sendbigDataBufState %d\r\n",bigDataBufState,sendbigDataBufState); #endif + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"bigDataBufState:%d and sendbigDataBufState %d\r\n",bigDataBufState,sendbigDataBufState); //if(Bleconnect == true ) { ret = svr_ble_bt_gatts_noyify_zhihui(buf,len); @@ -6450,6 +6483,30 @@ void typeHexFiveData(uint8_t *buf_cmd,uint8_t *data,uint16_t datalen) } } +// 设备 -> APP:请求星历文件通知 +void ble_request_epo_file(void) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ble_request_epo_file"); + uint8_t value[19] = {0}; + uint32_t tmp = 1; + uint16_t tmp_data = 1; + + //72 05 40 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 + + value[0] = 0x72; // direction: device -> app + value[1] = 0x05; // type + value[2] = 0x40; // id = REQUEST_EPO_FILE_CMD + memcpy(value + 3, &tmp, 4); // package_count + memcpy(value + 7, &tmp, 4); // total_data_len + memcpy(value + 11, &tmp, 4);// package_index + memcpy(value + 15, &tmp_data, 2); // package_len (no payload) + // value[17] 可扩展: 0=普通请求,1=强制刷新;当前默认0 + value[17] = 0; + value[18] = Calculate_CRC(value, 18); + + sendBleData_zhihui(value, sizeof(value)); +} + #define STEP_DATA_LEN 8 #define KCAL_DATA_LEN 8 @@ -6987,6 +7044,64 @@ void getPhoneInfo_test(void) } +/** + * @brief 处理EPO文件传输完成后的解压操作 + * @param buffer_handle 文件接收缓冲区句柄 + * @return ERRCODE_SUCC 成功, ERRCODE_FAIL 失败 + * + * 功能说明: + * - 检查文件类型是否为EPO文件(file_type == 7) + * - 检查文件名是否以.lzma结尾 + * - 如果是lzma文件,则调用解压函数解压 + * - 解压成功后删除原始lzma文件 + */ +static errcode_t handle_epo_file_decompress(ble_read_data *buffer_handle) +{ + // 检查是否为EPO文件 + if (buffer_handle->file_recv_stat.file_type != 7) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] EPO file check failed: file_type=%d\n", + buffer_handle->file_recv_stat.file_type); + return ERRCODE_FAIL; + } + + // 从runtime获取EPO文件完整路径(因为file_name可能已被释放) + char* lzma_file_path = uapi_dev_runtime_get_epo_file_path(); + if (lzma_file_path == NULL || strlen(lzma_file_path) == 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] Failed to get EPO file path from runtime\n"); + return ERRCODE_FAIL; + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] EPO file path from runtime: %s\n", lzma_file_path); + + // 检查文件扩展名是否为lzma + const char* ext = strrchr(lzma_file_path, '.'); + if (ext == NULL || (strcmp(ext, ".lzma") != 0 && strcmp(ext, ".LZMA") != 0)) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] EPO file is not a lzma file, skip extraction. ext=%s\n", + ext ? ext : "NULL"); + return ERRCODE_FAIL; + } + + // 执行解压 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] EPO lzma file received, start extracting: %s\n", lzma_file_path); + + errcode_t extract_ret = uapi_epo_lzma_decompress(lzma_file_path); + if (extract_ret == ERRCODE_SUCC) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] EPO lzma file extracted successfully\n"); + // 解压成功后删除lzma文件 + remove(lzma_file_path); + return ERRCODE_SUCC; + } + else + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[handle_epo_file_decompress] Failed to extract EPO lzma file, ret=0x%X\n", extract_ret); + return ERRCODE_FAIL; + } +} + bool creatMessageDir(void)//创建消息文件夹 { struct stat info; @@ -7372,6 +7487,7 @@ errcode_t decode_ota_data(ble_read_data *buffer_handle,uint8_t *data,uint16_t da } + //音乐文件传输 else if (buffer_handle->file_recv_stat.file_type == 2) { char musicpath[64]={0}; @@ -7381,9 +7497,10 @@ errcode_t decode_ota_data(ble_read_data *buffer_handle,uint8_t *data,uint16_t da create_mp3_name(filepath,musicpath,buffer_handle->file_recv_stat.file_name); buffer_handle->file_recv_stat.file = fopen(filepath,"w+"); - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"filepath %s\r\n",filepath); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"filepath %s\r\n",filepath); } - else if (buffer_handle->file_recv_stat.file_type == 4) + //app_file + else if (buffer_handle->file_recv_stat.file_type == 4) { char filepath[128]={0}; if (get_app_file_path(buffer_handle->file_recv_stat.file_name,strlen(buffer_handle->file_recv_stat.file_name),filepath,sizeof(filepath)) == ERRCODE_SUCC) @@ -7394,15 +7511,27 @@ errcode_t decode_ota_data(ble_read_data *buffer_handle,uint8_t *data,uint16_t da WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"create file %s error %X\n",buffer_handle->file_recv_stat.file_name,buffer_handle->file_recv_stat.file); } } + //gnss文件传输 + else if (buffer_handle->file_recv_stat.file_type == 7) + { + char filepath[128]={0}; + if (get_epo_file_path(buffer_handle->file_recv_stat.file_name,strlen(buffer_handle->file_recv_stat.file_name),filepath,sizeof(filepath)) == ERRCODE_SUCC) + { + buffer_handle->file_recv_stat.file = fopen(filepath,"w+"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"create file %s %X\n",filepath,buffer_handle->file_recv_stat.file); + }else{ + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"create file %s error %X\n",buffer_handle->file_recv_stat.file_name,buffer_handle->file_recv_stat.file); + } + } else { char filepath[128]={0}; if (get_file_recv_file_path(buffer_handle->file_recv_stat.file_name,strlen(buffer_handle->file_recv_stat.file_name),filepath,sizeof(filepath)) == ERRCODE_SUCC) { buffer_handle->file_recv_stat.file = fopen(filepath,"w+"); - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"create file %s %X\n",filepath,buffer_handle->file_recv_stat.file); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"create file %s %X\n",filepath,buffer_handle->file_recv_stat.file); }else{ - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"create file %s error %X\n",buffer_handle->file_recv_stat.file_name,buffer_handle->file_recv_stat.file); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"create file %s error %X\n",buffer_handle->file_recv_stat.file_name,buffer_handle->file_recv_stat.file); } } @@ -7465,6 +7594,10 @@ errcode_t decode_ota_data(ble_read_data *buffer_handle,uint8_t *data,uint16_t da if(buffer_handle->file_recv_stat.file_type==4) TRANSMIT_DATA=TRANS_BIN_TRANSMITTING; + //gnss文件传输 + if(buffer_handle->file_recv_stat.file_type==7) + TRANSMIT_DATA=TRANS_EPO_TRANSMITTING; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); } } @@ -7495,7 +7628,7 @@ errcode_t decode_ota_data(ble_read_data *buffer_handle,uint8_t *data,uint16_t da return ERRCODE_FAIL; } - //检查文件是否写完 + //检查文件是否写完 if(buffer_handle->file_recv_stat.file_size == buffer_handle->file_recv_stat.received_size) { if (buffer_handle->file_recv_stat.file!=NULL) @@ -7520,6 +7653,10 @@ errcode_t decode_ota_data(ble_read_data *buffer_handle,uint8_t *data,uint16_t da } ble_read_timer_p=NULL; + //gnss文件传输 + if(buffer_handle->file_recv_stat.file_type==7) + TRANSMIT_DATA=TRANS_EPO_TRANSMITTING; + else TRANSMIT_DATA=FILE_IS_DONE; CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); @@ -7726,7 +7863,7 @@ errcode_t decode_mp3_json(ble_read_data *buffer_handle) } else { - buffer_handle->file_recv_stat.received_size = 0; + buffer_handle->file_recv_stat.received_size = 0; buffer_handle->file_recv_stat.lrcvalue = 0; buffer_handle->receive_file_flag = 1; uapi_ble_connect_param_update(BLE_CONNECT_INTERVAL_HIGH); @@ -7741,6 +7878,92 @@ errcode_t decode_mp3_json(ble_read_data *buffer_handle) } +//解析蓝牙传输过来的 EPO 文件元数据(类型、名称、大小) +static errcode_t decode_epo_json(ble_read_data *buffer_handle) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"decode_epo_json"); + + uint8_t *data = buffer_handle->data+sizeof(msg_header_t); + uint16_t datalen = buffer_handle->totalDataLen; + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"EPO_file_save %s\n",data); + + cJSON* root = cJSON_Parse((const char *)data); + + if(root != NULL) + { + cJSON* epo_type_json = cJSON_GetObjectItem(root, "type"); + if(epo_type_json != NULL && cJSON_IsNumber(epo_type_json)) + { + buffer_handle->file_recv_stat.file_type = epo_type_json->valueint; + } + else + { + buffer_handle->file_recv_stat.file_type = 7; // fallback + } + + cJSON* epo_name_json = cJSON_GetObjectItem(root, "name"); + if ( epo_name_json != NULL && cJSON_IsString(epo_name_json)) { + uint32_t filenamelen=strlen(epo_name_json->valuestring)+1; + + if (filenamelen > 0 && filenamelen < 255) + { + if(buffer_handle->file_recv_stat.file_name) + { + free(buffer_handle->file_recv_stat.file_name); + buffer_handle->file_recv_stat.file_name=NULL; + } + buffer_handle->file_recv_stat.file_name = malloc(filenamelen); + if (buffer_handle->file_recv_stat.file_name!=NULL) + { + memset_s(buffer_handle->file_recv_stat.file_name,filenamelen,0,filenamelen); + memcpy_s(buffer_handle->file_recv_stat.file_name,filenamelen,epo_name_json->valuestring,filenamelen); + } + }else{ + buffer_handle->file_recv_stat.file_name = NULL; + } + } + + cJSON* epo_size_json = cJSON_GetObjectItem(root, "size"); + if(epo_size_json != NULL && cJSON_IsNumber(epo_size_json)) + { + buffer_handle->file_recv_stat.file_size = epo_size_json->valueint; + } + cJSON_Delete(root); + + // 保存EPO文件路径到runtime,以便后续解压时使用 + if (buffer_handle->file_recv_stat.file_name != NULL) + { + char epo_file_path[256] = {0}; + if (get_epo_file_path(buffer_handle->file_recv_stat.file_name, + strlen(buffer_handle->file_recv_stat.file_name), + epo_file_path, sizeof(epo_file_path)) == ERRCODE_SUCC) + { + uapi_dev_runtime_set_epo_file_path(epo_file_path); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[decode_epo_json] Saved EPO file path to runtime: %s\n", epo_file_path); + } + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP," EPO_type= %d, EPO_name=%s, EPO_size=%d \n", + buffer_handle->file_recv_stat.file_type, + buffer_handle->file_recv_stat.file_name, + buffer_handle->file_recv_stat.file_size); + + buffer_handle->file_recv_stat.received_size = 0; + buffer_handle->file_recv_stat.lrcvalue = 0; + buffer_handle->receive_file_flag = 1; + uapi_ble_connect_param_update(BLE_CONNECT_INTERVAL_HIGH); + + return ERRCODE_SUCC; + + }else{ + buffer_handle->receive_file_flag = 0; + return ERRCODE_FAIL; + } +} + + + + errcode_t decode_bin_json(ble_read_data *buffer_handle) { if (uapi_dev_runtime_get_train_status() == 1) { @@ -8311,6 +8534,7 @@ bool read_dir_send_json() } } +// 蓝牙文件传输完成后的校验与结果处理核心函数(指令码2AE6对应文件校验场景) void decode_file_checksum_2AE6(ble_read_data *buffer_handle) { uapi_ble_connect_param_update(BLE_CONNECT_INTERVAL_SLOW); @@ -8384,6 +8608,12 @@ void decode_file_checksum_2AE6(ble_read_data *buffer_handle) // ble_mp3_list_update(); + break; + case 7://epo + TRANSMIT_DATA=TRANS_EPO_SUCC; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); + handle_epo_file_decompress(buffer_handle); + ble_reply_cmd(FILE_SUCC_TRANSFER,1); break; default: break; @@ -8410,6 +8640,10 @@ void decode_file_checksum_2AE6(ble_read_data *buffer_handle) TRANSMIT_DATA= TRANS_BIN_FAIL; CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); + break; + case 7://epo + TRANSMIT_DATA= TRANS_EPO_FAIL; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); break; default: break; @@ -8437,6 +8671,10 @@ void decode_file_checksum_2AE6(ble_read_data *buffer_handle) TRANSMIT_DATA= TRANS_BIN_FAIL; CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); break; + case 7://epo + TRANSMIT_DATA= TRANS_EPO_FAIL; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); + break; default: break; } @@ -8508,6 +8746,12 @@ void decode_file_checksum(ble_read_data *buffer_handle) } break; + case 7://epo + TRANSMIT_DATA=TRANS_EPO_SUCC; + handle_epo_file_decompress(buffer_handle); + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); + ble_reply_cmd(FILE_SUCC_TRANSFER,1); + break; default: break; } @@ -8528,6 +8772,10 @@ void decode_file_checksum(ble_read_data *buffer_handle) TRANSMIT_DATA= TRANS_DIAL_FAIL; CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); + break; + case 7://epo + TRANSMIT_DATA= TRANS_EPO_FAIL; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); break; default: break; @@ -8551,6 +8799,10 @@ void decode_file_checksum(ble_read_data *buffer_handle) TRANSMIT_DATA= TRANS_DIAL_FAIL; CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); break; + case 7://epo + TRANSMIT_DATA= TRANS_EPO_FAIL; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); + break; default: break; } @@ -8778,7 +9030,19 @@ void type2AE6Data(ble_read_data *buffer_handle,uint8_t *cmd_buf ,uint8_t *data,u case GET_APP_MSG_CMD: get_watch_msg( cmd, data,datalen,crcvalue); - break; + break; + case GNSS_DATA_RECV_CMD : + { + if (decode_epo_json(buffer_handle) == ERRCODE_SUCC) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析蓝牙传输过来的 EPO 文件元数据 ,回复成功"); + ble_reply_cmd(GNSS_DATA_RECV_CMD,1); + }else{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"解析蓝牙传输过来的 EPO 文件元数据 ,回复失败"); + ble_reply_cmd(GNSS_DATA_RECV_CMD,2); + } + } + break; default: break; } @@ -8851,6 +9115,11 @@ errcode_t cancel_recv_file(uint8_t flag) { TRANSMIT_DATA=TRANS_BIN_FAIL; } + else if(ble_read_timer_p->file_recv_stat.file_type==7) + { + //gnss文件传输失败 + TRANSMIT_DATA=TRANS_EPO_FAIL; + } else { @@ -8892,6 +9161,16 @@ errcode_t cancel_recv_file(uint8_t flag) WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"filepath %s\r\n",filepath); } } + //gnss文件传输 + else if (ble_read_timer_p->file_recv_stat.file_type == 7) + { + char filepath[128]={0}; + if (get_epo_file_path(ble_read_timer_p->file_recv_stat.file_name,strlen(ble_read_timer_p->file_recv_stat.file_name),filepath,sizeof(filepath)) == ERRCODE_SUCC) + { + remove(filepath); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"filepath %s\r\n",filepath); + } + } if (ble_read_timer_p->file_recv_stat.file_name != NULL) { @@ -8989,7 +9268,7 @@ errcode_t handle_recv_file(ble_read_data *buffer_handle,uint8_t *data, uint16_t // return ERRCODE_FAIL; } } - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"handle_recv_file %d\n",event_size); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"handle_recv_file %d\n",event_size); save_package_sample(buffer_handle,data,event_size); @@ -9015,6 +9294,18 @@ errcode_t handle_recv_file(ble_read_data *buffer_handle,uint8_t *data, uint16_t buffer_handle->file_recv_stat.file = fopen(filepath,"w+"); WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,"filepath %s\r\n",filepath); } + //gnss文件传输 + else if (buffer_handle->file_recv_stat.file_type == 7) + { + char filepath[128]={0}; + if (get_epo_file_path(buffer_handle->file_recv_stat.file_name,strlen(buffer_handle->file_recv_stat.file_name),filepath,sizeof(filepath)) == ERRCODE_SUCC) + { + buffer_handle->file_recv_stat.file = fopen(filepath,"w+"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"filepath %s\r\n",filepath); + }else{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"create file %s error %X\n",buffer_handle->file_recv_stat.file_name,buffer_handle->file_recv_stat.file); + } + } else { char filepath[128]={0}; @@ -9064,6 +9355,10 @@ errcode_t handle_recv_file(ble_read_data *buffer_handle,uint8_t *data, uint16_t if(buffer_handle->file_recv_stat.file_type==2) TRANSMIT_DATA=TRANS_MP3_TRANSMITTING; + //gnss文件传输 + if(buffer_handle->file_recv_stat.file_type==7) + TRANSMIT_DATA=TRANS_EPO_TRANSMITTING; + CustomEventPublish(TOPIC_EVENT_BLE_TRANSMIT,&TRANSMIT_DATA,1); } diff --git a/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.h b/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.h index 52529c427..520de68dd 100644 --- a/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.h +++ b/code/sdk/zhihui/services/ble_service/bleservice/bleEvent.h @@ -756,7 +756,7 @@ typedef struct typedef struct { - uint8_t file_type; //1 ota file , 2 mp3 file , 3 dial file, 7 epo file + uint8_t file_type; //1 ota file , 2 mp3 file , 3 dial file, 4 bin/app , 7 epo/gnss uint32_t file_size; uint8_t ota_major; uint8_t ota_mid; @@ -808,6 +808,8 @@ void sendtraningstatedata(uint8_t state ,uint16_t time); void sendtraningstateend(void); +void ble_request_epo_file(void); + int char_to_dec(char ch);//字符数字转换成阿拉伯数字 void char_to_big(uint8_t *src_str,uint8_t *res_str,uint8_t len); @@ -874,6 +876,7 @@ uint8_t Calculate_CRC_Big(uint8_t *data,uint16_t len);//计算20byte #define APP_DELETE_CMD 0x060D #define GET_LIST_CMD 0x060E #define GET_APP_MSG_CMD 0x060F +#define GNSS_DATA_RECV_CMD 0x0610 #ifdef __cplusplus } diff --git a/code/sdk/zhihui/utils/custom_runtime.c b/code/sdk/zhihui/utils/custom_runtime.c index 8616cd99d..07603c9d9 100644 --- a/code/sdk/zhihui/utils/custom_runtime.c +++ b/code/sdk/zhihui/utils/custom_runtime.c @@ -68,6 +68,7 @@ typedef struct uint8_t vibmod; bt_runtime_status_t bt_runtime; char current_dial_name[32]; + char current_epo_file_path[128]; // EPO文件完整路径 uint8_t time_hour_show_model; uint8_t sleep_monitoring_status;//睡眠监测 uint8_t sleep_status;//睡眠状态 @@ -248,6 +249,29 @@ char* uapi_dev_runtime_get_dialname(void) { return (char*)&(g_runtime.current_dial_name[0]); } + +/** + * 设置 EPO 文件路径 +*/ +void uapi_dev_runtime_set_epo_file_path(char* file_path) +{ + if (file_path == NULL) return; + // 在runtime中备份EPO文件路径 + int pathlen = strlen((char*)file_path); + if (pathlen >= sizeof(g_runtime.current_epo_file_path)) { + pathlen = sizeof(g_runtime.current_epo_file_path) - 1; + } + memset_s(g_runtime.current_epo_file_path, sizeof(g_runtime.current_epo_file_path), 0, sizeof(g_runtime.current_epo_file_path)); + memcpy_s(g_runtime.current_epo_file_path, sizeof(g_runtime.current_epo_file_path), file_path, pathlen); +} + +/** + * 获取 EPO 文件路径 +*/ +char* uapi_dev_runtime_get_epo_file_path(void) +{ + return (char*)&(g_runtime.current_epo_file_path[0]); +} void uapi_dev_runtime_set_bt_powerstat(uint8_t stat) { g_runtime.bt_runtime.bt_powerOn = stat; diff --git a/code/sdk/zhihui/utils/custom_runtime.h b/code/sdk/zhihui/utils/custom_runtime.h index 7217d995e..b8e4354f7 100644 --- a/code/sdk/zhihui/utils/custom_runtime.h +++ b/code/sdk/zhihui/utils/custom_runtime.h @@ -39,6 +39,15 @@ void uapi_dev_runtime_set_dialname(char* dialname); */ char* uapi_dev_runtime_get_dialname(void); +/** + * 设置 EPO 文件路径 +*/ +void uapi_dev_runtime_set_epo_file_path(char* file_path); +/** + * 获取 EPO 文件路径 +*/ +char* uapi_dev_runtime_get_epo_file_path(void); + void uapi_dev_runtime_set_vibmod(uint8_t vibmod); diff --git a/code/sdk/zhihui/utils/custom_utils.h b/code/sdk/zhihui/utils/custom_utils.h index a21ee5c0e..cc50d3296 100644 --- a/code/sdk/zhihui/utils/custom_utils.h +++ b/code/sdk/zhihui/utils/custom_utils.h @@ -161,12 +161,8 @@ errcode_t uapi_get_buildversion(uint8_t *para, uint32_t para_len); //获取固件版本号 errcode_t uapi_get_fw_version(uint8_t *_major, uint8_t *_mid,uint8_t *_minor); -/** - * @} - */ - - //判断文件夹是否存在 - bool uapi_create_dir(const char *path); +//判断文件夹是否存在,如果不存在则创建 +bool uapi_create_dir(const char *path); //flash整片擦除 diff --git a/code/sdk/zhihui/utils/custom_utils_at_cmd.c b/code/sdk/zhihui/utils/custom_utils_at_cmd.c index 83e3daf60..cdc203be1 100644 --- a/code/sdk/zhihui/utils/custom_utils_at_cmd.c +++ b/code/sdk/zhihui/utils/custom_utils_at_cmd.c @@ -713,6 +713,36 @@ static uint32_t custom_utils_lzmadec(uint8_t *para, uint32_t paraLen, char *strC return ERRCODE_SUCC; } + +// EPO LZMA decompress +static uint32_t custom_utils_epo_lzmadec(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + UNUSED(strCommonCmd); + uint8_t argv[CUSTOM_UTILS_MAX_ARGC][CUSTOM_UTILS_MAX_ARGV_LEN] = { 0 }; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, paraLen, argv, &argc); + if (ret != CUSTOM_UTILS_SUCCESS) { + wstp_print("CUSTOM_UTILSTEST ATCMD Convert Command Failed\n"); + return ERRCODE_FAIL; + } + + if (argc != 1) { + wstp_print("CUSTOM_UTILSTEST ATCMD Set Parameter Failed argc %d, need 1 (lzma_file_path)\n",argc); + return ERRCODE_FAIL; + } + const char *infile = (const char*)argv[0]; + wstp_print("EPO LZMA decompress: %s\r\n", infile); + errcode_t rret = uapi_epo_lzma_decompress(infile); + if (rret == ERRCODE_SUCC) { + wstp_print("EPO LZMA decompress SUCCESS\r\n"); + } else { + wstp_print("EPO LZMA decompress FAILED, ret 0x%X\r\n", rret); + } + + return ERRCODE_SUCC; +} + static uint32_t custom_utils_pdudec(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { UNUSED(strCommonCmd); @@ -2292,6 +2322,7 @@ static const at_cmd_table_t g_custom_utils_cmd[] = { { (char *)"AT^GETSLEEP", custom_get_sleep, (char *)" " }, { (char *)"AT^LZMADEC", custom_utils_lzmadec, (char *)" " }, + { (char *)"AT^EPODEC", custom_utils_epo_lzmadec, (char *)" " }, { (char *)"AT^PDUDEC", custom_utils_pdudec, (char *)" " }, { (char *)"AT^SNTP", custom_test_sntp, (char *)" " }, { (char *)"AT^TIMESYNC", custom_test_time_synct, (char *)" " }, diff --git a/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp b/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp index c08a3d7a9..f5340f387 100644 --- a/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp +++ b/code/sdk/zhihui/utilscpp/custom_utilscpp.cpp @@ -284,6 +284,255 @@ errcode_t uapi_dial_pkg_decompress(const char* dial_pkg_path,char* dial_bin_path return 0; } +/** + * @brief 解压EPO LZMA文件到多个文件 + * @param lzma_file_path LZMA压缩文件路径 + * @return ERRCODE_SUCC 成功, ERRCODE_FAIL 失败 + * + * 文件格式说明: + * LZMA文件解压后的数据格式: + * - 文件头:文件数量(4字节,小端) + * - 对于每个文件: + * - 文件名长度(2字节,小端) + * - 文件名(变长,不含路径) + * - 文件大小(4字节,小端) + * - 文件数据(变长) + */ +/* 从文件名中提取"_"与"."之间的时间戳子串,失败返回空字符串 */ +static void extract_timestamp_from_name(const char *filename, char *ts_buf, size_t buf_len) +{ + if (ts_buf == NULL || buf_len == 0 || filename == NULL) { + return; + } + ts_buf[0] = '\0'; + const char *us = strrchr(filename, '_'); + const char *dot = strrchr(filename, '.'); + if (us == NULL || dot == NULL || dot <= us + 1) { + return; + } + size_t len = (size_t)(dot - us - 1); + if (len >= buf_len) { + len = buf_len - 1; + } + (void)memcpy(ts_buf, us + 1, len); + ts_buf[len] = '\0'; +} + +errcode_t uapi_epo_lzma_decompress(const char* lzma_file_path) +{ + printf("[uapi_epo_lzma_decompress] lzma_file_path: %s\n", lzma_file_path); + if (lzma_file_path == NULL) { + printf("[uapi_epo_lzma_decompress] lzma_file_path is NULL\n"); + return ERRCODE_FAIL; + } + + // 确保输出目录存在 + uapi_create_dir("/user/userdata/gnss"); + + // 先解压LZMA文件到内存缓冲区 + CFileSeqInStream inStream; + CFileOutStream outStream; + FileSeqInStream_CreateVTable(&inStream); + File_Construct(&inStream.file); + FileOutStream_CreateVTable(&outStream); + File_Construct(&outStream.file); + + if (InFile_Open(&inStream.file, lzma_file_path) != 0) { + printf("[uapi_epo_lzma_decompress] Can not open input file: %s\n", lzma_file_path); + return ERRCODE_FAIL; + } + + // 创建临时文件来存储解压后的数据 + const char* temp_file = "/user/userdata/gnss/epo_temp.dat"; + if (OutFile_Open(&outStream.file, temp_file) != 0) { + printf("[uapi_epo_lzma_decompress] Can not create temp file\n"); + File_Close(&inStream.file); + return ERRCODE_FAIL; + } + + // 解压LZMA文件 + SRes res = Decode(&outStream.vt, &inStream.vt); + File_Close(&outStream.file); + File_Close(&inStream.file); + + if (res != SZ_OK) { + printf("[uapi_epo_lzma_decompress] LZMA decompress failed: %d\n", res); + remove(temp_file); + return ERRCODE_FAIL; + } + + // 读取解压后的数据 + FILE* fp = fopen(temp_file, "rb"); + if (fp == NULL) { + printf("[uapi_epo_lzma_decompress] Can not open temp file\n"); + remove(temp_file); + return ERRCODE_FAIL; + } + + // 读取文件数量 + uint32_t file_count = 0; + if (fread(&file_count, 1, 4, fp) != 4) { + printf("[uapi_epo_lzma_decompress] Failed to read file count\n"); + fclose(fp); + remove(temp_file); + return ERRCODE_FAIL; + } + + printf("[uapi_epo_lzma_decompress] File count: %u\n", file_count); + + if (file_count == 0) { + printf("[uapi_epo_lzma_decompress] Invalid file count: %u\n", file_count); + fclose(fp); + remove(temp_file); + return ERRCODE_FAIL; + } + + /* 第一遍扫描,尝试找到AssistInfo_*文件的时间戳,用作目录名 */ + char assist_ts[32] = {0}; + long data_start_pos = ftell(fp); + for (uint32_t i = 0; i < file_count; i++) { + uint16_t filename_len = 0; + if (fread(&filename_len, 1, 2, fp) != 2) { break; } + if (filename_len == 0 || filename_len > 255) { break; } + char filename[256] = {0}; + if (fread(filename, 1, filename_len, fp) != filename_len) { break; } + uint32_t file_size = 0; + if (fread(&file_size, 1, 4, fp) != 4) { break; } + if (file_size > 0) { (void)fseek(fp, file_size, SEEK_CUR); } + if (assist_ts[0] == '\0' && strncmp(filename, "AssistInfo_", strlen("AssistInfo_")) == 0) { + extract_timestamp_from_name(filename, assist_ts, sizeof(assist_ts)); + } + } + /* 回到文件列表起始位置准备正式解压 */ + (void)fseek(fp, data_start_pos, SEEK_SET); + + int extracted_count = 0; + + // 遍历每个文件 + for (uint32_t i = 0; i < file_count; i++) { + // 读取文件名长度 + uint16_t filename_len = 0; + if (fread(&filename_len, 1, 2, fp) != 2) { + printf("[uapi_epo_lzma_decompress] Failed to read filename length for file %u\n", i); + break; + } + + if (filename_len == 0 || filename_len > 255) { + printf("[uapi_epo_lzma_decompress] Invalid filename length: %u for file %u\n", filename_len, i); + break; + } + + // 读取文件名 + char filename[256] = {0}; + if (fread(filename, 1, filename_len, fp) != filename_len) { + printf("[uapi_epo_lzma_decompress] Failed to read filename for file %u\n", i); + break; + } + + // 读取文件大小 + uint32_t file_size = 0; + if (fread(&file_size, 1, 4, fp) != 4) { + printf("[uapi_epo_lzma_decompress] Failed to read file size for file %u\n", i); + break; + } + + if (file_size == 0 || file_size > 10 * 1024 * 1024) { // 最大10MB + printf("[uapi_epo_lzma_decompress] Invalid file size: %u for file %u\n", file_size, i); + break; + } + + /* 确定目录时间戳:优先用AssistInfo中的时间戳,否则取自身文件名中的时间戳 */ + char dir_ts[32] = {0}; + if (assist_ts[0] != '\0') { + (void)strncpy(dir_ts, assist_ts, sizeof(dir_ts) - 1); + } else { + extract_timestamp_from_name(filename, dir_ts, sizeof(dir_ts)); + } + if (dir_ts[0] == '\0') { + printf("[uapi_epo_lzma_decompress] Failed to parse timestamp for file %s\n", filename); + break; + } + + /* 生成输出目录并确保存在 */ + char output_dir[128] = {0}; + int dir_ret = snprintf(output_dir, sizeof(output_dir), "/user/userdata/gnss/%s", dir_ts); + if (dir_ret < 0 || dir_ret >= (int)sizeof(output_dir)) { + printf("[uapi_epo_lzma_decompress] Failed to build output dir for file %u\n", i); + break; + } + uapi_create_dir(output_dir); + + /* AssistInfo 重命名为 AssistInfo.dat */ + const char *final_name = filename; + char renamed[64] = {0}; + if (strncmp(filename, "AssistInfo_", strlen("AssistInfo_")) == 0) { + (void)strncpy(renamed, "AssistInfo.dat", sizeof(renamed) - 1); + final_name = renamed; + } + + // 构建输出文件路径 + char output_path[256] = {0}; + int path_ret = snprintf(output_path, sizeof(output_path), "%s/%s", output_dir, final_name); + if (path_ret < 0 || path_ret >= (int)sizeof(output_path)) { + printf("[uapi_epo_lzma_decompress] Failed to build output path for file %u\n", i); + break; + } + + // 创建输出文件 + FILE* outfile = fopen(output_path, "wb"); + if (outfile == NULL) { + printf("[uapi_epo_lzma_decompress] Failed to create output file: %s\n", output_path); + break; + } + + // 读取并写入文件数据 + uint8_t* buffer = (uint8_t*)malloc(file_size); + if (buffer == NULL) { + printf("[uapi_epo_lzma_decompress] Failed to allocate buffer for file %u\n", i); + fclose(outfile); + break; + } + + size_t read_size = fread(buffer, 1, file_size, fp); + if (read_size != file_size) { + printf("[uapi_epo_lzma_decompress] Failed to read file data for file %u: read %zu, expected %u\n", + i, read_size, file_size); + free(buffer); + fclose(outfile); + remove(output_path); + break; + } + + size_t written = fwrite(buffer, 1, file_size, outfile); + free(buffer); + fclose(outfile); + + if (written != file_size) { + printf("[uapi_epo_lzma_decompress] Failed to write file: %s\n", output_path); + remove(output_path); + break; + } + + printf("[uapi_epo_lzma_decompress] Extracted: %s (%u bytes)\n", output_path, file_size); + extracted_count++; + } + + fclose(fp); + remove(temp_file); + + if (extracted_count == 0) { + printf("[uapi_epo_lzma_decompress] No files extracted\n"); + return ERRCODE_FAIL; + } + + if (extracted_count != file_count) { + printf("[uapi_epo_lzma_decompress] Warning: extracted %d/%u files\n", extracted_count, file_count); + } + + printf("[uapi_epo_lzma_decompress] Successfully extracted %d files\n", extracted_count); + return ERRCODE_SUCC; +} + void uapi_dump_meminfo(void) { diff --git a/code/sdk/zhihui/utilscpp/custom_utilscpp.h b/code/sdk/zhihui/utilscpp/custom_utilscpp.h index 88f35f642..2d913279e 100644 --- a/code/sdk/zhihui/utilscpp/custom_utilscpp.h +++ b/code/sdk/zhihui/utilscpp/custom_utilscpp.h @@ -25,6 +25,7 @@ extern "C" { #include errcode_t uapi_dial_pkg_decompress(const char* dial_pkg_path,char* dial_bin_path); +errcode_t uapi_epo_lzma_decompress(const char* lzma_file_path); void uapi_dump_meminfo(void); void CustomEventPublish(uint16_t topic, uint8_t *data , uint32_t size); diff --git a/tools/scripts/epo/1765447066.lzma b/tools/scripts/epo/1765447066.lzma new file mode 100644 index 0000000000000000000000000000000000000000..9f6a6be1f71c702a2ebf17957b14bfe536997724 GIT binary patch literal 90463 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%0AA@fwpV1KZEBdL=vSxf%;zIBlIzUQ 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

P<5?#l9e0xqh># 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 SUx3BZ{x#P4i;E>ov|s>3A}y!@ literal 0 HcmV?d00001 diff --git a/tools/scripts/epo/EPO_LZMA_TEST.md b/tools/scripts/epo/EPO_LZMA_TEST.md new file mode 100644 index 000000000..2e3bba8d5 --- /dev/null +++ b/tools/scripts/epo/EPO_LZMA_TEST.md @@ -0,0 +1,184 @@ +# EPO LZMA 解压功能测试文档 + +## 功能说明 + +`uapi_epo_lzma_decompress()` 函数用于解压包含多个星历文件的 LZMA 压缩包,并将文件提取到 `/user/userdata/gnss/` 目录。 + +## 文件格式 + +### LZMA 文件格式 +``` +[LZMA 头部: 13字节] + - LZMA 属性: 5字节 + - 未压缩大小: 8字节(小端) + +[压缩数据] +``` + +### 解压后的打包格式 +``` +[文件数量: 4字节,小端] + +[文件1] + - 文件名长度: 2字节,小端 + - 文件名: 变长(UTF-8编码) + - 文件大小: 4字节,小端 + - 文件数据: 变长 + +[文件2] + - ... + +[文件N] + - ... +``` + +## 测试流程 + +### 1. 创建测试文件 + +创建三个测试文件: +```bash +echo "EPH file content for testing" > 1.eph +echo "TXT file content for testing" > 2.txt +echo "DAT file content for testing" > 3.dat +``` + +### 2. 打包文件(Python脚本) + +使用提供的 Python 脚本打包: +```bash +python3 create_epo_lzma.py 1.eph 2.txt 3.dat epo.lzma +``` + +或者使用默认测试数据: +```bash +python3 create_epo_lzma.py +``` + +### 3. 传输到手表 + +通过 BLE 传输 `epo.lzma` 文件到手表: +- 使用 EPO 文件传输协议(`EPO_DATA_RECV_CMD`) +- 文件名必须包含 `.lzma` 扩展名 +- 文件会保存到 `/user/userdata/` 目录 + +### 4. AT 命令验证 + +手表接收完成后,使用 AT 命令验证解压: + +``` +AT^EPODEC=/user/userdata/epo.lzma +``` + +**预期输出:** +``` +EPO LZMA decompress: /user/userdata/epo.lzma +[uapi_epo_lzma_decompress] lzma_file_path: /user/userdata/epo.lzma +[uapi_epo_lzma_decompress] File count: 3 +[uapi_epo_lzma_decompress] Extracted: /user/userdata/gnss/1.eph (XX bytes) +[uapi_epo_lzma_decompress] Extracted: /user/userdata/gnss/2.txt (XX bytes) +[uapi_epo_lzma_decompress] Extracted: /user/userdata/gnss/3.dat (XX bytes) +[uapi_epo_lzma_decompress] Successfully extracted 3 files +EPO LZMA decompress SUCCESS +``` + +### 5. 验证解压结果 + +使用文件系统命令验证文件是否存在: +```bash +# 查看目录 +ls /user/userdata/gnss/ + +# 应该看到: +# 1.eph +# 2.txt +# 3.dat +``` + +## 使用 7-Zip 创建 LZMA 文件(备选方案) + +如果 Python 脚本不工作,可以使用 7-Zip: + +### 步骤1: 创建打包文件 + +使用 Python 脚本创建打包数据(不压缩): +```python +# create_packed_data.py +import struct + +files = [ + ("1.eph", b"EPH file content"), + ("2.txt", b"TXT file content"), + ("3.dat", b"DAT file content"), +] + +packed = struct.pack('} + ``` + +2. **APP 发送文件数据**: + - 分包发送 LZMA 文件内容 + - 文件保存到 `/user/userdata/epo.lzma` + +3. **手表自动解压**: + - 检测到 `.lzma` 扩展名 + - 自动调用 `uapi_epo_lzma_decompress()` + - 解压到 `/user/userdata/gnss/` 目录 + - 删除原始 LZMA 文件 + +### 步骤 3: AT 命令验证 + +如果文件已传输到手表,可以直接使用 AT 命令测试: + +``` +AT^EPODEC=/user/userdata/epo.lzma +``` + +**预期输出:** +``` +EPO LZMA decompress: /user/userdata/epo.lzma +[uapi_epo_lzma_decompress] lzma_file_path: /user/userdata/epo.lzma +[uapi_epo_lzma_decompress] File count: 3 +[uapi_epo_lzma_decompress] Extracted: /user/userdata/gnss/1.eph (XX bytes) +[uapi_epo_lzma_decompress] Extracted: /user/userdata/gnss/2.txt (XX bytes) +[uapi_epo_lzma_decompress] Extracted: /user/userdata/gnss/3.dat (XX bytes) +[uapi_epo_lzma_decompress] Successfully extracted 3 files +EPO LZMA decompress SUCCESS +``` + +### 步骤 4: 验证解压结果 + +使用文件系统命令验证: + +```bash +# 查看目录内容 +ls /user/userdata/gnss/ + +# 应该看到: +# 1.eph +# 2.txt +# 3.dat + +# 查看文件内容(如果支持) +cat /user/userdata/gnss/1.eph +cat /user/userdata/gnss/2.txt +``` + +## 文件格式说明 + +### LZMA 文件结构 +``` +[13字节头部] + - 5字节: LZMA 属性(lc, lp, pb, dict_size) + - 8字节: 未压缩大小(小端) + +[LZMA 压缩数据] +``` + +### 13字节头部详解 + +**完整格式:** +``` +字节0: 压缩参数编码 = (lc * 9 + lp) * 5 + pb +字节1-4: 字典大小(4字节,小端,0表示默认16MB) +字节5-12: 未压缩大小(8字节,小端) +``` + +**示例(未压缩大小=120字节):** +``` +5D 00 00 00 00 78 00 00 00 00 00 00 00 +│ │ │ │ │ +│ │ │ └─ 未压缩大小=120 (小端) +│ │ └─ 字典大小=0(默认) +│ └─ 压缩参数 (lc=3, lp=0, pb=2) +└─ 属性字节 +``` + +**详细说明请参考:** `LZMA_头部格式说明.md` + +### 解压后的打包格式 +``` +[4字节] 文件数量(小端) + +[文件1] + [2字节] 文件名长度(小端) + [N字节] 文件名(UTF-8) + [4字节] 文件大小(小端) + [M字节] 文件数据 + +[文件2] + ... + +[文件N] + ... +``` + +## 示例:创建包含 3 个文件的 LZMA + +假设有 3 个文件: +- `1.eph` (30 字节) +- `2.txt` (40 字节) +- `3.dat` (50 字节) + +打包后的数据格式: +``` +[04 00 00 00] # 文件数量 = 3 +[05 00] # 文件名长度 = 5 +[31 2E 65 70 68] # "1.eph" +[1E 00 00 00] # 文件大小 = 30 +[...30字节数据...] # 文件内容 +[05 00] # 文件名长度 = 5 +[32 2E 74 78 74] # "2.txt" +[28 00 00 00] # 文件大小 = 40 +[...40字节数据...] # 文件内容 +[05 00] # 文件名长度 = 5 +[33 2E 64 61 74] # "3.dat" +[32 00 00 00] # 文件大小 = 50 +[...50字节数据...] # 文件内容 +``` + +然后用 LZMA 压缩整个打包数据,并添加 13 字节头部。 + +## 故障排查 + +### 问题:解压失败 +- **检查点1**: LZMA 文件格式是否正确(13字节头部) +- **检查点2**: 打包数据格式是否符合规范 +- **检查点3**: 查看日志输出,定位具体错误 + +### 问题:文件未提取 +- **检查点1**: `/user/userdata/gnss/` 目录权限 +- **检查点2**: 磁盘空间是否充足 +- **检查点3**: 文件数量是否超过限制(100个) + +### 问题:AT 命令无响应 +- **检查点1**: 文件路径是否正确 +- **检查点2**: 文件是否存在 +- **检查点3**: AT 命令是否正确注册 + +## 测试检查清单 + +- [ ] 创建测试 LZMA 文件成功 +- [ ] 文件格式符合规范 +- [ ] 传输到手表成功 +- [ ] 自动解压触发 +- [ ] 解压日志正常 +- [ ] 文件提取到正确目录 +- [ ] 文件内容正确 +- [ ] 原始 LZMA 文件已删除 +- [ ] AT 命令测试通过 + +## 相关文件 + +- 实现代码: `code/sdk/zhihui/utilscpp/custom_utilscpp.cpp` +- 函数声明: `code/sdk/zhihui/utilscpp/custom_utilscpp.h` +- AT 命令: `code/sdk/zhihui/utils/custom_utils_at_cmd.c` +- 创建脚本: `tools/scripts/create_epo_lzma.py` +- 详细文档: `tools/scripts/EPO_LZMA_TEST.md` + diff --git "a/tools/scripts/epo/LZMA_\345\244\264\351\203\250\346\240\274\345\274\217\350\257\264\346\230\216.md" "b/tools/scripts/epo/LZMA_\345\244\264\351\203\250\346\240\274\345\274\217\350\257\264\346\230\216.md" new file mode 100644 index 000000000..bf73b8abe --- /dev/null +++ "b/tools/scripts/epo/LZMA_\345\244\264\351\203\250\346\240\274\345\274\217\350\257\264\346\230\216.md" @@ -0,0 +1,200 @@ +# LZMA 文件 13 字节头部格式说明 + +## 概述 + +LZMA 文件格式要求在压缩数据前添加 **13 字节的头部**,用于告诉解压器如何解压数据。 + +## 头部结构 + +``` +[5字节 LZMA 属性] + [8字节 未压缩大小] = 13字节 +``` + +## 详细格式 + +### 第 1 部分:LZMA 属性(5字节) + +#### 字节 0:压缩参数编码 +``` +字节0 = (lc * 9 + lp) * 5 + pb +``` + +**参数说明:** +- **lc** (Literal Context bits): 字面上下文位数,范围 0-8,常用值 3 +- **lp** (Literal Position bits): 字面位置位数,范围 0-4,常用值 0 +- **pb** (Position bits): 位置位数,范围 0-4,常用值 2 + +**解码公式:** +```c +lc = data[0] % 9; +d = data[0] / 9; +pb = d / 5; +lp = d % 5; +``` + +**常见值示例:** +- `lc=3, lp=0, pb=2` → 字节0 = (3*9+0)*5+2 = **137 (0x89)** +- `lc=3, lp=0, pb=2` → 也常用 **93 (0x5D)**(不同编码方式) + +#### 字节 1-4:字典大小(4字节,小端) +``` +字节1: 字典大小低字节 +字节2: 字典大小次低字节 +字节3: 字典大小次高字节 +字节4: 字典大小高字节 +``` + +**字典大小计算:** +```c +dicSize = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24); +``` + +**常用值:** +- `0x00 0x00 0x00 0x00` = 0(表示使用默认值,通常为 16MB = 0x01000000) +- `0x00 0x00 0x00 0x01` = 16MB = 16777216 字节 +- `0x00 0x00 0x00 0x02` = 32MB + +### 第 2 部分:未压缩大小(8字节,小端) + +``` +字节5: 未压缩大小最低字节 +字节6: 未压缩大小次低字节 +字节7: 未压缩大小次次低字节 +字节8: 未压缩大小次次次低字节 +字节9: 未压缩大小次高字节 +字节10: 未压缩大小次次高字节 +字节11: 未压缩大小次次次高字节 +字节12: 未压缩大小最高字节 +``` + +**大小计算:** +```c +unpackSize = 0; +for (i = 0; i < 8; i++) { + unpackSize += (UInt64)header[5 + i] << (i * 8); +} +``` + +## 完整示例 + +### 示例 1:典型的 LZMA 头部 + +假设: +- 压缩参数:`lc=3, lp=0, pb=2` +- 字典大小:16MB(默认,用0表示) +- 未压缩大小:120 字节 + +**13字节头部:** +``` +字节0: 0x5D (lc=3, lp=0, pb=2 的编码) +字节1: 0x00 (字典大小低字节) +字节2: 0x00 (字典大小次低字节) +字节3: 0x00 (字典大小次高字节) +字节4: 0x00 (字典大小高字节,0表示默认16MB) +字节5: 0x78 (120 的十六进制,最低字节) +字节6: 0x00 (次低字节) +字节7: 0x00 (次次低字节) +字节8: 0x00 (次次次低字节) +字节9: 0x00 (次高字节) +字节10: 0x00 (次次高字节) +字节11: 0x00 (次次次高字节) +字节12: 0x00 (最高字节) +``` + +**十六进制表示:** +``` +5D 00 00 00 00 78 00 00 00 00 00 00 00 +``` + +### 示例 2:更大的文件 + +假设: +- 压缩参数:`lc=3, lp=0, pb=2` +- 字典大小:16MB(默认) +- 未压缩大小:1024 字节 = 0x400 + +**13字节头部:** +``` +5D 00 00 00 00 00 04 00 00 00 00 00 00 +``` + +**解析:** +- `5D`: 压缩参数 +- `00 00 00 00`: 字典大小(默认) +- `00 04 00 00 00 00 00 00`: 1024(小端)= 0x0000000000000400 + +### 示例 3:实际 EPO 文件 + +假设打包了 3 个文件,总大小 120 字节: +- 文件数量:3 (4字节) +- 文件1信息+数据:40字节 +- 文件2信息+数据:40字节 +- 文件3信息+数据:36字节 +- **总计:120字节** + +**13字节头部:** +``` +5D 00 00 00 00 78 00 00 00 00 00 00 00 +``` + +## 头部验证方法 + +### 方法 1:使用十六进制查看器 + +```bash +# 查看文件前13字节 +xxd -l 13 epo.lzma +``` + +**预期输出:** +``` +00000000: 5d00 0000 0078 0000 0000 0000 00 ]....x....... +``` + +### 方法 2:Python 验证脚本 + +```python +with open('epo.lzma', 'rb') as f: + header = f.read(13) + + # 解析压缩参数 + prop_byte = header[0] + lc = prop_byte % 9 + d = prop_byte // 9 + pb = d // 5 + lp = d % 5 + print(f"压缩参数: lc={lc}, lp={lp}, pb={pb}") + + # 解析字典大小 + dic_size = header[1] | (header[2] << 8) | (header[3] << 16) | (header[4] << 24) + print(f"字典大小: {dic_size} ({dic_size/1024/1024:.1f}MB)" if dic_size > 0 else "字典大小: 默认(16MB)") + + # 解析未压缩大小 + unpack_size = 0 + for i in range(8): + unpack_size += header[5 + i] << (i * 8) + print(f"未压缩大小: {unpack_size} 字节") +``` + +## 常见问题 + +### Q1: 为什么是 13 字节? +A: LZMA SDK 标准格式要求 5 字节属性 + 8 字节未压缩大小 = 13 字节。 + +### Q2: 字典大小为什么常用 0? +A: 0 表示使用默认值(通常 16MB),这是最常用的配置。 + +### Q3: 未压缩大小为什么是 8 字节? +A: 8 字节(64位)可以表示最大 18EB 的文件大小,足够支持任何实际文件。 + +### Q4: 如何验证头部是否正确? +A: +1. 检查前 5 字节是否符合 LZMA 属性格式 +2. 检查后 8 字节的未压缩大小是否与实际打包数据大小一致 +3. 使用解压函数测试是否能正确解压 + +## 参考 + +- LZMA SDK 文档:`code/sdk/open_source/7-zip-lzma-sdk/lzma_21.07/C/LzmaDec.h` +- 解压实现:`code/sdk/zhihui/utilscpp/custom_utilscpp.cpp` 的 `Decode()` 函数 + diff --git "a/tools/scripts/epo/LZMA_\345\244\264\351\203\250\347\244\272\344\276\213.md" "b/tools/scripts/epo/LZMA_\345\244\264\351\203\250\347\244\272\344\276\213.md" new file mode 100644 index 000000000..b8428247b --- /dev/null +++ "b/tools/scripts/epo/LZMA_\345\244\264\351\203\250\347\244\272\344\276\213.md" @@ -0,0 +1,187 @@ +# LZMA 13字节头部具体示例 + +## 快速理解 + +LZMA 文件的**前13个字节**是头部信息,告诉解压器: +1. 如何解压(压缩参数) +2. 解压后数据有多大(未压缩大小) + +## 实际示例 + +### 示例:解压后数据为 120 字节的 LZMA 文件 + +**完整的13字节头部:** +``` +5D 00 00 00 00 78 00 00 00 00 00 00 00 +``` + +**逐字节解释:** + +| 字节位置 | 十六进制值 | 含义 | 说明 | +|---------|-----------|------|------| +| 0 | `5D` | 压缩参数 | lc=3, lp=0, pb=2 的编码值 | +| 1 | `00` | 字典大小(低) | 字典大小的最低字节 | +| 2 | `00` | 字典大小(中低) | 字典大小的次低字节 | +| 3 | `00` | 字典大小(中高) | 字典大小的次高字节 | +| 4 | `00` | 字典大小(高) | 0表示使用默认16MB | +| 5 | `78` | 大小(最低) | 120的十六进制 = 0x78 | +| 6 | `00` | 大小(次低) | | +| 7 | `00` | 大小(次次低) | | +| 8 | `00` | 大小(次次次低) | | +| 9 | `00` | 大小(次高) | | +| 10 | `00` | 大小(次次高) | | +| 11 | `00` | 大小(次次次高) | | +| 12 | `00` | 大小(最高) | | + +**验证:** +- 未压缩大小 = `0x78` + `0x00*256` + ... = 120 字节 ✓ + +### 示例:解压后数据为 1024 字节的 LZMA 文件 + +**13字节头部:** +``` +5D 00 00 00 00 00 04 00 00 00 00 00 00 +``` + +**解析:** +- 字节0: `5D` = 压缩参数 +- 字节1-4: `00 00 00 00` = 字典大小(默认) +- 字节5-12: `00 04 00 00 00 00 00 00` = 1024(小端) + +**验证:** +- 1024 = 0x400 +- 小端表示:`00 04 00 00 00 00 00 00` = 0x0000000000000400 = 1024 ✓ + +### 示例:解压后数据为 65536 字节的 LZMA 文件 + +**13字节头部:** +``` +5D 00 00 00 00 00 00 01 00 00 00 00 00 +``` + +**解析:** +- 字节5-12: `00 00 01 00 00 00 00 00` = 65536(小端) + +**验证:** +- 65536 = 0x10000 +- 小端:`00 00 01 00 00 00 00 00` = 0x0000000000010000 = 65536 ✓ + +## 如何查看文件的13字节头部 + +### 方法1:使用 xxd 命令 +```bash +xxd -l 13 epo.lzma +``` + +**输出示例:** +``` +00000000: 5d00 0000 0078 0000 0000 0000 00 ]....x....... +``` + +### 方法2:使用 Python +```python +with open('epo.lzma', 'rb') as f: + header = f.read(13) + print(' '.join(f'{b:02X}' for b in header)) +``` + +**输出:** +``` +5D 00 00 00 00 78 00 00 00 00 00 00 00 +``` + +### 方法3:使用 hexdump 命令 +```bash +hexdump -C -n 13 epo.lzma +``` + +**输出:** +``` +00000000 5d 00 00 00 00 78 00 00 00 00 00 00 00 |]....x........| +``` + +## 验证头部是否正确 + +### 检查点1:压缩参数(字节0) +- 应该是 `5D` 或 `89`(常见值) +- 如果为 `00` 或其他异常值,可能格式错误 + +### 检查点2:字典大小(字节1-4) +- 通常为 `00 00 00 00`(使用默认值) +- 如果非零,检查是否合理(通常 1MB-64MB) + +### 检查点3:未压缩大小(字节5-12) +- 应该与实际打包数据大小一致 +- 使用小端格式读取 +- 如果为全0,表示文件为空(异常) + +## 常见错误 + +### 错误1:缺少头部 +``` +文件直接是压缩数据,没有13字节头部 +→ 解压会失败,提示格式错误 +``` + +### 错误2:头部大小错误 +``` +头部只有5字节或8字节 +→ 解压会失败,无法读取完整头部 +``` + +### 错误3:未压缩大小不匹配 +``` +头部中的大小与实际打包数据不一致 +→ 解压可能失败或数据损坏 +``` + +## 快速检查脚本 + +```python +#!/usr/bin/env python3 +import sys + +def check_lzma_header(filename): + with open(filename, 'rb') as f: + header = f.read(13) + + if len(header) < 13: + print(f"错误: 文件头部不足13字节(只有{len(header)}字节)") + return False + + # 解析压缩参数 + prop = header[0] + lc = prop % 9 + d = prop // 9 + pb = d // 5 + lp = d % 5 + + # 解析字典大小 + dic_size = header[1] | (header[2] << 8) | (header[3] << 16) | (header[4] << 24) + + # 解析未压缩大小 + unpack_size = 0 + for i in range(8): + unpack_size += header[5 + i] << (i * 8) + + print(f"LZMA 头部检查:") + print(f" 压缩参数: lc={lc}, lp={lp}, pb={pb} (编码=0x{prop:02X})") + print(f" 字典大小: {dic_size} ({dic_size/1024/1024:.1f}MB)" if dic_size > 0 else " 字典大小: 默认(16MB)") + print(f" 未压缩大小: {unpack_size} 字节") + print(f" 头部十六进制: {' '.join(f'{b:02X}' for b in header)}") + + return True + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("用法: python3 check_lzma_header.py ") + sys.exit(1) + + check_lzma_header(sys.argv[1]) +``` + +**使用方法:** +```bash +python3 check_lzma_header.py epo.lzma +``` + diff --git a/tools/scripts/epo/analyze_lzma.py b/tools/scripts/epo/analyze_lzma.py new file mode 100644 index 000000000..2429607da --- /dev/null +++ b/tools/scripts/epo/analyze_lzma.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +分析 LZMA Alone 文件头信息并可选解压校验。 +可用于对比两份 .lzma(例如 iOS 生成 vs 本地 create_epo_lzma.py)。 + +输出: +- 压缩大小 +- 属性字节解析:lc/lp/pb、dict_size +- 未压缩大小字段(8 字节) +- 是否能解压及解压后大小 +""" + +import argparse +import binascii +import lzma +import sys +from pathlib import Path + + +def parse_props(props: bytes): + if len(props) != 5: + raise ValueError("props 长度应为 5 字节") + prop0 = props[0] + lc = prop0 % 9 + tmp = prop0 // 9 + lp = tmp % 5 + pb = tmp // 5 + dict_size = int.from_bytes(props[1:5], "little") + return lc, lp, pb, dict_size + + +def analyze_one(path: Path, do_decompress: bool): + info = {"path": str(path)} + data = path.read_bytes() + info["compressed_size"] = len(data) + if len(data) < 13: + info["error"] = "文件长度不足 13 字节" + return info + + props = data[:5] + lc, lp, pb, dict_size = parse_props(props) + raw_size = int.from_bytes(data[5:13], "little") + info.update( + { + "props_hex": props.hex(), + "lc": lc, + "lp": lp, + "pb": pb, + "dict_size": dict_size, + "raw_size_field": raw_size, + "head16": data[:16].hex(), + } + ) + + if do_decompress: + try: + raw = lzma.decompress(data, format=lzma.FORMAT_ALONE) + info["decompress_ok"] = True + info["decompressed_size"] = len(raw) + info["raw_prefix_hex"] = binascii.hexlify(raw[:64]).decode() + except Exception as e: + info["decompress_ok"] = False + info["decompress_error"] = str(e) + return info + + +def main(): + ap = argparse.ArgumentParser(description="分析 LZMA Alone 头信息") + ap.add_argument("files", nargs="+", help="待分析的 .lzma 文件") + ap.add_argument( + "--no-decompress", action="store_true", help="不尝试解压,只解析头部" + ) + args = ap.parse_args() + + do_decompress = not args.no_decompress + for p in args.files: + info = analyze_one(Path(p), do_decompress) + print("=" * 60) + print(info["path"]) + if "error" in info: + print(f"error: {info['error']}") + continue + print(f"compressed_size: {info['compressed_size']}") + print( + f"props: {info['props_hex']} (lc={info['lc']}, lp={info['lp']}, pb={info['pb']}, dict_size={info['dict_size']})" + ) + print(f"raw_size_field: {info['raw_size_field']}") + print(f"head16: {info['head16']}") + if do_decompress: + if info.get("decompress_ok"): + print(f"decompressed_size: {info['decompressed_size']}") + print(f"raw_prefix_hex: {info.get('raw_prefix_hex','')}") + else: + print(f"decompress_error: {info.get('decompress_error')}") + + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/tools/scripts/epo/create_epo_lzma.py b/tools/scripts/epo/create_epo_lzma.py new file mode 100644 index 000000000..f0deb4c3d --- /dev/null +++ b/tools/scripts/epo/create_epo_lzma.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +创建 EPO LZMA 测试文件 +用于测试手表端的 uapi_epo_lzma_decompress 函数 + +文件格式: +[文件数量: 4字节,小端] +[文件1: 文件名长度(2字节,小端) + 文件名(变长) + 文件大小(4字节,小端) + 文件数据(变长)] +[文件2: ...] +... + +然后使用 LZMA 压缩整个打包数据 +""" + +import struct +import lzma +import os +import sys + +def create_epo_lzma(output_file="epo.lzma", test_files=None): + """ + 创建 EPO LZMA 测试文件 + + Args: + output_file: 输出的 LZMA 文件名 + test_files: 测试文件列表,格式为 [(文件名, 文件内容), ...] + """ + if test_files is None: + # 默认测试文件 + test_files = [ + ("1.eph", b"EPH file content for testing\nThis is the first file.\n"), + ("2.txt", b"TXT file content for testing\nThis is the second file.\nLine 2\nLine 3\n"), + ("3.dat", b"DAT\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" + + b"Binary data for testing\n" * 10) + ] + + # 创建打包数据 + packed_data = bytearray() + + # 写入文件数量(4字节,小端) + file_count = len(test_files) + packed_data.extend(struct.pack(' {len(compressed_data)} 字节") + + # 添加 LZMA SDK 格式头部(5字节属性 + 8字节未压缩大小) + lzma_header = bytearray() + + # 5字节 LZMA 属性 + # LZMA SDK 格式: 第一个字节是属性,后4字节是字典大小(可选) + # 属性字节计算: (lc * 9 + lp) * 5 + pb + # lc=3, lp=0, pb=2: (3*9+0)*5+2 = 137 = 0x89 + # 但 0x5D 更常用: lc=3, lp=0, pb=2, dict_size=16MB + lzma_props = bytearray(5) + lzma_props[0] = 0x5D # 属性字节 + lzma_props[1] = 0x00 # 字典大小(低字节) + lzma_props[2] = 0x00 + lzma_props[3] = 0x00 + lzma_props[4] = 0x00 # 字典大小(高字节,0表示默认16MB) + lzma_header.extend(lzma_props) + + # 8字节未压缩大小(小端) + unpacked_size = len(packed_data) + lzma_header.extend(struct.pack(' 1: + # 从命令行参数指定的文件创建 + file_list = sys.argv[1:-1] if len(sys.argv) > 2 else sys.argv[1:] + output_file = sys.argv[-1] if sys.argv[-1].endswith('.lzma') else "epo.lzma" + + if not sys.argv[-1].endswith('.lzma'): + file_list = sys.argv[1:] + output_file = "epo.lzma" + + create_from_real_files(file_list, output_file) + else: + # 使用默认测试文件 + print("=" * 70) + print("创建 EPO LZMA 测试文件") + print("=" * 70) + print("\n使用默认测试文件:") + create_epo_lzma() + print("\n" + "=" * 70) + print("使用方法:") + print(" python3 create_epo_lzma.py # 使用默认测试文件") + print(" python3 create_epo_lzma.py 1.eph 2.txt 3.dat epo.lzma # 从真实文件创建") + print("=" * 70) + + +if __name__ == '__main__': + main() + diff --git a/tools/scripts/epo/epo.lzma b/tools/scripts/epo/epo.lzma new file mode 100644 index 0000000000000000000000000000000000000000..49ab4e97d432d2563ad6f7e9b909114828011adb GIT binary patch literal 154 zcmV;L0A>GO00000p#cB@0000000DqFmws?MHSE;Llw`VHe*!j0>M_PI8V0#^z?!9x z(~{ptZfTfVHy0sPz8XhZ2lXklNAUH2!?9+68|1>A{uA-e2DUg=RP8MCl_Q9Bmx>#L z={5q=Qo`%+Pv$2GkOho5@Ra6AlZz{@3ZByBUL}W%()5of?gQGCapJ?sJF&VpsT~hL I|M^9@05-oyvj6}9 literal 0 HcmV?d00001 diff --git a/tools/scripts/epo/epo_decompress.py b/tools/scripts/epo/epo_decompress.py new file mode 100644 index 000000000..bd36de714 --- /dev/null +++ b/tools/scripts/epo/epo_decompress.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +简易 EPO LZMA 解包脚本 +格式: +- LZMA 头 13 字节(5 字节属性 + 8 字节原始长度) +- 解压后的数据: + uint32 file_count + 重复 file_count 次: + uint16 name_len + name_len 字节文件名 + uint32 file_size + file_size 字节文件数据 + +解包规则(与设备端保持一致): +- 从文件名中取下划线后的时间戳作为目录,例如 GLO_1765263582.eph -> /user/userdata/gnss/1765263582/ +- AssistInfo_*.dat 重命名为 AssistInfo.dat 后写入对应时间戳目录 +""" + +import argparse +import lzma +import os +import struct +from pathlib import Path + + +def extract_ts(name: str) -> str: + """提取文件名中 '_' 与 '.' 之间的时间戳子串""" + try: + us = name.rindex("_") + dot = name.rindex(".") + if dot <= us + 1: + return "" + return name[us + 1 : dot] + except ValueError: + return "" + + +def parse_and_write(raw: bytes, out_root: Path) -> None: + pos = 0 + if len(raw) < 4: + raise ValueError("解压数据过短,无法读取文件数量") + (file_count,) = struct.unpack_from(" 1000: + print( + f"[warn] 文件数量异常({file_count}),尝试使用无文件数量头的备用格式解析..." + ) + return parse_and_write_no_count(raw, out_root) + + # 先扫描 AssistInfo_ 时间戳 + assist_ts = "" + scan_pos = pos + for _ in range(file_count): + if scan_pos + 2 > len(raw): + break + (name_len,) = struct.unpack_from(" 255 or scan_pos + name_len + 4 > len(raw): + break + name = raw[scan_pos : scan_pos + name_len].decode("utf-8", errors="ignore") + scan_pos += name_len + (file_size,) = struct.unpack_from(" len(raw): + raise ValueError(f"文件 {idx} 读取 name_len 失败") + (name_len,) = struct.unpack_from(" 255: + raise ValueError(f"文件 {idx} name_len 非法: {name_len}") + if pos + name_len + 4 > len(raw): + raise ValueError(f"文件 {idx} 读取文件名或大小失败") + name = raw[pos : pos + name_len].decode("utf-8", errors="ignore") + pos += name_len + (file_size,) = struct.unpack_from(" len(raw): + raise ValueError(f"文件 {idx} 数据越界") + data = raw[pos : pos + file_size] + pos += file_size + + dir_ts = assist_ts or extract_ts(name) + if not dir_ts: + raise ValueError(f"文件 {name} 无法解析时间戳") + + final_name = "AssistInfo.dat" if name.startswith("AssistInfo_") else name + target_dir = out_root / dir_ts + target_dir.mkdir(parents=True, exist_ok=True) + target_path = target_dir / final_name + with open(target_path, "wb") as f: + f.write(data) + print(f"[ok] {target_path} ({file_size} bytes)") + + +def main(): + ap = argparse.ArgumentParser(description="解压 EPO LZMA 包到 /user/userdata/gnss//") + ap.add_argument("lzma_file", help="输入 LZMA 文件路径") + ap.add_argument( + "-o", + "--output", + default="/user/userdata/gnss", + help="输出根目录,默认 /user/userdata/gnss", + ) + args = ap.parse_args() + + lzma_path = Path(args.lzma_file) + out_root = Path(args.output) + out_root.mkdir(parents=True, exist_ok=True) + + print(f"[info] decompressing {lzma_path} -> {out_root}") + with open(lzma_path, "rb") as f: + compressed = f.read() + print(f"[info] compressed size={len(compressed)} bytes, head={compressed[:16].hex()}") + + # 直接用 Python lzma 解,输入必须是裸 LZMA 头+数据 + try: + raw = lzma.decompress(compressed, format=lzma.FORMAT_ALONE) + except lzma.LZMAError as e: + raise SystemExit(f"[error] LZMA 解码失败: {e}. 该文件可能不是裸 LZMA 格式。") + + print(f"[info] decompressed size={len(raw)} bytes") + parse_and_write(raw, out_root) + + +def parse_and_write_no_count(raw: bytes, out_root: Path) -> None: + """备用解析:没有 file_count 头部,直接按条目顺序解析""" + pos = 0 + entries = [] + while pos < len(raw): + if pos + 2 > len(raw): + raise ValueError(f"offset {pos}: 读取 name_len 失败") + (name_len,) = struct.unpack_from(" 255: + raise ValueError(f"offset {pos}: name_len 非法 {name_len}") + if pos + name_len + 4 > len(raw): + raise ValueError(f"offset {pos}: 读取文件名或大小失败") + name = raw[pos : pos + name_len].decode("utf-8", errors="ignore") + pos += name_len + (file_size,) = struct.unpack_from(" len(raw): + raise ValueError(f"文件 {name} 数据越界") + data = raw[pos : pos + file_size] + pos += file_size + entries.append((name, data)) + + if not entries: + raise ValueError("未解析到任何文件条目") + + assist_ts = "" + for name, _ in entries: + if name.startswith("AssistInfo_"): + assist_ts = extract_ts(name) + break + + for idx, (name, data) in enumerate(entries): + dir_ts = assist_ts or extract_ts(name) + if not dir_ts: + raise ValueError(f"文件 {name} 无法解析时间戳") + final_name = "AssistInfo.dat" if name.startswith("AssistInfo_") else name + target_dir = out_root / dir_ts + target_dir.mkdir(parents=True, exist_ok=True) + target_path = target_dir / final_name + with open(target_path, "wb") as f: + f.write(data) + print(f"[ok] {idx+1}/{len(entries)} {target_path} ({len(data)} bytes)") + + +if __name__ == "__main__": + main() + diff --git a/tools/scripts/epo/temp_packed_data.bin b/tools/scripts/epo/temp_packed_data.bin new file mode 100644 index 0000000000000000000000000000000000000000..01982d15ba0f41c30197a3ce252cd13436ebfda4 GIT binary patch literal 417 zcmZQ(U|?WnFw{#e$S?&mT?0H6(lT>W6_WGwN>cMm6w>mG6iQNyOEUA)xk55Bixq&V zBqJ55sHhky3skDd#mZo$S5i^p0@NQ85kj2);#8mwDNqA^GV@XujJUv*G0-?;y_Ccf qW<~}E7sn6=MkZz!RyKAHPA+a9UOs-O%)G>+N(G>TL=?vjS8)IUB8J-l literal 0 HcmV?d00001 -- 2.17.1