#include "cm_iomux.h" #include "cm_gpio.h" #include "stdio.h" #include "stdlib.h" #include "stdarg.h" #include "math.h" #include "cm_os.h" #include "cm_fs.h" #include "cm_mem.h" #include "cm_sys.h" #include "cm_uart.h" #include "app_common.h" #include "app_uart.h" #include "control_out.h" #include "attr_broadcast.h" #include "gps_config.h" #include "local_tts.h" #if 0 #include "app_uart.h" #define DEBUG(fmt, args...) app_printf("[Broadcast]" fmt, ##args) #else #include "app_uart.h" #define DEBUG(fmt, ...) #endif #define MAX_ATTRACTIONS 50 // 最大景点数量 #define PLAY_DISTANCE_THRESHOLD 50.0 // 有效播报距离(米) #define DISTANCE_CHANGE_THRESHOLD 10.0 // 距离变化阈值(米) #define TTS_PRIORITY 5 // TTS播报优先级 #define MAX_TTS_SEGMENT_LEN 70 // TTS每段最大长度(字符) #define ATTRACTIONS_FILE "attr.txt" // 区域ID列表文件 static nmeaPARSER parser; //该版本仅实现基本的靠近播报功能,长文字播放功能待开发 // 文本分段结构 [新增] typedef struct { char** segments; // 分段文本数组 int count; // 分段数量 } SegmentedText; // 修改景点节点结构 typedef struct AttractionNode { uint32_t region_id; // 区域ID [新增] double longitude; // 经度 double latitude; // 纬度 double radius; // 半径 [新增] char name[50]; // 景点名称 double trigger_distance; // 触发距离(米) SegmentedText description; struct AttractionNode* next; } AttractionNode; // 播报状态 typedef struct { AttractionNode* last_broadcast; double last_distance; uint8_t is_playing; double distance_threshold; } BroadcastState; typedef int BOOL; #define true 1 #define false 0 const char *park_desc[] = { "尊敬的游客欢迎来到顾村公园游玩", "顾村公园,位于上海市宝山区顾村镇沪太路4788号", "东起沪太路,西至陈广路,北邻镜泊湖路,南靠外环", "高速公路,占帝总面积430公顷,其中一期占帝180", "公顷,二期占帝244.7公顷,顾村公园一期于2007年8月", "动工建设,二期于2012年10月22日动工建设.公园", "建设理念为“亲民人文,以生态休闲娱乐为主题.", "集生态防护、景观观赏、休闲健身、文化娱乐旅游", "度假等功能于一体,与传统的城市公园形成功能互补", "并以中心河为界分两期实施。顾村公园为国家4A级", "景区,上海市科普教育基地、上海市志愿者服务基地等。" }; static osMutexId_t attractions_mutex = NULL; static AttractionNode* attractions_head = NULL; static BroadcastState broadcast_state = {0}; static int tts_speed = 5; static int tts_volume = 10; static osThreadId_t Attr_Broadcast_ThreadId = NULL; //串口数据接收、解析任务Handle // 智能分段函数 SegmentedText smart_segment_text(const char* text, int max_segment_len) { SegmentedText result = {0}; if (!text || *text == '\0') return result; int text_len = strlen(text); int max_segments = (text_len / max_segment_len) + 2; result.segments = cm_malloc(max_segments * sizeof(char*)); int start = 0; int segment_count = 0; while (start < text_len && segment_count < max_segments) { int end = start + max_segment_len; if (end > text_len) end = text_len; if (end < text_len) { // 寻找最佳分割点(标点符号优先) int best_break = end; for (int i = end; i > start; i--) { if (strchr("。!?,;.!?", text[i])) { best_break = i + 1; break; } } end = best_break; } // 创建分段 int seg_len = end - start; result.segments[segment_count] = cm_malloc(seg_len + 1); strncpy(result.segments[segment_count], text + start, seg_len); result.segments[segment_count][seg_len] = '\0'; segment_count++; start = end; } result.count = segment_count; return result; } //多文字tts,景区播报专用 void safe_tts_play(const char* segments[], int count) { for(int i = 0; i < count; i++) { local_tts_text_play(segments[i], 0, 0); // 等待当前播放完成 while(local_tts_get_play_state() != 0) { osDelay(100); // 每100ms检查一次 } osDelay(200); // 额外缓冲 } } // 释放分段内存 [新增] void free_segmented_text(SegmentedText* st) { if (st && st->segments) { for (int i = 0; i < st->count; i++) { cm_free(st->segments[i]); } cm_free(st->segments); st->segments = NULL; st->count = 0; } } //计算到景点的距离 double location_service_calculate_distance(Location loc1, Location loc2) { // 使用Haversine公式计算真实距离 const double R = 6371000; // 地球半径(米) double dLat = (loc2.latitude - loc1.latitude) * M_PI / 180.0; double dLon = (loc2.longitude - loc1.longitude) * M_PI / 180.0; double a = sin(dLat/2) * sin(dLat/2) + cos(loc1.latitude * M_PI / 180.0) * cos(loc2.latitude * M_PI / 180.0) * sin(dLon/2) * sin(dLon/2); double c = 2 * atan2(sqrt(a), sqrt(1-a)); return R * c; } // 获取当前位置 Location location_service_get_current(void) { Location current = {0}; // 加锁保护全局数据 osMutexAcquire(gps_data.mutex, osWaitForever); // 检查数据是否有效 if (gps_data.info.sig > 0 && gps_data.info.fix > 0) { current.longitude = gps_data.longitude; current.latitude = gps_data.latitude; } osMutexRelease(gps_data.mutex); return current; } // 添加景点 void attr_broadcast_add_attraction(uint32_t region_id, double lon, double lat, double radius, // 新增半径参数 const char* name, const char* desc) { if (attractions_mutex == NULL) return; osMutexAcquire(attractions_mutex, osWaitForever); // ===== 检查ID是否已存在 ===== AttractionNode* existing = attractions_head; while (existing) { if (existing->region_id == region_id) { // 找到相同ID的景点,更新它而不是新建 existing->longitude = lon; existing->latitude = lat; existing->radius = radius; strncpy(existing->name, name, sizeof(existing->name)-1); existing->name[sizeof(existing->name)-1] = '\0'; // 释放旧的描述分段 free_segmented_text(&existing->description); // 生成新的描述(如果需要) // existing->description = smart_segment_text(desc, MAX_TTS_SEGMENT_LEN); osMutexRelease(attractions_mutex); attr_broadcast_save_attractions(); DEBUG("Updated attraction %u\n", region_id); return; } existing = existing->next; } // ===== 冲突检查结束 ===== AttractionNode* new_node = cm_malloc(sizeof(AttractionNode)); if (!new_node) { osMutexRelease(attractions_mutex); return; } // 填充景点信息 new_node->region_id = region_id; // 设置区域ID new_node->longitude = lon; new_node->latitude = lat; strncpy(new_node->name, name, sizeof(new_node->name)-1); new_node->name[sizeof(new_node->name)-1] = '\0'; new_node->radius = radius; // 设置半径 // 添加到链表头部 new_node->next = attractions_head; attractions_head = new_node; osMutexRelease(attractions_mutex); attr_broadcast_save_attractions(); } // 根据区域ID删除景点 void attr_broadcast_remove_attraction_by_id(uint32_t region_id) { if (attractions_mutex == NULL) return; osMutexAcquire(attractions_mutex, osWaitForever); AttractionNode* current = attractions_head; AttractionNode* prev = NULL; while (current) { if (current->region_id == region_id) { if (prev) { prev->next = current->next; } else { attractions_head = current->next; } free_segmented_text(¤t->description); cm_free(current); break; } prev = current; current = current->next; } osMutexRelease(attractions_mutex); attr_broadcast_save_attractions(); } // 删除所有景点 void attr_broadcast_remove_all(void) { // 直接调用现有函数 attr_broadcast_free_all(); attr_broadcast_save_attractions(); } // 释放所有景点内存 最新添加 void attr_broadcast_free_all() { if (attractions_mutex == NULL) return; osMutexAcquire(attractions_mutex, osWaitForever); AttractionNode* current = attractions_head; while (current) { AttractionNode* next = current->next; free_segmented_text(¤t->description); cm_free(current); current = next; } attractions_head = NULL; osMutexRelease(attractions_mutex); } // 保存景点数据到文件 int attr_broadcast_save_attractions(void) { if (attractions_mutex == NULL) return -1; osMutexAcquire(attractions_mutex, osWaitForever); // 计算需要保存的数据大小 uint32_t data_size = sizeof(int); // 景点数量字段 AttractionNode* current = attractions_head; while (current) { // 每个景点的大小 = 区域ID(4) + 经纬度(8*3) + 名称长度(1) + 名称内容 + 描述分段数量(4) + 每个分段 data_size += sizeof(uint32_t) + 3 * sizeof(double) + sizeof(uint8_t); data_size += strlen(current->name); // 名称长度 // 描述分段 data_size += sizeof(int); // 分段数量 for (int i = 0; i < current->description.count; i++) { data_size += sizeof(uint16_t); // 分段长度 data_size += strlen(current->description.segments[i]); // 分段内容 } current = current->next; } // 分配内存缓冲区 uint8_t* buffer = cm_malloc(data_size); if (!buffer) { osMutexRelease(attractions_mutex); return -2; } // 填充缓冲区 uint32_t offset = 0; int count = 0; // 先写入景点数量(稍后填充) offset += sizeof(int); // 写入每个景点 current = attractions_head; while (current) { count++; // 区域ID memcpy(buffer + offset, ¤t->region_id, sizeof(uint32_t)); offset += sizeof(uint32_t); // 经纬度和半径 memcpy(buffer + offset, ¤t->longitude, sizeof(double)); offset += sizeof(double); memcpy(buffer + offset, ¤t->latitude, sizeof(double)); offset += sizeof(double); memcpy(buffer + offset, ¤t->radius, sizeof(double)); offset += sizeof(double); // 名称 uint8_t name_len = strlen(current->name); memcpy(buffer + offset, &name_len, sizeof(uint8_t)); offset += sizeof(uint8_t); memcpy(buffer + offset, current->name, name_len); offset += name_len; // 描述分段 int seg_count = current->description.count; memcpy(buffer + offset, &seg_count, sizeof(int)); offset += sizeof(int); for (int i = 0; i < seg_count; i++) { uint16_t seg_len = strlen(current->description.segments[i]); memcpy(buffer + offset, &seg_len, sizeof(uint16_t)); offset += sizeof(uint16_t); memcpy(buffer + offset, current->description.segments[i], seg_len); offset += seg_len; } current = current->next; } // 现在写入景点数量 memcpy(buffer, &count, sizeof(int)); // 写入文件 int32_t fd = cm_fs_open(ATTRACTIONS_FILE, CM_FS_RBPLUS); if (fd < 0) { cm_free(buffer); osMutexRelease(attractions_mutex); return -3; } if (cm_fs_write(fd, buffer, data_size) != data_size) { cm_fs_close(fd); cm_free(buffer); osMutexRelease(attractions_mutex); return -4; } cm_fs_close(fd); cm_free(buffer); osMutexRelease(attractions_mutex); DEBUG("Saved %d attractions to file\n", count); return 0; } // 从文件加载景点数据 int attr_broadcast_load_attractions(void) { if (!cm_fs_exist(ATTRACTIONS_FILE)) { DEBUG("No attractions file found\n"); return -1; } // 确保互斥锁已创建 if (attractions_mutex == NULL) { attractions_mutex = osMutexNew(NULL); if (!attractions_mutex) return -2; } DEBUG("test1\n"); // 清空现有景点 attr_broadcast_free_all(); DEBUG("test2\n"); osMutexAcquire(attractions_mutex, osWaitForever); if(1 != cm_fs_exist(ATTRACTIONS_FILE)){ DEBUG("no local data\r\n"); return -1; } // 打开文件 int32_t fd = cm_fs_open(ATTRACTIONS_FILE, CM_FS_RB); if (fd < 0) { osMutexRelease(attractions_mutex); DEBUG("open fail\n"); return -3; } DEBUG("open success\n"); // 获取文件大小 int32_t file_size = cm_fs_filesize(ATTRACTIONS_FILE); if (file_size <= 0) { cm_fs_close(fd); osMutexRelease(attractions_mutex); DEBUG("filesize fail\n"); return -4; } DEBUG("filesize success\n"); // 读取景点数量 int count = 0; if (cm_fs_read(fd, &count, sizeof(int)) != sizeof(int)) { cm_fs_close(fd); osMutexRelease(attractions_mutex); DEBUG("get count fail\n"); return -5; } DEBUG("count=%d\n",count); DEBUG("get count success\n"); // 计算剩余数据大小 uint32_t data_size = file_size - sizeof(int); uint8_t* buffer = cm_malloc(data_size); if (!buffer) { cm_fs_close(fd); osMutexRelease(attractions_mutex); DEBUG("file size error\n"); return -6; } DEBUG("file size ok\n"); // 读取剩余数据 if (cm_fs_read(fd, buffer, data_size) != data_size) { cm_free(buffer); cm_fs_close(fd); osMutexRelease(attractions_mutex); DEBUG("remain data fail\n"); return -7; } DEBUG("remain data ok\n"); cm_fs_close(fd); // 解析景点数据 uint32_t offset = 0; for (int i = 0; i < count; i++) { // 创建新景点 AttractionNode* new_node = cm_malloc(sizeof(AttractionNode)); if (!new_node) { // 部分加载,继续处理但返回错误 DEBUG("Memory allocation failed for attraction %d\n", i); continue; } memset(new_node, 0, sizeof(AttractionNode)); // 读取区域ID if (offset + sizeof(uint32_t) > data_size) goto parse_error; memcpy(&new_node->region_id, buffer + offset, sizeof(uint32_t)); offset += sizeof(uint32_t); // 读取经纬度和半径 if (offset + 3 * sizeof(double) > data_size) goto parse_error; memcpy(&new_node->longitude, buffer + offset, sizeof(double)); offset += sizeof(double); memcpy(&new_node->latitude, buffer + offset, sizeof(double)); offset += sizeof(double); memcpy(&new_node->radius, buffer + offset, sizeof(double)); offset += sizeof(double); // 读取名称长度 if (offset + sizeof(uint8_t) > data_size) goto parse_error; uint8_t name_len = 0; memcpy(&name_len, buffer + offset, sizeof(uint8_t)); offset += sizeof(uint8_t); // 读取名称 if (offset + name_len > data_size) goto parse_error; if (name_len > 0) { memcpy(new_node->name, buffer + offset, name_len); new_node->name[name_len] = '\0'; offset += name_len; } else { strcpy(new_node->name, "Unknown"); } // 读取描述分段数量 if (offset + sizeof(int) > data_size) goto parse_error; int seg_count = 0; memcpy(&seg_count, buffer + offset, sizeof(int)); offset += sizeof(int); if (seg_count > 0) { new_node->description.segments = cm_malloc(seg_count * sizeof(char*)); if (!new_node->description.segments) { DEBUG("Memory allocation failed for segments %d\n", i); cm_free(new_node); continue; } new_node->description.count = seg_count; for (int j = 0; j < seg_count; j++) { // 读取分段长度 if (offset + sizeof(uint16_t) > data_size) goto parse_error; uint16_t seg_len = 0; memcpy(&seg_len, buffer + offset, sizeof(uint16_t)); offset += sizeof(uint16_t); // 读取分段内容 if (offset + seg_len > data_size) goto parse_error; new_node->description.segments[j] = cm_malloc(seg_len + 1); if (!new_node->description.segments[j]) { DEBUG("Memory allocation failed for segment %d-%d\n", i, j); // 释放已分配的分段 for (int k = 0; k < j; k++) { cm_free(new_node->description.segments[k]); } cm_free(new_node->description.segments); cm_free(new_node); continue; } memcpy(new_node->description.segments[j], buffer + offset, seg_len); new_node->description.segments[j][seg_len] = '\0'; offset += seg_len; } } /******************** 关键修改:ID去重检查 ********************/ // 检查链表中是否已存在相同ID的景点 BOOL is_duplicate = false; AttractionNode* current_node = attractions_head; while (current_node) { if (current_node->region_id == new_node->region_id) { DEBUG("Duplicate ID detected: %u, skipping\n", new_node->region_id); is_duplicate = true; break; } current_node = current_node->next; } if (is_duplicate) { // 释放重复景点的内存 if (new_node->description.segments) { for (int j = 0; j < new_node->description.count; j++) { if (new_node->description.segments[j]) { cm_free(new_node->description.segments[j]); } } cm_free(new_node->description.segments); } cm_free(new_node); continue; } /******************** ID去重检查结束 ********************/ // 添加到链表头部 new_node->next = attractions_head; attractions_head = new_node; continue; parse_error: DEBUG("Parse error for attraction %d\n", i); if (new_node) { if (new_node->description.segments) { for (int j = 0; j < new_node->description.count; j++) { if (new_node->description.segments[j]) { cm_free(new_node->description.segments[j]); } } cm_free(new_node->description.segments); } cm_free(new_node); } break; } cm_free(buffer); osMutexRelease(attractions_mutex); DEBUG("Loaded %d attractions from file\n", count); return 0; } // 检查景点文件是否存在 int attr_broadcast_file_exists(void) { return cm_fs_exist(ATTRACTIONS_FILE); } // 删除景点文件 void attr_broadcast_delete_file(void) { if (cm_fs_exist(ATTRACTIONS_FILE)) { cm_fs_delete(ATTRACTIONS_FILE); } } // 设置播报距离阈值 (米) void attr_broadcast_set_distance_threshold(double threshold) { if (threshold > 0) { broadcast_state.distance_threshold = threshold; } } // 根据当前位置查找最近的景区节点 static AttractionNode* find_nearest_attraction(Location current_pos) { if (attractions_head == NULL) return NULL; osMutexAcquire(attractions_mutex, osWaitForever); AttractionNode* current = attractions_head; AttractionNode* nearest = attractions_head; double min_distance = location_service_calculate_distance( (Location){current->longitude, current->latitude}, current_pos ); while (current != NULL) { double distance = location_service_calculate_distance( (Location){current->longitude, current->latitude}, current_pos ); if (distance < min_distance) { min_distance = distance; nearest = current; } current = current->next; } osMutexRelease(attractions_mutex); return nearest; } static void play_attraction_info(AttractionNode* attraction, double distance) { if (!attraction) return; // 构建距离提示 char distance_msg[100]; snprintf(distance_msg, sizeof(distance_msg), "您已到达%s,距离%.1f米。", attraction->name, distance); // 创建播放数组(距离提示 + 所有描述分段) int total_segments = attraction->description.count + 1; const char** segments = cm_malloc(total_segments * sizeof(char*)); if (!segments) return; segments[0] = distance_msg; for (int i = 0; i < attraction->description.count; i++) { segments[i+1] = attraction->description.segments[i]; } broadcast_state.is_playing = 1; DEBUG("Playing attraction info: %s (%d segments)\n", attraction->name, total_segments); // 播放所有分段 safe_tts_play(segments, total_segments); cm_free(segments); // 更新播报状态 broadcast_state.last_broadcast = attraction; broadcast_state.last_distance = distance; broadcast_state.is_playing = 0; } static void attr_broadcast_task(void* arg) { (void)arg; // 避免未使用参数警告 BOOL should_play; //是否播放最后判断量 DEBUG("task begin\r\n"); while (1) { // 获取当前位置 Location current_pos = location_service_get_current(); // 查找最近景点 AttractionNode* nearest = find_nearest_attraction(current_pos); DEBUG("location ok\r\n"); if (nearest != NULL) { // 计算距离 double distance = location_service_calculate_distance( (Location){nearest->longitude, nearest->latitude}, current_pos ); DEBUG("calculate ok\r\n"); // 检查是否在有效范围内 if (distance <= broadcast_state.distance_threshold) { // 检查是否需要播报 should_play = false; DEBUG("check ok:%d\r\n",should_play); if (broadcast_state.last_broadcast == NULL) { should_play = true; // 首次播报 DEBUG("check ok:%d\r\n",should_play); } else if (broadcast_state.last_broadcast != nearest) { should_play = true; // 新景点 } else if (fabs(distance - broadcast_state.last_distance) > 50.0) { should_play = true; // 同一景点但距离变化大 是否需要播报可以商榷 } DEBUG("stat ok:%d\r\n",should_play); // 触发播报 if ((should_play && !broadcast_state.is_playing) && (1 == sys_sta.MAG_MODE )) { DEBUG("ready to play\r\n"); play_attraction_info(nearest, distance); broadcast_state.is_playing=0; DEBUG("play ok\r\n"); } } DEBUG("dist:%0.3f,threshold:%0.3f\r\n",distance,broadcast_state.distance_threshold); } // 0.3秒检测一次 osDelay(200/5); } } void attr_broadcast_init(void) { // 初始化互斥锁 if (attractions_mutex == NULL) { attractions_mutex = osMutexNew(NULL); } if(1 == cm_fs_exist(ATTRACTIONS_FILE)) { DEBUG("file exist\n"); } else { int32_t fd = cm_fs_open(ATTRACTIONS_FILE, CM_FS_WB); if (fd >= 0) { int count = 0; // 空文件表示0个景点 cm_fs_write(fd, &count, sizeof(int)); cm_fs_close(fd); DEBUG("Created empty attractions file\n"); } } //加载本地景点 if (attr_broadcast_load_attractions() != 0) { DEBUG("No saved attractions\n"); } // 初始化状态 memset(&broadcast_state, 0, sizeof(broadcast_state)); broadcast_state.distance_threshold = 50.0; // 默认阈值50米 osThreadAttr_t attr_broadcast_task_attr = {0}; attr_broadcast_task_attr.name = "attr_broadcast_task"; attr_broadcast_task_attr.stack_size = 4096 * 10; attr_broadcast_task_attr.priority= osPriorityNormal; Attr_Broadcast_ThreadId= osThreadNew(attr_broadcast_task, 0, &attr_broadcast_task_attr); }