2 Star 12 Fork 1

孙振兴 / RoboCup3D

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
消息解析原理.md 7.63 KB
一键复制 编辑 原始数据 按行查看 历史
孙振兴 提交于 2021-07-13 10:16 . update 消息解析原理.md.

1. 消息解析原理

在我们的消息解析模块中,使用到了服务器的库文件,主要就是 predicate.hparameterlist.h,它们的作用就是用来处理 S-expression(S表达式)的。

  • 本文最初由 leisland 撰写和编辑,原作者陈银华(2010-05-13)
  • 最后修改:孙振兴(2021-07-10)
  • 常州工学院 - 计算机信息工程学院,仅用于学习材料

1.1 parameterList

先看 parameterList,它的定义是:

public:
    typedef std::vector<boost::any> TVector;
protected:
    TVector mList;

解释:

class ParameterList manages a list of values. Boost::Any is used as a typesafe
container to realize a sequence of values of arbitrary types.

它其实是一个数组,数组的每一个元素为任意类型。这个数组其实是 vector 容器,容器每一个元素的类型是 Boost::Any

1.2 predicate

解释:

class Predicate encapsulates a predicate name together with its list of
parameters.

一个 predicate 封装了判别式(predicate)的名称和这个判别式的所有数据。这个判别式就是一个 S表达式

我们先解释什么是 S-expression(S-表达式)

一个S表达式是一对左右括号之间的信息集合,里面的内容包括数字、符号、字符串。下图所示的是S表达式的结构。它广泛的应用在Lisp语言的编程中,一般用来进行编码和存放数据。

一个S表达式通常有以下格式

  • Atom(原子)
    • Number(数字)
      • Fixed Point Number(定点数)
      • Floating Point Number(浮点数)
    • Symbol(符号)
  • String(字符串)
  • List(串行)

利用 S-expressions 的优点在于其数据格式易于分析,简洁的语法在一定程度上为我们的调试提供了可读性。并且,S-expressions 还便于增加新的传感器信息。

S-expressions 还便于增加新的传感器信息。

这就是我们为什么要搞清楚消息解析原理,一旦服务器增加新的传感器消息,我们现在的代码是不能够解析出来的。

这就要求我们修改消息解析的代码,以便取出新的数据。

一个判别式就是一个 S-expression

我们还是到服务器发给 agent 的消息中取个例子来分析分析吧!服务器发给 agent 的时间消息是这样的:

(time (now 13.62))

而这就是一个S-expression,解析它时,它就对应一个 predicate。在 predicate 的定义中有以下这两行代码:

/** the name of this predicate */
std::string name;
/** the list of parameter values */
zeitgeist::ParameterList parameter;

我们可以很清楚的发现,一个 predicate 包含该 predicate 的名字和 parameterList。用上面的例子来解释,就是 name 的值是 time,parameter 是一个数组,它有两个数组元素,分别是 now14.62。parameter 数组的两个元素,一个是字符串型(string),另一个却是浮点型(float),这确实很奇妙!

Predicate中需要我们知道的函数:

bool DescentList(Iterator& iter) const;
void GetValue(const Iterator& iter, T& value) const;
bool FindParameter(Iterator& iter, const std::string& name) const;
void AdvanceValue(Iterator& iter, T& value) const;
  1. DescentList这个函数的作用是把 predicate 中 parameter 的数组元素转变成 parameterList,并使得 iter 指向这个 parameterList。当然,这是对于parameter 中每个元素都是 parameterList 而言的,如果是上面的那个例子,是没必要使用这个函数的。
  2. GetValue可以把iter所指向的parameterList中的标记元素取出来,若是上面的例子,那么取出来的就是“now”。
  3. FindParameter依据 name 的标记,使 iter 指向 name 之后的数据,也就是找到 name 所对应的数据。
  4. AdvanceValue取出 iter 所指向的数据,并存放在 value 中。

2. 如何去做

2.1 一个需要提及的问题:predicatelist 的生成

消息解析器(mparser)首先将服务器的消息(massage)解析成 predicatelist(判别式数组),每一个数组元素都是一个 predicate(判别式)

在服务器的消息中,每一对最外层的括号就会对应一个 predicate,因此,服务器消息中有多少对这样的括号, predicatelist 就有多少个 predicate元素。而在这样的一对括号里面,有多少对括号,就对应多少 parameterList。

这是不是像绕口令?还是让我们看个例子吧!

假设服务器的视觉消息是这样的:

(see
 (G1L(pol 6.27 74.18 -6.37))
 (G2R(pol 6.05 87.14 -6.61))
)

整个 see,也就是最外层的这对括号,对应一个 predicate,其层次结构如下图所示:

2.2 拿出我要的数据

服务器的消息有不同类型,包括:time,GSGYR,See,HJ,FRP等。根据其首字母,基本可将消息区分出来;如不能区分,则再比较第二字母,以此类推。

如,以'G'开头的有 GS 和 GYR,此时比较它们的第一个字符并不能区分这两种消息,所以还要比较第二个字符。消息解析的主循环就是依据以上原则进行工作的:

shared_ptr<PredicateList> predList = mParser->Parse(message);

调用 Parse 的作用就是把服务器的消息打包成许多 predicate,然后放在一个 PredicateList 中。以下是消息解析的主循环,它逐个处理 PredicateList 中的每个 predicate

if (predList.get() != 0)
{
    PredicateList &list = *predList;
    for (
        PredicateList::TList::const_iterator iter = list.begin();
        iter != list.end();
        ++iter)
    {
        const Predicate &predicate = (*iter);
        // check for a joint percept
        switch (predicate.name[0])
        {
        case 'h': // hear
            ParseHearInfo(predicate);
            break;
        case 't': //get simTime
            ParseServerTime(predicate);
            break;
        case 'G': //Parse Game state
            switch (predicate.name[1])
            {
            case 'S': //paring GameState
                ParseGameState(predicate);
                break;
            case 'Y': //Parsing GYR
                ParseGYR(predicate);
                break;
            default:
                break;
            }
            break;
        case 'S': //parsing VIsion
            ParseVision(predicate);
            break;
        case 'H': // hinge joint (HJ)
            ParseHingeJointInfo(predicate);
            break;
        case 'F': //paring FRP sensor
            ParseFRP(predicate);
            break;
        default:
            break;
        }
    }
}

其中每个 case 调用函数的处理过程大体是这样的,只是有些许的差别: 逐一取出 predicate 中的每一个 parameterList,对每个 parameterList 作如下操作:

  1. 取得 parameterList 的名称,例如 (G1L(pol6.2774.18-6.37)) 对应的 paraterList 的名称为 G1L。对应的函数为:GetValue(const Iterator&iter, T&value) const;
  2. 以名称 G1L 找到 map容器中对应的映射;
  3. 定位到 parameterList 中 pol 中的位置,将其后的三个位置提取到步骤2中的 映射内。对应的函数为:FindParameterAdvanceValue

3. 附录

  1. predicate的头文件目录:lib/oxygen/gamecontrolserver/predicate.h
  2. parameterList的头文件目录:lib/zeitgeist/parameterList.h
  3. parser的头文件目录:utility/sfsexp/parser.h
其他
1
https://gitee.com/sun-zhenxing/robo-cup3-d.git
git@gitee.com:sun-zhenxing/robo-cup3-d.git
sun-zhenxing
robo-cup3-d
RoboCup3D
master

搜索帮助