TT12-MCU/applications/func/func.c
CSSC-WORK\murmur 3d9fb6a6bc 添加appendInfo函数
更新getLoc函数
2023-07-24 14:57:28 +08:00

830 lines
21 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2023-07-06 murmur the first version
*/
#include "func.h"
#define LOG_TAG "func"
#define LOG_LVL LOG_LVL_DBG
#include <ulog.h>
//3.2.1双模通信功能
//1为TT通信0为BD短报文通信
void setCommMode(int isTT)
{
if (isTT) {
//change mode
}
else {
}
//write to cfg file
set_cfg("commMode", isTT);
LOG_D("set commMode to %s",isTT?"TT":"BD");
}
int getCommMode()
{
//load from cfg file
int flag = get_cfg("commMode");
if (flag < 0) {
LOG_W("get mode fault.");
}
return flag;
}
//3.2.2状态自检
/**
* 获取磁盘剩余空间大小单位MB
* @param path指定磁盘挂载的路径为空时表示挂载在根目录的磁盘
* @return 返回结果单位MB
*/
static uint16_t getFreeSpace(const char *path)
{
long long cap;
struct statfs buffer;
int result = dfs_statfs(path ? path : "/", &buffer);
if (result != 0)
{
LOG_E("dfs_statfs failed.");
return 0;
}
cap = (uint16_t)((long long)buffer.f_bsize) * ((long long)buffer.f_bfree) / 1024LL / 1024LL ;//转换为MB
return cap;
}
void d_getFreeSpace()
{
LOG_D("free space of flash is %d MB.",getFreeSpace(NULL));
uint16_t rst = getFreeSpace("/sd");
LOG_D("free space of sd is %d MB.%02X,%02X",rst,rst>>8,rst&0xff);
}
/**
* 獲取電池電量。
* @return 返回電量百分比
*/
RT_WEAK int getPowerLevel(void)
{
return 0;
}
/**
* 上傳天通數據
* @param din 待發送數據
* @param len 待發送數據的長度
* @return
*/
RT_WEAK int upSend(uint8_t *din, size_t len)
{
//此函数有打包操作,需线程操作
LOG_D("upsend.");
return 0;
uint8_t dout[200];
//打包数据
static MSG cfg;
rt_memset(&cfg, 0, sizeof(MSG)); // 分配空间
char *fin;
time2Str(fin);
packInit(&cfg, fin, 0); //写入配置
size_t rst = packMsg(&cfg, din, len, dout);
LOG_HEX("upSend", 16, dout, rst);
return 0;
}
/**
* 系统自检,自动发送自检结果
*/
void selfTest()
{
LOG_I("FUNC = selftest");
rt_uint8_t rst[100]={0x5A, 0xA5, 0x32, 0x3E, 0x0A, 0x41};
int p = 6;
rt_uint8_t sysSta=1,xh=0,jh=0,commSpeed=0;
//长度
rst[p++]=0x00;
rst[p++]=0x09;
rst[p++] = sysSta;
rst[p++] = xh;
rst[p++] = jh;
rst[p++] = commSpeed;
rst[p++] = getPowerLevel();
//flash 剩餘空間
uint16_t cap = getFreeSpace("/");
rst[p++] = (uint8_t)(cap >> 8);
rst[p++] = (uint8_t)(cap & 0xff);
//SD卡剩餘空間
cap = getFreeSpace("/sd");
rst[p++] = (uint8_t)(cap >> 8);
rst[p++] = (uint8_t)(cap & 0xff);
rst[p++] = bccCRC(rst+2, p-1); ////校验位为1个字节采用异或运算从指令的第3个字节开始到奇偶校验位的前一个字节结束
rst[p++] = 0xED; //结束位
LOG_HEX("selfTestRes",16,rst,p);
//发送结果
upSend(rst,p);
}
//3.2.3日志记录
//日志功能由各函数通过LOG_D()实现
//3.2.4自毁功能
/**
* 设置自毁功能开关
* @param setON 1-自毁功能开启0-关闭
*/
void setSelfDestructSWT(int setON)
{
//write to cfg file
set_cfg("SelfDesSW", setON);
LOG_D("set SelfDesSW to %s",setON?"ON":"OFF");
}
/**
* 获取自毁开关状态
* @return 1-自毁功能开启0-关闭
*/
int getSelfDestructSWT()
{
//load from cfg file
int flag = get_cfg("SelfDesSW");
if (flag < 0) {
LOG_W("get mode fault.");
}
return flag;
}
/**
* 启动自毁
*/
void selfDestruct()
{
if (getSelfDestructSWT()) {
//硬件自毁
LOG_W("SELF DESTRUCT START.");
}
}
//3.2.5开、关窗功能
extern void updateAlarm(int *t);
/**
* 更新开窗时间,目前支持两组开窗时段。更新会清除之前的开窗设置
*/
void setCommWindow(int *t)
{
updateAlarm(t);
LOG_D("更新开窗时间完成。");
}
/**
* 手动控制开窗
* @param t 开窗时长单位分钟时间到则自动关窗。t=0时需要手动关窗。
*/
void openWindow(int t)
{
//开启TT
pwTT_thread_entry("1");//开机
if (!t) {
LOG_D("手动开窗完成,需手动关窗。");
return;
}
//设置定时器,定时器到则关窗
/* 创建定时器,单次定时器 */
rt_timer_t timer1;
timer1 = rt_timer_create("window", pwTT_thread_entry,
0, rt_tick_from_millisecond(t*60*1000),
RT_TIMER_FLAG_ONE_SHOT);
/* 启动定时器 */
if (timer1 != RT_NULL)
{
rt_timer_start(timer1);
LOG_D("手动开窗完成,%d分钟后自动关窗。",t);
}
}
/**
* 手动关窗
*/
void closeWindow()
{
pwTT_thread_entry("0");//关 机
LOG_D("手动关窗完成。");
}
//3.2.6工作参数配置、状态查询
//包含浮体自身、标体透传、两者结合
//这里主要实现浮体自身
//浮体自身的参数配置各功能函数有实现,此处需规定参数下发协议和解码实现
//获取当前位置
/**
* 获取当前经纬度信息
* @param dout 存储结果的数组
* @param cnt 获取几组位置数据
* @return 数组长度。0表示位置数据未准备好。
*/
RT_WEAK int getLoc(uint8_t * dout, size_t cnt)
{
LOG_D("待实现获取位置函数此处以0xCD代替");
uint8_t tmp[200];
memset(tmp,0xCD,200);
memcpy(dout,tmp,cnt*10);
return cnt*10;//4+4+2
}
/**
* 确保获取正常的位置数据
* @param dout 位置数据结果
* @param pairCnt 获取几组位置数据
* @return 数组长度
*/
static int getAndCheckLoc(uint8_t *dout, size_t pairCnt)
{
uint8_t loc[200];
size_t cnt = getLoc(loc,pairCnt);
while (!cnt)
{
static uint8_t i=0;
LOG_W("位置信息还未准备好。");
rt_thread_mdelay(4000);//状态数据默认3s更新一次
cnt = getLoc(loc,pairCnt);
if (i++ > 20) {
LOG_E("位置信息获取异常");
break;
}
}
memcpy(dout,loc,cnt);
return cnt;
}
/**
* c回应深度数据含位置信息
* @param din
* @param len
* @return
*/
int reportDepth(uint8_t *din,size_t len)
{
uint8_t rst[len];
memcpy(rst,din,len);
// 000000000000000000005AA53E320608000A170407101B33FFFFFFFF24ED
getAndCheckLoc(rst+0,1);//有10个位置的空余字节尚不清楚定义
upSend(rst, len);
}
//天通指令类型0x70 0x01
//3.2.8定时自报位置信息
//每小时传数据时同步传位置信息
//与告警信息共用编码方式
//首字节00为定时发送未检测围栏其它为在围栏外
//单次最多可上报15条位置信息
/**
* 按15条经纬度打包位置信息
* @param dout 存储位置信息的数组
* @return 数组大小
*/
static int packLocMsg(uint8_t *dout)
{
uint8_t alertMsg[200] =
{ 0x5A, 0xA5, ADDR_ANJI, ADDR_TT, _CFG_LOCATION_ALERT >> 8, _CFG_LOCATION_ALERT & 0xFF, 0, 0 };
alertMsg[8] = 0; //首字节00为定时发送未检测围栏其它为在围栏外
int len = time2Byte(alertMsg+9);//添加时间戳
len += (getAndCheckLoc(alertMsg + 9 + len, 15)+1); //p指向第2个数据
alertMsg[7] = len; //update len of raw data
alertMsg[8 + len] = bccCRC(alertMsg + 2, 8 + len - 2); //update CRC
alertMsg[8 + len + 1] = 0xED;
len = 8 + len + 2;
LOG_HEX("packLocMsg", 16, alertMsg, len); //bccCRC+Tail
memcpy(dout, alertMsg, len);
return len;
}
static void d_packLocMsg(void)
{
uint8_t tmp[200];
packLocMsg(tmp);
}
/**
* 加密位置信息。对位置信息的更改需在加密前操作
* @param din 存储待加密位置信息的数组
* @param len 带加密长度
* @param dout 存储加密结果的数组
* @return 加密后的长度
*/
static int cryptLocMsg(uint8_t *din, size_t len, uint8_t *dout)
{ //加密。因加密后数据长度会变化,故不能只加密位置数据。
uint8_t cd[200]={0xAB,0xAB};
size_t nlen = 0;
//#define FULL_DATA_CRYPT
#ifdef FULL_DATA_CRYPT
nlen = cryp_data(din, len, cd);
#else
//单独加密时在加密后数据头部添加0xABAB便于识别、解析
nlen = cryp_data(din, len, cd + 2) + 2;
#endif
memcpy(dout, cd, nlen);
return nlen;
}
int packAndSendLoc()
{
uint8_t rst[200];
int len = packLocMsg(rst);
LOG_D("len=%d",len);
len = cryptLocMsg(rst, len, rst);
LOG_D("len=%d",len);
LOG_HEX("crypt",16,rst,len);
}
//3.2.9深度异常告警
/**
* 将位置信息合入深度信息
* @param din
* @param len
* @param dout
* @return
*/
int packDepthMsg(uint8_t *din, int len)
{
//获取并更新位置信息
uint8_t loc[10];
uint8_t dout[200];
size_t nlen = getAndCheckLoc(loc, 1);
rt_memcpy(din+0, loc, nlen);//位置数据从【】字节开始共len个字节
LOG_HEX("depth",16,din,len);
//加密。因加密后数据长度会变化,故不能只加密位置数据。
nlen = cryptLocMsg(din, len, dout);
LOG_D("位置数据加密完成");
upSend(dout, nlen);
}
//3.2.10位置异常告警
//map.c中实现
/**
* 判断是否在电子围栏内部
* @param x 当前位置经度
* @param y 当前位置纬度
* @return 在内部则返回true反之false
*/
static int isInFence(float x, float y)
{
float polyX[]={},polyY[]={};
int polyCorners = mapParse("/map.geojson",polyX,polyY);
return pointInPolygon(polyCorners,polyX,polyY,x,y);
}
/**
* 设置位置告警功能开关
* @param setON 1-告警功能开启0-关闭
*/
void setLocationAlertSWT(int setON)
{
}
/**
* 检查是否在电子围栏内部,并发出告警
*/
void checkLocAndAlert()
{
//get Lon and Lat
uint8_t loc[8];
getAndCheckLoc(loc,1);
float x,y =0;
memcpy(&x,loc,4);
memcpy(&y,loc+4,4);
int isIN = isInFence(x, y);
if (isIN) {
LOG_I("设备在预设范围内,位置正常。");
}
else {
LOG_W("警告!设备不在预设范围内!");
uint8_t msg[200];
int len = packLocMsg(msg);
len = cryptLocMsg(msg,len,msg);
//告警信息与3.2.8定时自报位置信息的共用编码方式
// uint8_t alertMsg[]={0x5A,0xA5,ADDR_ANJI,ADDR_TT};
// alertMsg[4] = _CFG_LOCATION_ALERT >> 8;
// alertMsg[5] = _CFG_LOCATION_ALERT & 0xFF;
//
// //加密。
// uint8_t cd[200];
// size_t nlen = cryptByte(din, len, cd);
// upSend(alertMsg, sizeof(alertMsg));
}
}
//3.2.12数据存储区清空
//
/**
* 采用格式化命令对存储区进行清空,谨慎使用!
*/
void clearAllData()
{
mkfs("elm","sd0");//format SD
mkfs("elm", "W25Q128");//format flash
}
/**
* @brief TT根据下发的指令执行对应的功能
*
* @param din 待执行的指令数据
* @param len 数据长度,单位字节
*/
void ttRunCMD(uint8_t *din, size_t len)
{
/**
+--------+--------+------------+------------+---------+--------+------+------+-------+-----+-------+---------+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 8+N | 9+N | 10+N |
+--------+--------+------------+------------+---------+--------+------+------+-------+-----+-------+---------+------+
| Header | Header | targetAddr | sourceAddr | mainCMD | subCMD | lenH | lenL | data1 | ... | dataN | BbccCRC | tail |
+--------+--------+------------+------------+---------+--------+------+------+-------+-----+-------+---------+------+
*/
int cmd = (din[4] << 8) + din[5];
// int para[]
switch (cmd)
{
case _CFG_COMM_MODE:
setCommMode(din[8]);
break;
case _CMD_SELF_TEST:
selfTest();
break;
case _CFG_SELF_DESTRUCT:
setSelfDestructSWT(din[8]);
break;
case _CMD_SELF_DESTRUCT:
selfDestruct();
break;
case _CFG_COMM_WINDOW:
setCommWindow(din+8);//只支持两组开窗
break;
case _CMD_OPEN_WINDOW:
openWindow(din[7]?((din[8] << 8) + din[9]):0);//两字节开窗时间
break;
case _CMD_CLOSE_WINDOW:
closeWindow();
break;
case _CFG_LOCATION_ALERT:
setLocationAlertSWT(din[8]);
break;
case _CMD_CLEAR_DATA:
clearAllData();
break;
default:
LOG_W("未支持的指令。");
break;
}
}
RT_WEAK int xpParse(uint8_t * din, size_t len)
{
// LOG_D("直接调用小彭的函数进行处理。");
char str[200] = "RCV:";
int cmd = (din[4] << 8) + din[5];
switch (cmd)
{
case _CMD_RTC_REQUEST: //不加前后缀的指令,仅有少数
bytes2str(din, len, 16, "", str);
break;
default: //默认加前后缀
bytes2str(din, len, 16, "", str + 4);
strcat(str, "\r\n");
break;
}
LOG_D("send '%s' to 3S.", str);
sendTo3S(str, strlen(str));//作为字符串发送
return 0;
}
int chk3SDataValid(uint8_t *din, size_t count)
{
uint8_t head[]={0x5A,0xA5};
//header[4] addr[2]  func[2]  len[2]  data[N]  fcrc[1] tail[1]
if (memcmp(din,head,sizeof(head)))
{
LOG_W("0x5AA5[√] != 0x%02X%02X[×],帧头不匹配",din[0],din[1]);
return -RT_ERROR;
}
if (din[count-2] != bccCRC(din+2,count-2-2))//校验位为1个字节采用异或运算从指令的第3个字节开始到奇偶校验位的前一个字节结束
{
LOG_W("0x%02X[√] != 0x%02X[×],校验值不匹配", bccCRC(din+2,count-2-2),din[count-2] );
return -RT_ERROR;
}
if (din[count-1] != 0xED)
{
LOG_W("0xED[√] != 0x%02X[×],帧尾不匹配",din[count-1]);
return -RT_ERROR;
}
// LOG_D("valid data.");
return RT_EOK;
}
//原计划将指令粗解析放在上位机考虑到上位机到位时间晚现放到MCU端
/**
* @brief 校验、解析3S数据以0x5AA5开头0xED结尾。
*
* @param din 待解析数据
* @param count 待解析数据长度
*/
void parse3SData(uint8_t *din, size_t count)
{
/**
+--------+--------+------------+------------+---------+--------+------+------+-------+-----+-------+---------+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 8+N | 9+N | 10+N |
+--------+--------+------------+------------+---------+--------+------+------+-------+-----+-------+---------+------+
| Header | Header | targetAddr | sourceAddr | mainCMD | subCMD | lenH | lenL | data1 | ... | dataN | BbccCRC | tail |
+--------+--------+------------+------------+---------+--------+------+------+-------+-----+-------+---------+------+
*/
if (chk3SDataValid(din, count) != RT_EOK) {
return;
}
// uint8_t dout[200];
// 未采用switch case
if (din[2] == ADDR_TT)//仅给TT的消息
{
ttRunCMD(din,count);
}
if (din[2] == ADDR_3S)//给3S的指令需要再加工返回数据可能也需要再加工
{
//
xpParse(din,count);
}
if (din[2] == ADDR_ANJI)
{
//可能需要对回传信息再加工,如查询深度需要加入位置坐标
//或是缓存任务数据
//故需要对数据进行简单判断
int cmd = (din[4] << 8) + din[5];
switch (cmd)
{
case _CMD_DEPTH_REQUEST:
packDepthMsg(din, count);
break;
default:
cacheData(din, count);
}
// upSend(din, count);
}
}
void chkACK(uint8_t *msg, size_t size)
{
rt_uint8_t ackgood[] = { 0x88, 0xAA, 0xBB, 0x88, 0x41, 0x43, 0x4B }; //前四字节=帧头、后三字节=ACK
if (rt_memcmp(msg, ackgood, 4) == 0 && rt_memcmp(msg + size - 3, ackgood + 4, 3) == 0)
{
LOG_I("data is ACK.");
}
else {
LOG_W("INVALID DATA.");
}
}
/**
* @brief 解析TT数据TT收到的指令必是单指令解析容易。
*
* @param din
* @param len
*/
void parseTTData(uint8_t *din, size_t len)
{
/**
* +---------------------+-----------+-----------+-----------+-----------+---------+---------------------+------------+
* | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
* +---------------------+-----------+-----------+-----------+-----------+---------+---------------------+------------+
* | fstart[4] | fnum[2] | bak[2] | ftype[2] | fdlen[2] | fcrc[1] | ftccid[4] | rawData[N] |
* | 0x88,0xAA,0xBB,0x88 | 0x00,0x01 | 0x00,0x00 | 0x70,0x21 | 0x00,0xAA | 0x00 | 0x27,0x22,0x22,0x22 | |
* +---------------------+-----------+-----------+-----------+-----------+---------+---------------------+------------+
*/
uint8_t head[]={0x88,0xAA,0xBB,0x88, 0x00,0x01, 0x00,0x00, 0x70,0x21, 0x00,0xaa, 0x00, 0x27,0x22,0x22,0x22 };
//fstart[4] fnum[2]  bak[2]  ftype[2]  fdlen[2]  fcrc[1] ftccid[4]
uint8_t index[10];
size_t n=isInByte(din, len, head, 10, index);
uint8_t ndin[200];
if (!n) {
LOG_W("无匹配数据");
return;
}
for (size_t i = 0; i < n; i++)
{
//按帧头分割
int cnt=(i+1<n)?index[i+1]-index[i]:len-index[i];
memcpy(ndin,din+index[i],cnt);
LOG_HEX("frame",16,ndin,cnt);
//判断是否为ACK
if ((ndin[10]<<8) | ndin[11] == 0x03) {//数据长度只有3
chkACK(ndin, cnt);
}
//数据
else
{
uint8_t rst = memcmp(ndin,head,10);//只比较到ftype
if (rst)
{
LOG_W("帧头不匹配");
return;
}
uint8_t id[30]="";
LOG_I("data info: id=\"%s\", cur/all=[%d/%d]",bytes2str(ndin+17,7,10,"_",id),ndin[25],ndin[26]);
if (ndin[24] >> 7) // fcfg=数据类型。解析TT收到的数据时仅需解析“命令”“数据”传输是单向的。
{
LOG_W("浮标端仅接受指令,暂不支持数据。");
return;
}
uint8_t rawData[200];
uint8_t rawDataLen=cnt-27;
memcpy(rawData, ndin + 27, rawDataLen);
parse3SData(rawData,rawDataLen);
}
}
}
/**
* 缓存任务数据
* @param din 单次收到的任务数据
* @param len 任务数据长度
* @return 0--正常,-1--异常
*/
int cacheData(uint8_t *din, size_t len)
{
LOG_I("FUNC = cache and upsend");
static int cnt = 0;
LOG_D("cached size=%d", cnt);
char rootDir[22] = "/sd/rxdata/";
mkdir(rootDir, 0);
strcat(rootDir, "2023_07_19/");
//name of cachefile
static char f[60] = "";
if (cnt == 0 || cnt > 1024)
{
//广播待发送文件
cnt = 0;
//更新时戳
char ts[30] = "";
time2Str(ts);
//更新文件夹
strncpy(rootDir + strlen(rootDir) - 9, ts, 8);
mkdir(rootDir, 0);
//更新文件名
f[0] = '\0';
strcat(f, rootDir);
// strcpy(f,rootDir);
strcat(f, "23_07_19_16_38_36_36.bin");
LOG_D("need to creat new file");
strncpy(f + strlen(rootDir), ts, strlen(ts));
}
LOG_D("f=%s", f);
int fd = open(f, O_WRONLY | O_CREAT | O_APPEND);
if (fd < 0)
{
LOG_E("open file %s failed!", f);
return -RT_ERROR;
}
else
{
int rst = write(fd, din, len);
if (rst != len)
{
LOG_E("write to file %s failed!", f);
close(fd);
return -RT_ERROR;
}
cnt += len;
close(fd);
}
return RT_EOK;
}
void d_cacheData()
{
uint8_t demo[200];
size_t len = sizeof(demo);
memset(demo,0xAB,len);
cacheData(demo, len);
}
void parseRS232(uint8_t *din, size_t len)
{
//有HEX有ASCII统一按HEX解析
uint8_t head[]={0x41, 0x54, 0x2B, 0x53, 0x4E, 0x44, 0x20 };//"AT+SND "
uint8_t ndin[400];
//如果有无"AT+SND "头的数据混发则难以处理粘包,如果所有数据均以"AT+SND "开头则可以按以下方法(未完全实现)处理
//是否存在混发需要核实
#ifdef _MIX_DATA
uint8_t index[10];
size_t n=isInByte(din, len, head, sizeof(head), index);
if (!n) {
LOG_W("无匹配数据");
return;
}
for (size_t i = 0; i < n; i++)
{
//按帧头分割
int cnt=(i+1<n)?index[i+1]-index[i]:len-index[i];
memcpy(ndin,din+index[i],cnt);
LOG_HEX("frame",16,ndin,cnt);
#endif
#ifndef _MIX_DATA
int cnt=len;
memcpy(ndin,din,len);
#endif
//parse
size_t offset = 0;
if (memcmp(ndin,head,sizeof(head)) == 0) {
//有“AT+SND ”帧头,需要处理
offset = sizeof(head);
}
ndin[cnt]='\0';
size_t ncnt = str2Byte(ndin+offset, 2, 16, ndin);
LOG_HEX("decode",16,ndin,ncnt);
if (chk3SDataValid(ndin, ncnt) != RT_EOK) {
return;
}
parse3SData(ndin,ncnt);
#ifdef _MIX_DATA
}
#endif
}
#define FUNC_DEMO
#ifdef FUNC_DEMO //测试时导出命令到控制台
MSH_CMD_EXPORT(d_getFreeSpace,getFreeSpace);
MSH_CMD_EXPORT(selfTest,sysSelfTest);
MSH_CMD_EXPORT(d_packLocMsg,dpackLocMsg);
MSH_CMD_EXPORT(packAndSendLoc,packAndSendLoc);
MSH_CMD_EXPORT(d_cacheData,d_cacheData);
#endif