4G_module/custom/attr_broadcast/src/attr_broadcast.c

420 lines
14 KiB
C
Raw Normal View History

#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"
2025-07-10 10:01:23 +08:00
#if 1
#include "app_uart.h"
2025-07-10 10:01:23 +08:00
#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播报优先级
2025-07-10 11:57:26 +08:00
#define MAX_TTS_SEGMENT_LEN 50 // TTS每段最大长度(字符)
static nmeaPARSER parser;
//该版本仅实现基本的靠近播报功能,长文字播放功能待开发
2025-07-10 11:57:26 +08:00
// 文本分段结构 [新增]
typedef struct {
char** segments; // 分段文本数组
int count; // 分段数量
} SegmentedText;
// 修改景点节点结构 [修改]
typedef struct AttractionNode {
2025-07-10 11:57:26 +08:00
double longitude;
double latitude;
char name[50];
SegmentedText description; // 从char[]改为SegmentedText结构 [关键修改]
struct AttractionNode* next;
} AttractionNode;
2025-07-10 11:57:26 +08:00
// 播报状态
typedef struct {
2025-07-10 10:01:23 +08:00
AttractionNode* last_broadcast;
double last_distance;
uint8_t is_playing;
double distance_threshold;
} BroadcastState;
2025-07-10 10:01:23 +08:00
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
2025-07-10 11:57:26 +08:00
// 智能分段函数
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;
}
2025-07-10 10:01:23 +08:00
//多文字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); // 额外缓冲
}
}
2025-07-10 11:57:26 +08:00
// 释放分段内存 [新增]
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};
// 加锁保护全局数据
2025-07-10 10:01:23 +08:00
osMutexAcquire(gps_data.mutex, osWaitForever);
// 检查数据是否有效
if (gps_data.info.sig > 0 && gps_data.info.fix > 0) {
2025-07-10 10:01:23 +08:00
current.longitude = gps_data.longitude;
current.latitude = gps_data.latitude;
}
2025-07-10 10:01:23 +08:00
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;
}
// 填充景点信息
2025-07-10 11:57:26 +08:00
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->description = smart_segment_text(desc, MAX_TTS_SEGMENT_LEN);
// 添加到链表头部
new_node->next = attractions_head;
attractions_head = new_node;
osMutexRelease(attractions_mutex);
}
2025-07-10 11:57:26 +08:00
// 释放所有景点内存 最新添加
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(&current->description);
cm_free(current);
current = next;
}
attractions_head = NULL;
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(
2025-07-10 11:57:26 +08:00
(Location){current->longitude, current->latitude},
current_pos
);
while (current != NULL) {
double distance = location_service_calculate_distance(
2025-07-10 11:57:26 +08:00
(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;
2025-07-10 11:57:26 +08:00
// 构建距离提示
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;
2025-07-10 11:57:26 +08:00
segments[0] = distance_msg;
for (int i = 0; i < attraction->description.count; i++) {
segments[i+1] = attraction->description.segments[i];
2025-07-10 10:01:23 +08:00
}
2025-07-10 11:57:26 +08:00
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;
2025-07-10 11:57:26 +08:00
broadcast_state.is_playing = 0;
}
static void attr_broadcast_task(void* arg) {
(void)arg; // 避免未使用参数警告
2025-07-10 10:01:23 +08:00
BOOL should_play; //是否播放最后判断量
BOOL introduc=true; //是否播报景区介绍
DEBUG("task begin\r\n");
if(0)
{
safe_tts_play(park_desc,11);
2025-07-10 10:01:23 +08:00
introduc=false;
}
while (1) {
// 获取当前位置
Location current_pos = location_service_get_current();
// 查找最近景点
AttractionNode* nearest = find_nearest_attraction(current_pos);
2025-07-10 10:01:23 +08:00
DEBUG("location ok\r\n");
if (nearest != NULL) {
// 计算距离
double distance = location_service_calculate_distance(
2025-07-10 11:57:26 +08:00
(Location){nearest->longitude, nearest->latitude},
current_pos
);
2025-07-10 10:01:23 +08:00
DEBUG("calculate ok\r\n");
// 检查是否在有效范围内
if (distance <= broadcast_state.distance_threshold) {
// 检查是否需要播报
2025-07-10 10:01:23 +08:00
should_play = false;
DEBUG("check ok:%d\r\n",should_play);
if (broadcast_state.last_broadcast == NULL) {
should_play = true; // 首次播报
2025-07-10 10:01:23 +08:00
DEBUG("check ok:%d\r\n",should_play);
}
else if (broadcast_state.last_broadcast != nearest) {
should_play = true; // 新景点
2025-07-10 10:01:23 +08:00
}
2025-07-10 10:01:23 +08:00
else if (fabs(distance - broadcast_state.last_distance) > 50.0) {
should_play = true; // 同一景点但距离变化大 是否需要播报可以商榷
}
2025-07-10 10:01:23 +08:00
DEBUG("stat ok:%d\r\n",should_play);
// 触发播报
if (should_play && !broadcast_state.is_playing) {
2025-07-10 10:01:23 +08:00
DEBUG("ready to play\r\n");
play_attraction_info(nearest, distance);
2025-07-10 10:01:23 +08:00
broadcast_state.is_playing=0;
DEBUG("play ok\r\n");
}
}
2025-07-10 10:01:23 +08:00
DEBUG("dist:%0.3f,threshold:%0.3f\r\n",distance,broadcast_state.distance_threshold);
}
// 5秒检测一次
2025-07-10 10:01:23 +08:00
osDelay(5000/5);
}
}
2025-07-10 10:01:23 +08:00
void attr_broadcast_init(void) {
// 初始化互斥锁
if (attractions_mutex == NULL) {
attractions_mutex = osMutexNew(NULL);
}
2025-07-10 10:01:23 +08:00
//增加初始景点
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米
2025-07-10 10:01:23 +08:00
osThreadAttr_t attr_broadcast_task_attr = {0};
attr_broadcast_task_attr.name = "attr_broadcast_task";
2025-07-10 11:57:26 +08:00
attr_broadcast_task_attr.stack_size = 4096 * 8;
2025-07-10 10:01:23 +08:00
attr_broadcast_task_attr.priority= osPriorityNormal;
Attr_Broadcast_ThreadId= osThreadNew(attr_broadcast_task, 0, &attr_broadcast_task_attr);
}