待发文件存入独立ini

更新upSend,在线程中判断TT状态
添加isDataASCII getNewCacheFileName getLstCacheFileName 功能
This commit is contained in:
CSSC-WORK\murmur 2023-07-29 16:08:15 +08:00
parent 5816baef54
commit 79d4b61bce
12 changed files with 351 additions and 128 deletions

View File

@ -1,2 +1,3 @@
--解决关闭TT时异常重启的问题
--文件上传
--配置文件更新,配置上传下载

View File

@ -47,7 +47,8 @@ SYS_CFG scfg={
.maxTTRetryCnt = 3,
.minTTPeriCnt=5,
.minTTsinal=5,
.timeout=5
.timeout=5,
.maxSizePerFile=1024
};
@ -69,6 +70,7 @@ static void updatecfg(void)
scfg.minTTPeriCnt = get_cfg("minTTPeriCnt");
scfg.minTTsinal = get_cfg("minTTsinal");
scfg.timeout = get_cfg("timeout");
scfg.maxSizePerFile = get_cfg("maxSizePerFile");
}
LOG_D("cfg updated.");
}
@ -290,7 +292,7 @@ void resetTM()
return;
}
rt_tick_t t= 10*1000;//rt_tick_from_millisecond(scfg.timeout*60*1000);
rt_tick_t t= rt_tick_from_millisecond(scfg.timeout*60*1000);
rt_timer_control(tmrToPNTT, RT_TIMER_CTRL_SET_TIME,(void*) &t);
rt_timer_stop(tmrToPNTT);
rt_timer_start(tmrToPNTT);
@ -408,7 +410,7 @@ void initTT()
}
/* 创建 serial 线程 */
thread = rt_thread_create("deInitTT", deInitTT_thread_entry, RT_NULL, 1024 * 2, 30, 10);
thread = rt_thread_create("deInitTT", deInitTT_thread_entry, RT_NULL, 1024 * 2, 19, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{

View File

@ -11,7 +11,8 @@
#include "minIni.h"
#ifdef PKG_USING_MININI
#define LJW_CFG_FILE_NAME "/sd/cfg.ini"
#define LJW_CFG_FILE_NAME "/cfg.ini"
#define FILE_TO_SEND "/sd/tosend.ini"//避免读写出错造成系统配置文件丢失
#define LOG_TAG "cfg"
#define LOG_LVL LOG_LVL_DBG
@ -20,23 +21,54 @@
#include "cfg.h"
static uint8_t islock=0;
static void setLock()
{
while(islock)
{
rt_thread_mdelay(1000);
}
islock=1;
}
static void clearLock()
{
islock=0;
}
extern rt_sem_t cfgUpdate;
//extern struct rt_messagequeue update_cfg;//main线程
//struct rt_event update_cfg;
//void iniEvent(void)
//{
// /* 事 件 控 制 块 */
//
// rt_err_t result = rt_event_init(&update_cfg, "cfg", RT_IPC_FLAG_FIFO);
// if (result != RT_EOK)
// {
// LOG_E("init event failed.\n");
//// return -1;
// }
//}
static struct rt_messagequeue upfilelist;
typedef struct
{
char msg[60];
uint8_t index;
}FILE_INFO;
static uint8_t msg_pool[512] ;
void addToList_thread_entry(void *parameter);
static void iniUFMsg(void)
{
/* 初始化消息队列 */
rt_mq_init(&upfilelist, "updatelist",
msg_pool, /* 存放消息的缓冲区 */
sizeof(FILE_INFO), /* 一条消息的最大长度 */
sizeof(msg_pool), /* 存放消息的缓冲区大小 */
RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("filelist", addToList_thread_entry, RT_NULL, 1024*3, 30-2, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
LOG_E("thread 'updatelist' create failure.");
}
}
/* 导出到自动初始化 */
//INIT_COMPONENT_EXPORT(iniEvent);
INIT_COMPONENT_EXPORT(iniUFMsg);
/**
* config项
@ -47,6 +79,8 @@ extern rt_sem_t cfgUpdate;
*/
int set_cfg(const char *k, const char*v)
{
setLock();
if (rt_strcmp(v,"NULL") == 0) {//delete key
v = NULL;
}
@ -62,11 +96,14 @@ int set_cfg(const char *k, const char*v)
else {
LOG_E("set value fault.");
}
clearLock();
return rst;
}
int get_cfg(const char *k)
{
setLock();
// char buf[MAX_KEY_LEN];
// int rst = ini_gets("config",k,"000000",buf,MAX_KEY_LEN,LJW_CFG_FILE_NAME);
// if(strcmp(buf, "000000") == 0) {
@ -74,16 +111,18 @@ int get_cfg(const char *k)
int rst = ini_getl("config", k, -1, LJW_CFG_FILE_NAME);
if (rst == -1) {
LOG_W("no such KEY:%s",k);
clearLock();
return -RT_ERROR;
}
else {
// LOG_I("%s = %s",k,buf);
// LOG_I("%s = %d",k,rst);
}
clearLock();
return rst;
}
void get_cfg_all(void)
static void get_cfg_all(void)
{
char buf[MAX_KEY_LEN];
char kstr[MAX_KEY_LEN];
@ -99,7 +138,7 @@ void get_cfg_all(void)
void cfg(int argc, char ** argv)
static void cfg(int argc, char ** argv)
{
if (argc == 1) {//无键无值,遍历
get_cfg_all();
@ -132,24 +171,30 @@ MSH_CMD_EXPORT(cfg, config params. 配置系统参数,支持参数)
*/
long get_val(const char *k)
{
setLock();
long v= ini_getl("stats", k, -1, LJW_CFG_FILE_NAME);
if( v == -1)
{
LOG_W("no such KEY:%s",k);
clearLock();
return -RT_ERROR;
}
else {
clearLock();
return v;
}
}
int set_val(const char *k, long v)
{
setLock();
if(!ini_putl("stats",k,v,LJW_CFG_FILE_NAME))
{
LOG_E("write %s error.",k);
clearLock();
return -RT_ERROR;
}
clearLock();
return RT_EOK;
}
@ -160,6 +205,7 @@ int set_val(const char *k, long v)
*/
int add_val(const char *k)
{
// setLock();
long ori = get_val(k);
rt_thread_mdelay(100);
if (ori != -1) {
@ -170,6 +216,7 @@ int add_val(const char *k)
else {
return ori;
}
// clearLock();
}
static void get_sta(const char *k)
{
@ -179,7 +226,7 @@ static void get_sta(const char *k)
}
}
void sta(int argc, char ** argv)
static void sta(int argc, char ** argv)
{
if (argc == 1)
{ //无键无值,遍历
@ -209,7 +256,19 @@ static void clear_sta(void)
}
MSH_CMD_EXPORT_ALIAS(clear_sta,clsSta, )
static uint8_t nislock=0;
static void nsetLock()
{
while(nislock)
{
rt_thread_mdelay(1000);
}
nislock=1;
}
static void nclearLock()
{
nislock=0;
}
/**
*
* @param f
@ -218,11 +277,16 @@ MSH_CMD_EXPORT_ALIAS(clear_sta,clsSta, 重置系统统计数据)
*/
int setFileToSend(const char *f, int v)
{
int rst = ini_putl(SECTION_TO_SEND, f, v, LJW_CFG_FILE_NAME);
nsetLock();
int rst = ini_putl(SECTION_TO_SEND, f, v, FILE_TO_SEND);
if (!rst) {
LOG_E("add file to send error.");
clearLock();
return RT_ERROR;
}
return rst;
nclearLock();
return RT_EOK;
}
/**
*
@ -234,11 +298,13 @@ int getFilesToSend(char (*kstr)[MAX_KEY_LEN], int *v)
{
// char buf[MAX_KEY_LEN];
// char kstr[MAX_KEY_LEN];
nsetLock();
size_t len=0;
for (size_t k = 0; ini_getkey(SECTION_TO_SEND, k, kstr[len], MAX_KEY_LEN, LJW_CFG_FILE_NAME) > 0; k++) {
v[len] = ini_getl(SECTION_TO_SEND, kstr[len], -1, LJW_CFG_FILE_NAME);
for (size_t k = 0; ini_getkey(SECTION_TO_SEND, k, kstr[len], MAX_KEY_LEN, FILE_TO_SEND) > 0; k++) {
v[len] = ini_getl(SECTION_TO_SEND, kstr[len], -1, FILE_TO_SEND);
len +=1;
}
nclearLock();
return len;
}
/**
@ -246,20 +312,24 @@ int getFilesToSend(char (*kstr)[MAX_KEY_LEN], int *v)
*/
int clearFileToSend(const char *k)
{
int rst = ini_puts(SECTION_TO_SEND, k, NULL, LJW_CFG_FILE_NAME);
if (!rst) {
LOG_E("clear file to send error.");
}
return rst;
nsetLock();
int rst = ini_puts(SECTION_TO_SEND, k, NULL, FILE_TO_SEND);
if (!rst)
{
LOG_E("clear file to send error.");
}
nclearLock();
return rst;
}
void gf()
{
int v[MAX_KEY_LEN];
char kstr[10][MAX_KEY_LEN];
size_t cnt = getFilesToSend(kstr, v);
for (size_t var = 0; var < cnt; ++var) {
LOG_I("%s -- %d",kstr[var],v[var]);
char kstr[10][MAX_KEY_LEN];
size_t cnt = getFilesToSend(kstr, v);
for (size_t var = 0; var < cnt; ++var)
{
LOG_I("%s -- %d", kstr[var], v[var]);
}
}
void add(int argc, char **argv)
@ -270,8 +340,34 @@ void add(int argc, char **argv)
gf();
}
void addToList_thread_entry(void *parameter)
{
FILE_INFO msg;
while(1)
{
rt_memset(&msg, 0, sizeof(msg));
if (rt_mq_recv(&upfilelist, &msg, sizeof(msg), RT_WAITING_FOREVER) == RT_EOK) {
LOG_D("get %s",msg.msg);
setFileToSend(msg.msg,msg.index);
}
}
}
void postFileInfo(const char *fin, uint8_t index)
{
// setFileToSend(fin,0);
FILE_INFO msg;
strcpy(msg.msg,fin);
msg.index=index;
rt_mq_send(&upfilelist, &msg, sizeof(msg));
}
MSH_CMD_EXPORT(gf, )
//MSH_CMD_EXPORT_ALIAS(add, cf,查看待发送文件列表)
MSH_CMD_EXPORT_ALIAS(add, cf,)
//set_if()
#endif

View File

@ -13,9 +13,9 @@
//typedef struct
//{
// char key[10];
// char value[10];
//} CFG_MSG;
// char msg[60];
// uint8_t index;
//}FILEINFO_MSG;
#define CFGCHANGEED 1
#define SENDINTERVAL 1<<1
@ -24,7 +24,7 @@
#define ENCRYTTYPE 1<<4
#define MAX_KEY_LEN 30
#define MAX_KEY_LEN 60
#define SECTION_TO_SEND "tosend"

View File

@ -1,9 +1,9 @@
[config]
# V1.7
# V1.8
#发送间隔M以零点为基准时刻
sendInterval=60
# 最文件大小,超过此大小则进入发送流程
minSizeToSend=4096
# 最文件大小,超过此大小则进入发送流程
maxSizePerFile=4096
# 最小等待时间S超时后进入待机模式
minSecToSleep=30
# TT最长等待激活时间M超时后重启

View File

@ -46,7 +46,7 @@ int trDataTolog(uint8_t *din, size_t len, uint8_t isTx)
}
else
{
char log[300]="";
char log[1024]="";
char tmp[200]="";
strcat(log,getTimestmp(tmp));
strcat(log,isTx?" [T]: ":" [R]: ");
@ -148,6 +148,29 @@ void upSend_thread_entry(void* parameter)
{
SMSG* msg = RT_NULL;
msg = (SMSG*) parameter;
uint8_t sta = 1;
while (1 && !isTTjh()) //判断TT状态
{
static uint8_t trycnt = 0;
rt_thread_mdelay(4000);
trycnt += 1;
if (trycnt > 3)
{
sta = 0; //try 3 time
break;
}
}
if (!sta) {
//cache to file
LOG_W("TT is not ready, try to cache %d bytes data to file.",msg->len);
cacheDataToFile(msg->data, msg->len);
return -RT_ERROR;
}
//打包数据
uint8_t dout[200];
static MSG cfg;
@ -188,31 +211,31 @@ void upSend_thread_entry(void* parameter)
RT_WEAK int upSend(uint8_t *din, size_t len)
{
LOG_D("try to upsend to TT.");
uint8_t sta = 1;
while (1 && !isTTjh()) //判断TT状态
{
static uint8_t trycnt = 0;
rt_thread_mdelay(4000);
trycnt += 1;
if (trycnt > 3)
{
sta = 0; //try 3 time
break;
}
}
if (!sta) {
//cache to file
LOG_W("TT is not ready, try to cache %d bytes data to file.",len);
cacheDataToFile(din, len);
// trDataTolog(din, len, 1);
return -RT_ERROR;
}
//此函数有打包操作,需线程操作
// LOG_D("upsend.");
// return 0;
//
// uint8_t sta = 1;
// while (1 && !isTTjh()) //判断TT状态
// {
// static uint8_t trycnt = 0;
// rt_thread_mdelay(4000);
// trycnt += 1;
// if (trycnt > 3)
// {
// sta = 0; //try 3 time
// break;
// }
// }
// if (!sta) {
// //cache to file
// LOG_W("TT is not ready, try to cache %d bytes data to file.",len);
// cacheDataToFile(din, len);
//// trDataTolog(din, len, 1);
// return -RT_ERROR;
// }
//
// //此函数有打包操作,需线程操作
//// LOG_D("upsend.");
//// return 0;
//
static SMSG msg;
memset(&msg, 0, sizeof(SMSG));
memcpy(msg.data,din,len);
@ -405,6 +428,7 @@ static int getAndCheckLoc(uint8_t *dout, size_t pairCnt)
memcpy(dout,loc,cnt);
return cnt;
}
int cryptLocMsg(uint8_t *din, size_t len, uint8_t *dout);
/**
* c回应深度数据
* @param din
@ -416,8 +440,8 @@ int reportDepth(uint8_t *din,size_t len)
// 000000000000000000005AA53E320608000A170407101B33FFFFFFFF24ED
//获取并更新位置信息
uint8_t loc[10];
uint8_t dout[200];
memset(dout,0,200);
size_t nlen = getAndCheckLoc(dout, 1);
rt_memcpy(dout+10, din, len);//5aa5从第11字节开始共len个字节
@ -425,7 +449,7 @@ int reportDepth(uint8_t *din,size_t len)
//加密。因加密后数据长度会变化,故不能只加密位置数据。
nlen = cryptLocMsg(din, len+10, dout);
LOG_HEX("crypt",16,dout,nlen);
LOG_D("位置数据加密完成");
// LOG_D("位置数据加密完成");
upSend(dout, nlen);
}
//天通指令类型0x70 0x01
@ -786,7 +810,7 @@ void parseTTData(uint8_t *din, size_t len)
size_t n=isInByte(din, len, head, 10, index);
uint8_t ndin[200];
if (!n) {
LOG_W("无匹配数据");
LOG_W("TTData中无匹配帧");
return;
}
for (size_t i = 0; i < n; i++)
@ -830,7 +854,7 @@ void parseTTData(uint8_t *din, size_t len)
}
/**
* get file size
* get file size in byte
* @param file file name whith fullpath
*/
unsigned long getFileSize(char *file)
@ -864,6 +888,8 @@ void d_getFileSize(int argc, char ** argv)
LOG_D("size = %d Byte.",size);
}
extern SYS_CFG scfg;
static uint8_t iscdlock=0;
/**
*
* @param din
@ -872,37 +898,50 @@ void d_getFileSize(int argc, char ** argv)
*/
int cacheDataToFile(uint8_t *din, size_t len)
{
LOG_I("FUNC = cache and upsend");
static uint16_t cnt = 0;
LOG_D("already cached %d bytes.", 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_I("FUNC = cache to upsend");
// static uint16_t cnt = 0;
// LOG_D("already cached %d bytes.", 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)
// {
// if (cnt) {
// //加入待发列表
// postFileInfo(f,0);
// }
// //广播待发送文件
// 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);
char f[60]="";
getLstCacheFileName(f);
//lock file
if (iscdlock) {
rt_thread_mdelay(1000);
}
iscdlock=1;
int fd = open(f, O_WRONLY | O_CREAT | O_APPEND);
if (fd < 0)
{
@ -916,13 +955,21 @@ int cacheDataToFile(uint8_t *din, size_t len)
{
LOG_E("write to file %s failed!", f);
close(fd);
iscdlock=0;
return -RT_ERROR;
}
cnt += len;
// cnt += len;
close(fd);
LOG_I("cached %d bytes data to '%s'.",cnt,f);
LOG_I("cached %d bytes data to '%s'.",len,f);
if (getFileSize(f) > scfg.maxSizePerFile) {
postFileInfo(f,0);//加入待发列表
getNewCacheFileName(f);//更新文件名
}
}
iscdlock=0;
return RT_EOK;
}
void d_cacheData()
@ -943,19 +990,21 @@ void parseRS232(uint8_t *din, size_t len)
uint8_t hexHead[]={0x5a, 0xa5};//"5AA5"
//由于帧头有多种且ascii和hex混发无法处理粘包
// LOG_D("data is %s",isDataASCII(din, len)?"ASCII":"BIN");
//check
if (memcmp(din,asciiHead,sizeof(asciiHead)) == 0) {
// if (memcmp(din,asciiHead,sizeof(asciiHead)) == 0) {
if (isDataASCII(din, len)) {
//ascii
trDataTolog(din, len, 0);
LOG_I("type = ASCII");
trDataTolog(din, len, 0);
uint8_t index[10];
uint8_t tmpHead[]={0x35, 0x41, 0x41, 0x35};
size_t n=isInByte(din, len, tmpHead, sizeof(tmpHead), index);
// LOG_D("n=%d",n);
if (!n) {
LOG_W("无匹配数据");
LOG_W("RS232中无匹配帧");
return;
}
for (size_t i = 0; i < n; i++)
@ -977,14 +1026,15 @@ void parseRS232(uint8_t *din, size_t len)
else//如果不是ASCII则统一按HEX计// if (memcmp(din,hexHead,sizeof(hexHead)) == 0)
{
//hex
LOG_I("type = BIN");
char tmp[200]="";
trDataTolog(bytes2str(din, len, 16, " ", tmp), strlen(tmp), 0);
LOG_I("type = HEX");
uint8_t index[10];
size_t n=isInByte(din, len, hexHead, sizeof(hexHead), index);
if (!n) {
LOG_W("无匹配数据");
LOG_W("RS232中无匹配帧");
return;
}
for (size_t i = 0; i < n; i++)

View File

@ -79,7 +79,7 @@ enum
#define ADDR_TT 0x41
#define ADDR_3S 0x32
#define ROOT_PATH_LOG "/sd/log/"
#define ROOT_PATH_DATA "/sd/rxdata/"
//#define ROOT_PATH_LOG "/sd/log/"
//#define ROOT_PATH_DATA "/sd/rxdata/"
#endif /* APPLICATIONS_FUNC_FUNC_H_ */

View File

@ -165,7 +165,7 @@ char *getTimestmp(char *str)
str[0]='\0';
strcat(str,"20");
bytes2str(t, len, 10, "-", str+2);
str[10]='_';
str[10]=' ';
len = strlen(str);
str[len]='\0';
return str;
@ -382,16 +382,88 @@ size_t isInByte(uint8_t *din, size_t len, uint8_t *s, size_t slen, uint8_t *dout
}
static char cfname[60]="";
/**
*
* @param fin
* @return
*/
char *getNewCacheFileName(char *fin)
{
char f[60]="";
char rootDir[22] = ROOT_PATH_DATA;
mkdir(rootDir, 0);
strcat(rootDir, "2023_07_19/");
//更新时戳
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));
strcpy(cfname,f);
strcpy(fin,f);
return fin;
}
/**
*
* @param fin
* @return
*/
char *getLstCacheFileName(char *fin)
{
if (strcmp(cfname,"") == 0) {
getNewCacheFileName(fin);
}
strcpy(fin,cfname);
return fin;
}
void d_gcf()
{
char f[60];
LOG_D("--%s",getLstCacheFileName(f));
LOG_D("--%s",getNewCacheFileName(f));
}
MSH_CMD_EXPORT(d_gcf,cache file);
/**
* ASCII还是还是BIN
* @param din
* @param len
* @return 1-ASCII,0-BIN
*/
int isDataASCII(uint8_t *din, size_t len)
{
int rst=0;
for (size_t i = 0; i < len; i++)
{
if (!isprint(din[i]))
{
rst = 0;
break;
}
if (i == len-1) {
rst = 1;
}
}
return rst;
}

View File

@ -155,7 +155,7 @@ static int uart_dma_sample(int argc, char *argv[])
#endif
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024*5, 25+1, 10);
rt_thread_t thread = rt_thread_create("RS232", serial_thread_entry, RT_NULL, 1024*5, 25+1, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{

View File

@ -37,11 +37,13 @@ typedef struct
int minActiveTime;
int maxActiveTime;
int timeout;
int maxSizePerFile;
} SYS_CFG;
//struct rt_event sw_check;//软件条件
#define ROOT_PATH_LOG "/sd/log/"
#define ROOT_PATH_DATA "/sd/rxdata/"

View File

@ -68,7 +68,7 @@
/* #define HAL_I2S_MODULE_ENABLED */
#define HAL_IWDG_MODULE_ENABLED
/* #define HAL_LTDC_MODULE_ENABLED */
/* #define HAL_RNG_MODULE_ENABLED */
#define HAL_RNG_MODULE_ENABLED
#define HAL_RTC_MODULE_ENABLED
/* #define HAL_SAI_MODULE_ENABLED */
#define HAL_SD_MODULE_ENABLED

View File

@ -305,7 +305,7 @@
#define SYSWATCH_EXCEPT_CONFIRM_TMO 15
#define SYSWATCH_EXCEPT_RESUME_DLY 15
#define SYSWATCH_THREAD_PRIO 0
#define SYSWATCH_THREAD_STK_SIZE 1024
#define SYSWATCH_THREAD_STK_SIZE 1024*2
#define SYSWATCH_THREAD_NAME "syswatch"
#define SYSWATCH_WDT_NAME "wdt"
#define SYSWATCH_WDT_TIMEOUT 5