void updateReverseQuality(am_addr_t neighbor, uint8_t outquality) {
uint8_t idx;
idx = findIdx(neighbor);
if (idx != INVALID_RVAL) {
NeighborTable[idx].outquality = outquality;
NeighborTable[idx].outage = MAX_AGE;
}
}
void updateEETX(neighbor_table_entry_t *ne, uint16_t newEst) {
ne->eetx = (ALPHA * ne->eetx + (10 - ALPHA) * newEst)/10;
}
void updateDEETX(neighbor_table_entry_t *ne) {
uint16_t estETX;
if (ne->data_success == 0) {
estETX = (ne->data_total - 1)* 10;
} else {
estETX = (10 * ne->data_total) / ne->data_success - 10;
ne->data_success = 0;
ne->data_total = 0;
}
updateEETX(ne, estETX);
}
uint8_t computeEETX(uint8_t q1) {
uint16_t q;
if (q1 > 0) {
q = 2550 / q1 - 10;
if (q > 255) {
q = INFINITY;
}
return (uint8_t)q;
} else {
return INFINITY;
}
}
uint8_t computeBidirEETX(uint8_t q1, uint8_t q2) {
uint16_t q;
if ((q1 > 0) && (q2 > 0)) {
q = 65025u / q1;
q = (10*q) / q2 - 10;
if (q > 255) {
q = LARGE_EETX_VALUE;
}
return (uint8_t)q;
} else {
return LARGE_EETX_VALUE;
}
}
CTP链路估计层的实现主要在/tos/lib/net/le/LinkestimatorP.nc 中。
TOS与nesC
TinyOS的实现并没有使用C语言而是使用nesC这种基于模块的变成方式,具体的语法和C语言很想,唯一的区别主要在程序的结构上是基于模块的。TinyOS的数据类型很多事基于网络来定制的,比如以nx开头的数据类型,还有nx_struct这样的语法。
nesC的学习可以依靠这个文档:
http://wenku.baidu.com/link?url=t-xuM0txJiWCwG3f2TuE9MtntCE_oo97NebXBNjrd6lpFPk8ttuNVr-7wESL4lir5fZaf1syMUFY19GsLezWjkkePBVIJFww5A9uuWfXEoa
在百度文库中,写的比较清楚简单,并且够用。
源码实现结构
下面是源码的主要部分的实现。
箭头的含义很简单,A→B就是B这个函数使用了A。可以看到链路质量估计层主要向上提供路由表的修改以及信息的发送接口。DEETX的相关函数就是用来进行基于数据包的链路质量估计的。下面将列举所有函数的实现和功能。善用ctrl+F。
链路质量估计器的全局变量
enum {
EVICT_EETX_THRESHOLD = 55,
MAX_AGE = 6,
MAX_PKT_GAP = 10,
BEST_EETX = 0,
INVALID_RVAL = 0xff,
INVALID_NEIGHBOR_ADDR = 0xff,
INFINITY = 0xff,
ALPHA = 2,
DLQ_PKT_WINDOW = 5,
BLQ_PKT_WINDOW = 3,
LARGE_EETX_VALUE = 60
};
这些是链路质量估计器中使用的枚举。
EVICT_EETX_THRESHOLD主要主要是规定了从邻居表中删除一个条目,那么这个条目对应的邻居节点的eetx不能少于55。邻居表中应该放的是连接质量最好的一些节点,但是如果网络整体的情况不错,只要有连接质量更好的出现就替换邻居表会造成邻居表(链路质量估计表)的频繁变换,不利于程序的效率和拓扑结构额稳定。因为邻居表的空间是有限的,所以如果出现了有新的节点想要加入的情况,我们需要选出链路质量最差的节点,并且这个节点与本节点之间的eetx不能低于55(差到一定程度)才可以替换。
MAX_AGE和inage和以及outage有关系。
MAX_PKT_GAP这个变量说明了如果这次收到的某一个节点的LEEP和上一次收到的LEEP帧序号相差10,那么就要清空之前的所有LEEP帧信息,因为这样子说明很久没有和这个节点建立连接了,直线收到的LEEP帧不再拥有时效性。
BEST_EETX就是代表了最佳的链路开销(pathETX)。
INVALID_RVAL这个是当没有从链路质量估计表的时候返回的索引值。
ALPHA这个值代表了新的链路质量估计值和上一次的链路质量估计值要28开取加权平局。
DLQ代表了转发引擎每发送5个包就计算一次链路质量,然后和之前的链路质量会做一次加权平局。
BLQ代表了每收到3个LEEP帧就计算一次链路质量,然后和之前的链路质量会做一次加权平局。
LARGE_EETX代表了最大的EETX值。
neighbor_table_entry_t NeighborTable[NEIGHBOR_TABLE_SIZE];
uint8_t linkEstSeq = 0;
uint8_t prevSentIdx = 0;
还有三个全局变量,第一个是邻居表。第二个是LEEP帧中的序号。第三个是如果邻居表中的信息很多,一个LEEP帧装不下,那么就要分两次发送,这个变量记录了上次发送到哪了。
下面是所有函数的作用:
linkest_header_t* getHeader(message_t* m) {
return (linkest_header_t*)call SubPacket.getPayload(m, NULL);
}
linkest_footer_t* getFooter(message_t* m, uint8_t len) {
return (linkest_footer_t*)(len + (uint8_t *)call Packet.getPayload(m,NULL));
}
uint8_t addLinkEstHeaderAndFooter(message_t *msg, uint8_t len) {
uint8_t newlen;
linkest_header_t *hdr;
linkest_footer_t *footer;
uint8_t i, j, k;
uint8_t maxEntries, newPrevSentIdx;
dbg("LI", "newlen1 = %d\n", len);
hdr = getHeader(msg);
footer = getFooter(msg, len);
maxEntries = ((call SubPacket.maxPayloadLength() - len - sizeof(linkest_header_t))
/ sizeof(linkest_footer_t));
if (maxEntries > NUM_ENTRIES_FLAG) {
maxEntries = NUM_ENTRIES_FLAG;
}
dbg("LI", "Max payload is: %d, maxEntries is: %d\n", call SubPacket.maxPayloadLength(), maxEntries);
j = 0;
newPrevSentIdx = 0;
for (i = 0; i < NEIGHBOR_TABLE_SIZE && j < maxEntries; i++) {
k = (prevSentIdx + i + 1) % NEIGHBOR_TABLE_SIZE;
if (NeighborTable[k].flags & VALID_ENTRY) {
footer->neighborList[j].ll_addr = NeighborTable[k].ll_addr;
footer->neighborList[j].inquality = NeighborTable[k].inquality;
newPrevSentIdx = k;
dbg("LI", "Loaded on footer: %d %d %d\n", j, footer->neighborList[j].ll_addr,
footer->neighborList[j].inquality);
j++;
}
}
prevSentIdx = newPrevSentIdx;
hdr->seq = linkEstSeq++;
hdr->flags = 0;
hdr->flags |= (NUM_ENTRIES_FLAG & j);
newlen = sizeof(linkest_header_t) + len + j*sizeof(linkest_footer_t);
dbg("LI", "newlen2 = %d\n", newlen);
return newlen;
}
这是一组函数和LEEP帧的头部和尾部有关系,SubPacket这个接口提供的是链路质量估计下层的数据包操作。LEEP帧自然是在下层数据帧的payload部分。所以获取指向这个部分第一位的指针再进行强制类型转化就可以了。函数返回的是一个指针。
getfooter是获取尾部的函数,函数范围的是一个指针,我们要获取这个指针然后顺着这个指针向后进行footer的添加。函数使用的是Packet接口,这个接口是对当前数据链路估计层的数据帧进行操作,这里获取的是LEEP帧的payload部分。通过再获取LEEP帧payload(也就是路由帧)的长度就可以向后找到LEEP帧尾部的位置了。
addLinkEstHeaderAndFooter这个函数为一个来自于路由引擎的路由帧添加一个LEEP帧头和帧尾。值得一提的是prevSentIdx变量,因为LEEP帧的长度有限,可能不能好好把所有链路质量估计表中节点地址和质量估计值放进去,所以要分两次放,prevSentIdx这个变量存的就是上一次放到哪里了,因为这个函数要在LEEP帧发送之前调用,将链路质量估计表中的信息放到这个里面就要接着上次没放完的放。
void initNeighborIdx(uint8_t i, am_addr_t ll_addr) {
neighbor_table_entry_t *ne;
ne = &NeighborTable[i];
ne->ll_addr = ll_addr;
ne->lastseq = 0;
ne->rcvcnt = 0;
ne->failcnt = 0;
ne->flags = (INIT_ENTRY | VALID_ENTRY);
ne->inage = MAX_AGE;
ne->outage = MAX_AGE;
ne->inquality = 0;
ne->outquality = 0;
ne->eetx = 0;
}
uint8_t findIdx(am_addr_t ll_addr) {
uint8_t i;
for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {
if (NeighborTable[i].flags & VALID_ENTRY) {
if (NeighborTable[i].ll_addr == ll_addr) {
return i;
}
}
}
return INVALID_RVAL;
}
uint8_t findEmptyNeighborIdx() {
uint8_t i;
for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {
if (NeighborTable[i].flags & VALID_ENTRY) {
} else {
return i;
}
}
return INVALID_RVAL;
}
uint8_t findWorstNeighborIdx(uint8_t thresholdEETX) {
uint8_t i, worstNeighborIdx;
uint16_t worstEETX, thisEETX;
worstNeighborIdx = INVALID_RVAL;
worstEETX = 0;
for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {
if (!(NeighborTable[i].flags & VALID_ENTRY)) {
dbg("LI", "Invalid so continuing\n");
continue;
}
if (!(NeighborTable[i].flags & MATURE_ENTRY)) {
dbg("LI", "Not mature, so continuing\n");
continue;
}
if (NeighborTable[i].flags & PINNED_ENTRY) {
dbg("LI", "Pinned entry, so continuing\n");
continue;
}
thisEETX = NeighborTable[i].eetx;
if (thisEETX >= worstEETX) {
worstNeighborIdx = i;
worstEETX = thisEETX;
}
}
if (worstEETX >= thresholdEETX) {
return worstNeighborIdx;
} else {
return INVALID_RVAL;
}
}
这一组函数是用来对链路质量估计表进行操作的,主要是有初始化、从表中找一个空条目、从表中找一个最烂的并且可以被替换的条目,后面两个函数主要是发生在收到了新的节点的LEEP帧,这个LEEP的源节点在之前没有收到过,那么就需要把这个信息放到链路质量估计表中,首先看看有没有空位置,如果有就直接插入,如果没有那就找一个链路质量最差并且差到一定程度的替换。
initNeighborIdx这个函数是用来初始化一个链路质量估计表条目,形参i就是要初始化第几条,i是一个索引值。整个初始化的过程很普通,但是值得一提的是这个条目的状态被赋为了INIT_ENTRY和VALID_ENTRY,要注意一个链路质量估计表中的条目是可以叠加的。INIT_ENTRY的二进制形式是0000 0100,VALID_ENTRY的二进制形式是0000 0001,用了或运算就把这个这个条目的状态变成了0000 0101,将两个状态------初始态和有效态叠加。
findIdx这个函数是根据邻居节点的地址去查询在这个邻居节点的信息在链路质量估计表中的位置。如果找不到就返回INVALID_RVAL这个枚举,这个在之前有定义。
findEmptyNeighborIdx这个函数是用来在链路质量估计器中找到这么一个条目:这个条目是有效的,成熟的,非绑定的,EETX最大的,并且大于我们设定的thresholdEETX这个阈值的。一个条目几乎在所有时刻都是有效的,只有在一种极为罕见的情况会置为无效,和inage以及outage这两个值有关。当一个条目的链路质量算出来之后就会进入成熟的状态,然后除非这一行重新被初始化,要不就不会出现非成熟的状态,因为只要收到一个节点的LEEP帧,那么就会把这个节点的地址信息放到链路质量估计表中,但是这个时候是没有链路质量估计值的,没有估计值选出链路质量最低就无从谈起。绑定状态是一种特殊状态,当这个条目对应的节点是根节点和父节点的时候就会被绑定,被绑定的节点条目不能被替换和去除,无论是什么情况都要保留在链路质量估计表中。EETX是单跳链路质量的一种表现,和某一个节点EETX越高说明和这个节点的连接质量越差,当时为了防止链路质量估计表的频繁变化,所以只要EETX不差到一定程度就不替换一个路由表条目。
void updateReverseQuality(am_addr_t neighbor, uint8_t outquality) {
uint8_t idx;
idx = findIdx(neighbor);
if (idx != INVALID_RVAL) {
NeighborTable[idx].outquality = outquality;
NeighborTable[idx].outage = MAX_AGE;
}
}
void updateEETX(neighbor_table_entry_t *ne, uint16_t newEst) {
ne->eetx = (ALPHA * ne->eetx + (10 - ALPHA) * newEst)/10;
}
void updateDEETX(neighbor_table_entry_t *ne) {
uint16_t estETX;
if (ne->data_success == 0) {
// if there were no successful packet transmission in the
// last window, our current estimate is the number of failed
// transmissions
estETX = (ne->data_total - 1)* 10;
} else {
estETX = (10 * ne->data_total) / ne->data_success - 10;
ne->data_success = 0;
ne->data_total = 0;
}
updateEETX(ne, estETX);
}
uint8_t computeEETX(uint8_t q1) {
uint16_t q;
if (q1 > 0) {
q = 2550 / q1 - 10;
if (q > 255) {
q = INFINITY;
}
return (uint8_t)q;
} else {
return INFINITY;
}
}
uint8_t computeBidirEETX(uint8_t q1, uint8_t q2) {
uint16_t q;
if ((q1 > 0) && (q2 > 0)) {
q = 65025u / q1;
q = (10*q) / q2 - 10;
if (q > 255) {
q = LARGE_EETX_VALUE;
}
return (uint8_t)q;
} else {
return LARGE_EETX_VALUE;
}
}
这些是跟链路质量以及EETX有关系的一组函数第一个函数是讲某个节点的出度质量写到链路质量估计表中,先找到索引值,然后在对应索引值的条目里面添加数据。
updateEETX这个函数是用来更新单跳的链路质量估计的,每接收到5个LEEP帧和发送3个数据帧就可以进行一次EETX的更新,新的EETX要和老的EETX以82开进行结合,ALPHA代表了老的EETX值所占的比重。
updateDEETX这个函数进行数据包驱动的链路质量更新算的方法在函数里已经表达了,data_total就是一共发的数据包数,一般情况下就是3。
computeEETX这个函数是将链路质量估计值向单向EETX做一个转换,链路质量估计值是由LEEP帧驱动的,而基于数据包发送成功率的链路质量估计值是直接作用于EETX的。毫无疑问这个函数是基于LEEP帧的链路质量估计。EETX = ETX - 1 , 这个函数的返回值是EETX × 10 。这个函数实际上不如下面那个函数常用,这个函数主要是提供给路由引擎的。
computeBidirEETX,BidirEETX = BidirETX - 1 , computeBidirEETX 返回值是 BidirEETX*10 。BidirEETX就是双向的链路质量估计。这个值是最后要写入链路质量估计表的。