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

836 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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(&current->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(&current->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, &current->region_id, sizeof(uint32_t));
offset += sizeof(uint32_t);
// 经纬度和半径
memcpy(buffer + offset, &current->longitude, sizeof(double));
offset += sizeof(double);
memcpy(buffer + offset, &current->latitude, sizeof(double));
offset += sizeof(double);
memcpy(buffer + offset, &current->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);
}