完成景点数据云端接收,保存本地,开机加载本地数据功能。注意重新烧写程序后本地文件内容清空。

This commit is contained in:
wjw 2025-07-16 13:31:57 +08:00
parent 501f096df6
commit 9e558dc926
10 changed files with 568 additions and 141 deletions

View File

@ -4,8 +4,6 @@
#include "nmea/nmea.h" #include "nmea/nmea.h"
#define DEBUG(fmt, arg...)
// 景点信息 // 景点信息
typedef struct { typedef struct {
@ -22,7 +20,6 @@ typedef struct {
double latitude; double latitude;
} Location; } Location;
extern const char *park_desc[]; extern const char *park_desc[];
@ -34,11 +31,21 @@ void safe_tts_play(const char* segments[], int count);
void attr_broadcast_init(void); void attr_broadcast_init(void);
// 添加景点 // 添加景点
void attr_broadcast_add_attraction(double lon, double lat, void attr_broadcast_add_attraction(uint32_t region_id,
const char* name, const char* desc); double lon, double lat,
double radius, // 新增半径参数
const char* name,
const char* desc); // 新增触发距离参数
// 根据区域ID删除景点
void attr_broadcast_remove_attraction_by_id(uint32_t region_id);
// 停止景点播报任务 // 停止景点播报任务
void attr_broadcast_stop(void); void attr_broadcast_stop(void);
// 删除所有景点
void attr_broadcast_remove_all(void);
// 设置播报距离阈值 (米) // 设置播报距离阈值 (米)
void attr_broadcast_set_distance_threshold(double threshold); void attr_broadcast_set_distance_threshold(double threshold);

View File

@ -5,6 +5,7 @@
#include "stdarg.h" #include "stdarg.h"
#include "math.h" #include "math.h"
#include "cm_os.h" #include "cm_os.h"
#include "cm_fs.h"
#include "cm_mem.h" #include "cm_mem.h"
#include "cm_sys.h" #include "cm_sys.h"
#include "cm_uart.h" #include "cm_uart.h"
@ -16,7 +17,7 @@
#include "gps_config.h" #include "gps_config.h"
#include "local_tts.h" #include "local_tts.h"
#if 0 #if 1
#include "app_uart.h" #include "app_uart.h"
#define DEBUG(fmt, args...) app_printf("[Broadcast]" fmt, ##args) #define DEBUG(fmt, args...) app_printf("[Broadcast]" fmt, ##args)
#else #else
@ -29,6 +30,7 @@
#define DISTANCE_CHANGE_THRESHOLD 10.0 // 距离变化阈值(米) #define DISTANCE_CHANGE_THRESHOLD 10.0 // 距离变化阈值(米)
#define TTS_PRIORITY 5 // TTS播报优先级 #define TTS_PRIORITY 5 // TTS播报优先级
#define MAX_TTS_SEGMENT_LEN 70 // TTS每段最大长度(字符) #define MAX_TTS_SEGMENT_LEN 70 // TTS每段最大长度(字符)
#define ATTRACTIONS_FILE "attr.txt" // 区域ID列表文件
static nmeaPARSER parser; static nmeaPARSER parser;
@ -43,12 +45,15 @@ typedef struct {
} SegmentedText; } SegmentedText;
// 修改景点节点结构 [修改] // 修改景点节点结构
typedef struct AttractionNode { typedef struct AttractionNode {
double longitude; uint32_t region_id; // 区域ID [新增]
double latitude; double longitude; // 经度
char name[50]; double latitude; // 纬度
SegmentedText description; // 从char[]改为SegmentedText结构 [关键修改] double radius; // 半径 [新增]
char name[50]; // 景点名称
double trigger_distance; // 触发距离(米)
SegmentedText description;
struct AttractionNode* next; struct AttractionNode* next;
} AttractionNode; } AttractionNode;
@ -89,6 +94,7 @@ static int tts_volume = 10;
static osThreadId_t Attr_Broadcast_ThreadId = NULL; //串口数据接收、解析任务Handle static osThreadId_t Attr_Broadcast_ThreadId = NULL; //串口数据接收、解析任务Handle
// 智能分段函数 // 智能分段函数
SegmentedText smart_segment_text(const char* text, int max_segment_len) { SegmentedText smart_segment_text(const char* text, int max_segment_len) {
SegmentedText result = {0}; SegmentedText result = {0};
@ -195,12 +201,39 @@ Location location_service_get_current(void) {
// 添加景点 // 添加景点
void attr_broadcast_add_attraction(double lon, double lat, void attr_broadcast_add_attraction(uint32_t region_id,
const char* name, const char* desc) { double lon, double lat,
double radius, // 新增半径参数
const char* name,
const char* desc) {
if (attractions_mutex == NULL) return; if (attractions_mutex == NULL) return;
osMutexAcquire(attractions_mutex, osWaitForever); 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)); AttractionNode* new_node = cm_malloc(sizeof(AttractionNode));
if (!new_node) { if (!new_node) {
osMutexRelease(attractions_mutex); osMutexRelease(attractions_mutex);
@ -208,20 +241,58 @@ void attr_broadcast_add_attraction(double lon, double lat,
} }
// 填充景点信息 // 填充景点信息
new_node->region_id = region_id; // 设置区域ID
new_node->longitude = lon; new_node->longitude = lon;
new_node->latitude = lat; new_node->latitude = lat;
strncpy(new_node->name, name, sizeof(new_node->name)-1); strncpy(new_node->name, name, sizeof(new_node->name)-1);
new_node->name[sizeof(new_node->name)-1] = '\0'; new_node->name[sizeof(new_node->name)-1] = '\0';
new_node->radius = radius; // 设置半径
new_node->description = smart_segment_text(desc, MAX_TTS_SEGMENT_LEN);
// 添加到链表头部 // 添加到链表头部
new_node->next = attractions_head; new_node->next = attractions_head;
attractions_head = new_node; attractions_head = new_node;
osMutexRelease(attractions_mutex); 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() { void attr_broadcast_free_all() {
@ -241,6 +312,347 @@ void attr_broadcast_free_all() {
osMutexRelease(attractions_mutex); 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);
}
}
// 设置播报距离阈值 (米) // 设置播报距离阈值 (米)
@ -270,11 +682,12 @@ static AttractionNode* find_nearest_attraction(Location current_pos) {
current_pos current_pos
); );
if(distance <= current->radius) {
if (distance < min_distance) { if (distance < min_distance) {
min_distance = distance; min_distance = distance;
nearest = current; nearest = current;
} }
}
current = current->next; current = current->next;
} }
@ -323,21 +736,15 @@ static void attr_broadcast_task(void* arg) {
DEBUG("task begin\r\n"); DEBUG("task begin\r\n");
if(0)
{
safe_tts_play(park_desc,11);
}
while (1) { while (1) {
// 获取当前位置 // 获取当前位置
Location current_pos = location_service_get_current(); Location current_pos = location_service_get_current();
// 查找最近景点 // 查找最近景点
AttractionNode* nearest = find_nearest_attraction(current_pos); AttractionNode* nearest = find_nearest_attraction(current_pos);
DEBUG("location ok\r\n"); DEBUG("location ok\r\n");
if (nearest != NULL) { if (nearest != NULL) {
// 计算距离 // 计算距离
@ -379,76 +786,39 @@ static void attr_broadcast_task(void* arg) {
} }
// 5秒检测一次 // 0.3秒检测一次
osDelay(5000/5); osDelay(200/5);
} }
} }
void attr_broadcast_init(void) { void attr_broadcast_init(void) {
// 初始化互斥锁 // 初始化互斥锁
if (attractions_mutex == NULL) { if (attractions_mutex == NULL) {
attractions_mutex = osMutexNew(NULL); attractions_mutex = osMutexNew(NULL);
} }
//增加初始景点
attr_broadcast_add_attraction(121.377232, 31.347311, "谭杨桥", "");
attr_broadcast_add_attraction(121.378406, 31.346974, "荷花亭", "");
attr_broadcast_add_attraction(121.377243, 31.346191, "广福桥", "取名于顾村镇原刘行镇广福村广福村以广福寺得名。传说广福寺规模庞大鼎盛时期占地方圆近1公里号称江南第一寺为当年名僧的游学之地。 \"八·一三\"淞沪抗战,广福寺及广福桥等建筑物毁于战火。而今,杏花疏影中的广福寺虽不复存在,然而公园内新建的汉白玉七孔长桥——广福桥,犹如一道彩虹飞架于粼粼碧波之上,为公园徒增许多诗情画意。");
attr_broadcast_add_attraction(121.377107, 31.345321, "梅樱林", "梅樱林位于樱花大道和红粉路交叉口占地面积约5000平方米种植以梅花和樱花相融合为主题的特色林带樱花品种以华中樱为主。");
attr_broadcast_add_attraction(121.363918, 31.342592, "恐龙乐园后门", "");
attr_broadcast_add_attraction(121.377183, 31.345215, "小卖部", "");
attr_broadcast_add_attraction(121.377747, 31.344542, "紫藤阁", "");
attr_broadcast_add_attraction(121.375954, 31.343419, "梦熊桥", "原位于顾村镇白杨村境内。经查,原\"梦熊桥\"建于公元1695年(即康熙33年)距今已有314年历史。\"梦熊\"一词出自《诗经·小雅·斯干》,意指生男孩,或为祝贺生男孩之语。据说,该桥的来历与当时宅上妇女大都生育男孩有关。顾村公园的梦熊桥是园内唯一的一座双桥,采用超越天然大理石建造。桥旁碧水荡漾,绿树掩映,是绝佳的赏景之桥。");
attr_broadcast_add_attraction(121.375293, 31.345056, "太平桥", "原太平桥位于顾村镇(原刘行镇)老安村境内沪太路上,始建于清同治年间。沪太路曾是上海市最早的一条省际公路,故太平桥的作用十分重要。现顾村公园太平桥又称十二生肖桥,桥两侧栏杆共雕刻着二十四只栩栩如生相对应的生肖动物。如此走在桥上,游客都会情不自禁地边走边摸生肖动物,看看属于自己的生肖可爱形象,真是情趣横生。");
attr_broadcast_add_attraction(121.374492, 31.344662, "严家港桥", "");
attr_broadcast_add_attraction(121.373485, 31.344425, "砚池桥", "");
attr_broadcast_add_attraction(121.373685, 31.346686, "东南亚风情园", "");
attr_broadcast_add_attraction(121.373297, 31.346479, "樱花文化艺术展示馆", "顾村公园\"樱花文化艺术展示馆\"建于2020年展馆以顾村公园樱花文化为设计理念\"永不落幕的·上海樱花节\"为主题,浓缩了十届\"上海樱花节\"的盛景,多形态的展示樱花摄影作品、樱花历史文化、樱花科普知识、樱花系列文创产品等内容,传播并丰富公园的樱花文化,呈现更绚烂的樱花情景。");
attr_broadcast_add_attraction(121.371509, 31.345411, "樱悦书院", "");
attr_broadcast_add_attraction(121.372022, 31.344942, "万年桥", "");
attr_broadcast_add_attraction(121.372601, 31.344801, "1号游船码头", "");
attr_broadcast_add_attraction(121.373195, 31.342009, "2号游船码头", "");
attr_broadcast_add_attraction(121.372599, 31.342228, "樱舞桥", "");
attr_broadcast_add_attraction(121.371710, 31.341834, "小樱广场", "");
attr_broadcast_add_attraction(121.370535, 31.342174, "游乐园", "");
attr_broadcast_add_attraction(121.370519, 31.342064, "摩天轮", "");
attr_broadcast_add_attraction(121.369894, 31.341977, "商店", "");
attr_broadcast_add_attraction(121.370991, 31.343946, "樱悦大桥", "");
attr_broadcast_add_attraction(121.369933, 31.341000, "星火服务站", "");
attr_broadcast_add_attraction(121.369659, 31.340968, "汉服体验馆", "");
attr_broadcast_add_attraction(121.36613, 31.33925, "樱花广场", "亲爱的游客现在您看到的是顾村公园樱花广场,一寸春心十年相守,为纪念上海樱花节十周年,顾村公园特意打造了樱花广场,广场设计简洁通畅,十朵樱花十个花坛,十颗樱树,片片花瓣和公园图标都融入了广场之中,十年之恋景观墙更是将上海樱花节十年历程铭刻记录,让我们可以借此回顾过往,展望将来,愿明年的樱花更烂漫,祖国的未来更辉煌");
attr_broadcast_add_attraction(121.364349, 31.339023, "盛宅桥", "");
attr_broadcast_add_attraction(121.363623, 31.338906, "迎春林", "");
attr_broadcast_add_attraction(121.362796, 31.339624, "3号游船码头", "游客三号游船码头到了顾村公园浏中湖景区有约10公顷的湖面和沿岸森林湿地沿岸包括水上码头鸟岛湿地垂钓索桥等景点绝对会让您觉得不虚此行");
attr_broadcast_add_attraction(121.364350, 31.342175, "彩虹桥", "");
attr_broadcast_add_attraction(121.36799, 31.34449, "恐龙乐园正门", "您好自然谷恐龙园到了这是一个人工搭建的模拟三叠纪侏罗纪白垩纪等时代的恐龙生活场景的乐园在这里您可以穿梭于恐龙像群欣赏超前的7D电影在自然中感受野趣在怪石和岩洞中穿行充分体验远古时代洪荒洞窟的神奇刺激和绝妙采用这里的各类恐龙有着高仿真活动自如的特性虫现远古时代恐龙生活打斗等场景让恐龙如同真的复活园内霸王龙三角龙阿马加龙剑龙恐爪龙等几十条恐龙与您零距离接触带您进入远古霸主的惊险旅程");
attr_broadcast_add_attraction(121.367888, 31.343220, "矮人部落站", "");
attr_broadcast_add_attraction(121.361532, 31.341848, "彩弹射击射箭运动中心", "上海顾村公园彩弹射击射箭运动中心坐落于顾村公园2号门往西200米冬青路游客在这里可以进行彩弹射击真人CS实战对抗、气手枪竞技射击培训体验、M4-16气步枪打靶、10-50米靶射箭体验、worgame_BB弹真人吃鸡大作战。欢迎广大游客朋友们前来体验、游玩。");
attr_broadcast_add_attraction(121.362142, 31.340588, "想家桥", "");
attr_broadcast_add_attraction(121.359204, 31.337386, "孔雀园", "这里是东方鸟世界的孔雀园,园内看到的是六大明星之一孔雀\"花花\"。在进入孔雀园时,请仔细阅读入园须知,不要投喂自带食物,照顾好您的宝宝,防止抓伤啄伤,如果您不认同与鸟类接触行为,请在护栏外观赏哦!孔雀园内居住着上百只孔雀,孔雀是鸡形目、雉科动物。一直以来,孔雀被视为\"百鸟之王\"是最美丽的观赏鸟象征着爱情更是吉祥、善良、美丽、华贵的象征。我们园内的孔雀是以蓝孔雀和白孔雀为主孔雀体长可达2米以上其中尾屏长约1.5米体重达6公斤左右。尾部华丽的为雄孔雀而雌孔雀其貌不扬。");
attr_broadcast_add_attraction(121.359030, 31.337262, "东方鸟世界便利店", "");
attr_broadcast_add_attraction(121.359178, 31.337357, "鸵鸟园", "这里是东方鸟世界的\"鸵鸟园\",眼前看到的是六大明星之一\"大个\"。鸵鸟属鸵形目、鸵鸟科是世界上最大的一种鸟类成鸟身高可达2.5米雄鸵鸟体重可达150千克。像蛇一样细长的脖颈上支撑着一个很小的头部上面有一张短而扁平的、由数片角质鞘所组成的三角形的嘴主要特点是龙骨突不发达不能飞行也是世界上现存鸟类中唯一的二趾鸟类在它双脚的每个大脚趾上都长有长约7厘米的危险趾甲后肢粗壮有力适于奔走。作为世界上最大的鸟类它的蛋也异乎寻常的坚毅一般人的力量根本踩不碎它。它的长度15公分宽度则有八公分左右重量却能达到1.5公斤。我们园内有鸵鸟和鸸鹋两种。鸸鹋原产于澳洲,是澳洲的国鸟,体形大但不会飞,生活在较开阔的半沙漠、草原和开阔的林地等栖息地。鸸鹋是澳大利亚个子最高的鸟,以擅长奔跑而著名,是澳洲地区的特产,是世界上第二大的鸟类,由于外形与鸵鸟相似,因而也被称作澳洲鸵鸟。翅膀比非洲鸵鸟和美洲鸵鸟的更加退化,足三趾,是世界上最古老的鸟种之一。非洲鸵鸟拥有世界上最大的眼球,美丽而明亮,赶快把小游停在路边,和她比一比吧!");
attr_broadcast_add_attraction(121.357985, 31.338391, "仙境奇缘", "");
attr_broadcast_add_attraction(121.358695, 31.337023, "小鸟市集", "在天鹅湖的马路对面就是小鸟市集哦,这里是儿童的欢乐营地,更是各种萌宠的温馨之家,小朋友可以在这里与萌宠和小鸟们亲密互动,同时还有各种小朋友喜欢的精彩活动哦!");
attr_broadcast_add_attraction(121.357883, 31.336482, "鹦鹉世界", "这是一个彩色的世界,来自五大洲的鹦鹉在此集结,是一个名副其实的世界鹦鹉博览园,这里有大型鹦鹉、中型和小型鹦鹉,这里不仅可以观赏到五彩缤纷的毛孩子,大朋友小朋友还可以与它们近距离亲密互动哦,但是在互动过程中千万不要用力抓它们,以免把他们弄疼哦,爱护鸟类,人人有责,从我做起。");
attr_broadcast_add_attraction(121.356022, 31.336296, "孔雀东南飞", "孔雀被誉为百鸟之王,是最美丽的观赏鸟,是美丽、华贵、吉祥的象征。孔雀的头顶翠绿,羽冠蓝绿而呈尖形;尾部上覆羽特别长,形成尾屏,开屏的时候像一个圆形的扇子,鲜艳美丽;孔雀东南飞表演非常的壮观美丽,上百只孔雀倾情演绎,一场凄美壮观的爱情故事让您不虚此行,流连忘返。孔雀东南飞,五里一徘徊。 大家一定要记好表演时间,准时前往孔雀东南飞表演场看台观看壮观的表演哦!");
attr_broadcast_add_attraction(121.355376, 31.335896, "彩虹滑梯", "");
attr_broadcast_add_attraction(121.355366, 31.336917, "兰兰广场", "兰兰广场位于悦林湖畔1972年9月29日将大熊猫\"兰兰\"\"康康\"赠送给日本,没想到在日本大受欢迎,后为纪念熊猫\"兰兰\",松前在熊猫纪念日将这个新品种命名为\"兰兰\"2021年1月1日在公园建此兰兰广场并树立熊猫雕塑愿美好与和平永在。");
attr_broadcast_add_attraction(121.355916, 31.337299, "望湖桥", "");
attr_broadcast_add_attraction(121.355457, 31.339899, "胭脂林", "");
attr_broadcast_add_attraction(121.353600, 31.339272, "悦林湖", "悦林湖位于公园二期,湖上鸟类汇集、且拥有皮划艇这项老少皆宜的娱乐项目,沿湖岸边走一圈能欣赏到各种各样精彩纷呈的美景,有娇艳的月季、有灿烂的樱花、有羞涩的小花一阵风徐徐吹来,感受到别具一格的公园风采。");
attr_broadcast_add_attraction(121.34949, 31.33833, "樱花烧烤园", "您好樱花烧烤园到了,顾村公园樱花烧烤园通过户外野趣的烧烤方式,让您在享受美食的同时还可以尽情享受绿树红花和湖边美景,是家人,友人以及团体聚会的绝佳场所,在绿草如茵的花园旁品尝美食,与大自然共同感受此刻的美好");
attr_broadcast_add_attraction(121.364371, 31.343164, "2号门便民处", "");
attr_broadcast_add_attraction(121.359268, 31.339659, "洋庭花园餐厅", "");
attr_broadcast_add_attraction(121.35677, 31.33876, "水上运动基地", "在上海大都市里不需要经过严格的专业学习和训练就可以亲身体验集健身休闲娱乐于一体的奥运会水上比赛项目顾村公园水上运动基地带给您的绝对是一份意外的惊喜这里阳光和煦碧水蓝天清新怡人和公园集生态休闲娱乐一体的公园设施融为一体水上运动基地设有皮划艇平台舟独木舟SUP浆板摩托艇冲锋艇香蕉船拖拉圈OP帆船滑水等娱乐项目或惊险刺激或悠然自得在悦林湖亦静亦动的水面无论是运动休闲还是观水赏景都别有一番情趣让人亲近自然之水生命之水");
attr_broadcast_add_attraction(121.35781, 31.33859, "东方鸟世界", "亲爱的游客东方鸟世界到了,这里是一个集观赏性,趣味性,参与性,科普性为一体的鸟类主题园区, 园内栖息着来自世界各地分属六十多个物种的两千余只珍稀鸟禽建有孔雀东南飞飞禽知识馆鹦鹉世界火烈鸟湾鸵鸟园游禽综合园猛禽园孵化中心等16个展区涵盖游禽涉禽攀禽鸣禽猛禽走禽六大鸟类品种这里是要单独购票的哟您要去游览的话记得将我停放在路边临时锁定哦");
attr_broadcast_add_attraction(121.357591, 31.338533, "月季园", "月季主题花园位于二期悦林湖畔总面积约534亩其中陆地面积约358亩月季种植面积22575平方米展示品种128种有灌木月季、藤本月季、大花月季、微型月季、树状月季等11个类别。月季主题花园将二期由生态林带向生态花园的品质内涵提升建造上海第一大以月季为主题的花园。");
attr_broadcast_add_attraction(121.376079, 31.347508, "1号门归还点", "您好一号门停放点到了,您可以把我归还至这里,并在手机小程序中结束订单,如果还想与我去其他地方玩耍我们就继续出发吧");
attr_broadcast_add_attraction(121.364270, 31.343103, "2号门归还点", "您好二号门停放点到了,您可以把我归还至这里,并在手机小程序中结束订单,如果还想陪我去其他地方玩耍我们就继续出发吧");
attr_broadcast_add_attraction(121.356886, 31.340930, "3号门归还点", "您好三号门停放点到了,您可以把我归还至这里,并在手机小程序中结束订单,如果还想陪我去其他地方玩耍我们就继续出发吧");
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)); memset(&broadcast_state, 0, sizeof(broadcast_state));

View File

@ -28,7 +28,7 @@
#include "local_tts.h" #include "local_tts.h"
#if 1 #if 0
#include "app_uart.h" #include "app_uart.h"
#define DEBUG(fmt, args...) app_printf("[main]" fmt, ##args) #define DEBUG(fmt, args...) app_printf("[main]" fmt, ##args)
#else #else
@ -314,8 +314,8 @@ void my_appimg_enter(char *param){
jt808_init(); jt808_init();
tcp_client_init(); tcp_client_init();
gps_config_init(); gps_config_init();
attr_broadcast_init();
radar_init();// 雷达测离初始化 radar_init();// 雷达测离初始化
attr_broadcast_init();
while(1){ while(1){
osDelay(1000/5); osDelay(1000/5);

View File

@ -6,7 +6,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "jt808_util.h" #include "jt808_util.h"
#define JT808_DEBUG_ENABLE 0 #define JT808_DEBUG_ENABLE 1
#if JT808_DEBUG_ENABLE #if JT808_DEBUG_ENABLE
#include "app_uart.h" #include "app_uart.h"

View File

@ -26,9 +26,10 @@ typedef enum {
ID_GetLocInfoResp = 0x0201, // 位置信息查询应答 ID_GetLocInfoResp = 0x0201, // 位置信息查询应答
ID_LocTrackingCtrl = 0x8202, // 临时位置跟踪控制 ID_LocTrackingCtrl = 0x8202, // 临时位置跟踪控制
ID_TxtMsgdelivery = 0x8300, // 文本信息下发 ID_TxtMsgdelivery = 0x8300, // 文本信息下发
ID_Paly_Introduction =0x8302, //播放景区介绍
ID_Car_Ctrl = 0x8500, // 车辆控制 ID_Car_Ctrl = 0x8500, // 车辆控制
ID_Car_CtrlResp = 0x0500, // 车辆控制应答 ID_Car_CtrlResp = 0x0500, // 车辆控制应答
ID_Set_Circle_Area = 0x8600, //设置圆形区域 增加景点
ID_Delete_Circle_area = 0x8601, // 删除圆形区域 删除景点
ID_Set_Polygon_area = 0x8604, // 设置多边形区域 ID_Set_Polygon_area = 0x8604, // 设置多边形区域
ID_Delete_Polygon_area = 0x8605, // 删除多边形区域 ID_Delete_Polygon_area = 0x8605, // 删除多边形区域
ID_Data_Down = 0x8900, // 数据透传下行 ID_Data_Down = 0x8900, // 数据透传下行

View File

@ -13,7 +13,7 @@ typedef struct {
uint32_t area_id; // 围栏区域ID uint32_t area_id; // 围栏区域ID
uint16_t area_att; // 区域属性0x0001=景区围栏, 0x0002=禁停区围栏 uint16_t area_att; // 区域属性0x0001=景区围栏, 0x0002=禁停区围栏
uint16_t points_num; // 区域顶点数量 uint16_t points_num; // 区域顶点数量
AreaPoint_t *points; // 区域顶点坐标(最多支持4边形 AreaPoint_t *points; // 区域顶点坐标(支持4边形
} LocalFenceConfig_t; } LocalFenceConfig_t;

View File

@ -2,9 +2,11 @@
#include "jt808_pkg_transmit.h" #include "jt808_pkg_transmit.h"
#include "local_tts.h" #include "local_tts.h"
#include "jt808_electronic_fence.h" #include "jt808_electronic_fence.h"
#include "attr_broadcast.h"
PrsResult_t PrsResult; PrsResult_t PrsResult;
// 消息体解析 // 消息体解析
static int jt808_BodyParse(void *Prsmsg_body, PrsResult_t *Result){ static int jt808_BodyParse(void *Prsmsg_body, PrsResult_t *Result){
switch (Result->msg_head.msg_id){ switch (Result->msg_head.msg_id){
@ -129,8 +131,90 @@ static int jt808_BodyParse(void *Prsmsg_body, PrsResult_t *Result){
jt808_pkg_send(ID_Car_CtrlResp, 0);// 发送车辆控制应答,设置本消息无发送应答 jt808_pkg_send(ID_Car_CtrlResp, 0);// 发送车辆控制应答,设置本消息无发送应答
break; break;
} }
case ID_Set_Circle_Area:{// 设置圆形区域(0x8600)
int ret = 0;
uint8_t *p = (uint8_t *)Prsmsg_body;
// 解析固定字段
uint32_t Area_ID = Swap32(*(uint32_t *)p);
uint16_t Area_att = Swap16(*(uint16_t *)(p + 4));
uint32_t center_lat = Swap32(*(uint32_t *)(p + 6));
uint32_t center_lon = Swap32(*(uint32_t *)(p + 10));
uint32_t radius = Swap32(*(uint32_t *)(p + 14));
// 解析景点名称字符串
uint16_t fixed_len = 18; // 前18字节是固定字段
uint16_t name_len = Result->msg_head.msgbody_attr.msgbodylen - fixed_len;
char *scenic_name = NULL;
if (name_len > 0) {
scenic_name = (char *)jt808_malloc(name_len + 1);
if (scenic_name) {
memcpy(scenic_name, p + fixed_len, name_len);
scenic_name[name_len] = '\0'; // 添加字符串终止符
}
}
// 转换为实际经纬度除以10^6
double actual_lat = (double)center_lat / 1000000.0;
double actual_lon = (double)center_lon / 1000000.0;
// 调用区域添加函数
attr_broadcast_add_attraction(
Area_ID, // 区域ID
actual_lon, // 经度
actual_lat, // 纬度
(double)radius, // 半径(米)
scenic_name ? scenic_name : "",// 景点名称
"" // 描述(可选)
);
// 释放名称内存
if (scenic_name) jt808_free(scenic_name);
// 设置响应结果
Result->Rsp_result = (ret == 0) ? Msg_ok : Msg_err;
Result->Rsp_flow_num = Result->msg_head.msg_flow_num;
Result->Rsp_msg_id = Result->msg_head.msg_id;
// 发送终端通用应答
jt808_pkg_send(ID_Term_GenResp, 0);
JT808_DEBUG("attraction data accepted\r\n");
break;
}
case ID_Delete_Circle_area:{// 删除圆形区域(0x8601)
int ret = 0;
uint8_t area_count = *((uint8_t*)Prsmsg_body); // 区域个数(1字节)
uint32_t* area_ids = (uint32_t*)((uint8_t*)Prsmsg_body + 1);
// 特殊处理0表示删除所有区域
if (area_count == 0) {
attr_broadcast_remove_all();
}
// 正常删除指定区域
else {
Result->Rsp_result = Msg_ok; //删不删干净都是ok
for (int i = 0; i < area_count; i++) {
uint32_t area_id = Swap32(area_ids[i]);
attr_broadcast_remove_attraction_by_id(area_id);
}
}
// 设置应答信息
Result->Rsp_flow_num = Result->msg_head.msg_flow_num;
Result->Rsp_msg_id = Result->msg_head.msg_id;
jt808_pkg_send(ID_Term_GenResp, 0); // 发送终端通用应答
break;
}
case ID_Set_Polygon_area:{// 设置多边形区域 case ID_Set_Polygon_area:{// 设置多边形区域
/* int ret = 0; int ret = 0;
uint32_t Area_ID; // 区域ID uint32_t Area_ID; // 区域ID
uint16_t Area_att; // 区域属性 uint16_t Area_att; // 区域属性
uint16_t Area_Points_Num; // 区域内点的数量 uint16_t Area_Points_Num; // 区域内点的数量
@ -151,14 +235,14 @@ static int jt808_BodyParse(void *Prsmsg_body, PrsResult_t *Result){
Result->Rsp_result = Msg_invalid; Result->Rsp_result = Msg_invalid;
} }
*/
Result->Rsp_flow_num = Result->msg_head.msg_flow_num; Result->Rsp_flow_num = Result->msg_head.msg_flow_num;
Result->Rsp_msg_id = Result->msg_head.msg_id; Result->Rsp_msg_id = Result->msg_head.msg_id;
jt808_pkg_send(ID_Term_GenResp, 0);// 发送终端通用应答 jt808_pkg_send(ID_Term_GenResp, 0);// 发送终端通用应答
break; break;
} }
case ID_Delete_Polygon_area:{// 删除多边形区域 case ID_Delete_Polygon_area:{// 删除多边形区域
/* int ret = 0; int ret = 0;
uint8_t Area_ID_Num = ((uint8_t *)Prsmsg_body)[0]; // 区域ID个数 uint8_t Area_ID_Num = ((uint8_t *)Prsmsg_body)[0]; // 区域ID个数
Prsmsg_body = (void *)((uint8_t *)Prsmsg_body + 1); // 跳过1字节 Prsmsg_body = (void *)((uint8_t *)Prsmsg_body + 1); // 跳过1字节
Result->Rsp_result = Msg_ok; Result->Rsp_result = Msg_ok;
@ -169,7 +253,7 @@ static int jt808_BodyParse(void *Prsmsg_body, PrsResult_t *Result){
break; break;
} }
} }
*/
Result->Rsp_flow_num = Result->msg_head.msg_flow_num; Result->Rsp_flow_num = Result->msg_head.msg_flow_num;
Result->Rsp_msg_id = Result->msg_head.msg_id; Result->Rsp_msg_id = Result->msg_head.msg_id;
jt808_pkg_send(ID_Term_GenResp, 0);// 发送终端通用应答 jt808_pkg_send(ID_Term_GenResp, 0);// 发送终端通用应答

View File

@ -63,43 +63,6 @@ static const LocalFenceConfig_t local_fences[] = {
}; };
// 打印已加载的围栏信息
void print_loaded_fences(void) {
osMutexAcquire(Polygon_fence_mutex, osWaitForever);
fence_Polygon_area_t *area = jt808_term_param_item.fence_polygon_area;
int count = 0;
if (area == NULL) {
JT808_DEBUG("No fence areas loaded\n");
osMutexRelease(Polygon_fence_mutex);
return;
}
JT808_DEBUG("Loaded fence areas:\n");
while (area != NULL) {
JT808_DEBUG("Area %d: ID=%u, Attr=0x%04X, Points=%d\n",
++count,
area->Area_ID,
area->Area_att,
area->Area_Points_Num);
for (int i = 0; i < area->Area_Points_Num; i++) {
uint32_t lat = Swap32(area->Area_Points[i].lat);
uint32_t lng = Swap32(area->Area_Points[i].lng);
JT808_DEBUG(" Point %d: Lat=%.6f(%u), Lng=%.6f(%u)\n",
i,
(double)lat / 1e6, lat,
(double)lng / 1e6, lng);
}
area = area->next;
}
osMutexRelease(Polygon_fence_mutex);
}
void load_local_fence_data(void) { void load_local_fence_data(void) {
JT808_DEBUG("Loading local fence data...\n"); JT808_DEBUG("Loading local fence data...\n");
@ -120,14 +83,9 @@ void load_local_fence_data(void) {
config->points config->points
); );
JT808_DEBUG(" - Added fence ID %u: %s with %d points\n",
config->area_id,
(config->area_att == 0x0001) ? "Scenic" : "Forbidden",
config->points_num);
} }
// 打印加载结果
print_loaded_fences();
} }
@ -712,7 +670,7 @@ void jt808_set_term_param_init(void){
memcpy(jt808_term_param_item.big_term_attr_resp.term_ICCID+4, jt808_term_param_item.phone_BCDnum, 6); // 终端手机号码 memcpy(jt808_term_param_item.big_term_attr_resp.term_ICCID+4, jt808_term_param_item.phone_BCDnum, 6); // 终端手机号码
char str_hw_ver[] = "1.0.0"; // 硬件版本 char str_hw_ver[] = "1.0.0"; // 硬件版本
char str_fw_ver[] = "1.0.0"; // 固件版本 char str_fw_ver[] = "1.0.1"; // 固件版本 原本为1.0.0
jt808_term_param_item.big_term_attr_resp.hw_ver_len = strlen(str_hw_ver); // 硬件版本长度 jt808_term_param_item.big_term_attr_resp.hw_ver_len = strlen(str_hw_ver); // 硬件版本长度
jt808_term_param_item.big_term_attr_resp.fw_ver_len = strlen(str_fw_ver); // 固件版本长度 jt808_term_param_item.big_term_attr_resp.fw_ver_len = strlen(str_fw_ver); // 固件版本长度
memcpy(jt808_term_param_item.big_term_attr_resp.str_hw_ver, str_hw_ver, strlen(str_hw_ver)); // 硬件版本 memcpy(jt808_term_param_item.big_term_attr_resp.str_hw_ver, str_hw_ver, strlen(str_hw_ver)); // 硬件版本

View File

@ -132,6 +132,8 @@ static void RADAR_TaskHandle(void *param){
if(0==sys_sta.P_Radar_EN) if(0==sys_sta.P_Radar_EN)
{ {
sys_sta.A_Speed_Cut =0; // 清空自动减速状态
sys_sta.A_brake =0; // 清空自动刹车状态
continue; continue;
} }
@ -164,10 +166,14 @@ static void RADAR_TaskHandle(void *param){
else if (2==sys_sta.P_Radar_EN) else if (2==sys_sta.P_Radar_EN)
{ {
time_count++; time_count++;
if(time_count>=143) sys_sta.A_Speed_Cut =0; // 清空自动减速状态
sys_sta.A_brake =0; // 清空自动刹车状态
if(time_count>=100)
{ {
sys_sta.P_Radar_EN=1; sys_sta.P_Radar_EN=1;
time_count=0; time_count=0;
} }
} }
@ -181,6 +187,7 @@ static void RADAR_TaskHandle(void *param){
void radar_init(void){ void radar_init(void){
radar_data.radar_id = RADAR_ID_Front; radar_data.radar_id = RADAR_ID_Front;
radar_data.distance = 0; radar_data.distance = 0;
radar_mutex = osMutexNew(NULL);
osThreadAttr_t radar_task_attr = { osThreadAttr_t radar_task_attr = {
.name = "uart_tx_task", .name = "uart_tx_task",
.stack_size = 4096*4, .stack_size = 4096*4,