最近很懒一直没有更新,都是在随便搞搞NesC,写点东西,多出时间来关注下日本地震。最近需要调试个程序,所以关注了一下TOSSIM。
TOSSIM是TinyOS自带的模拟器,但是说它是模拟器似乎又不是很准确,按照文档自己的说法,TOSSIM更像是一个library,就是所谓的类库。需要编译以后执行。按照我自己的理解,此前你将你的NesC程序编译,安装进传感器,你的程序执行时是和传感器上的实物硬件沟通,但是使用TOSSIM编译之后,TOSSIM会模仿系统各个组件的工作,从而你的程序实际上是在和TOSSIM的组件沟通。说白了就是,TOSSIM把你的NesC程序封装起来了。不知道里面的措辞是否准确,或者是理解上面是否还有些问题,但是我觉得差不多就是这个意思。
TOSSIM目前只支持虚拟micaz传感器,但是如果是用来比较两种不同的算法,或者是纯粹为了调试程序,适用平台不是特别重要。编译也非常简单,就是命令由此前的:
make micaz |
变成:
make micaz sim |
一般情况下是不会出现问题的,Ubuntu默认安装了Python。最常见的不过就是无法找到Python.h文件:
Python.h: No such file or directory |
或者是找不到tinyos.tossim.TossimApp包:
No module named tinyos.tossim.TossimApp |
两种情况在TOSSIM的文档当中都有介绍解决办法。前者是前往文件/opt/tinyos-2.x/support/make/sim.extra,在PFLAGS行下指定Python.h文件的路径,通常是在/usr/include/python2.x/文件夹内,我系统上面的是2.6。
1 | CFLAGS += -I /usr/include/python2 .6/ |
后者是需要在PYTHONPATH环境变量内添加TinyOS附带的Python API的路径,我将此行添加在/opt/tinyos-2.x/tinyos.sh文件内:
1 | export PYTHONPATH=$PYTHONPATH:$TOSROOT /support/sdk/python |
一般就没什么问题了。编译的过程当中可能会出现一打warning,不是什么大问题,感觉主要是python版本引起的,只要成功编译就没问题了。
*** Successfully built micaz TOSSIM library. |
我目前主要是用来调试程序,所以主要是一个需要输出调试信息,另一个就是需要访问变量。NesC带来的四条命令,分别是dbg,dbg_clear,dbgerror还有dbgerror_clear。dbg是输出一条带传感器节点ID的普通调试信息,dbgerror则是输出一条带ID的错误信息,两者唯一的区别就是输出信息时候的开头不一样。dbg输出信息开头为DEBUG,dbgerror输出时则显示ERROR。而带clear后缀的两条命令和前两者使用一样,唯一的区别就是不带ID。
dbg带两个参数,前者是信息输出的频道(不是真正意义上的频道,而是一个比喻),后者是信息本身,信息的书写和sprintf函数一样,例如:
1 | dbg ( "Boot" , "Mote booted" ); |
则输出Mote booted到Boot频道。不同的频道可以绑定到不同的输出口,可以直接显示在屏幕上,也可以将输出信息写入文件、以便事后查阅。
现在有一段NesC程序,描述的是Boot接口提供的booted事件:
1 2 3 4 | event void Boot.booted() { dbg ( "Boot" , "Application booted.\n" ); dbg ( "Radio" , "Application booted again.\n" ); } |
按照要求,传感器节点在启动以后,需要输出两条消息,Application booted输出到Boot 频道,Application booted again输出到Radio频道。
执行TOSSIM需要一个简单的Python脚本,可以直接保存进入一个py文件,由python直接一口气载入执行,也可以在python互动模式下,逐条输入指令运行。脚本的开头基本都是一样的:
1 2 3 | from TOSSIM import * from tinyos.tossim.TossimApp import * t = Tossim([]) |
因为我从没学过python,所以只能凭着经验去理解这些脚本指令。编译后会生成一个TOSSIM.py的文件,前两句应该是和Java一样载入文件内的类,第三句则是实例化一个Tossim类。方括号内是该类构建器的参数,这里默认不带任何参数。需要知道Tossim类型对象内的属性和方法,则可以通过dir命令:
1 2 3 4 5 6 7 8 | dir (t) [ '__class__' , '__del__' , '__delattr__' , '__dict__' , '__doc__' , '__format__' , '__getattr__' , '__getattribute__' , '__hash__' , '__init__' , '__module__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__swig_getmethods__' , '__swig_setmethods__' , '__weakref__' , 'addChannel' , 'currentNode' , 'getNode' , 'init' , 'mac' , 'newPacket' , 'radio' , 'randomSeed' , 'removeChannel' , 'runNextEvent' , 'setCurrentNode' , 'setTime' , 'this' , 'thisown' , 'ticksPerSecond' , 'time' , 'timeStr' ] |
带__前后缀的为对象属性,不带前后缀的则为Tossim类的方法。然后我们可以使用该对象的getNode方法创建一个节点。
1 | m = t.getNode( 1 ) |
这里我们创建一个ID为1的节点m。在NesC代码中,我们的两条信息,是分别输出到Boot和Radio两个不同的频道,但是目前这两个频道都尚未派上用场。我们希望Boot频道内的信息直接通过屏幕输出、显示出来,而Radio频道的信息则写入log.txt文件内。
1 2 3 4 | f = open ( "log.txt" , "w" ) t.addChannel( "Boot" , sys.stdout) t.addChannel( "Radio" , f) m.turnOn() |
我们首先以写入模式(w)打开一个名叫log.txt的文件,然后开始向t,也就是Tossim对象内添加频道。Boot频道被指向sys.stdout,也就是标准输出,而Radio频道则被添加到f,也就是我们的文件。最后使用m节点的turnOn方法,启动节点。随着节点启动,booted事件被执行,两条dgb执行也被逐一执行。不出意外的话,屏幕上会显示?Applicaion booted。然后在当前目录下会多出一个log.txt文件,打开会发现Application booted again.信息。整个脚本完成的代码是:
1 2 3 4 5 6 7 8 9 | from TOSSIM import * from tinyos.tossim.TossimApp import * t = Tossim([]) m = t.getNode( 1 ) f = open ( "log.txt" , "w" ) t.addChannel( "Boot" , sys.stdout) t.addChannel( "Radio" , f) m.turnOn() |
你可以直接启动python,逐行输入执行,也可以保存为,例如sim.py文件,然后通过指令python sim.py来执行。这里Tossim还有一个有用的方法,就是runNextEvent。顾名思义,就是执行下一个事件。但是在执行事件前,记得需要先创建并且启动一个节点。
然后是一个简单的查看变量值的例子,TOSSIM目前尚不支持查看struct内的值,但是可以查看描述程序执行状态的普通变量的值。程序编译完以后会生成一个app.xml文件,里面包含了所有程序内的变量、组件信息,所以只要把这些信息“喂”给TOSSIM,TOSSIM便可以在执行过程当中跟踪节点程序的执行情况。我们把刚才的程序该一下:
1 2 3 4 5 6 | uint8_t counter = 0; event void Boot.booted() { dbg ( "Boot" , "Application booted.\n" ); dbg ( "Radio" , "Application booted again.\n" ); counter ++; } |
也就是变量counter初始化为0,在节点启动之后,自增为1。然后将sim.py也做相应的修改:
1 2 3 4 5 6 7 8 9 10 | from TOSSIM import * from tinyos.tossim.TossimApp import * n = NescApp() v = n.variables.variables() t = Tossim(v) m = t.getNode( 1 ) m.turnOn() v = m.getVariable( "RadioCountToLedsC.counter" ) print (v.getData()) |
首先是创建一个NescApp的对象,然后通过该对象的variables属性的variables方法返回程序内所有的变量。然后实例化Tossim对象的时候,将变量作为参数地交给Tossim类的构建器,这样Tossim执行时便会跟踪程序内的变量变化。再往后就是前面已经介绍过了,通过getNode创建一个节点,然后启动该节点。按照程序内的描述,节点在启动之后,counter变量会自增。然后通过节点的getVariable方法取回counter变量,而参数里面的RadioCountToLedsC则为NesC组件的名称。最后,用print命令把代表变量counter的对象v的值显示出来。不出意外的话,显示为1。
这些只是TOSSIM的简单应用,但是已经可以帮到不少忙了。还有其他更复杂一点的应用,例如多个节点间的交互等等,详细可以看Doc。先写到这里了,下班回家了。