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

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

目录


模块引用

编程语言介绍

代码实现

Makefile

TestNetwork.h

TestNetworkC.h

TestNetworkAppC.nc

TestNetworkC.nc


正文


模块引用


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

000000000.png从收到的数据包中我们观察到数据包的时间戳、节点编号、序列号、父节点、父节点更改次数、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;
    }
}


相关实践学习
钉钉群中如何接收IoT温控器数据告警通知
本实验主要介绍如何将温控器设备以MQTT协议接入IoT物联网平台,通过云产品流转到函数计算FC,调用钉钉群机器人API,实时推送温湿度消息到钉钉群。
阿里云AIoT物联网开发实战
本课程将由物联网专家带你熟悉阿里云AIoT物联网领域全套云产品,7天轻松搭建基于Arduino的端到端物联网场景应用。 开始学习前,请先开通下方两个云产品,让学习更流畅: IoT物联网平台:https://iot.console.aliyun.com/ LinkWAN物联网络管理平台:https://linkwan.console.aliyun.com/service-open
相关文章
|
2月前
|
编解码 移动开发 流计算
【开源视频联动物联网平台】流媒体传输协议HLS,FLV的功能和特点
【开源视频联动物联网平台】流媒体传输协议HLS,FLV的功能和特点
65 2
|
2月前
|
XML 编解码 JSON
【开源视频联动物联网平台】协议包管理
【开源视频联动物联网平台】协议包管理
62 1
|
2月前
|
负载均衡 网络协议 安全
【开源视频联动物联网平台】SIP协议的特点
【开源视频联动物联网平台】SIP协议的特点
53 1
|
2月前
|
消息中间件 边缘计算 物联网
【开源视频联动物联网平台】如何解决物联网协议多样性问题
【开源视频联动物联网平台】如何解决物联网协议多样性问题
79 0
|
2月前
|
传感器 存储 安全
【物联网】Arduino 实验合集
【物联网】Arduino 实验合集
52 2
|
27天前
|
传感器 物联网
物联网协议概述:MQTT、CoAP 和 HTTP
【6月更文挑战第3天】探索物联网的三大协议——MQTT、CoAP 和 HTTP。MQTT 是高效的消息传递使者,适用于大规模、不稳定网络环境;CoAP 小巧灵活,适合资源有限的设备;HTTP 则是熟悉的网络通信老将。根据不同场景选择合适的协议,让物联网设备有效交流。示例代码展示它们的使用方式。
45 0
|
2月前
|
传感器 存储 监控
编写Arduino代码:构建物联网设备,实现上网行为管理软件对网络的实时监控
使用Arduino和ESP8266/ESP32等Wi-Fi模块,结合传感器监控网络活动,本文展示了如何编写代码实现实时监控并自动将数据提交至网站。示例代码展示如何连接Wi-Fi并检测网络状态,当连接成功时,通过HTTP POST请求将“Network activity detected.”发送到服务器。通过调整POST请求的目标URL和数据,可将监控数据上传至所需网站进行处理和存储。
113 0
|
10天前
|
供应链 监控 物联网
未来技术的潮流:区块链、物联网与虚拟现实的融合与创新
【6月更文挑战第20天】在技术不断进步的时代,新兴技术如区块链、物联网(IoT)和虚拟现实(VR)正在逐渐改变我们的生活和工作方式。本文将深入探讨这些技术的发展趋势和应用场景,以及它们如何相互融合,创造出新的商业模式和用户体验。我们将看到,随着这些技术的成熟和应用,未来的世界将变得更加智能、互联和沉浸。
|
3天前
|
传感器 安全 物联网
物联网(IoT)设备的硬件选型与集成技术博文
【6月更文挑战第28天】物联网设备硬件选型与集成聚焦关键要素:功能匹配、性能稳定性、兼容扩展及成本效益。嵌入式系统、通信协议、数据处理和安全性技术确保集成效果,支撑高效、智能的IoT系统,驱动家居、城市与工业自动化变革。
|
3天前
|
供应链 物联网 区块链
新技术趋势与应用:探讨新兴技术如区块链、物联网、虚拟现实等的发展趋势和应用场景
在科技日新月异的今天,新兴技术的发展趋势和应用成为我们关注的焦点。本文将深入探讨区块链技术、物联网以及虚拟现实等新兴技术的发展趋势和应用场景。我们将从数据导向的角度出发,引用权威的研究和统计数据,科学严谨地分析这些技术的发展现状和未来前景。同时,我们也将逻辑严密地探讨这些技术在不同领域的实际应用,以期为读者提供一个全面而深入的理解。

相关产品

  • 物联网平台