作者 | firr
本篇记录RVB2601使用NTP进行网络授时的开发过程,本人历时三次大时间段投入才搞定,也是SDK使用以来遇到的最大一坑。
1、SNTP行不通
阶段一,历时8小时(一个下午加晚上),使用SNTP组件,各种编译错误,稀里糊涂到直接放弃。最后也是联系平头哥技术支持,了解到SNTP没有适配。这段工作已经过了一段时间,当时也没心情做测评记录,大概问题是SNTP组件需要LWIP包的支持,而SDK中包含SAL组件已经适配了LWIP包,但是功能上,特别是应用层协议适配没有做完整。而单独加载LWIP包,又会出现大量重复定义的情况(与SAL组件中的冲突),因此除非下力气扩展SAL组件,自己添加SNTP支持,否则此路行不通了。
2、尝试NTP
阶段二,大概是两天后,活动微信群中已经有很多网友都反馈了SNTP的问题,有人提到可以使用NTP,但是NTP会遇到SDK中的Bug,就是drv_wifi_at_w800组件中的w800_connect_remote()函数的UDP连接Bug(NTP使用的UDP协议),关键点就是函数实现连接远端TCP或UDP服务器使用的是同一条语句,也就是相同的AT命令,但实际W800的TCP连接和UDP连接,虽然AT命令一样,但是默认参数项有差别。
?图6-1?UDP连接Bug点
图6-2?AT+CIPSTART命令格式要求
针对上述问题,解决方法有两个,一是修改“atparser_send()”语句,增加AT命令的本地端口参数——因为TCP也可以传递这个参数,并且改“tcp_client”为“udp_unicast”。二是增加“case?NET_TYPE_UDP_UNICAST:”分支的代码,额外提供一个UDP连接的函数调用。
图6-3?两种修改方法
很明显,方法一TCP和UDP使用同一传递的本地端口参数,这样就限制了板子同时开启TCP和UDP的能力,所以本人最终采用了方法二,那么索性将NET_TYPE_TCP_CLIENT分支也做了修改,只保留tcp_client模式的实现。
图6-4?项目最终修改方法
上述方法的确保证TCP和UDP测试的成功,但是NTP依然没有产生效果,于是进一步做代码追踪,最后自己给自己挖了一个坑。因为距离写下此篇也有一段时间了,所以具体过程记不清了,大概是在w800_devops.c中看到函数w800_wifi_module_conn_start()会调用w800_connect_remote(),猜想它就是上一层入口——这一猜想后面通过查看list文件“项目目录/Lst/项目名.asm”发现是错误的。
带着这个错误猜想,本人发现函数中会动态生成一个本地端口,但是没有传递给w800_connect_remote(),于是就以为UDP也要用这里生成的端口,于是做了下述的修改。
图6-5?并不成功的阶段二过程,事实是猜测有误!
因为有了阶段一的挫败,阶段二的UDP成功和自我猜测带来的谬误,又是历时大半天的研究,结果还是放弃,甚至决定不在项目中使用网络授时了。
3、NTP组件的问题
前两个阶段的尝试还包括对NTP组件版本的实验。
?图6-6?项目测试的NTP组件版本
??图6-7?NTP组件7.4.5版本的同步时间函数
??图6-8?NTP组件7.4.3版本的同步时间函数
两种版本的时间同步函数都会报错,其实它们都是又调用了_ntp_sync_time(),而这个函数有一系列错误返回判断,随便一个卡住就会不成功。
后来也是在微信群中看到一个群友@杨工,NTP测试成功,而且分享的修改截图与本人在阶段二的尝试一致,于是联想到自己的猜测错误,UDP本地端口就是要自己定义,那么只可能是NTP测试函数中有问题,于是追踪_ntp_sync_time(),将其中直接输出时间信息的语句去掉注释,瞬间成功了。
?图6-9?NTP同步时间输出
?图6-10?NTP同步时间测试函数的修改
写下此篇时,本人是利用工作空当完成的,而且在新建工程中逐步复盘前期各种操作并作记录,此时又发现一个bug,就是如果注册了接收回调,ntp就失败,而去掉回调则成功。应该说,在下对NTP或SNTP的使用,还是处于不求甚解阶段,只是直接加载封装好的API,这个bug出现的原因也只能猜想是:“NTP请求中,本地会与NTP?Server进行多次交互,而回调函数会影响这多次交互的过程?”——其实这个猜想自以为是很不靠谱的!!
??图6-11?NTP的新bug发现,与输入回调注册有冲突
随着代码编写,更有意思的地方出现了。之前虽然发现是_ntp_sync_time()中有bug,但是还不确定是哪里,NTP可以输出信息,但是最后还是输出一句“NTP?sync?error”蛮膈应人的。于是本人尝试逐句添加printf输出,判断出错位置,没想到的是这回居然返回了“NTP?sync?success”了。
??图6-12?NTP同步时间莫名成功
4、NTP现阶段测试代码
NTP组件最后选用v7.4.3与Demo的版本一致了,ntp.c中修改_ntp_sync_time()函数。
static?int?_ntp_sync_time(char?*server)
{
????char???????????????buf[BUFSIZE];
????size_t?????????????nbytes;
????int????????????????sockfd,?maxfd1;
????struct?sockaddr_in?servaddr?=?{0,};
????fd_set?????????????readfds;
????struct?timeval?????timeout,?recvtv,?tv,?rcvtimeout?=?{3,?0};
????double?????????????offset;
?
????servaddr.sin_family?=?AF_INET;
????servaddr.sin_port???=?htons(NTP_PORT);
?
????if?(server?==?NULL)?{
????????//1.cn.pool.ntp.org?is?more?reliable
????????servaddr.sin_addr.s_addr?=?inet_host("ntp1.aliyun.com");
????????LOGD(TAG,?"ntp1.aliyun.com");
????}?else?{
????????servaddr.sin_addr.s_addr?=?inet_host(server);
????????LOGD(TAG,?"%s",?server);
????}
????printf("--------------------->check?server?done!?_ntp_sync_time()\n");
?
????if?((sockfd?=?socket(AF_INET,?SOCK_DGRAM,?0))?<?0)?{
????????//LOGE(TAG,?"socket?error");
????????return?-1;
????}
?
????setsockopt(sockfd,?SOL_SOCKET,?SO_RCVTIMEO,?&rcvtimeout,?sizeof(struct?timeval));
?
????if?(connect(sockfd,?(struct?sockaddr?*)&servaddr,?sizeof(struct?sockaddr))?!=?0)?{
????????//LOGE(TAG,?"connect?error");
????????close(sockfd);
????????return?-errno;
????}
????printf("--------------------->connect?server?done!?_ntp_sync_time()\n");
?
????nbytes?=?BUFSIZE;
?
????if?(get_ntp_packet(buf,?&nbytes)?!=?0)?{
????????//LOGE(TAG,?"construct?ntp?request?errorr");
????????close(sockfd);
????????return?-1;
????}
?
????send(sockfd,?buf,?nbytes,?0);
????printf("--------------------->send?request?pack?done!?_ntp_sync_time()\n");
?
????FD_ZERO(&readfds);
????FD_SET(sockfd,?&readfds);
????maxfd1?=?sockfd?+?1;
?
????timeout.tv_sec??=?TIMEOUT;
????timeout.tv_usec?=?0;
?
????if?(select(maxfd1,?&readfds,?NULL,?NULL,?&timeout)?>?0)?{
????????if?(FD_ISSET(sockfd,?&readfds))?{
????????????if?((nbytes?=?recv(sockfd,?buf,?BUFSIZE,?0))?<?0)?{
????????????????//LOGE(TAG,?"recv?error");
????????????????close(sockfd);
????????????????return?-1;
????????????}
?
????????????//printf("nbytes?=?%d\n",?nbytes);
????????????print_ntp((struct?ntphdr?*)?buf);
????????????gettimeofday(&recvtv,?NULL);
????????????offset?=?get_offset((struct?ntphdr?*)buf,?&recvtv);
?
????????????gettimeofday(&tv,?NULL);
????????????//TODO:?ctime?has?some?problem
#if?0
????????????LOGD(TAG,?"system?time:\t%s",?ctime((time_t?*)?&tv.tv_sec));
#else
????????????//char?*tbuf?=?NULL;
????????????//char?tbuf[64];
????????????//strftime(tbuf,?sizeof(tbuf),?"%Y-%m-%d?%T",?localtime(&tv.tv_sec));
????????????//memset(&tv,?0,?sizeof(tv));
????????????//tbuf?=?ctime((time_t?*)&tv.tv_sec);
????????????//LOGD(TAG,?"system?time1:%s",?tbuf);
#endif
#if?1
?
????????????tv.tv_sec?+=?(int)offset;
????????????tv.tv_usec?+=?offset?-?(int)offset;
?
????????????if?(settimeofday(&tv,?NULL)?!=?0)?{
????????????????//LOGE(TAG,?"set?time");
????????????????close(sockfd);
????????????????return?-1;
????????????}
????printf("--------------------->settimeofday?done!?_ntp_sync_time()\n");
?
????????????//TODO:?ctime?has?some?problem
????????????//LOGD(TAG,?"ntp?time:\t%s",?ctime((time_t?*)?&tv.tv_sec));
#endif
????????}
????}?else?{
????????close(sockfd);
????printf("--------------------->select?body?error!?_ntp_sync_time()\n");
????????return?-1;
????}
?
????close(sockfd);
????printf("--------------------->whole?done!?_ntp_sync_time()\n");
????return?0;
}
w800_api.c中修改w800_connect_remote()函数,增加UDP分支。其中端口号是自己随意定义的,本人觉得还是单独定义端口号比较方便。?
int32_t?udp_local_port?=?1338;
int?w800_connect_remote(int?id,?net_conn_e?type,?char?*srvname,?uint16_t?port)
{
????int?ret?=?-1;
????int?ret_id;
?
????if?(g_net_status?<?NET_STATUS_GOTIP)?{
????????LOGE(TAG,?"net?status?error\n");
????????return?-1;
????}
?
????aos_mutex_lock(&g_cmd_mutex,?AOS_WAIT_FOREVER);
?
????atparser_clr_buf(g_atparser_uservice_t);
?
????switch?(type)?{
????????case?NET_TYPE_TCP_SERVER:
????????????/*?TCP?Server?can?NOT?ignore?lport?*/
????????????break;
?
????????case?NET_TYPE_UDP_UNICAST:
????printf("UDP_UNICAST?mode\n");
????ret?=?atparser_send(g_atparser_uservice_t,?"AT+CIPSTART=%d,%s,%s,%d,%d",?id,?"udp_unicast",?srvname,?port,?udp_local_port);
????break;
????
????????case?NET_TYPE_TCP_CLIENT:
????printf("TCP_CLIENT?mode\n");
????????????ret?=?atparser_send(g_atparser_uservice_t,?"AT+CIPSTART=%d,%s,%s,%d",?id,?"tcp_client",?srvname,?port);
????????????break;
?
????????default:
????????????LOGE(TAG,?"type=%d?err!",?type);
????????????return?-1;
?
????}
?
????if?(ret?==?0)?{
????????ret?=?-1;
?
????????if?((atparser_recv(g_atparser_uservice_t,?"OK\n")?==?0)?\
????????????&&?(atparser_recv(g_atparser_uservice_t,?"+EVENT=CONNECT,%d\n",?&ret_id)?==?0))?{
????????????if?(ret_id?==?id)?{
????????????????ret?=?0;
????????????}
????????}
????}
?
????atparser_cmd_exit(g_atparser_uservice_t);
?
????aos_mutex_unlock(&g_cmd_mutex);
?
????return?ret;
}
基于Hello?World?Demo扩展,init.c中添加一个ntp函数,这样在ntp同步后再进行传输层input回调函数的注册。其中Delay()函数是Demo?OLED驱动中定义,查询SysTick的逻辑,没有用aos_sleep(),是怕任务跑飞。另外,这里的延时也是为了和前面的网络初始化间隔开来,不加也可以。然后,就是网络事件回调中,在获得IP即连接AP后进行ntp测试。目前,写到这里时还没有研究添加时区矫正和RTC,后续可以在启动时做一次时间同步就好。
//ntp?test?func
void?aita_ntptest(void)?{
????Delay(1000);?//delay?1s?then?use?ntp
????ntp_sync_time("ntp1.aliyun.com");
????
????//register?input?event?callback?for?w800?module
????w800_packet_input_cb_register(&aita_w800in_cb);
}
?
//network?event?callback?function
static?void?network_event(uint32_t?event_id,?const?void?*param,?void?*context)?{
????switch(event_id)?{
????case?EVENT_NETMGR_GOT_IP:?{
????LOGD(TAG,?"EVENT_NETMGR_GOT_IP\n");
????printf("start?polling_timer.?periodic:?%d?min\n",?period);
????aita_StartTimer;
????aita_ntptest();
????break;
????}
????case?EVENT_NETMGR_NET_DISCON:?{
????LOGD(TAG,?"EVENT_NETMGR_NET_DISCON\n");
????printf("ready?to?restart?wifi?connection...\n");
????aita_InitNetwork();
//????netmgr_reset(app_netmgr_hdl,?30);
????break;
????}????
????}
}
本文源自:平头哥芯片开放社区
欢迎关注公众号:芯片开放社区(ID:OCC_THEAD),查看更多应用实战文章。
|