#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_mem.h" #include "cm_sys.h" #include "cm_uart.h" #include "app_common.h" #include "app_uart.h" #include "attr_broadcast.h" #include "gps_config.h" #include "local_tts.h" #if 1 #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播报优先级 static nmeaPARSER parser; // 全局景点链表 typedef struct AttractionNode { Attraction attraction; 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 //多文字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); // 额外缓冲 } } //计算到景点的距离 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(double lon, double lat, const char* name, const char* desc) { if (attractions_mutex == NULL) return; osMutexAcquire(attractions_mutex, osWaitForever); AttractionNode* new_node = malloc(sizeof(AttractionNode)); if (!new_node) { osMutexRelease(attractions_mutex); return; } // 填充景点信息 new_node->attraction.longitude = lon; new_node->attraction.latitude = lat; strncpy(new_node->attraction.name, name, sizeof(new_node->attraction.name)-1); strncpy(new_node->attraction.description, desc, sizeof(new_node->attraction.description)-1); // 添加到链表头部 new_node->next = attractions_head; attractions_head = new_node; osMutexRelease(attractions_mutex); } // 设置播报距离阈值 (米) 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->attraction.longitude, current->attraction.latitude}, current_pos ); while (current != NULL) { double distance = location_service_calculate_distance( (Location){current->attraction.longitude, current->attraction.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 buffer[300]; snprintf(buffer, sizeof(buffer), "您已到达%s,%s。距离%.1f米。", attraction->attraction.name, attraction->attraction.description, distance); broadcast_state.is_playing = 1; // 调用TTS接口播放文本 local_tts_text_play(buffer, 0, 1); //等待 while(local_tts_get_play_state() != 0) { osDelay(100); } // 更新播报状态 broadcast_state.last_broadcast = attraction; broadcast_state.last_distance = distance; } static void attr_broadcast_task(void* arg) { (void)arg; // 避免未使用参数警告 BOOL should_play; //是否播放最后判断量 BOOL introduc=true; //是否播报景区介绍 DEBUG("task begin\r\n"); if(0) { safe_tts_play(park_desc,11); introduc=false; } 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->attraction.longitude, nearest->attraction.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) { 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); } // 5秒检测一次 osDelay(5000/5); } } void attr_broadcast_init(void) { // 初始化互斥锁 if (attractions_mutex == NULL) { attractions_mutex = osMutexNew(NULL); } //增加初始景点 attr_broadcast_add_attraction(121.364371, 31.343164, "2号门便民处", "测试景点,看起来应该非常的方便游客"); attr_broadcast_add_attraction(121.364270, 31.343103, "2号门归还点", "您好二号门停放点到了,您可以把我归还至这里,并在手机小程序中结束订单,如果"); attr_broadcast_add_attraction(121.364350, 31.342175, "彩虹桥", "测试景点,看起来有很多很多彩虹"); attr_broadcast_add_attraction(121.36799,31.34449, "恐龙乐园正门", "您好自然谷恐龙园到了,这是一个人工搭建的,模拟三叠纪,侏罗纪,白垩纪等时代的恐龙生活场景的乐园,在这里您可以穿梭于恐龙像群,欣赏超前的7D电影,在自然中感受野趣,在怪石和岩洞中穿行,充分体验远古时代洪荒洞窟的神奇刺激和绝妙采用,这里的各类恐龙有着高仿真,活动自如的特性,虫现远古时代恐龙生活打斗等场景,让恐龙如同真的复活,园内霸王龙,三角龙,阿马加龙,剑龙,恐爪龙等几十条恐龙与您零距离接触,带您进入远古霸主的惊险旅程"); attr_broadcast_add_attraction(121.362142,31.340588, "想家桥", "测试景点,看起来非常的让人想要回家"); attr_broadcast_add_attraction(121.362796,31.339624, "3号游船码头", "游客三号游船码头到了,顾村公园浏中湖景区有约10公顷的湖面和沿岸森林湿地"); attr_broadcast_add_attraction(121.36613,31.33925, "樱花广场", "亲爱的游客现在您看到的是顾村公园樱花广场,一寸春心十年相守,为纪念上海樱花节十周"); attr_broadcast_add_attraction(121.366656983, 31.344300423, "集装箱房间", "测试景点,看起来应该非常的方便测试"); // 初始化状态 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 * 5; attr_broadcast_task_attr.priority= osPriorityNormal; Attr_Broadcast_ThreadId= osThreadNew(attr_broadcast_task, 0, &attr_broadcast_task_attr); }