#include "pet_init.h" #include "esp_log.h" #include "nvs_flash.h" #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #include #include "esp_netif_sntp.h" #include "freertos/queue.h" #include "esp_random.h" #define TAG "PET" static TaskHandle_t pet_task_handle = NULL; static void pet_time_sync_task(void *arg); static void pet_user_write_task(void *arg); QueueHandle_t user_interact_Queue = NULL; bool is_ntp_initalized = false; static void feed_ready_handler(TimerHandle_t xTimer); TimerHandle_t feed_timer=NULL; static void travel_ready_handler(TimerHandle_t xTimer); TimerHandle_t travel_timer=NULL; uint8_t remaining_hunger=0; uint8_t remaining_cleanliness=0; uint8_t remaining_mood=0; static pet_status_t s_pet_status = { .hunger = 0, //饱腹值 .cleanliness = 0, //清洁度 .mood = 0, //心情 .intimacy = 0, //亲密度 .stage = PET_STAGE_BABY, //成长状态 .vehicle = PET_VEHICLE_WALK, //出行方式 .last_timestamp = 0, //时间 .last_time = {0}, .travel_time =0, //当前旅行所需时间 .is_travelling = false, //是否在旅游 .travel_timestamp = 0, //触发旅游出行的时间(可计算) .can_travel = false, //能否旅游状态(可计算) .feed_time = 14400, //喂食所需时间(固定值) .feed_timestamp = 0, //上一次喂食时间 .is_feed_cd = false, //是否在喂食冷却 }; /* 城市中文名数组 */ const char *city_names[CITY_COUNT] = { "公园", "街道", "河流", "古镇", "山道", "水库", "聚落", "果园", "惠州", "东莞", "佛山", "中山", "江门", "清远", "肇庆", "河源", "梅州", "重庆", "西安", "广州", "南京", "三亚", "北京", "长沙", "张家界", "湘西", "凤凰古镇", "上海", "香格里拉", "丽江", "大理", "西藏", "厦门", "景德镇", "平潭", "青岛", "北海", "万宁", "淄博", "延吉", "柳州", "宜昌", "漠河", "桂林", "杭州", "西塘", "青海", "西双版纳", "敦煌", "云南", }; /* 宠物阶段介绍文案 */ static const char baby_introduction[] = "我还小小的呢,先在附近逛逛就很开心啦!"; static const char teen_introduction[] = "我长大啦!现在可以去更远的城市看一看咯!"; static const char adult_introduction[] = "谢谢你一直陪着我!现在,天涯海角我都能去到啦!"; //static const char travel_return[] = "我回来啦!快看我带回来的好东西~"; //static const char hunger[] = "我回来啦!快看我带回来的好东西~"; static const char *pet_stage_introduction[3] = { baby_introduction, teen_introduction, adult_introduction, }; /* 全局状态标志 */ bool g_pet_need_first_sync = false; //第一次初始化NVS bool g_pet_ntp_synced = false; //开机第一次同步NTP bool g_pet_animation_ready = false; //授时完成等待显示 bool go_travelling() { // 1. 获取一个32位硬件随机数 uint32_t random_num = esp_random(); // 2. 将随机数映射到 0-99 的范围,判断是否小于20 // 由于随机数在32位空间均匀分布[reference:5],取余100即可得到0-99的均匀分布 if (random_num % 100 < 20) { return true; // 旅行,award = 1 } else { return false; // 不旅行,award = 0 } } void print_date_time(void) { time_t now; struct tm timeinfo; time(&now); // 获取当前时间戳(秒) localtime_r(&now, &timeinfo); // 转换为本地时间(需先设置时区) // 提取年、月、日 int year = timeinfo.tm_year + 1900; // tm_year 从 1900 开始计数 int month = timeinfo.tm_mon + 1; // tm_mon 范围 0~11,需加 1 int day = timeinfo.tm_mday; // tm_mday 为当月第几天(1~31) // 也可以同时获取时、分、秒 int hour = timeinfo.tm_hour; int minute = timeinfo.tm_min; int second = timeinfo.tm_sec; // 打印日期和时间 ESP_LOGI("NTP", "日期: %04d-%02d-%02d", year, month, day); ESP_LOGI("NTP", "时间: %02d:%02d:%02d", hour, minute, second); // 或者格式化输出完整时间 char buffer[64]; strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo); ESP_LOGI("NTP", "当前本地时间: %s", buffer); } bool obtain_time(void) { ESP_LOGI("NTP", "Initializing SNTP"); if(!is_ntp_initalized){ // 1. 设置SNTP服务器 esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org"); // 国内用户可更换为: "cn.pool.ntp.org" 或 "ntp.aliyun.com" // 2. 初始化并启动SNTP服务 esp_netif_sntp_init(&config); is_ntp_initalized = true; } // 3. 等待时间同步完成 (超时10秒) if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(10000)) == ESP_OK) { ESP_LOGI("NTP", "Time synchronized successfully"); } else { ESP_LOGE("NTP", "Failed to synchronize time"); return false; // 同步失败,返回 false } // 4. 设置时区 (中国时区 UTC+8) setenv("TZ", "CST-8", 1); tzset(); // 5. 获取并打印当前时间 time_t now; struct tm timeinfo; time(&now); localtime_r(&now, &timeinfo); char strftime_buf[64]; strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); ESP_LOGI("NTP", "Current time: %s", strftime_buf); return true; // 同步成功,返回 true } static uint8_t clamp_u8(uint8_t value, uint8_t min, uint8_t max) { if (value < min) return min; if (value > max) return max; return value; } static uint16_t clamp_u16(uint16_t value, uint16_t min, uint16_t max) { if (value < min) return min; if (value > max) return max; return value; } static void pet_save_status_to_nvs(void) { nvs_handle_t handle; esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle); if (err != ESP_OK) { ESP_LOGW(TAG, "打开 NVS pet_data 失败: %s", esp_err_to_name(err)); return; } err = nvs_set_u8(handle, "hunger", s_pet_status.hunger); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 hunger 失败: %s", esp_err_to_name(err)); } err = nvs_set_u8(handle, "cleanliness", s_pet_status.cleanliness); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 cleanliness 失败: %s", esp_err_to_name(err)); } err = nvs_set_u8(handle, "mood", s_pet_status.mood); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 mood 失败: %s", esp_err_to_name(err)); } err = nvs_set_u16(handle, "intimacy", s_pet_status.intimacy); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 intimacy 失败: %s", esp_err_to_name(err)); } /* 保存最后时间戳(u64) */ err = nvs_set_u64(handle, "last_ts", s_pet_status.last_timestamp); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 last_ts 失败: %s", esp_err_to_name(err)); } err = nvs_set_u8(handle, "is_travelling", s_pet_status.is_travelling); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 is_travelling 失败: %s", esp_err_to_name(err)); } err = nvs_set_u8(handle, "is_feed_cd", s_pet_status.is_feed_cd); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 is_feed_cd 失败: %s", esp_err_to_name(err)); } err = nvs_set_u64(handle, "travel_ts", s_pet_status.travel_timestamp); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 travel_timestamp 失败: %s", esp_err_to_name(err)); } err = nvs_set_u64(handle, "feed_timestamp", s_pet_status.feed_timestamp); if (err != ESP_OK) { ESP_LOGW(TAG, "保存 feed_timestamp 失败: %s", esp_err_to_name(err)); } err = nvs_commit(handle); if (err != ESP_OK) { ESP_LOGW(TAG, "NVS 提交失败: %s", esp_err_to_name(err)); } nvs_close(handle); } static void pet_load_status_from_nvs(void) { nvs_handle_t handle; esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle); uint8_t hunger = 0; uint8_t cleanliness = 0; uint8_t mood = 0; uint16_t intimacy = 0; uint8_t is_travelling = 0; uint8_t is_feed_cd = 0; esp_err_t err_hunger = nvs_get_u8(handle, "hunger", &hunger); esp_err_t err_cleanliness = nvs_get_u8(handle, "cleanliness", &cleanliness); esp_err_t err_mood = nvs_get_u8(handle, "mood", &mood); esp_err_t err_intimacy = nvs_get_u16(handle, "intimacy", &intimacy); esp_err_t err_last_ts = nvs_get_u64(handle, "last_ts", &s_pet_status.last_timestamp); esp_err_t err_is_travelling = nvs_get_u8(handle,"is_travelling",&is_travelling); if (err_is_travelling == ESP_OK) { s_pet_status.is_travelling = is_travelling; }; esp_err_t err_travel_timestamp = nvs_get_u64(handle,"travel_ts",&s_pet_status.travel_timestamp); esp_err_t err_is_feed_cd = nvs_get_u8(handle,"is_feed_cd",&is_feed_cd); if (err_is_feed_cd == ESP_OK) { s_pet_status.is_feed_cd = is_feed_cd; }; esp_err_t err_feed_timestamp = nvs_get_u64(handle,"feed_timestamp",&s_pet_status.feed_timestamp); if (err_hunger == ESP_OK && err_cleanliness == ESP_OK && err_mood == ESP_OK && err_intimacy == ESP_OK && err_last_ts == ESP_OK) { ESP_LOGW(TAG, "打开 NVSdata 成功,进入读取分支 "); s_pet_status.hunger = clamp_u8(hunger, 0, 100); s_pet_status.cleanliness = clamp_u8(cleanliness, 0, 100); s_pet_status.mood = clamp_u8(mood, 0, 100); s_pet_status.intimacy = clamp_u16(intimacy, 0, 300); /* 将 u64 转换为 struct tm(本地时区) */ time_t t = (time_t)s_pet_status.last_timestamp; localtime_r(&t, &s_pet_status.last_time); g_pet_need_first_sync = false; nvs_close(handle); }else{ ESP_LOGW(TAG, "打开 NVSdata 失败,进入初始化分支 "); s_pet_status.hunger = 0; s_pet_status.cleanliness = 0; s_pet_status.mood = 0; s_pet_status.intimacy = 0; nvs_set_u8(handle, "hunger", s_pet_status.hunger); nvs_set_u8(handle, "cleanliness", s_pet_status.cleanliness); nvs_set_u8(handle, "mood", s_pet_status.mood); nvs_set_u16(handle, "intimacy", s_pet_status.intimacy); nvs_set_u8(handle,"is_travelling",s_pet_status.is_travelling); nvs_set_u64(handle,"travel_ts",s_pet_status.travel_timestamp); nvs_set_u8(handle,"is_feed_cd",s_pet_status.is_feed_cd); nvs_set_u64(handle,"feed_timestamp",s_pet_status.feed_timestamp); err = nvs_commit(handle); if (err != ESP_OK) { ESP_LOGW(TAG, "初始化 NVS pet_data 提交失败: %s", esp_err_to_name(err)); } g_pet_need_first_sync = true; // 标记需要第一次时间同步 nvs_close(handle); } return; } pet_stage_t pet_get_stage(uint16_t intimacy) { if (intimacy >= 200) { return PET_STAGE_ADULT; } if (intimacy >= 100) { return PET_STAGE_TEEN; } return PET_STAGE_BABY; } static void pet_update_stage(void) { s_pet_status.stage = pet_get_stage(s_pet_status.intimacy); } static void pet_update_can_travel(void) { if (s_pet_status.is_travelling) { s_pet_status.can_travel = false; return; } if (s_pet_status.mood >= 60 && s_pet_status.intimacy >= 30 && s_pet_status.hunger >= 30) { s_pet_status.can_travel = true; } else { s_pet_status.can_travel = false; } } static void pet_update_travel_time(void) { switch (s_pet_status.vehicle) { case PET_VEHICLE_WALK: s_pet_status.travel_time = 30 * 60; /* 30 minutes */ break; case PET_VEHICLE_BIKE: s_pet_status.travel_time = 60 * 60; /* 60 minutes */ break; case PET_VEHICLE_MOTORCYCLE: s_pet_status.travel_time = 3 * 60 * 60; /* 3 hours */ break; case PET_VEHICLE_CAR: s_pet_status.travel_time = 6 * 60 * 60; /* 6 hours */ break; case PET_VEHICLE_PLANE: s_pet_status.travel_time = 12 * 60 * 60; /* 12 hours */ break; default: s_pet_status.travel_time = 30 * 60; break; } } pet_vehicle_t pet_get_vehicle_stage(uint16_t intimacy) { if (intimacy >= 200) { return PET_VEHICLE_PLANE; } if (intimacy >= 150) { return PET_VEHICLE_CAR; } if (intimacy >= 100) { return PET_VEHICLE_MOTORCYCLE; } if (intimacy >= 50) { return PET_VEHICLE_BIKE; } return PET_VEHICLE_WALK; } static void pet_update_vehicle_stage(void) { s_pet_status.vehicle = pet_get_vehicle_stage(s_pet_status.intimacy); } static const char *pet_stage_name(pet_stage_t stage) { switch (stage) { case PET_STAGE_BABY: return "幼崽"; case PET_STAGE_TEEN: return "少年"; case PET_STAGE_ADULT: return "成年"; default: return "未知"; } } static const char *pet_stage_vehicle_name(pet_vehicle_t stage) { switch (stage) { case PET_VEHICLE_WALK: return "步行"; case PET_VEHICLE_BIKE: return "单车"; case PET_VEHICLE_MOTORCYCLE: return "摩托"; case PET_VEHICLE_CAR: return "汽车"; case PET_VEHICLE_PLANE: return "飞机"; default: return "未知"; } } static void pet_log_status(const char *action) { ESP_LOGI(TAG, "%s: 饱腹=%u, 清洁=%u, 心情=%u, 亲密度=%u, 形态=%s, 出行方式=%s", action, s_pet_status.hunger, s_pet_status.cleanliness, s_pet_status.mood, s_pet_status.intimacy, pet_stage_name(s_pet_status.stage), pet_stage_vehicle_name(s_pet_status.vehicle)); } void pet_init(void) { nvs_handle_t handle; esp_err_t err = nvs_open("pet_data", NVS_READWRITE, &handle); nvs_set_u8(handle, "hunger", 30); nvs_set_u8(handle, "cleanliness", 30); nvs_set_u8(handle, "mood", 30); nvs_set_u8(handle,"is_travelling",0); nvs_set_u64(handle,"travel_ts",s_pet_status.travel_timestamp); nvs_set_u8(handle,"is_feed_cd",0); nvs_set_u64(handle,"feed_timestamp",s_pet_status.feed_timestamp); err = nvs_commit(handle); nvs_close(handle); pet_load_status_from_nvs(); pet_update_stage(); pet_update_vehicle_stage(); ESP_LOGI(TAG, "宠物加载完成,当前形态=%s,出行方式=%s", pet_stage_name(s_pet_status.stage), pet_stage_vehicle_name(s_pet_status.vehicle)); pet_log_status("加载后状态"); pet_task_stack = heap_caps_malloc(PET_TASK_STACK_SIZE, MALLOC_CAP_INTERNAL); if (pet_task_stack == NULL) { ESP_LOGE("LVGL", "Failed to allocate PET task stack"); return; } write_nvs_task_stack = heap_caps_malloc(HOUR_WRITE_STACK_SIZE, MALLOC_CAP_INTERNAL); if (write_nvs_task_stack == NULL) { ESP_LOGE("LVGL", "Failed to allocate PET_WRITE task stack"); return; } user_interact_Queue = xQueueCreate(5, sizeof(int)); if (user_interact_Queue == NULL) { ESP_LOGE(TAG, "Failed to create user_interact_Queue"); return; } xTaskCreateStaticPinnedToCore(pet_time_sync_task, "PET", PET_TASK_STACK_SIZE, NULL, PET_TASK_PRIORITY, pet_task_stack, &pet_task_tcb, 1); xTaskCreateStaticPinnedToCore(pet_user_write_task, "PET_WRITE", HOUR_WRITE_STACK_SIZE, NULL, HOUR_WRITE_TASK_PRIORITY, write_nvs_task_stack, &write_nvs_task_tcb, 1); } const pet_status_t *pet_get_status(void) { return &s_pet_status; } /*交互动作数值计算*/ void pet_feed(void) { s_pet_status.hunger = clamp_u8(s_pet_status.hunger + 30, 0, 100); s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100); s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 2, 0, 300); pet_update_stage(); pet_update_vehicle_stage(); time_t now; time(&now); s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); pet_save_status_to_nvs(); pet_log_status("喂食"); } void pet_bath(void) { s_pet_status.cleanliness = clamp_u8(s_pet_status.cleanliness + 100, 0, 100); s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100); s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 2, 0, 300); pet_update_stage(); pet_update_vehicle_stage(); time_t now; time(&now); s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); pet_save_status_to_nvs(); pet_log_status("洗澡"); } void pet_interact(void) { s_pet_status.mood = clamp_u8(s_pet_status.mood + 5, 0, 100); s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 1, 0, 300); pet_update_stage(); pet_update_vehicle_stage(); time_t now; time(&now); s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); pet_save_status_to_nvs(); pet_log_status("点击互动"); } void pet_travel_return(void) { s_pet_status.mood = clamp_u8(s_pet_status.mood + 20, 0, 100); s_pet_status.intimacy = clamp_u16(s_pet_status.intimacy + 5, 0, 300); pet_update_stage(); pet_update_vehicle_stage(); time_t now; time(&now); s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); pet_save_status_to_nvs(); pet_log_status("旅行归来"); } void cal_deacy(uint64_t elapsed){ if(elapsed> 172800){ s_pet_status.hunger = 0; s_pet_status.cleanliness = 0; s_pet_status.mood = 0; } else{ uint8_t decay_hunger = elapsed / 450; uint8_t decay_cleanliness = elapsed / 1200; uint8_t decay_mood = elapsed / 1500; uint8_t remaining_hunger = elapsed % 450; uint8_t remaining_cleanliness = elapsed % 1200; uint8_t remaining_mood = elapsed % 1500; s_pet_status.hunger = (decay_hunger >= s_pet_status.hunger) ? 0 : s_pet_status.hunger - decay_hunger; s_pet_status.cleanliness = (decay_cleanliness >= s_pet_status.cleanliness) ? 0 : s_pet_status.cleanliness - decay_cleanliness; s_pet_status.mood = (decay_mood >= s_pet_status.mood) ? 0 : s_pet_status.mood - decay_mood; pet_log_status("加载后状态"); } } static void compare_time_cd(uint64_t now){ if(s_pet_status.is_feed_cd){ uint64_t elapsed = now - s_pet_status.feed_timestamp; if(elapsed > 14400){ s_pet_status.is_feed_cd = false; s_pet_status.feed_timestamp = 0; } } if(s_pet_status.is_travelling){ uint64_t elapsed = now - s_pet_status.travel_timestamp; if(elapsed > s_pet_status.travel_time){ s_pet_status.is_travelling = false; s_pet_status.travel_timestamp = 0; } } return; } static void feed_ready_handler(TimerHandle_t xTimer) { s_pet_status.is_feed_cd = false; s_pet_status.feed_timestamp = 0; ESP_LOGW("PET", "进入定时器结算"); pet_save_status_to_nvs(); feed_timer = NULL; xTimerDelete(xTimer, 0); } static void travel_ready_handler(TimerHandle_t xTimer) { s_pet_status.is_travelling = false; s_pet_status.travel_timestamp = 0; ESP_LOGW("PET", "进入定时器结算"); pet_save_status_to_nvs(); travel_timer = NULL; xTimerDelete(xTimer, 0); } /* * 时间同步任务骨架 * - 如果无网络/未能 NTP,同步失败后进入休眠/等待 * - 同步成功后设置 g_pet_animation_ready * - 如果是第一次初始化(g_pet_need_first_sync==true),写入当前时间到 NVS * - 否则根据过去时间计算状态衰减(此处留占位注释,具体公式可在 TODO 中实现) */ static void pet_time_sync(void){ if (!g_wifi_state){ // 无网络则休眠,等待外部事件唤醒或下次循环 ESP_LOGE("PET", "无WIFI进入待机状态"); vTaskDelay(pdMS_TO_TICKS(5000)); } else{ g_pet_ntp_synced = obtain_time(); if(!g_pet_ntp_synced){ ESP_LOGE("PET", "有WIFI但网络同步失败,进入休眠状态"); vTaskDelay(pdMS_TO_TICKS(5000)); } else{ ESP_LOGW("PET", "有WIFI且时钟同步成功,开启动画窗口"); time_t now; time(&now); if (g_pet_need_first_sync) { ESP_LOGW("PET", "第一次同步,写入当前时间到 NVS"); s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); pet_save_status_to_nvs(); g_pet_need_first_sync = false; g_pet_animation_ready = true; }else { ESP_LOGW("PET", "不是第一次同步,计算过去时间并衰减属性"); uint64_t elapsed = (uint64_t)now - s_pet_status.last_timestamp; ESP_LOGW("PET", "相比之前过去了%llu秒",elapsed); s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); compare_time_cd((uint64_t)now); char strftime_buf[64]; strftime(strftime_buf, sizeof(strftime_buf), "%c", &s_pet_status.last_time);//覆盖上次启动时间 ESP_LOGI("NTP", "同步时间: %s", strftime_buf); cal_deacy(elapsed); pet_save_status_to_nvs(); pet_update_stage(); pet_update_vehicle_stage(); g_pet_animation_ready = true; } } } } static void pet_time_timer(void){ const TickType_t delay_ms = pdMS_TO_TICKS(60000*5); // 5 分钟间隔 TickType_t last_wake = xTaskGetTickCount(); uint32_t tick_count = 0; int times_hunger; int times_cleanliness; int times_mood; while (1) { vTaskDelayUntil(&last_wake, delay_ms); tick_count++; ESP_LOGI("NTP", "五分钟间隔"); time_t now; time(&now); //uint64_t elapsed = (uint64_t)now - s_pet_status.last_timestamp; uint8_t compensate_tickcount_hunger= remaining_hunger / 300; //掉电补偿 uint8_t compensate_tickcount_cleanliness= remaining_cleanliness / 300; //掉电补偿 uint8_t compensate_tickcount_mood= remaining_mood / 300; int cmd1=1;int cmd2=2;int cmd3=3;int cmd4=4; if(s_pet_status.is_feed_cd){ uint64_t elapsed = (uint64_t)now - s_pet_status.feed_timestamp; if(elapsed > s_pet_status.feed_time - 300){ //最后五分钟 feed_timer = xTimerCreate("feed_ready_can_use", pdMS_TO_TICKS((s_pet_status.feed_time-elapsed)*1000), pdFALSE, (void*)0, feed_ready_handler); xTimerStart(feed_timer, 0); ESP_LOGW("PET", "即将完成喂食冷却,启用高精度定时"); } } if(s_pet_status.is_travelling){ uint64_t elapsed = (uint64_t)now - s_pet_status.travel_timestamp; if(elapsed > s_pet_status.travel_time - 300){ //最后五分钟 travel_timer = xTimerCreate("travel_ready_done", pdMS_TO_TICKS((s_pet_status.travel_time-elapsed)*1000), pdFALSE, (void*)0, travel_ready_handler); xTimerStart(travel_timer, 0); ESP_LOGW("PET", "即将完成旅行冷却,启用高精度定时"); } } /* (15 分钟) 饱腹 -2 */ if ((tick_count + compensate_tickcount_hunger) % 3 == 0) { if (s_pet_status.hunger > 2) s_pet_status.hunger -= 2; else s_pet_status.hunger = 0; s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); remaining_hunger = 0; ESP_LOGW("PET", "饱腹-2"); } /* (20 分钟) 清洁 -1 */ if ((tick_count + compensate_tickcount_cleanliness) % 4 == 0) { if (s_pet_status.cleanliness > 0) s_pet_status.cleanliness -= 1; else s_pet_status.cleanliness = 0; s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); ESP_LOGW("PET", "清洁-1"); } /* (25 分钟) 心情 -1 */ if ((tick_count + compensate_tickcount_cleanliness)% 5 == 0) { if (s_pet_status.mood > 0) s_pet_status.mood -= 1; else s_pet_status.mood = 0; s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); ESP_LOGW("PET", "心情-1"); } if(tick_count % 2 ==0 && go_travelling() && s_pet_status.can_travel){//缺少场景模式判断 xQueueSend(user_interact_Queue, &cmd4,0); } /* 每 12 次(60 分钟) 写入 NVS */ if ((tick_count % 12) == 0) { s_pet_status.last_timestamp = (uint64_t)now; localtime_r(&now, &s_pet_status.last_time); pet_save_status_to_nvs(); ESP_LOGI(TAG, "Hourly NVS write (tick %u)", tick_count); } } } static void pet_time_sync_task(void *arg) { while (1){ uxTaskGetStackHighWaterMark(NULL); if(g_pet_animation_ready == false){ pet_time_sync(); }else{ pet_time_timer(); } } } static void pet_user_write_task(void *arg) { int received_cmd = 0; while (1) { /* 阻塞直到收到数据,等待用户命令 */ uxTaskGetStackHighWaterMark(NULL); if (xQueueReceive(user_interact_Queue, &received_cmd, portMAX_DELAY) == pdPASS) { ESP_LOGI(TAG, "pet_user_write_task: received command %d", received_cmd); switch (received_cmd) { case 1: pet_feed(); ESP_LOGI(TAG, "命令1执行"); break; case 2: pet_bath(); ESP_LOGI(TAG, "命令2执行"); break; case 3: pet_interact(); ESP_LOGI(TAG, "命令3执行"); break; case 4: pet_travel_return(); //pet_start_travelling(); ESP_LOGI(TAG, "命令4执行"); break; default: ESP_LOGW(TAG, "未知用户命令 %d", received_cmd); break; } } } } void pet_start_travelling(void) { s_pet_status.mood = clamp_u8(s_pet_status.mood + 20, 0, 100); }