
Lesson11TOSSIM仿真PPT课件.ppt
49页Lesson11 TOSSIM 仿真•介绍TOSSIM仿真器熟悉编译TOSSIM的步骤,掌握常用的仿真手段,具体有以下几个方面:–编译TOSSIM–用Python 配置仿真器–检查变量–注入包•注意:本节内容针对TinyOS 2.0.1及以上版本的TOSSIM仿真TinyOS 2.0.0版本的TOSSIM仿真会有稍微的不同,两者的主要区别在于无线射频仿真中噪声的指定方法2021/3/91目 录•一、介绍 ---了解•二、编译TOSSIM ---了解•三、使用Python运行TOSSIM ---掌握•四、调试语句 ---掌握•五、配置网络 ---掌握•六、变量 ---掌握•七、注入数据包 ---掌握 •八、C++接口 ---了解2021/3/92一、介绍•TOSSIM 仿真整个TinyOS 应用程序,是通过替换组件中的实现部分(implementation)实现的•可以灵活的选择组件来替换,可以是通信组件,也可以是芯片组件•仿真组件的是实现部分是多平台通用的,但它缺少对真实芯片情况的捕捉•例如,有毫秒级的定时器HilTimerMilliC 组件有针对 atmega128 平台的实现,也有intelmote2平台的实现,还有TOSSIM的仿真实现。
如下图:2021/3/93一、介绍•TOSSIM 是离散事件仿真器•当它运行时,把事件队列(event queue)里的事件根据时间排序抽出来执行•仿真事件可以表现为硬件中断或者高级别的系统事件(如事信息包的接收) 由仿真的级别决定•另外,任务也是仿真事件2021/3/94一、介绍•TOSSIM 是一个库(其核心代码位于tos/lib/tossim):必须编写程序来配置仿真,然后运行它•TOSSIM 支持两种编程接口: Python 和 C++ •Python 允许与正在运行的仿真进行动态地交互,调试功能强大然而,python解释器是性能瓶颈•TOSSIM 也有C++ 接口通常,从一种代码转换到另一种代码是非常简单的•TOSSIM 目前不支持能耗测量2021/3/95二、编译TOSSIM•TOSSIM 是TinyOS 的一个库它的核心代码位于 tos/lib/tossim文件夹中 •每个TinyOS 源代码目录有一个可选的 sim 子目录,里面包含有那个包package 的仿真实现例如,tos/chips/atm128/timer/sim 里面就含有 Atmega 128 定时器抽象的TOSSIM 实现。
•编译TOSSIM, 只需在 make 时加上可选的 sim 如: make micaz sim•目前, micaz 是唯一被TOSSIM 支持的平台 2021/3/96$ cd blink$ make micaz sim二、编译TOSSIM2021/3/97二、编译TOSSIM•仔细观察上图中的编译过程,我们可以得出编译TOSSIM的5个步骤如下:–1. Writing an XML Schema, 编写 XML计划;–2. Compiling the TinyOS Application, 编译 TinyOS 程序;–3. Compiling the Programming Interface, 编译编程接口;–4. Building the Shared Object, 构建共用对象;–5. Copying Python Support. 复制 Python 支持2021/3/982.1 编写XML计划 •“writing XML schema to app.xml”生成的xml文件描述了这个应用程序,以及其中每个变量的名字和类型2021/3/992.2 编译 TinyOS 应用-Ia -Ib -Ic-Ia/sim -Ib/sim -Ic/sim -I%T/lib/tossim -Ia -Ib -Icsim 选项会改变应用程序的include路径•这意味着 先使用特定系统的仿真实现,然后是通用的 TOSSIM 仿真实现,再接着是 标准的实现。
•这一步的产物是 目标文件 sim.o ,它位于平台的 build 目录下这个目标文件含有一组C函数,其配置了仿真,并控制执行 2021/3/9102.3 编译编程接口•这一步编译支持C++ 和 Python两个编程接口•Python 接口实际上是建立在C++接口之上的调用的 Python 对象会调用 C++ 对象,由其通过C 接口调用TOSSIM 2021/3/911•2.4 构建共享对象•2.5 复制Python 脚本2021/3/912三、 使用Python运行TOSSIM•以 RadioCountToLeds 应用程序为例,构建TOSSIM仿真•/opt/tinyos-2.x/apps/RadioCountToLeds$ cd RadioCountToLeds$ make micaz sim•下一步就是编写python脚本,然后用Python运行脚本也可以,交互地使用python2021/3/913•Python脚本的第一行通常是:#! /usr/local/bin/python#! /usr/bin/python#! /usr/bin/env python•这取决于python 二进制文件的位置。
•最后一条入口是最安全或最方便的,将会自动搜索你的特定环境(PATH)寻找Python的位置2021/3/914•第1件事是:导入 TOSSIM 并创建一个TOSSIM 对象•运行TOSSIM仿真的方法是用runNextEevent 函数例如: >>> from TOSSIM import *>>> t = Tossim([])>>> t.runNextEvent()0当告诉TOSSIM 运行 下一个 事件event,它返回 0 这意味着:接下去没有事件可以运行在这里,没有下一个事件是因为我们还没有让节点启动起来 2021/3/915•这段代码将会告诉节点32 在45654时间标记点启动(仿真时间标记 simulation tick) ,然后运行它的第1个事(booting)•为了不使用原始的仿真时间标记为单位,可以调用 ticksPerSecond() >>> m = t.getNode(32);>>> m.bootAtTime(45654);>>> t.runNextEvent()1>>> m = t.getNode(32);>>> m.bootAtTime(4 * t.ticksPerSecond() + 242119); >>> t.runNextEvent()1现在,runNextEvent 返回了 1 ,因为有一个事件要运行。
但程序员没有办法知道这个节点到底有没有启动接下来介绍两种办法来确认节点是否启动2021/3/916•第1种方法:直接用相关函数查询>>> m.isOn()1>>> m.turnOff()>>> m.isOn()02021/3/917一个TOSSIM 对象有几个有用的函数可以用dir 函数查看如下图:前后有两个下划线 “__”的通常是不能手动使用的内部函数例如,__init__ 在创建对象时在内部被自动调用的2021/3/918⑴ currentNode():返回当前节点的ID⑵ getNode(id):返回一个节点对象⑶ runNextEvent():运行一个仿真事件 ⑷ time():返回当前时间,用仿真时间标记表示,是一个大型整数 ⑸ timeStr():返回当前时间的字符串⑹ init():初始化TOSSIM⑺ mac():返回介质访问层的对象⑻ radio():返回无线电模型的对象⑼ addChannel(ch, output):增加output作为到ch通道的输出⑽ removeChannel(ch, output):移除output作为到ch通道的输出⑾ ticksPerSecond():返回1s仿真时间对应的仿真时间标记数。
Tossim 对象的几个常见的工具函数2021/3/919四、调试语句•得知节点是否打开的第2种方法是让它在启动后打印出信息告诉程序员•TOSSIM 有一个调试输出系统,称为 dbg 有4个 dbg 命令:–dbg:打印调试语句,以节点ID 开头–dbg_clear:打印调试语句,不以节点ID 开头–dbgerror: 打印错误信息,以节点ID 开头–dbgerror_clear:打印错误信息,不以节点ID 开头2021/3/920四、调试语句•修改 RadioCountToLedsC 里的boot.booted事件,打印出当它启动时的调试信息例如:event void Boot.booted() { call Leds.led0On(); dbg("Boot", "Application booted.\n"); call AMControl.start();}•dbg() 语句带有两个或更多的参数•第1个参数(上述例子中的”Boot” )定义了输出通道一个输出通道是一个string 字符串•紧接的参数是输出的消息内容和变量格式 2021/3/921event message_t* Receive.receive(message_t* bufPtr, void* payload, uint8_t len) { dbg("RadioCountToLedsC", "Received packet of length %hhu.\n", len); ...}打印出了收到数据包的长度,是一个8位的无符号值(%hhu)。
dbg("RadioCountToLedsC", "Time: %s\n", sim_time_string());打印出此处仿真的时间一旦在事件event 里增加这样的调试语句,需要重新编译make micaz sim dbg语句2021/3/922•TOSSIM 的调试输出可以对每个输出通道进行配置所以,可以把 “Boot”通道发送到文件和标准输出,而“RadioCountToLedsC” 发送到标准输出•默认情况下,通道是没有目的地的,给它的输出消息也会丢失的•为了发送到标准输出,需要导入sys:>>> import sys>>> f = open(“log.txt”, “w”) //打开或创建文件log.txt,可写>>> t.addChannel(“Boot”, f) //发送到文件>>> t.addChannel(“Boot”, sys.stdout) //发送到标准输出>>> t.addChannel("RadioCountToLedsC", sys.stdout)调试通道2021/3/923•如果多个通道共用1条信息的输出,那TOSSIM 只打印1次消息 event void Boot.booted() { call Leds.led0On(); dbg("Boot,RadioCountToLedsC", "Application booted.\n"); dbg("RadioCountToLedsC", "Application booted again.\n"); dbg("Boot", "Application booted a third time.\n"); call AMControl.start();}>>> import sys>>> t.addChannel("Boot", sys.stdout)>>> t.addChannel("RadioCountToLedsC", sys.stdout)>>>t.runNextEvent()DEBUG (32): Application booted.DEBUG (32): Application booted again.DEBUG (32): Application booted a third time.2021/3/924五、配置网络•当开启TOSSIM时,没有一个节点可以与另外的节点通信。
为了可以仿真网络行为,需要指定网络的拓扑结构 •默认的TOSSIM无线电模型是基于信号强度的可以向仿真器提供一组数据来描述传播强度、噪声底层值、接收的灵敏度•所有的这些是通过脚本接口来实现的,一些底层的原始函数如下:2021/3/925加入噪声•TOSSIM 仿真出了RF(无线电频率)噪声,干扰节点的接收,不管信号是来自其他的节点还是外部的信号源•使用了Closet Pattern Matching (CPM) 就近模式匹配算法CPM根据噪声踪迹noise trace作为输入,由此产生静态的模型 noise = open(“meyer-heavy.txt”, “r”) // 从tos/lib/tossim/noise 复制lines = noise.readlines()//该文件内一行一个噪声数据for line in lines: str = line.strip() if (str != ""): val = int(str) for i in range(0, 7): t.getNode(i).addNoiseTraceReading(val)for i in range(0, 7): t.getNode(i).createNoiseModel() //要用到这两个函数2021/3/926拓扑结构•无线电的联通数据可以存在一个文件里,可以轻松的在文件里创建拓扑结构,然后使用 Python 脚本 加载这个文件并把这些拓扑信息存到radio 对象里。
•这个文件里的每一行用3个数值指明每一个连接:源节点、目标节点和增益gain 如下:1 2 -54.0意味着: 节点1 发送,节点2以-54dBm接收创建如下的topo.txt :12 -54.021 -55.013 -60.031 -60.023 -64.033 2 -64.0f = open("topo.txt", "r")lines = f.readlines()for line in lines: s = line.split() if (len(s) > 0): print " ", s[0], " ", s[1], " ", s[2]; r.add(int(s[0]), int(s[1]), float(s[2]))2021/3/927Test.py#! /usr/bin/pythonfrom TOSSIM import *import syst = Tossim([])r = t.radio()f = open("topo.txt", "r")lines = f.readlines() //构建拓扑结构for line in lines: s = line.split() if (len(s) > 0): print " ", s[0], " ", s[1], " ", s[2]; r.add(int(s[0]), int(s[1]), float(s[2]))t.addChannel("RadioCountToLedsC", sys.stdout)t.addChannel("Boot", sys.stdout)//加入噪声noise = open("meyer-heavy.txt", "r")lines = noise.readlines()for line in lines: str = line.strip() if (str != ""): val = int(str) for i in range(1, 4): t.getNode(i).addNoiseTraceReading(val)for i in range(1, 4): print "Creating noise model for ",i; t.getNode(i).createNoiseModel()//启动节点t.getNode(1).bootAtTime(100001);t.getNode(2).bootAtTime(800008);t.getNode(3).bootAtTime(1800009);for i in range(0, 100): t.runNextEvent()编写脚本test.py 然后运行$ python test.py2021/3/9282021/3/929六、变量•TOSSIM 允许在运行的TinyOS程序中观察变量。
当前,仅可以观察基本类型变量不能查看结构体里的域(成员),但可以查看状态变量•此前make 系统编译产生了一个XML文件,里面包含了大量关于当前TinyOS程序的信息,也包括了其中每个组件变量和它的类型 •观察变量需要在TOSSIM里导入对python的支持包先设定python的目录,此步不可少:$ export PYTHONPATH=/opt/tinyos-2.x/support/sdk/python2021/3/930•NescApp 有两个可选参数•第1个是要加载的应用的名字,默认名字是Unknown App •第2个是要加载的XML文件名字,默认的是app.xml,这是make 系统自动生成文件的名字from tinyos.tossim.TossimApp import *n = NescApp()故等价于:from tinyos.tossim.TossimApp import *n = NescApp("Unknown App", "app.xml")接下来调用NescApp 对象的 variables 域里的variables() 函数获得一份变量列表vars = n.variables.variables()为了观察变量,在创建Tossim对象时,把这份列表传给 Tossim 对象。
t = Tossim(vars)2021/3/931•刚才说过,目前只支持简单类型的变量,不能访问结构体里的域(成员)但是,如果要读取RadioCountToLedsC 里的counter 值因为网络中的每个节点都有它自己的变量实例,可以从特定的节点里读取它m = t.getNode(0)v = m.getVariable("RadioCountToLedsC.counter")counter = v.getData()•变量的名字通常是 C.V的形式,其中C 代表的是组件的名字,而V是变量•如果是通用组件,那名字就是C.N.V,其中N是个整数,用来表示是哪个实例 变量的获取2021/3/932variables.py •编写一个脚本,启动5个节点的仿真,一直运行到节点0的counter值计数到10停下•由于使用了5个节点,需修改topo.txt ,修改如下 0 2 -66.0 2 0 -67.0 1 2 -54.0 2 1 -55.0 1 3 -60.0 3 1 -60.0 2 3 -64.0 3 2 -64.0这个例子脚本位于 \opt\tinyos-2.x\tos\lib\tossim\examples 目录下:2021/3/933from sys import *from random import *from TOSSIM import *from tinyos.tossim.TossimApp import *n = NescApp()t = Tossim(n.variables.variables())r = t.radio()f = open("topo.txt", "r")lines = f.readlines()for line in lines: s = line.split() if (len(s) > 0): if (s[0] == "gain"): r.add(int(s[1]), int(s[2]), float(s[3]))noise = open("meyer-heavy.txt", "r")lines = noise.readlines()for line in lines: str = line.strip() if (str != ""): val = int(str) for i in range(0, 4):4 t.getNode(i).addNoiseTraceReading(val)for i in range (0, 4): t.getNode(i).createNoiseModel() t.getNode(i).bootAtTime(i * 2351217 + 23542399)m = t.getNode(0)v = m.getVariable("RadioCountToLedsC.co unter")while (v.getData() < 10): t.runNextEvent()print "Counter variable at node 0 reached 10."Variables.py2021/3/934七、注入数据包•TOSSIM 允许动态的加入信息包packets 到 网络中。
•包可以预定在任何时候到达如果包是设定在过去到达,那它就立即到达 •新加入的包是围绕无线电堆栈 radio stack :因此当一个节点正在用无线电接收另一个节点的包时,它也能接收新加入的包•TinyOS 2.0 支持构建Python 包注入正如标准的Java 工具包,可以构建一个基于C结构的包类 packet class 这个包类允许你对包进行修改和访问如果一个应用有数据包格式,先为它生成一个包类,再创建一个包packet 对象,设定它的内部成员,使节点可以接收它们2021/3/935•RadioCountToLeds 应用的makefile文件就很好地说明了具体如何做•调用带python 参数的mig 工具来产生RadioCountMsg.py COMPONENT=RadioCountToLedsAppCBUILD_EXTRA_DEPS = RadioCountMsg.py RadioCountMsg.classRadioCountMsg.py: RadioCountToLeds.hmig python -target=$(PLATFORM) $(CFLAGS) -python-classname=RadioCountMsg RadioCountToLeds.h radio_count_msg -o $@RadioCountMsg.class: RadioCountMsg.javajavac RadioCountMsg.javaRadioCountMsg.java: RadioCountToLeds.hmig java -target=$(PLATFORM) $(CFLAGS) -java-classname=RadioCountMsg RadioCountToLeds.h radio_count_msg -o $@include $(MAKERULES)2021/3/936•RadioCountToMsg.py 定义了数据包的格式,但这个包包含在另一种格式的有效数据载荷区域里。
•如果一个节点是通过AM 发送 RadioCountMsg 消息,那 RadioCountMsg 结构应该包含在AM 的payload 有效载荷区域里,格式类似如下:AM Header RadioCountMsg AM Footer •如果是通过路由协议来发送的,那包packet 应该放在routing payload 路由信息的有效载荷里,此时格式类似如下:AM Header Routing Header RadioCountMsg AM Footer 2021/3/937•为了支持 RadioCountMsg ,需要导入它:from RadioCountMsg import *•例如,下面这段代码就创建了一个RadioCountMsg 对象,设定了counter值为7,创建了AM信息包,把RadioCountMsg 存在 AM信息包里,并配置AM信息包,使它能正确的发送(指配置目的地和类型)from RadioCountMsg import *msg = RadioCountMsg()msg.set_counter(7);pkt = t.newPacket();pkt.setData(msg.data)pkt.setType(msg.get_amType())pkt.setDestination(0)//变量pkt就是活跃消息active message//也就是RadioCountMsg 信息包的 所在的AM活跃消息2021/3/938•可以用deliver() 函数把 这个信息包发送到节点。
•deliver 函数带有两个参数:目标节点和发送时间:•这个命令传送了pkt 到节点0,在当前时间加3 ticks(例如 3ns)时发送 •注意,如果pkt的目标节点被设为1,那么这个TinyOS 应用就没法接收到消息,因为pkt被发送到了节点0所以两者必须一致•下面的脚本packets.py 启动了仿真,配置了基于topo.txt的拓扑,传递信息包到节点0位于/opt/tinyos-2.x/tos/lib/tossim /examples/文件夹中pkt.deliver(0, t.time() + 3)2021/3/939#! /usr/bin/pythonimport sysfrom TOSSIM import *from RadioCountMsg import *t = Tossim([])m = t.mac();r = t.radio();t.addChannel("RadioCountToLedsC", sys.stdout);t.addChannel("LedsC", sys.stdout);for i in range(0, 2): m = t.getNode(i); m.bootAtTime((31 + t.ticksPerSecond() / 10) * i + 1);f = open("topo.txt", "r")lines = f.readlines()for line in lines: s = line.split() if (len(s) > 0): if (s[0] == "gain"): r.add(int(s[1]), int(s[2]), float(s[3]))noise = open("meyer-heavy.txt", "r")lines = noise.readlines()for line in lines: st = line.strip() if (st != ""): val = int(st) for i in range(0, 4): t.getNode(i).addNoiseTraceReading(val)for i in range (0, 4): t.getNode(i).createNoiseModel()for i in range(0, 60): t.runNextEvent();packets.py2021/3/940接上一页:msg = RadioCountMsg()msg.set_counter(7);pkt = t.newPacket();pkt.setData(msg.data)pkt.setType(msg.get_amType())pkt.setDestination(0)print "Delivering " + str(msg) + " to 0 at " + str(t.time() + 3);pkt.deliver(0, t.time() + 3)for i in range(0, 20): t.runNextEvent();packets.py2021/3/941八、C++•Python 非常的有用,因为它简捷,容易编写,可以交互式地使用。
然而,对python的解释编译需要很高的代价:每个事件从python转变为C的资源代价是很高的 •利用C++接口的脚本进行TOSSIM仿真时,无法查看变量值 •为了使用C++接口注入消息包类,就必须为消息包建立C语言支持,并手动建立消息包当前,MIG工具还不支持自动地生成C/C++消息包结构 •C++ 接口与 Python 接口的比较范例请详见书中6.3.8节2021/3/942九、gdb调试•gdb是一个强大的命令行调试工具,一般来说主要调试C/C++程序当仿真驱动是C++代码时,可以使用gdb工具单步执行TinyOS代码,从而便于观察变量,设置断点以及其它常见的调试行为•TOSSIM的一个显著优点就是它可以运行在PC上,这样可以运用传统的调试工具来调试nesC程序2021/3/943九、gdb调试 -- 断点•gdb工具并不是为nesC设计的nesC的组件模型意味着单个命令可能有多个提供者所以,单个命令必须指定其所处的模块、配件或者接口,才能唯一地确定究竟是哪个命令•例如,为了在Leds接口led0On命令处设置断点,运行如下命令:(gdb) break *LedsP$Leds$led0On2021/3/944九、gdb调试 -- 变量•nesC编译器将nesC代码编译生成为C代码(即build/micaz/app.c文件)时,就将它们的名字用符号$改写了。
•通过查找app.c文件,可以发现,led0On命令的函数名被改写为:LedsP$Leds$led0On •变量也有类似的名称例如,在RadioCountToLeds实例中,为了观察节点6上RadioCountToLeds组件中的消息包变量packetgdb) print RadioCountToLedsC$packet[6] $2 = {header = {{data = ""}, {data = ""}, {data = ""}, {data = ""}, { data = ""}}, data = {{data = ""} }, footer = {{ data = ""}, {data = ""}}, metadata = {{data = ""}, {data = ""}, { data = ""}, {data = ""}, {data = ""}}}2021/3/945九、gdb调试 -- 观察点•除了断点,gdb工具还可以设置观察点gdb) watch CpmModelC$receiving[23]Hardware watchpoint 2: CpmModelC$receiving[23]•变量CpmModelC$receiving是包级别网络仿真中的内部变量,它可以跟踪无线电是否接收到消息包。
上述观察点的作用是,一旦23号节点开始接收消息包,gdb就会暂停仿真运行2021/3/946九、gdb调试 -- 通用组件•通用组件的调试需要注意:因为通用组件使用了代码重用复制的方法,其每个实例组件都有各自的函数和变量•nesC代码在编译时会给通用组件的每个实例化组件分配一个唯一的编号若只有一个实例组件,其名称编号为0•例如,在RadioCountToLeds应用中,在无线通信的AM堆栈和串口通信栈都有使用AMQueueImplP组件,可以这样调试:(gdb) break *AMQueueImplP$0$Send$sendBreakpoint 5 at 0x8051b29: file AMQueueImplP.nc, line 79.注:当同一通用组件有多个实例时,除了查看编译后的源代码(即app.c文件)或进行单步调试,没有其它的简便方法来获知每个实例组件的名称 2021/3/947zjutwsn@放映结束 感谢各位的批评指导! 谢谢 谢!谢!让我们共同进步2021/3/949。












