TinyOS 系列文章【一】:TinyOS 配置教程
TinyOS 系列文章【二】:Tossim 教程
本文主要用于记录在 WSN 课程中,配置大作业所需使用的 Tossim 仿真工具
TOSSIM (TinyOS simulator)
是 TinyOS
自带的一个仿真工具,可以支持大规模的网络仿真。由于 TOSSIM
运行和传感器硬件相同的代码,所以仿真编译器能直接从 TinyOS
应用的组件表编译仿真程序。通过替换 TinyOS
下层部分硬件相关的组件,TOSSIM
把硬件中断换成离散仿真事件,由仿真器事件抛出的中断来驱动上层应用,其他的 TinyOS
组件尤其是上层的应用组件都无须更改。
Tossim
是一个库,你必须写程序配置仿真运行,TOSSIM
支持两种编程接口:Python
和 C++
Python
允许你动态的和仿真交互,就像一个强大的 debugger
Python
的解释器本 身就是一个性能瓶颈,执行效率没有 C++
高这两种接口各有优缺点,面我们只介绍 Python
这种接口
TOSSIM
是 TinyOS
系统的一个程序库,其核心代码位于 tos/lib/tossim
在每个 TinyOS
源代码目录里,都可能有一个对应的 sim
子目录,其中包含改代码的仿真实现
例如:tos/chips/atm128/timer/sim
含有 Atmega128 定时器的仿真组件
先进入 TinyOS
所在的根目录:
cd /opt/tinyos-2.1.2/
为了编译 TOSSIM
,你应该在 make
命令后面加上可选的 sim
选项:
cd apps/Blink
make micaz sim
现在 TOSSIM
只支持 micaz
平台
如果 TOSSIM
编译通过,会得到下面的输出信息:
下面将以 RadioCountToLeds 应用程序为例,演示 TOSSIM
仿真的一般步骤
RadioCountToLeds 应用程序有一个 4Hz 的计数器,每当数值更新时,就把数值无线广播出去。当另一个 RadioCountToLeds 节点接收到技术值,就将其低 3 位显示在 LED 灯上
apps
目录下)程序所在目录:cd tinyos-2.1.2/apps/RadioCountToLeds
make micaz sim
编译成功后,终端出现如下输出:
python
终端出现类似下面的输出:
TOSSIM
对象,导入 TOSSIM
仿真器,并创建一个 TOSSIM
对象,输入如下命令:>>> from TOSSIM import *
>>> t = Tossim([])
TOSSIM
仿真,利用 t.runNextEvent
命令可以运行一个仿真器事件,t
就是刚才创建的 TOSSIM
对象,runNextEvent
是 TOSSIM
对象的一个函数,输入函数格式如下:>>> t.runNextEvent()
0
此时输入 t.runNextEvent
,返回 0
,表示没有事件需要运行,因为当前还没有启动任何节点
Boot.booted
事件此时,执行 runNextEvent
返回 1
, 因为它有一个启动事件并成功运行了该事件,代码如下:
>>> m = t.getNode(32)
>>> m.bootAtTime(45654)
>>> t.runNextEvent()
1
tickPerSecond
函数来表示一秒的仿真时间点总和,调用函数的代码如下:>>> m = t.getNode(32)
>>> m.bootAtTime(4 * t.ticksPerSecond() + 242119)
>>> t.runNextEvent()
1
isOn
命令直接查询,代码如下:>>> m.isOn()
1
>>> m.turnOff()
>>> m.isOn()
0
>>> m.bootAtTime(560000)
>>> t.runNextEvent()
0
>>> t.runNextEvent()
1
注意到,节点关闭并再重新开启后执行的第一个 runNextEvent
函数返回 0
,这是因为当节点关掉时,队列里仍然有一个事件
但是,当轮到处理这个事件时,节点仍处于关闭状态, runNextEvent
就返回 0
第二个 runNextEvent
命令针对于 560000 时间点的启动事件,故返回 1
TOSSIM
对象还有很多功能函数,在 Python 里,一般可用 dir
函数查看某对象的功能函数,如下:>>> t = Tossim([])
>>> dir(t)
['__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__g
etattr__',
'__getattribute__', '__hash__', '__init__', '__module__', '__new_
_',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str_
_',
'__swig_getmethods__', '__swig_setmethods__', '__weakref__', 'addCh
annel',
'currentNode', 'getNode', 'init', 'mac', 'newPacket', 'radio', 'rem
oveChannel',
'runNextEvent', 'setCurrentNode', 'setTime', 'this', 'thisown', 'ti
cksPerSecond', 'time', 'timeStr']
常用函数有:
currentNode(): returns the ID of the current node.
getNode(id): returns an object representing a specific mote
runNextEvent(): run a simulation event
time(): return the current time in simulation ticks as a large inte
ger
timeStr(): return a string representation of the current time
init(): initialize TOSSIM
mac(): return the object representing the media access layer
radio(): return the object representing the radio model
addChannel(ch, output): add output as an output to channel ch
removeChannel(ch, output): remove output as an output to channel ch
ticksPerSecond(): return how many simulation ticks there are in a s
imulated second
TOSSIM
有一个称为 dbg
的调试系统
dbg
:打印调试信息,以节点 ID 开头修改 RadioCountToLedsC.nc 里的 Boot.booted
事件,当启动时打印一条 debug
信息,如下所示:
event void Boot.booted() {
call Leds.led0On();
dbg("Boot", "Application booted.\n");
call AMControl.start();
}
每次修改完 RadioCountToLedsC.nc 文件都要重新执行 make micaz sim 这条命令才会使之生效
dbg()
有两个或者更多的参数:第一个参数 Boot
,定义了输出通道,输出通道是一个字符串,dbg
命令和 C++ 语言中的 sprintf
语句十分相似例如,RadioCountToLedsC.nc 有如下调用:
event message_t* Receive.receive(message_t* bufPtr, void* payload,
uint8_t len) {
dbg("RadioCountToLedsC", "Received packet of length %hhu.\n", le
n);
...
}
输出通道需要利用 TOSSIM
对象中的 addChannel
函数将它与具体输出设备绑定,为了这些, 我们需要导入 Python 的 sys
包,告诉 TOSSIM
将 Boot
信息输出到指定地方
进行如下测试:
>>> from TOSSIM import *
>>> t = Tossim([])
>>> m = t.getNode(32)
>>> m.bootAtTime(45654)
>>> import sys
>>> t.addChannel("Boot", sys.stdout);
1
返回信息表示绑定成功,运行第一个仿真事件,节点启动:
>>> t.runNextEvent()
DEBUG (32): Application booted.
1
如果没有信息输出,你可能需要运行事件直到它输出:
>>> while not m.isOn():
>>> t.runNextEvent()
TOSSIM
只打印这些信息一次,例如, Boot
通道和 RadioCountTOLedsC 通道连接到标准输出,将只会打印出一条信息,例如: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();
}
在 Python 的交互模式里输入如下命令:
>>> import sys
>>> t.addChannel("Boot", sys.stdout)
>>> t.addChannel("RadioCountToLedsC", sys.stdout)
运行 runNextEvent
之后,将会打印出如下结果:
>>> t.runNextEvent()
DEBUG (32): Application booted.
DEBUG (32): Application booted again.
DEBUG (32): Application booted a third time.
仿真刚启动时,节点之间不能互相通信
只有配置好网络拓扑结构,TOSSIM
仿真才能模拟网络行为
TOSSIM
中默认的无线电模型是以信号强度为基础的,需要程序员通过脚本接口提供一组描述网络传播强度、底噪声和接收灵敏度的数据
TOSSIM
已经提供了一组基于 CC2420 芯片的数据
通过 Python 中的 radio
对象可以仿真无线电行为,利用 dir
命令还可以查看 radio
对象的具体函数,其代码如下:
>>> from TOSSIM import *
>>> t = Tossim([])
>>> r = t.radio()
>>> dir(r)
['__class__', '__del__', '__delattr__', '__dict__', '__doc__',
'__getattr__', '__getattribute__', '__hash__', '__init__',
'__module__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__str__', '__swig_getmethods__',
'__swig_setmethods__', '__weakref__', 'add', 'connected',
'gain', 'remove', 'setNoise', 'this', 'thisown',
]
TOSSIM
不但可以仿真无限电的传播模型,还可以利用就近匹配算法 CPM
来模拟射频噪 声、节点间的相互干扰以及外部信号源节点的干扰
CPM
算法利用噪声道文件产生一个统计模型,通过调用节点对象的 addNoiseTraceReading
命令创建噪声模型
在 tos/lib/tossim/noise
目录下有几个简单的噪声文件
例如,meyer-heavy.txt
文件保存的是斯坦福大学 Meyer 图书馆的噪声道数据,它的前十行如下:
-39
-98
-98
-98
-99
-98
-94
-98
-98
-98
需要将 tos/lib/tossim/noise
目录下的meyer-heavy.txt
文件移动到 tinyos-2.1.2/apps/RadioCountToLeds
目录下
下面是一段根据噪声道数据分别为节点 07 创建噪声模型的代码:
noise = open("meyer-heavy.txt", "r")
lines = noise.readlines()
for line in lines:
str1 = line.strip()
if str1:
val = int(str1)
for i in range(7):
t.getNode(i).addNoiseTraceReading(val)
for i in range(7):
t.getNode(i).createNoiseModel()
因为无线电的链路信息可以存在文本文件里,所以 TOSSIM
允许创建一个描述拓扑结构的文 件,然后用 Python 脚本加载该文件,并把这些拓扑信息保存到 radio
对象里
在文本文件中,3 个数值指明一条链路:源节点、目标节点和增益,例如:
1 2 -54.0
表示节点 1 发送消息,节点 2 以 54dBm 的增益接收
创建一个拓扑文件 topo.txt
gedit topo.txt
其内容如下:
1 2 -54.0
2 1 -55.0
1 3 -60.0
3 1 -60.0
2 3 -64.0
3 2 -64.0
以下脚本代码会读取该文件,并把数据存到 radio
对象里:
>>> f = open("topo.txt", "r")
>>> for line in f:
... s = line.split()
... if s:
... print " ", s[0], " ", s[1], " ", s[2];
... r.add(int(s[0]), int(s[1]), float(s[2]))
此时,当一个节点发送数据包,其邻居节点就能接收到数据包
创建 test.py
文件:
gedit test.py
下面是 RadioCountToLedsC 实例的完整仿真脚本代码,保存在 test.py
中:
#! /usr/bin/python
from TOSSIM import *
import sys
t = Tossim([])
r = t.radio()
f = open("topo.txt", "r")
for line in f:
s = line.split()
if s:
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")
for line in noise:
str1 = line.strip()
if str1:
val = int(str1)
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(100):
t.runNextEvent()
运行该脚本,得到输出如下:
这些调试输出语句,都对应着 RadioCountToLedsC.nc 文件里的 dbg
调试语句,前面打印的网络拓扑语句是在 test.py 文件里 print " ", s[0], " ", s[1], " ", s[2];
这条语句输出的
一份简单的配置指南