HAND_SLEEP_MODE_END_NOTIFY, // 哄睡模式结束通知
HAND_DOWNLOAD_EMOJI_NOTIFY, // 下载表情通知
HAND_CHAT_STAMINA_NOTIFY, // 对话体力与流量通知
+ HAND_API_ERROR_NOTIFY, // 协议错误通知
MAX_NUM_HANDLER,
} UiServiceMsgId;
static void HandSleepModeEndNotify(Request *request);
static void HandChatStaminaNotify(Request *request);
static void HandDownloadEmojiNotify(Request *request);
+static void HandApiErrorNotify(Request *request);
// 消息处理程序项数组(每个消息类型对应一个处理函数,函数名以 Handle 开头)
MsgHandlerEntry msgHandlers[] = {
{HANDLE_JUMP_OTA_VIEW, HandleJumpOtaView},
{HAND_SLEEP_MODE_END_NOTIFY,HandSleepModeEndNotify},
{HAND_CHAT_STAMINA_NOTIFY,HandChatStaminaNotify},
{HAND_DOWNLOAD_EMOJI_NOTIFY,HandDownloadEmojiNotify},
+ {HAND_API_ERROR_NOTIFY,HandApiErrorNotify},
};
/* -------------------------------------------------------------------------- */
void ScreenTurnOn(int32_t timeout)
});
}
+// 协议错误通知
+void HandApiErrorNotify(Request *request)
+{
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_UI_SERVICE, "HandApiErrorNotify msg_id = %d", request->msgId);
+ std::string value(static_cast<char*>(request->data), request->len);
+}
/* -------------------------------------------------------------------------- */
#ifdef __cplusplus
}
return ERRCODE_SUCC;
}
+/* AT^LN_API_ERROR - 推送协议错误 */
+static uint32_t ln_api_error_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ VAD_UNUSED(strCommonCmd);
+
+ uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+ const char *message = ""; // 默认空消息
+
+ if (para != NULL && paraLen > 0) {
+ if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) {
+ printf("ERROR: Failed to parse parameters\n");
+ return ERRCODE_FAIL;
+ }
+ if (argc >= 1) {
+ message = (char*)argv[0];
+ }
+ }
+
+ if (message != NULL && strlen(message) > 512) {
+ printf("ERROR: Message too long (max 512 characters)\n");
+ return ERRCODE_FAIL;
+ }
+
+ push_api_error_notification(message);
+ printf("+LN_API_ERROR: OK\n");
+ printf("[LN][API_ERROR] message=%s\n", message);
+ return ERRCODE_SUCC;
+}
+
static const at_cmd_table_t g_ln_api_cmd[] = {
/* ----------------------------------- VAD ---------------------------------- */
{(char *)"AT^VAD_INIT", vad_init, (char *)"初始化 VAD"},
{(char *)"AT^LN_DOWNLOAD_EMOJI", ln_download_emoji_at, (char *)"下载表情: AT^LN_DOWNLOAD_EMOJI[=characterId,eyesColor,eyesShape]"},
{(char *)"AT^LN_CHAT_STAMINA_DEMO", ln_chat_stamina_demo_at, (char *)"演示对话体力/流量UI: AT^LN_CHAT_STAMINA_DEMO=member|enough|low|empty 或自定义"},
{(char *)"AT^LN_SWITCH_EXPRESSION", ln_switch_expression_at, (char *)"切换表情视频: AT^LN_SWITCH_EXPRESSION=<video_path>"},
+ {(char *)"AT^LN_API_ERROR", ln_api_error_at, (char *)"推送协议错误: AT^LN_API_ERROR[=message]"},
/* 内存查看相关AT命令 */
{(char *)"AT^LN_MEMORY_INFO", ln_memory_info_at, (char *)"查看系统内存信息: AT^LN_MEMORY_INFO"},
{(char *)"AT^LN_FOLDER_SIZE", ln_folder_size_at, (char *)"查看文件夹大小: AT^LN_FOLDER_SIZE[=/path/to/folder]"},
int g_ln_last_package_size = 0; // 最近一次查询到的 OTA 包大小(字节)
int g_ln_is_device_bound = 0; /* 设备绑定状态 */
char g_ln_device_user_id[64] = {0}; // 设备绑定用户ID
+static int g_ln_is_file_downloading = 0; // 是否正在下载文件
/* 简单的字符串读写(以.bin形式读写原始字节,末尾补'\0')*/
static int ln_read_string_file(const char *file_path, char **out_str)
{
g_ln_is_device_bound = val;
}
+
+/* 下载文件
+@param url 文件URL
+@param save_path 保存路径
+@return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败
+*/
int uapi_ln_download_file(const char *url, const char *save_path)
{
if (!url || !save_path)
return ERRCODE_FAIL;
}
+ if(g_ln_is_file_downloading == 1)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 已有文件正在下载中");
+ return ERRCODE_FAIL;
+ }
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 开始下载: %s -> %s", url, save_path);
+ g_ln_is_file_downloading = 1;
HttpClient client = {0};
HttpClientData client_data = {0};
if (!fd)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] fopen failed for %s", save_path);
+ g_ln_is_file_downloading = 0;
return ERRCODE_FAIL;
}
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] prepare failed: %d", prep);
fclose(fd);
+ g_ln_is_file_downloading = 0;
return ERRCODE_FAIL;
}
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] connection failed, ret=0x%x", ret);
fclose(fd);
HttpClientUnprepare(&client_data);
+ g_ln_is_file_downloading = 0;
return ERRCODE_FAIL;
}
fclose(fd);
HttpClientClse(&client);
HttpClientUnprepare(&client_data);
+ g_ln_is_file_downloading = 0;
return ERRCODE_FAIL;
}
HttpClientUnprepare(&client_data);
HttpClientClse(&client);
fclose(fd);
+ g_ln_is_file_downloading = 0;
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] download process finished,ret=%d", ret);
return ret;
}
/* 设备ID/ICCID 本地缓存文件路径(.bin)*/
#define LN_DEVICE_ID_FILE LN_DATA_PATH "/device_id.bin"
#define LN_DEVICE_ICCID_FILE LN_DATA_PATH "/device_iccid.bin"
+#define LN_DEVICE_SCREEN_IMAGE_FILE LN_DATA_PATH "/device_screen_image.bin" // 屏保图片文件
#define LN_CLIENT_PREPARE_SIZE 2 * 1024
#define TEST_MP3_DOWNLOAD 0 // 测试下载mp3
printf(" avatar_screen_image: %s\n", g_ln_character_config.avatar_screen_image ? g_ln_character_config.avatar_screen_image : "");
printf(" avatar_screen_image_as_emoji: %d\n", g_ln_character_config.avatar_screen_image_as_emoji);
+ // 如果屏保图片不为空,则保存到本地
+ int ret = ERRCODE_SUCC;
+ if (g_ln_character_config.avatar_screen_image)
+ {
+ // 下载屏保图片,eg:"https://example.com/new_avatar.bin"
+ ret = uapi_ln_download_file(g_ln_character_config.avatar_screen_image, LN_DEVICE_SCREEN_IMAGE_FILE);
+ if (ERRCODE_SUCC != ret)
+ {
+ int i = 1;
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载失败,重试3次,第%d次", i);
+ // 如果下载失败重试3次
+ for (i = 1; i <= 3; i++)
+ {
+ ret = uapi_ln_download_file(g_ln_character_config.avatar_screen_image, LN_DEVICE_SCREEN_IMAGE_FILE);
+ if (ERRCODE_SUCC == ret)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载成功,第%d次", i);
+ break;
+ }
+ }
+ }
+ }
+ if (ERRCODE_SUCC != ret)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载失败,最终失败");
+ }
+
/* 推送角色配置更改通知 */
push_character_config_change_notification(g_ln_character_config.character_id,
g_ln_character_config.character_name,
g_ln_character_config.avatar_hair_color,
g_ln_character_config.avatar_eyes_color,
g_ln_character_config.avatar_hair_style,
- g_ln_character_config.avatar_screen_image_as_emoji);
+ ret == ERRCODE_SUCC ? 1 : 0);
cJSON_Delete(root);
free(text);
free(header_buf);
return ERRCODE_FAIL;
}
- if (clientData.responseBuf && clientData.responseBufLen > 0)
- {
- int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
- char *response_buf = (char *)malloc(real_len + 1);
- if (!response_buf)
- {
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_STATUS] malloc failed for response_buf");
- free(header_buf);
- return ERRCODE_FAIL;
- }
- memcpy(response_buf, clientData.responseBuf, real_len);
- response_buf[real_len] = '\0';
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_STATUS] response body: %s", response_buf);
- free(response_buf);
- }
char *json_text = (char *)malloc((size_t)body_len + 1);
if (!json_text)
json_text[body_len] = '\0';
/* 若非200,打印原始响应体便于排查 */
- if (code != 200) {
+ if (code != 200)
+ {
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] HTTP code=%d", code);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] body=%s", json_text);
}
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] server error: %s, msg=%s",
cJSON_IsString(errorCode) ? errorCode->valuestring : "<unknown>",
(message && cJSON_IsString(message)) ? message->valuestring : "<none>");
+ /* 推送协议错误到UI */
+ push_api_error_notification((message && cJSON_IsString(message)) ? message->valuestring : "<none>");
+
cJSON_Delete(root);
free(json_text);
HttpClientUnprepare(&clientData);
#include <stdlib.h>
#include "upg_common_porting.h" /* 使用统一的 UPG_FILE_NAME 路径宏 */
#include <string.h>
-#include <stdlib.h>
#include "mbedtls/md5.h"
#include "errcode.h"
#include "custom_utils.h"
+#include "cJSON.h"
/* OTA下载状态枚举(对应时序图中的下载阶段)*/
typedef enum
break;
}
}
-#include "cJSON.h"
+
//解析 OTA 任务 JSON,决定是否触发下载
static void ln_handle_ota_task_response(const char *json_buf, int json_len)
HTTPC_RESULT res = HttpClientGetRequest(&client, url_with_params, &clientData);
if (res != HTTP_SUCCESS)
{
- printf("[OTA] getTask failed %d\n", res);
+ printf("[OTA] HttpClientGetRequest error %d\n", res);
goto end;
}
int code = HttpClientGetResponseCode(&client);
if (code != 200)
{
- printf("[OTA] getTask failed %d\n", code);
+ printf("[OTA] HttpClientGetResponseCode %d\n", code);
if (clientData.responseBuf && clientData.responseBufLen > 0)
{
int real_len = clientData.responseContentLen > 0 ? clientData.responseContentLen : clientData.responseBufLen;
int ret = -1;
size_t total_written = 0; // 已写入文件的总字节数
int last_percent = -1; // 上次上报的进度百分比
- char *prog = (char *)malloc(64);
- if (!prog)
- {
- return -1;
- }
// 打开文件准备写入
FILE *fd = fopen(save_path, "wb+");
if (percent != last_percent)
{
last_percent = percent;
- int n = snprintf(prog, 64,
- "{\"status\":%d,\"progress\":%d}",
- OTA_DOWNLOAD_STATUS_PROGRESS, percent);
- if (n > 0) {
- OtaDownloadMsg om = {0};
- om.status = OTA_DOWNLOAD_STATUS_PROGRESS;
- om.progress = percent;
- ln_ota_handle_download_msg(&om);
- }
+ OtaDownloadMsg om = {0};
+ om.status = OTA_DOWNLOAD_STATUS_PROGRESS;
+ om.progress = percent;
+ ln_ota_handle_download_msg(&om);
}
// 检查是否超过预期总大小
fclose(fd);
HttpClientClse(&client);
HttpClientUnprepare(&client_data);
- if (prog)
- free(prog);
+
printf("[OTA] download process finished\n");
return ret;
}
if (dres != 0)
{
- char *msg = (char *)malloc(64);
- if (msg)
- {
- int n = snprintf(msg, 64, "{\"status\":%d,\"error\":\"download failed\"}", OTA_DOWNLOAD_STATUS_FAILED);
- if (n > 0) {
- OtaDownloadMsg om = {0};
- om.status = OTA_DOWNLOAD_STATUS_FAILED;
- snprintf(om.error_msg, sizeof(om.error_msg), "%s", "download failed");
- ln_ota_handle_download_msg(&om);
- }
- free(msg);
- }
+ OtaDownloadMsg om = {0};
+ om.status = OTA_DOWNLOAD_STATUS_FAILED;
+ snprintf(om.error_msg, sizeof(om.error_msg), "%s", "download failed");
+ ln_ota_handle_download_msg(&om);
free(save_path);
return ERRCODE_FAIL;
}
- size_t done_cap = 64 + strlen(save_path) + (g_ln_last_package_md5 ? strlen(g_ln_last_package_md5) : 0);
- char *done = (char *)malloc(done_cap);
- if (done) {
+ {
OtaDownloadMsg om = {0};
om.status = OTA_DOWNLOAD_STATUS_COMPLETED;
snprintf(om.file_path, sizeof(om.file_path), "%s", save_path);
if (g_ln_last_package_md5) snprintf(om.md5, sizeof(om.md5), "%s", g_ln_last_package_md5);
ln_ota_handle_download_msg(&om);
- free(done);
}
free(save_path);
return ERRCODE_SUCC;
#include "system/include/MemoryManager.h"
#include <string>
#include <cstring>
+#include "wearable_log.h"
// 前向声明网络检测函数,避免循环依赖
extern "C" {
// 组装简易数据载荷:标题+内容(若 UI 端需要更复杂结构,可定义专用 struct)
// 这里直接传两段以'\n'拼接的字符串给 UI,UI 再自行拆分。
char payload[384] = {0};
- printf("title:%s,content%s\r\n",title,content);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "title:%s,content%s\r\n",title,content);
snprintf(payload, sizeof(payload) - 1, "%s\n%s", title, content);
SendMsgToUIService(HAND_SCHEDULE_REMINDER_NOTIFY, 0,(void *)payload, (int32_t)strlen(payload));
}
// 推送任务提醒通知
void push_task_reminder_notification(const char *scheduleName)
{
- printf("scheduleName:%s\r\n",scheduleName);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "scheduleName:%s\r\n",scheduleName);
SendMsgToUIService(HAND_TASK_REMINDER_NOTIFY, 0,(void *)scheduleName, (int32_t)strlen(scheduleName));
}
const char *id = characterId ? characterId : "";
const char *ec = eyesColor ? eyesColor : "";
const char *es = eyesShape ? eyesShape : "";
- printf("push_download_emoji_notification: characterId=%s, eyesColor=%s, eyesShape=%s\r\n", id, ec, es);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "push_download_emoji_notification: characterId=%s, eyesColor=%s, eyesShape=%s\r\n", id, ec, es);
(void)snprintf(payload, sizeof(payload) - 1, "%s|%s|%s", id, ec, es);
//切换表情通知ui
SendMsgToUIService(HAND_DOWNLOAD_EMOJI_NOTIFY, 0, (void *)payload, (int32_t)strlen(payload));
bool switch_expression_video(const char *video_path)
{
if (video_path == nullptr) {
- printf("[LN][SWITCH_VIDEO] Error: video_path is null\n");
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN][SWITCH_VIDEO] Error: video_path is null\n");
return false;
}
OHOS::ThunderBirdModel* mainView = OHOS::ThunderBirdModel::GetInstance();
if (mainView == nullptr) {
- printf("[LN][SWITCH_VIDEO] Error: ThunderBirdModel instance is null\n");
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN][SWITCH_VIDEO] Error: ThunderBirdModel instance is null\n");
return false;
}
bool result = mainView->SwitchExpressionVideo(video_path);
- printf("[LN][SWITCH_VIDEO] Switching to video: %s, result: %s\n",
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[LN][SWITCH_VIDEO] Switching to video: %s, result: %s\n",
video_path, result ? "success" : "failed");
return result;
}
+// 推送协议错误通知到UI
+void push_api_error_notification(const char *message)
+{
+ const char *msg = message ? message : "";
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[API_ERROR] msg=%s", msg);
+ SendMsgToUIService(HAND_API_ERROR_NOTIFY, 0, (void *)msg, (int32_t)strlen(msg));
+}
// 检测是否有外网连接
int check_internet_connection(void)
const char *qrcodeUrl);
// 切换表情视频
bool switch_expression_video(const char *video_path);
+void push_api_error_notification(const char *message);
// 网络连接检测
int check_internet_connection(void);