CTP协议的组成原理与具体实现(实验篇,含代码)_物联网竞赛挑战赛

目录

模块引用

编程语言介绍

代码实现

Makefile

TestNetwork.h

TestNetworkC.h

TestNetworkAppC.nc

TestNetworkC.nc


 如果大家队CTP协议的底层逻辑与原理有需求的话,可以看看笔者的这篇文章,相信能然让你更好的理解CTP协议的实现(因为合在一起太长了,所以分成两篇,也更有目的性一些😁😁):CTP协议的组成原理与具体实现(原理版)_物联网竞赛(挑战赛)_勾栏听曲_0的博客-CSDN博客 


模块引用

本实验的组件关系图如下图所示:

CTP协议的组成原理与具体实现(实验篇,含代码)_物联网竞赛挑战赛

从收到的数据包中我们观察到数据包的时间戳、节点编号、序列号、父节点、父节点更改次数、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语言特定:组件化 + 基于事件驱动 = 能很好地支持并发

  1. nesC语言都是由组件(component)构成的,由双向性质的接口(interface)连接(wiring)而成
  2. 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协议的组成原理与具体实现(实验篇,含代码)_物联网竞赛挑战赛

相关推荐: 什么是物联网数据采集网关?物联网数据采集网关的特点

什么是物联网数据采集网关? 从一个网络向另一个网络发送信息,也必须经过一道“关口”,这道关口就是网关 网关是一种充当转换重任的计算机系统或设备。在使用不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。 相比于互联网时代,物联…