#include "websocket_server_manager.h"
#endif
+namespace {
+
+// CloseEyes 内置资源目录中 calm-eyes / round-eyes 文件夹与业务眼型命名相反,仅映射内置眨眼资源
+static std::string MapCloseEyesResourceShape(const std::string &eyesShape)
+{
+ // if (eyesShape == "calm-eyes") {
+ // return "round-eyes";
+ // }
+ // if (eyesShape == "round-eyes") {
+ // return "calm-eyes";
+ // }
+ return eyesShape;
+}
+
+static std::string BuildCloseEyesVideoPath(const std::string &eyesShape, const std::string &eyesColor)
+{
+ return "/user/res/video/CloseEyes/" + MapCloseEyesResourceShape(eyesShape) + "/" + eyesColor + ".mp4";
+}
+
+} // namespace
+
//最低视频帧率
#define LOW_FREQUENCE 18
//最高视频帧率
cJSON *hairColor = cJSON_GetObjectItem(characterAvatar, "hairColor");
if (hairColor && cJSON_IsString(hairColor)) {
- if(strcmp(hairColor->valuestring, "warm-orange") == 0)//特殊颜色映射
- {
- hairColor_ = "orange";
- }
- else//正常颜色映射
- {
- hairColor_ = std::string(hairColor->valuestring);
- }
+ hairColor_ = std::string(hairColor->valuestring); // 直接保存颜色值
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::ParseCharacterConfigData: hairColor = %s", hairColor_.c_str());
}
std::string newHairStyle = hairStyle ? hairStyle : "";
std::string newHairColor = hairColor ? hairColor : "";
- // 处理warm-orange到orange的映射
- if (newHairColor == "warm-orange")
- {
- newHairColor = "orange";
- WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, " hairColor mapped from warm-orange to orange");
- }
// 比较传入的新值和上次保存的值(last_xxx_)
// 屏保角色(hasScreenImage 或 screenImageAsEmoji 为真)即使配置未变也需执行切换,以便刷新刚下载的屏保图等
// 检查是否是默认/空配置(calm-eyes, amber, warm-orange, curly-hair)
if(newEyesShape == "calm-eyes" && newEyesColor == "amber" &&
- newHairColor == "orange" && newHairStyle == "curly-hair" && screenImageAsEmoji == false &&
+ newHairColor == "warm-orange" && newHairStyle == "curly-hair" && screenImageAsEmoji == false &&
last_eyesShape_.empty() && last_hairColor_.empty() && last_eyesColor_.empty() && last_hairStyle_.empty() && last_hasScreenImage_ == false && last_screenImageAsEmoji_ == false)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::HandleCharacterConfigChange: 角色配置为默认配置,不进行处理");
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::ReadlocalExpression: eyesShape or eyesColor is empty");
return "";
}
- return "/user/res/video/CloseEyes/" + eyesShape_ + "/" + eyesColor_ + ".mp4";
+ return BuildCloseEyesVideoPath(eyesShape_, eyesColor_);
}
//开机读取发型
std::string ThunderBirdModel::ReadlocalHair()
// 检查屏幕状态,如果息屏则自动亮屏
uapi_set_screen_on_api(); // 亮屏
- // 构建表情文件路径:/user/res/video/CloseEyes/{eyeShape}/{eyeColor}.mp4
- std::string expressionPath = "/user/res/video/CloseEyes/";
- expressionPath += eyeShape;
- expressionPath += "/";
- expressionPath += eyeColor;
- expressionPath += ".mp4";
+ std::string expressionPath = BuildCloseEyesVideoPath(eyeShape, eyeColor);
// 检查文件是否存在(宽松检查)
if (access(expressionPath.c_str(), F_OK) != 0) {
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::SwitchExpressionVideo: file does not exist: %s", expressionPath.c_str());
shape.c_str(), color.c_str());
}
- std::string winkingExpressionPath = "/user/res/video/CloseEyes/" + shape + "/" + color + ".mp4";
+ std::string winkingExpressionPath = BuildCloseEyesVideoPath(shape, color);
// 检查文件是否存在
if (access(winkingExpressionPath.c_str(), F_OK) == 0) {
// 检查屏幕状态,如果息屏则自动亮屏
uapi_set_screen_on_api(); // 亮屏
- // 处理特殊颜色映射
- std::string mappedHairColor = hairColor;
- if(mappedHairColor == "warm-orange")//特殊颜色映射
- {
- mappedHairColor = "orange";
- }
// 构建发型文件路径:/user/res/hair/{hairShape}/{hairColor}.bin
std::string hairPath = "/user/res/hair/";
hairPath += hairShape;
hairPath += "/";
- hairPath += mappedHairColor;
+ hairPath += hairColor;
hairPath += ".bin";
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::SwitchHair: switching to %s", hairPath.c_str());
}
} else {
// 正常情况,切换到眨眼表情(winking)
- std::string winkingExpressionPath = "/user/res/video/CloseEyes/" + eyesShape_ + "/" + eyesColor_ + ".mp4";
+ std::string winkingExpressionPath = BuildCloseEyesVideoPath(eyesShape_, eyesColor_);
// 检查文件是否存在
if (access(winkingExpressionPath.c_str(), F_OK) == 0) {
}
// 切换到眨眼表情(winking)
- std::string winkingExpressionPath = "/user/res/video/CloseEyes/" + eyesShape_ + "/" + eyesColor_ + ".mp4";
+ std::string winkingExpressionPath = BuildCloseEyesVideoPath(eyesShape_, eyesColor_);
// 检查文件是否存在
if (access(winkingExpressionPath.c_str(), F_OK) == 0) {
{
//获取当前角色配置
//为默认表情
- if((eyesColor_.empty() == true || eyesShape_.empty() == true) || (eyesShape_ == "calm-eyes" && eyesColor_ == "amber"))
+ if((eyesColor_.empty() == true || eyesShape_.empty() == true) || (eyesShape_ == "round-eyes" && eyesColor_ == "amber"))
{
std::string emotionFileName = MapEmotionToFileName(targetExpression.c_str());
expressionPath = "/user/res/video/Default/"+ emotionFileName + ".mp4";
{
if(targetExpression == "winking")//如果为眨眼表情,则在默认文件目录里面
{
- expressionPath = "/user/res/video/CloseEyes/" + eyesShape_ + "/" + eyesColor_ + ".mp4";
+ expressionPath = BuildCloseEyesVideoPath(eyesShape_, eyesColor_);
}
else//非眨眼表情,则在下载文件目录里面
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::not find expression file %s", targetExpression.c_str());
// 当原始目标表情资源缺失时,若目标不是眨眼,则回退到眨眼表情,避免停留在睡觉/黑屏
if(targetExpression != "winking" && m_currentExpression != "winking") {
- std::string fallbackWinking = "/user/res/video/CloseEyes/" + eyesShape_ + "/" + eyesColor_ + ".mp4";
+ std::string fallbackWinking = BuildCloseEyesVideoPath(eyesShape_, eyesColor_);
if(access(fallbackWinking.c_str(), F_OK) != -1)
{
bool switched = SwitchExpressionVideo(fallbackWinking.c_str());
//表情切换相关AT命令函数声明
static uint32_t ln_switch_expression_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd);
+static uint32_t ln_switch_hair_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd);
//帧率控制相关AT命令函数声明
static uint32_t ln_set_fps_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd);
// 拼接完整路径:/user/userdata/leiniao/emoji + 用户输入的路径
char full_path[512];
- const char *base_path = "/user/userdata/leiniao/emoji";
- // const char *base_path = "/user/res/video/Default";
+ //const char *base_path = "/user/userdata/leiniao/emoji";
+ const char *base_path = "/user/res/video";
// 检查总路径长度限制
if (strlen(base_path) + strlen(video_path) + 1 > sizeof(full_path)) {
}
}
+/* AT^LN_SWITCH_HAIR=<hairStyle>,<hairColor> */
+static uint32_t ln_switch_hair_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ LN_UNUSED(strCommonCmd);
+
+ uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0};
+ uint32_t argc = 0;
+
+ if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) {
+ printf("ERROR: Failed to parse parameters\n");
+ return ERRCODE_FAIL;
+ }
+
+ if (argc < 2) {
+ printf("ERROR: Missing parameters\n");
+ printf("Usage: AT^LN_SWITCH_HAIR=<hairStyle>,<hairColor>\n");
+ printf("Example: AT^LN_SWITCH_HAIR=side-parted-comma,pink\n");
+ return ERRCODE_FAIL;
+ }
+
+ const char *hairStyle = (char *)argv[0];
+ const char *hairColor = (char *)argv[1];
+ if (hairStyle == NULL || hairColor == NULL || hairStyle[0] == '\0' || hairColor[0] == '\0') {
+ printf("ERROR: Empty hairStyle or hairColor\n");
+ return ERRCODE_FAIL;
+ }
+
+ printf("[LN][SWITCH_HAIR] style=%s color=%s\n", hairStyle, hairColor);
+
+ bool result = switch_hair(hairStyle, hairColor);
+ if (result) {
+ printf("+LN_SWITCH_HAIR: OK\n");
+ return ERRCODE_SUCC;
+ }
+
+ printf("+LN_SWITCH_HAIR: FAIL\n");
+ return ERRCODE_FAIL;
+}
+
/* AT^LN_API_ERROR - 推送协议错误 */
static uint32_t ln_api_error_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
{
{(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_SWITCH_HAIR", ln_switch_hair_at, (char *)"切换发型: AT^LN_SWITCH_HAIR=<hairStyle>,<hairColor>"},
{(char *)"AT^LN_API_ERROR", ln_api_error_at, (char *)"推送协议错误: AT^LN_API_ERROR[=message]"},
{(char *)"AT^LN_NOTIFY_BIND_AND_UNBIND", ln_notify_bind_and_unbind_at, (char *)"通知用户绑定或解绑: AT^LN_NOTIFY_BIND_AND_UNBIND=1/0"},
{(char *)"AT^LN_NOTIFY_DOWNLOAD_SUCCESS", ln_download_success_notification_at, (char *)"通知切换屏保: AT^LN_NOTIFY_DOWNLOAD_SUCCESS=0/1"},