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

目录

模块引用

编程语言介绍

代码实现

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语言特定:组件化 + 基于事件驱动 = 能很好地支持并发

  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 <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

声明引用的头文件

#include <Timer.h>
#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<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;
    }
}

如果有帮助的话,欢迎点赞收藏哦~🤩,有不同见解或更好的观点也可以在评论区留言,也可以笔者点点关注,互通有无,互相进步。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 上期ctp_api_c源代码.rar是一个包CTP (中国金融期货交易系统) API的源代码压缩文件。CTP API是为了与中国金融期货交易系统进行交互而开发的一套接口。它允许开发人员通过编程语言实现与交易系统的通信和交易操作。 该源代码了使用C语言编写的CTP API的实现代码。C语言是一种广泛使用的编程语言,非常适合开发高性能的软件。通过研究和理解这份源代码开发人员可以了解CTP API的具体实现细节,并在此基础上进行二次开发。 该源代码压缩文件通常包以下内容: 1. CTP API的源代码文件:这些文件包CTP API的各个功能模块的具体实现代码,如行情查询、委托下单、成交查询等。开发人员可以通过阅读这些代码来了解CTP API的调用方式和相关参数。 2. 相关的头文件和函数库:这些文件包CTP API的函数声明和必要的宏定义等信息。开发人员可以在自己的项目中包这些头文件并链接相关的函数库来使用CTP API的功能。 3. 示例代码:源代码压缩文件中可能还包了一些示例代码,用于演示如何使用CTP API进行实际的交易操作。开发人员可以参考这些示例代码来快速上手并进行二次开发。 总而言之,上期ctp_api_c源代码.rar提供了一个基于C语言CTP API实现的源代码,通过研究和使用该代码开发人员可以在自己的软件项目中实现与中国金融期货交易系统的交互和交易操作。 ### 回答2: 上期ctp_api_c 源代码.rar 是一个压缩文件,里面包ctp_api_c的源代码ctp_api_c是一个针对中国金融期货交易的API接口库,主要用于开发相关的交易软件。 在这个压缩文件中,我们可以找到所有关于ctp_api_c的源代码文件。这些源代码文件是以.c和.h为后缀的,分别表示源代码和头文件。 在解压缩后的文件夹中,我们可以看到一些主要的文件,如ctp_md.c,ctp_td.c等。这些文件对应着行情数据接口和交易接口的实现。除此之外,还有一些辅助文件,如ctp_common.c,ctp_utils.c等,用于提供一些共用的函数和工具。 通过阅读这些源代码文件,可以了解ctp_api_c的整体架构和各个功能模块的实现细节。其中,会包一些关于行情订阅、交易下单、账户查询等相关的代码。 值得一提的是,ctp_api_c是基于C语言编写的,因此在阅读这些源代码时,需要对C语言有一定的了解。通过分析和理解这些代码,我们可以更好地理解ctp_api_c的使用方法和原理,并在此基础上进行二次开发,以满足特定的交易需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勾栏听曲_0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值