代码改变世界

我的OO实践---由GPS消息处理抽象出一通用命令处理类

2010-11-02 09:39  Loning  阅读(2098)  评论(2编辑  收藏  举报

 

  1. 实验要求

(1)综合运用以前学到的控制语句、继承、封装、接口等知识,完成具有实际运用功能的程序。

(2)通过运用学过的知识进一步的巩固和掌握学到的知识。

  1. 实验内容

    1. 使用GPS GATE软件模拟GPS卫星发出的GPS信号,编写程序对GPS GATE发出的信息进行接收、解析、处理。将处理好的信息按照固定的格式存储至文件中(经度、纬度、时间、速度、高度)。

      下面是主要用到的GPS信息的格式:

      1. GPS/TRANSIT Data(RMC)推荐定位信息 (在项目中就使用了这个报文的定位数据)

    $GPRMC,(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)*hh(CR)(LF)

    (1)UTC时间,hhmmss(时分秒)格式
    (2)定位状态,A=有效定位,V=无效定位
    (3)纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
    (4)纬度半球N(北半球)或S(南半球)
    (5)经度dddmm.mmmm(度分)格式(前面的0也将被传输)
    (6)经度半球E(东经)或W(西经)
    (7)地面速率(000.0~999.9节,前面的0也将被传输)

    (8)地面航向(000.0~359.9度,以真北为参考基准,前面的0也将被传输) (9)UTC日期,ddmmyy(日月年)格式
    (10)磁偏角(000.0~180.0度,前面的0也将被传输)
    (11)磁偏角方向,E(东)或W(西)
    (12)模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

    (13) hh(CR)(LF) hh是前面从第一个数据到最后一个数据的校验和,(CR)(LF)是回车换行,表示一个字符串的结束。

    具体示例:

    $GPRMC,121212.456,A,3232.1234,N,12121.3322,W,0.15,305.12,121299, ,*22

     

    2. GPS Fix Data(GGA)GPS定位信息

    $GPGGA,(1),(2),(3),(4),(5),(6),(7),(8),(9),M,(10),M,(11),(12)*hh(CR)(LF)

    (1)UTC时间,hhmmss(时分秒)格式
    (2)纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
    (3)纬度半球N(北半球)或S(南半球)
    (4)经度dddmm.mmmm(度分)格式(前面的0也将被传输)
    (5)经度半球E(东经)或W(西经)
    (6)GPS状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
    (7)正在使用解算位置的卫星数量(00~12)(前面的0也将被传输)
    (8)HDOP水平精度因子(0.5~99.9)
    (9)海拔高度(-9999.9~99999.9)
    (10)地球椭球面相对大地水准面的高度
    (11)差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
    (12)差分站ID号0000~1023(前面的0也将被传输,如果不是差分定位将为空)

    (13) hh(CR)(LF) hh是前面从第一个数据到最后一个数据的校验和,(CR)(LF)是回车换行,表示一个字符串的结束。

    具体示例:

$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F

校验和是指这一行的所有非数字字符,按照"字母、空格、句点、正号= 0;负号=1"的规则换算成0和1后,将这一行中原来的全部数字加起来,以10为模计算后所得的和。校验和可以检查出90%的数据存储或传送错误。按十进制加起来的个位数字的校验和,用于精确纠正误差。

 

  1. 使用异常处理,对接收信息的过程中可能产生的异常进行处理。
  1. (选作)编写C#Winfrom 程序,能够将解析到的数据轨迹绘制到相应的地图上。
  1. 详细设计

    该实现主要是实践我在前文《谈谈我处理异常的一般方法》中提出的一些观点,以及一些面向对象设计的思想,和一些设计模式的运用。

    在看到这个问题的时候,首先要对需求进行分析。该问题的数据流程比较清晰,见Figure 1。

Figure 1 数据流

    从个人经验来讲,我认为在指令的解析部分可以抽象出一个比较通用的方式,我们把GPS指令文本流替换为二进制流,将单条指令文本替换为单条指令的二进制信息。

Figure 2 通用数据流

    我做的抽象框架如所示Figure 3 类视图所示。

Figure 3 类视图

    首先我们应当有一个类来处理整个问题,称之为CommandProcessor。它负责从外部设备接收数据、并且将处理后的消息通过事件的方式向外部提供。该类还应当能够将单条指令从流中取出来,因而我在这个类中提供了一个虚的方法GetSingleCommandString(),并且进行了一个通用的简单实现、即每行为一条指令。这里参数都用string来表示了,用string来表示二进制数据也没太大问题,但对于纯文本的数据处理起来则比较方便。

    在将命令分出来之后,我们需要将一条单独的指令转换为一个强类型的命令。我们用ICommand来表示一个被分析后的命令,则问题就是如何由一个string产生一个ICommand。

    根据面向对象设计的一些原则,数据与实现应都放在一个类当中。那么对于特殊的Command,它自身才知道如何对它对应的string进行分析。我们在ICommand中定义Parse(string s)方法对string进行分析。

    ICommand的定义如下,关于DoCommand()方法在后文将有说明。

    

public interface ICommand
    {

        /// <summary>
        /// If it is right command,return true
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        bool Parse(string str);
        void DoCommand();
    }

 

    那么如何找到string->ICommand的对应关系呢?

    比较简单的实现是我们维护一个ICommand的列表,然后用穷举的方式调用Parse方法,直到该方法返回true。如果没有方法返回true,则表示该string是一个未知的命令。这样实现是比较OO,在编程阶段添加ICommand比较方便。缺点是效率比较低,而且还需要维护一个已经实例化ICommand的列表。而有这样一个列表,在多线程编程时就会产生一些同步问题。在编程时需要在Parse(string s)方法中先对传入的string做一个初步检测,如果非该指令则立即返回false。而在当前的设计中,没法对子类重写Parse方法内的内容进行约束,如果实现不好则会更大的造成效率的降低。

    我现在的实现是,引入一个ICommandFactory对象,那么这个问题就成为了string->ICommandFactory->ICommand的问题了,由于实现接口ICommandFactory与ICommand都是同一开发者实现的,该开发者可以分析string的特殊性,在实现ICommandFactory接口的类中找到string->ICommand的关系。还有一个优点是,我们完全可以在自己定义的CommandFactory中实现在上一段提到的方法。以下是该部分具体的实现代码。

public interface ICommandFactory
{
/// <summary>
/// return an ICommand object from a string.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
ICommand Parse(string str);
}

 

 

//实现该接口的一个具体示例

public class GPSCommandFactory : ICommandFactory
    {
        public IUnityContainer UnityContainer { get; set; }

        private ICommand Parse<C>(string str)
            where C : ICommand
        {
            var cmd = UnityContainer.Resolve<C>();
            if (!cmd.Parse(str))
            {
                throw new InvalidCommandStringException(str, null);
            }
            return cmd;
        }

        #region ICommandFactory Members

        public ICommand Parse(string str)
        {
            try
            {
                string command;
                command = str.Substring(0, str.IndexOf(','));
                switch (command)
                {
                    case "$GPRMC":
                        return Parse<GPRMCommand>(str);
                    case "$GPGGA":
                        return Parse<GPGGACommmand>(str);
                }
                throw new InvalidCommandStringException(str, null);
            }
            catch (ArgumentOutOfRangeException ex)
            {
               …
            }
        }

        #endregion
    }

 

 

    在解决完命令分析的问题之后,最后的一个问题是如何将分析出的命令应用的问题。在这个设计中,我通过事件来通知其他类。

    在前文给出ICommand的描述中给出了一个DoCommand()方法。我们可以在需要应用的项目中重写该方法,做出相应的动作。这样就完全的实现了多态,在接收到CommandReceived事件的消息后直接调用DoCommand()方法就好了,不需要对Command类型进行显示分析。当然,我们需要借助IOC容器来进行这样的实现。这里不阐述IOC容器的具体功能,有兴趣Google下就好啦。当然这样实现也许还是有一些复杂性的,我们需要对每一个Command类进行重写,然后更改IOC容器的映射关系。

    还有一种实现是使用is操作,对ICommand对象进行测试,然后将ICommand中的信息进行利用。下面给出第二种实现方法的一个示例。

        void CommandProcessor_CommandRecevied(object sender, CommandEventArgs e)
        {
            Invoke(new ThreadStart(delegate()
            {
                if (e.Command is GPGGACommmand)
                {
                    ProcessCommand((GPGGACommmand)e.Command);
                }
                if (e.Command is GPRMCommand)
                {
                    ProcessCommand((GPRMCommand)e.Command);
                }
            }));

            //throw new NotImplementedException();
        }

 

 

    这样一个基本的命令分析器的Library工程就基本写完了。然后就应当着手解决GPS消息的问题了。在前面的示例中已经贴了一些实现。

    分析GPS的消息,很容易的发现每条命令是以$\.+?, 方式开始的,我们可以根据这样的特性实现ICommandFactory,这里再贴一下主要的实现代码

string command;
command = str.Substring(0, str.IndexOf(','));
switch (command)
{
case "$GPRMC":
return Parse<GPRMCommand>(str);
case "$GPGGA":
return Parse<GPGGACommmand>(str);
}

 

 

    然后就可以产生Command了。我们根据要求建立了两个Command,分别是GPRMCommand和GPGGACommmand。然后根据对应的命令格式重写Parse()方法。而消息分割正好是每行一条命令的方式,因而就无需重写CommandProcessor的GetSingleCommand方法了。然后就完了,不需要写什么了,基本的东西都已经在我们之前谈到的项目中定义好了。

    具体视图如下:

Figure 4 GPS类视图


如果您有兴趣,可以在这里下载代码,如果有问题欢迎与我联系:)

http://loningproject.googlecode.com/svn/trunk/cnblogs/gps.7z