目录
模块引用
编程语言介绍
代码实现
Makefile
TestNetwork.h
TestNetworkC.h
TestNetworkAppC.nc
TestNetworkC.nc
如果大家队CTP协议的底层逻辑与原理有需求的话,可以看看笔者的这篇文章,相信能然让你更好的理解CTP协议的实现(因为合在一起太长了,所以分成两篇,也更有目的性一些😁😁):CTP协议的组成原理与具体实现(原理版)_物联网竞赛(挑战赛)_勾栏听曲_0的博客-CSDN博客
模块引用
本实验的组件关系图如下图所示:
从收到的数据包中我们观察到数据包的时间戳、节点编号、序列号、父节点、父节点更改次数、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
#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
声明引用的头文件
#include
#include "TestNetwork.h"
#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 as DisseminationPeriod;
uses interface Send;
uses interface Leds;
uses interface Read as ReadSensor;
uses interface Timer;
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;
uses interface Pool;
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()
如果有帮助的话,欢迎点赞收藏哦~🤩,有不同见解或更好的观点也可以在评论区留言,也可以笔者点点关注,互通有无,互相进步。
本文章来源于互联网,如有侵权,请联系删除!原文地址:CTP协议的组成原理与具体实现(实验篇,含代码)_物联网竞赛挑战赛
相关推荐: 什么是物联网数据采集网关?物联网数据采集网关的特点
什么是物联网数据采集网关? 从一个网络向另一个网络发送信息,也必须经过一道“关口”,这道关口就是网关 网关是一种充当转换重任的计算机系统或设备。在使用不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。 相比于互联网时代,物联…