上一篇说明了TestLinkLocal例程的功能,本篇将对TestLinkLocal例程的代码进行分析。
1 配置文件TestLinkLocalAppC.nc
/** Test the link-local communication in the blip stack
*/
configuration TestLinkLocalAppC {
} implementation {
components MainC, LedsC;
components TestLinkLocalC;
components IPStackC;
components new TimerMilliC();
components new UdpSocketC();
TestLinkLocalC.Boot -> MainC;
TestLinkLocalC.SplitControl -> IPStackC;
TestLinkLocalC.Sock -> UdpSocketC;
TestLinkLocalC.Timer -> TimerMilliC;
TestLinkLocalC.Leds -> LedsC;
components StaticIPAddressTosIdC; // Use TOS_NODE_ID in address
//components StaticIPAddressC; // Use LocalIeee154 in address
}
MainC、LedsC、TimerMilliC、都是前面见过的组件,IPStackC和UdpSocketC是本例程比较重要的组件。各组件间的接口关系如上所示。
2 模块组件TestLinkLocalC.nc
<pre name="code" class="html">#include "blip_printf.h"
#include <lib6lowpan/ip.h>
module TestLinkLocalC {
uses {
interface Boot;
interface SplitControl;
interface UDP as Sock;
interface Timer<TMilli>;
interface Leds;
}
}
首先交代了本模块中使用的接口。
implementation {
nx_struct echo_state {
nx_int8_t cmd;
nx_uint32_t seqno;
} m_data;
enum {
SVC_PORT = 10210,
CMD_ECHO = 1,
CMD_REPLY = 2,
};
定义了一个结构体变量m_data,包含cmd命令及seqno数据包序列号两个变量。这个对一些常量采用enum的方法而不是为define。
event void Boot.booted() {
//printfUART_init();
call SplitControl.start();
m_data.seqno = 0;
}
event void SplitControl.startDone(error_t e) {
call Timer.startPeriodic(2048);
call Sock.bind(SVC_PORT);
}
event void SplitControl.stopDone(error_t e) {}
在Boot.booted事件中干了两件事:第一,初始化数据包序列号seqno。第二:SplitControl.start()。而在SplitControl.startDone()中启动了一个两秒的定时器和绑定了SVC_PORT端口。
为何不在Boot.booted事件中直接启动定时器和绑定端口??
event void Timer.fired() {
struct sockaddr_in6 dest;
inet_pton6("ff02::1", &dest.sin6_addr);
dest.sin6_port = htons(SVC_PORT);
m_data.cmd = CMD_ECHO;
m_data.seqno ++;
call Sock.sendto(&dest, &m_data, sizeof(m_data));
call Leds.led0Toggle();
}
定时时间到后,进行命令数据的发送。在这段代码中先使用inet_pton6()函数、htons()函数对地址和端口进行转换。这两个函数在ip.h中进行了定义。
#ifndef NO_LIB6LOWPAN_ASCII
/*
* parse a string representation of an IPv6 address
*/
void inet_pton6(char *addr, struct in6_addr *dest);
int inet_ntop6(struct in6_addr *addr, char *buf, int cnt);
#endif
接下来为m_data结构体填充数据。然后调用Sock.sendto()函数发送数据,其三个参数分别为目的地址、带发送数据的缓存及数据长度。最后调用函数实现Led0的反转。
event void Sock.recvfrom(struct sockaddr_in6 *src, void *payload,
uint16_t len, struct ip6_metadata *meta) {
nx_struct echo_state *cmd = payload;
printf("TestLinkLocalC: recv from: ");
printf_in6addr(&src->sin6_addr);
printf("\n");
if (cmd->cmd == CMD_ECHO) {
cmd->cmd = CMD_REPLY;
call Sock.sendto(src, payload, len);
call Leds.led1Toggle();
} else {
printf("TestLinkLocalC: reply seqno: %li\n", cmd->seqno);
call Leds.led2Toggle();
}
}
}
以上这段代码的作用为Sock收到数据后的事件处理函数。当Sock端口收到数据后,由Sock.recvfrom()事件处理函数来接收、处理数据包。该数据包的有效载荷为playload即为命令,故而赋值给了cmd。
printf("TestLinkLocalC: recv from: ");
printf_in6addr(&src->sin6_addr);
printf("\n");
这三行代码对数据包的源地址进行打印输出。接下来的if...else...对收到的命令进行判断,如果收到的命令是CMD_ECHO则回复消息给发送方并且反转Led1。如果收到的数据是一个回复消息,则打印
printf("TestLinkLocalC: reply seqno: %li\n", cmd->seqno);
并反转Led2。
By:霜月孤鸟
2015.12.28