目录
模块引用
编程语言介绍
代码实现
Makefile
TestNetwork.h
TestNetworkC.h
TestNetworkAppC.nc
TestNetworkC.nc
正文
模块引用
本实验的组件关系图如下图所示:
从收到的数据包中我们观察到数据包的时间戳、节点编号、序列号、父节点、父节点更改次数、ETX以及收包率PRR。
收包率(Packet Reception Ratio,PRR)被视为一种直观且准确的链路质量估计量用来表征无线链路质量。收包率即A节点接收到B节点的数据包数量与B节点发送到A节点的数据包数量的比值,我们可以根据seqno来计算出收包率。
编程语言介绍
本次实验所用的编程语言为nesC
nesC (network embedded systems C,读作"NES-see"),是一种基于组件的事件驱动编程语言,用于为 TinyOS 平台构建应用程序。. nesC是对 C语言 的扩展,它基于体现TinyOS的结构化概念和执行模型而设计。. TinyOS是为 传感器网络 节点 而设计的一个 事件驱动 的操作系统,传感器网络节点拥有非常有限的资源(举例来说,8KB的程序 储存器 ,512B的随机存取储存器)。TinyOS用nesC重新编写。
nesC语言特定:组件化 + 基于事件驱动 = 能很好地支持并发
nesC语言都是由组件(component)构成的,由双向性质的接口(interface)连接(wiring)而成
nesC定义了并发模型,该模型是基于任务 (task) 和硬件事件句柄 (hadware event handler),并且在编译期间有数据竞争的检测
代码实现
我们直接进入正题。看代码来理解CTP协议的具体实现
Makefile
Makefile文件,就是定义代码的书写规则
"\" : 代表换行符
#顶层组件名称为TestNetworkAppC COMPONENT=TestNetworkAppC #CTP协议代码库的支持 CFLAGS += -I$(TOSDIR)/lib/net \ -I$(TOSDIR)/lib/net/drip \ -I$(TOSDIR)/lib/net/4bitle \ -I$(TOSDIR)/lib/net/ctp -DNO_DEBUG include $(MAKERULES)
TestNetwork.h
这个.h文件就是定义了一个数据包的结构体而已。
source | 包的源地址。转发的节点不可修改这个字段。 |
seqno | 源顺序号。源节点设置了这个字段,转发节点不可修改它。就是用来计算收包率的 |
parent: | 节点的当前父节点 |
metric(ETX) | 点的当前ETX值 |
data | 要发送的目标数据 |
hopcount | 跳跃总数,就是改节点到根节点一共需要几跳 |
sendCount | 发包总数 |
sendSuccessCount | 成功发包数 |
//TestNetwork.h中定义了数据包的数据结构。 #ifndef TEST_NETWORK_H #define TEST_NETWORK_H #include <AM.h> #include "TestNetworkC.h" typedef nx_struct TestNetworkMsg { nx_am_addr_t source; nx_uint16_t seqno; nx_am_addr_t parent; nx_uint16_t metric; nx_uint16_t data; nx_uint8_t hopcount; nx_uint16_t sendCount; nx_uint16_t sendSuccessCount; } TestNetworkMsg; #endif
TestNetworkC.h
定义一共枚举体,然后给数据赋值
#ifndef TEST_NETWORK_C_H #define TEST_NETWORK_C_H enum { AM_TESTNETWORKMSG = 0x05, SAMPLE_RATE_KEY = 0x1, CL_TEST = 0xee, TEST_NETWORK_QUEUE_SIZE = 8, }; #endif
TestNetworkAppC.nc
头文件之类的,就不过多赘述了
#include "TestNetwork.h" #include "Ctp.h" configuration TestNetworkAppC {}
implementation函数里,就是 .C 文件里要用到的一直组件的声明和连接。每个模块我都分开放置并添加了注释。
implementation { //main,leds components TestNetworkC, MainC, LedsC; TestNetworkC.Boot -> MainC; TestNetworkC.Leds -> LedsC; //timer components new TimerMilliC(); TestNetworkC.Timer -> TimerMilliC; //radio(无线通信) components ActiveMessageC; TestNetworkC.RadioControl -> ActiveMessageC; TestNetworkC.RadioPacket -> ActiveMessageC; TestNetworkC.AMPacket -> ActiveMessageC; //drip components DisseminationC; components new DisseminatorC(uint32_t, SAMPLE_RATE_KEY) as Object32C; TestNetworkC.DisseminationControl -> DisseminationC; TestNetworkC.DisseminationPeriod -> Object32C; //ctp components CollectionC as Collector; components new CollectionSenderC(CL_TEST); TestNetworkC.RoutingControl -> Collector; TestNetworkC.Send -> CollectionSenderC; TestNetworkC.RootControl -> Collector; TestNetworkC.Receive -> Collector.Receive[CL_TEST]; TestNetworkC.CollectionPacket -> Collector; TestNetworkC.CtpInfo -> Collector; TestNetworkC.CtpCongestion -> Collector; //sensor(传感器) components new DemoSensorC(); TestNetworkC.ReadSensor -> DemoSensorC; //serial(串口通信) components new SerialAMSenderC(CL_TEST); components SerialActiveMessageC; TestNetworkC.SerialControl -> SerialActiveMessageC; TestNetworkC.UARTSend -> SerialAMSenderC.AMSend; //tools components RandomC; //随机数 components new QueueC(message_t*, 12); //队列 components new PoolC(message_t, 12); //缓存值 TestNetworkC.Random -> RandomC; TestNetworkC.Pool -> PoolC; TestNetworkC.Queue -> QueueC; #ifndef NO_DEBUG components new SerialAMSenderC(AM_COLLECTION_DEBUG) as UARTSender; components UARTDebugSenderP as DebugSender; components new PoolC(message_t, 10) as DebugMessagePool; components new QueueC(message_t*, 10) as DebugSendQueue; DebugSender.Boot -> MainC; DebugSender.UARTSend -> UARTSender; DebugSender.MessagePool -> DebugMessagePool; DebugSender.SendQueue -> DebugSendQueue; Collector.CollectionDebug -> DebugSender; TestNetworkC.CollectionDebug -> DebugSender; #endif }
TestNetworkC.nc
声明引用的头文件
1. #include <Timer.h> 2. #include "TestNetwork.h" 3. #include "CtpDebugMsg.h"
模块的声明,这里对应的是TestNetworkAppC.nc里的implementation这个函数
module TestNetworkC { uses interface Boot; uses interface SplitControl as RadioControl; uses interface SplitControl as SerialControl; uses interface StdControl as RoutingControl; uses interface StdControl as DisseminationControl; uses interface DisseminationValue<uint32_t> as DisseminationPeriod; uses interface Send; uses interface Leds; uses interface Read<uint16_t> as ReadSensor; uses interface Timer<TMilli>; uses interface RootControl; uses interface Receive; uses interface AMSend as UARTSend; uses interface CollectionPacket; uses interface CtpInfo; uses interface CtpCongestion; uses interface Random; uses interface Queue<message_t*>; uses interface Pool<message_t>; uses interface CollectionDebug; uses interface AMPacket; uses interface Packet as RadioPacket; }
接下来是这个文件里的主要函数了implementation函数,是主要的执行函数
总的逻辑为:
先所有节点打开无线通信和串口通信;
然后所有节点再打开数据分发与ctp路由;
然后每个节点对包的负载赋值;
然后无线发包,在这个发包过程中,ctp路由会自动计算路由,再按这个路由进行无线发包;
根节点触发收包函数,将所有数据包收集;
收包的时候使用一个队列,可以利用队列先进先出的特性;
最后由根节点通过串口发包。
接下来的代码解释,笔者都写在代码注释里啦,代码虽然有点长,但是还是很好理解的啦,大家耐心看吧~😁😁
implementation { task void uartEchoTask(); message_t packet; //无线包(eadio) message_t uartpacket; //串口包(senial) message_t* recvPtr = &uartpacket; //定义一个指针指向串口包 uint8_t msglen; //包的长度 bool sendBusy = FALSE; //无线发包锁机制标识符 bool uartbusy = FALSE; //串口锁包锁机制标识符 bool firstTimer = TRUE; //第一次发送的时间 uint16_t seqno; //序列号 enum { SEND_INTERVAL = 8192 //发送间隔 }; event void ReadSensor.readDone(error_t err, uint16_t val) { } //实现触发Boot事件 event void Boot.booted() { //打开串口通信模块(有打开就一定有关闭函数,在下面会讲到) call SerialControl.start(); } event void SerialControl.startDone(error_t err) { //err是否打开成功的参数 //打开无线通信模块(同理也有关闭函数) call RadioControl.start(); } event void RadioControl.startDone(error_t err) { if (err != SUCCESS) { //如果打开失败,就重复打开,一直到打开成功,进入else语句块 call RadioControl.start(); } else { //打开数据分发模块 call DisseminationControl.start(); //开启ctp路由 call RoutingControl.start(); if (TOS_NODE_ID % 500 == 0) { //开启CTP组网功能,并且将ID能被500整除的节点设置为汇聚节点 call RootControl.setRoot(); //作为根节点,搜集数据 } seqno = 0; //定时器,并且时间random一下,转到event void Timer.fired() call Timer.startOneShot(call Random.rand16() & 0x1ff); } } event void RadioControl.stopDone(error_t err) {} //无线通信关闭函数 event void SerialControl.stopDone(error_t err) {} //串口通信关闭函数 void failedSend() { //无线发送函数,第一个参数为数据包指针地址,第二个参数为数据包长度 dbg("App", "%s: Send failed.\n", __FUNCTION__); call CollectionDebug.logEvent(NET_C_DBG_1); } void sendMessage() { TestNetworkMsg* msg = (TestNetworkMsg*)call Send.getPayload(&packet, sizeof(TestNetworkMsg)); //这个send是CTP的send,定义msg指向packet的负载 uint16_t metric; am_addr_t parent = 0; call CtpInfo.getParent(&parent); //副节点的ID号 call CtpInfo.getEtx(&metric); //到副节点需要几跳 //对包的负载赋值 msg->source = TOS_NODE_ID; msg->seqno = seqno; msg->data = 0xCAFE; msg->parent = parent; msg->hopcount = 0; msg->metric = metric; if (call Send.send(&packet, sizeof(TestNetworkMsg)) != SUCCESS) { //发送包,这个send不带目标地址 failedSend(); //发送失败,打印信息//无线发送函数,第一个参数为数据包指针地址,第二个参数为数据包长度 call Leds.led0On(); //如果某个节点LED0亮了,就说明发包失败 dbg("TestNetworkC", "%s: Transmission failed.\n", __FUNCTION__); } else { sendBusy = TRUE; seqno++; dbg("TestNetworkC", "%s: Transmission succeeded.\n", __FUNCTION__); } } //利用random使各节点发包时间随机,这样发包不产生冲突 event void Timer.fired() { uint32_t nextInt; dbg("TestNetworkC", "TestNetworkC: Timer fired.\n"); nextInt = call Random.rand32() % SEND_INTERVAL; nextInt += SEND_INTERVAL >> 1; call Timer.startOneShot(nextInt); if (!sendBusy) sendMessage(); //发包 } //关闭发包 event void Send.sendDone(message_t* m, error_t err) { if (err != SUCCESS) { call Leds.led0On(); //发送失败,LED0亮 } sendBusy = FALSE; dbg("TestNetworkC", "Send completed.\n"); } //未用到uodata.changed,这个函数不会被用到 event void DisseminationPeriod.changed() { const uint32_t* newVal = call DisseminationPeriod.get(); call Timer.stop(); call Timer.startPeriodic(*newVal); //更新/刷新定时器 } uint8_t prevSeq = 0; uint8_t firstMsg = 0; //这个是接收数据的函数,只用根节点会触发,而根节点是在函数里自己设置的 event message_t* Receive.receive(message_t* msg, void* payload, uint8_t len) { dbg("TestNetworkC", "Received packet at %s from node %hhu.\n", sim_time_string(), call CollectionPacket.getOrigin(msg)); call Leds.led1Toggle(); if (call CollectionPacket.getOrigin(msg) == 1) { //判断是否为1号节点 if (firstMsg == 1) { if (call CollectionPacket.getSequenceNumber(msg) - prevSeq > 1) { //一次收包是2号节点,这次是4号节点,那么就是丢包了 call Leds.led2On(); //丢包。LED2亮 } } else { firstMsg = 1; } prevSeq = call CollectionPacket.getSequenceNumber(msg); } if (!call Pool.empty() && call Queue.size() < call Queue.maxSize()) { //如果缓存值不为空,且队列值小于队列最大值 message_t* tmp = call Pool.get(); //将缓存值拿出一个来,放入tmp call Queue.enqueue(msg); //将包msg入队(先进先出) if (!uartbusy) { post uartEchoTask(); //发包 } return tmp; } return msg; } task void uartEchoTask() { dbg("Traffic", "Sending packet to UART.\n"); if (call Queue.empty()) { //队列不为空 return; } else if (!uartbusy) { //队列不忙 message_t* msg = call Queue.dequeue(); //msg指向出队的一个值 dbg("Traffic", "Sending packet to UART.\n"); if (call UARTSend.send(0xffff, msg, call RadioPacket.payloadLength(msg)) == SUCCESS) { //用串口把刚刚出队的值发送出去 uartbusy = TRUE; } else { call CollectionDebug.logEventMsg(NET_C_DBG_2, call CollectionPacket.getSequenceNumber(msg), call CollectionPacket.getOrigin(msg), call AMPacket.destination(msg)); } } } event void UARTSend.sendDone(message_t *msg, error_t error) { dbg("Traffic", "UART send done.\n"); uartbusy = FALSE; call Pool.put(msg); // msg放入缓存 if (!call Queue.empty()) { //如果队列里还有包 post uartEchoTask(); //继续发送 } else { // call CtpCongestion.setClientCongested(FALSE); } } /* Default implementations for CollectionDebug calls. * These allow CollectionDebug not to be wired to anything if debugging * is not desired. */ default command error_t CollectionDebug.logEvent(uint8_t type) { return SUCCESS; } default command error_t CollectionDebug.logEventSimple(uint8_t type, uint16_t arg) { return SUCCESS; } default command error_t CollectionDebug.logEventDbg(uint8_t type, uint16_t arg1, uint16_t arg2, uint16_t arg3) { return SUCCESS; } default command error_t CollectionDebug.logEventMsg(uint8_t type, uint16_t msg, am_addr_t origin, am_addr_t node) { return SUCCESS; } default command error_t CollectionDebug.logEventRoute(uint8_t type, am_addr_t parent, uint8_t hopcount, uint16_t metric) { return SUCCESS; } }