2025-07-03 15:12:57 +08:00
|
|
|
|
#include "cm_iomux.h"
|
|
|
|
|
|
#include "cm_gpio.h"
|
|
|
|
|
|
#include "stdio.h"
|
|
|
|
|
|
#include "stdlib.h"
|
|
|
|
|
|
#include "stdarg.h"
|
|
|
|
|
|
#include "math.h"
|
|
|
|
|
|
#include "cm_os.h"
|
2025-07-16 13:31:57 +08:00
|
|
|
|
#include "cm_fs.h"
|
2025-07-03 15:12:57 +08:00
|
|
|
|
#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-16 13:31:57 +08:00
|
|
|
|
#if 1
|
2025-07-03 15:12:57 +08:00
|
|
|
|
#include "app_uart.h"
|
2025-07-10 10:01:23 +08:00
|
|
|
|
#define DEBUG(fmt, args...) app_printf("[Broadcast]" fmt, ##args)
|
2025-07-03 15:12:57 +08:00
|
|
|
|
#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-11 11:22:25 +08:00
|
|
|
|
#define MAX_TTS_SEGMENT_LEN 70 // TTS每段最大长度(字符)
|
2025-07-16 13:31:57 +08:00
|
|
|
|
#define ATTRACTIONS_FILE "attr.txt" // 区域ID列表文件
|
2025-07-10 11:57:26 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
|
|
|
|
|
static nmeaPARSER parser;
|
|
|
|
|
|
|
2025-07-10 10:05:00 +08:00
|
|
|
|
//该版本仅实现基本的靠近播报功能,长文字播放功能待开发
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
2025-07-10 11:57:26 +08:00
|
|
|
|
// 文本分段结构 [新增]
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
|
char** segments; // 分段文本数组
|
|
|
|
|
|
int count; // 分段数量
|
|
|
|
|
|
} SegmentedText;
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-16 13:31:57 +08:00
|
|
|
|
// 修改景点节点结构
|
2025-07-03 15:12:57 +08:00
|
|
|
|
typedef struct AttractionNode {
|
2025-07-16 13:31:57 +08:00
|
|
|
|
uint32_t region_id; // 区域ID [新增]
|
|
|
|
|
|
double longitude; // 经度
|
|
|
|
|
|
double latitude; // 纬度
|
|
|
|
|
|
double radius; // 半径 [新增]
|
|
|
|
|
|
char name[50]; // 景点名称
|
|
|
|
|
|
double trigger_distance; // 触发距离(米)
|
|
|
|
|
|
SegmentedText description;
|
2025-07-03 15:12:57 +08:00
|
|
|
|
struct AttractionNode* next;
|
|
|
|
|
|
} AttractionNode;
|
|
|
|
|
|
|
2025-07-10 11:57:26 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +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;
|
2025-07-03 15:12:57 +08:00
|
|
|
|
} 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-16 13:31:57 +08:00
|
|
|
|
|
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-03 15:12:57 +08:00
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
|
|
|
|
|
//计算到景点的距离
|
|
|
|
|
|
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);
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 检查数据是否有效
|
|
|
|
|
|
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-03 15:12:57 +08:00
|
|
|
|
}
|
2025-07-10 10:01:23 +08:00
|
|
|
|
osMutexRelease(gps_data.mutex);
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
|
|
|
|
|
return current;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加景点
|
2025-07-16 13:31:57 +08:00
|
|
|
|
void attr_broadcast_add_attraction(uint32_t region_id,
|
|
|
|
|
|
double lon, double lat,
|
|
|
|
|
|
double radius, // 新增半径参数
|
|
|
|
|
|
const char* name,
|
|
|
|
|
|
const char* desc) {
|
2025-07-03 15:12:57 +08:00
|
|
|
|
if (attractions_mutex == NULL) return;
|
|
|
|
|
|
|
|
|
|
|
|
osMutexAcquire(attractions_mutex, osWaitForever);
|
2025-07-16 13:31:57 +08:00
|
|
|
|
// ===== 检查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;
|
|
|
|
|
|
}
|
|
|
|
|
|
// ===== 冲突检查结束 =====
|
2025-07-11 11:22:25 +08:00
|
|
|
|
AttractionNode* new_node = cm_malloc(sizeof(AttractionNode));
|
2025-07-03 15:12:57 +08:00
|
|
|
|
if (!new_node) {
|
|
|
|
|
|
osMutexRelease(attractions_mutex);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 填充景点信息
|
2025-07-16 13:31:57 +08:00
|
|
|
|
new_node->region_id = region_id; // 设置区域ID
|
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';
|
2025-07-16 13:31:57 +08:00
|
|
|
|
new_node->radius = radius; // 设置半径
|
2025-07-10 11:57:26 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 添加到链表头部
|
|
|
|
|
|
new_node->next = attractions_head;
|
|
|
|
|
|
attractions_head = new_node;
|
|
|
|
|
|
|
|
|
|
|
|
osMutexRelease(attractions_mutex);
|
2025-07-16 13:31:57 +08:00
|
|
|
|
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();
|
2025-07-03 15:12:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-16 13:31:57 +08:00
|
|
|
|
// 删除所有景点
|
|
|
|
|
|
void attr_broadcast_remove_all(void) {
|
|
|
|
|
|
// 直接调用现有函数
|
|
|
|
|
|
attr_broadcast_free_all();
|
|
|
|
|
|
attr_broadcast_save_attractions();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
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(¤t->description);
|
|
|
|
|
|
cm_free(current);
|
|
|
|
|
|
current = next;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
attractions_head = NULL;
|
|
|
|
|
|
osMutexRelease(attractions_mutex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-16 13:31:57 +08:00
|
|
|
|
// 保存景点数据到文件
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-10 11:57:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 设置播报距离阈值 (米)
|
|
|
|
|
|
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},
|
2025-07-03 15:12:57 +08:00
|
|
|
|
current_pos
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
while (current != NULL) {
|
|
|
|
|
|
double distance = location_service_calculate_distance(
|
2025-07-10 11:57:26 +08:00
|
|
|
|
(Location){current->longitude, current->latitude},
|
2025-07-03 15:12:57 +08:00
|
|
|
|
current_pos
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-07-16 13:31:57 +08:00
|
|
|
|
if(distance <= current->radius) {
|
2025-07-03 15:12:57 +08:00
|
|
|
|
if (distance < min_distance) {
|
|
|
|
|
|
min_distance = distance;
|
|
|
|
|
|
nearest = current;
|
|
|
|
|
|
}
|
2025-07-16 13:31:57 +08:00
|
|
|
|
}
|
2025-07-03 15:12:57 +08:00
|
|
|
|
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-03 15:12:57 +08:00
|
|
|
|
|
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);
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 更新播报状态
|
|
|
|
|
|
broadcast_state.last_broadcast = attraction;
|
|
|
|
|
|
broadcast_state.last_distance = distance;
|
2025-07-10 11:57:26 +08:00
|
|
|
|
broadcast_state.is_playing = 0;
|
2025-07-03 15:12:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void attr_broadcast_task(void* arg) {
|
|
|
|
|
|
(void)arg; // 避免未使用参数警告
|
2025-07-10 10:01:23 +08:00
|
|
|
|
BOOL should_play; //是否播放最后判断量
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG("task begin\r\n");
|
|
|
|
|
|
|
2025-07-16 13:31:57 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
while (1) {
|
2025-07-16 13:31:57 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 获取当前位置
|
|
|
|
|
|
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");
|
2025-07-03 15:12:57 +08:00
|
|
|
|
if (nearest != NULL) {
|
|
|
|
|
|
// 计算距离
|
|
|
|
|
|
double distance = location_service_calculate_distance(
|
2025-07-10 11:57:26 +08:00
|
|
|
|
(Location){nearest->longitude, nearest->latitude},
|
2025-07-03 15:12:57 +08:00
|
|
|
|
current_pos
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-07-10 10:01:23 +08:00
|
|
|
|
DEBUG("calculate ok\r\n");
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 检查是否在有效范围内
|
|
|
|
|
|
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);
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
2025-07-03 15:12:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
else if (broadcast_state.last_broadcast != nearest) {
|
|
|
|
|
|
should_play = true; // 新景点
|
2025-07-10 10:01:23 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +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-03 15:12:57 +08:00
|
|
|
|
}
|
2025-07-10 10:01:23 +08:00
|
|
|
|
DEBUG("stat ok:%d\r\n",should_play);
|
2025-07-03 15:12:57 +08:00
|
|
|
|
// 触发播报
|
|
|
|
|
|
if (should_play && !broadcast_state.is_playing) {
|
2025-07-10 10:01:23 +08:00
|
|
|
|
DEBUG("ready to play\r\n");
|
2025-07-03 15:12:57 +08:00
|
|
|
|
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-03 15:12:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-10 10:01:23 +08:00
|
|
|
|
DEBUG("dist:%0.3f,threshold:%0.3f\r\n",distance,broadcast_state.distance_threshold);
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-16 13:31:57 +08:00
|
|
|
|
// 0.3秒检测一次
|
|
|
|
|
|
osDelay(200/5);
|
2025-07-03 15:12:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-10 10:01:23 +08:00
|
|
|
|
|
2025-07-03 15:12:57 +08:00
|
|
|
|
void attr_broadcast_init(void) {
|
|
|
|
|
|
// 初始化互斥锁
|
|
|
|
|
|
if (attractions_mutex == NULL) {
|
|
|
|
|
|
attractions_mutex = osMutexNew(NULL);
|
|
|
|
|
|
}
|
2025-07-16 13:31:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
}
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化状态
|
|
|
|
|
|
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-11 11:22:25 +08:00
|
|
|
|
attr_broadcast_task_attr.stack_size = 4096 * 10;
|
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);
|
2025-07-03 15:12:57 +08:00
|
|
|
|
|
|
|
|
|
|
}
|