论文描述了如何运用UML(统一建模语言)设计一个简单的家用报警器,并实现到VxWorks操作系统上。本文分两个部分,第一部分描述了如何用UML设计和验证家用报警器的模型,以使其独立于特定的硬件和操作系统。第二部分则细节地描述如何将此模型实现于运行VxWorks操作系统的486硬件平台上。模型的设计、开发和验证使用了I-Logix公司的可视化编程环境“Rhapsody”。
第一部分:设计一个独立于操作系统的家用报警器
一个好的面向对象设计力图将不变的东西与变化的东西相互分开。在家用报警器的设计中,一个可能的变化是实际的硬件平台。本文描述了如何用UML设计这个模型,以使它可以容易地应用到不同的硬件结构中去。文中也表现了如何通过“Rhapsody”工具,可以由模型产生全部代码,并通过“设计级调试”进行验证。设计级调试使我们可以运用描述模型中用到的相同图示来进行调试,基本上可以让状态图和消息序列图活动起来。这样就可以使我们快速地验证所设计的模型。
需求
我们要设计的家用报警器有如下需求:
可以通过遥控器或者简单的小键盘进入戒备/取消戒备状态。当用小键盘设置戒备时,一个四位数字密码必须输入随着要按下ON键。如要取消戒备时,一个四位数字密码必须输入随着要按下OFF键。密码应该可以改变。当在警戒时检测到情况,马上会发声报警。设置警戒时,将有一个延时供户主离开。警戒状态时开门,会有一个入门延时,这时户主可以取消警戒,如果不能消除警戒警报就要响了。
报警器要有两个LED,一个为绿色,另一个为红色。红色的LED表示报警器处于激活状态,在报警器激活时或者报警器已激活且门打开时闪烁。绿色的LED显示报警器启动并且在密码成功修改后闪动四次。
一个未来的需求是家用报警系统应可以通过开关电灯模拟房间被闯入。
设计UML模型
用例图
第一步是根据需求建立用例图。下面的用例图表明了两件事:一个显示了主要用处(或用例)即试图给“家用报警器(home alarm)”建模。另一个表示谁使用家用报警器;有两个主要的角色或者用户,即户主(homeowner)和闯入者(intruder)。用例是系统功能反映给角色的可观察的结果但并不揭示系统的内部构造。
在上图中我们可以看到报警器的主要用途是“检测入侵(detect intrusions)”的用例,这时户主和闯入者都会参与。“占用模拟(occupation
simulation)”用例扩展了“检测入侵(detect intrusions)”,在警戒时会通过开关灯模拟屋内有人。其他两个有户主(homeowner)参与的用例是“警戒和消除警戒(arming
and disarming)”和“修改密码(changing code)”;两者都使用或包含了“密码输入(code entry)”用例中的服务。每一个用例可以加一个文本描述,并且如果需要,用例可以在另一用例图中进一步分解。
用例不隐含任何内部结构,只是一个功能视图。
黑盒场景
已经建立了用例图,下一步就是看一些黑盒场景。这些场景的目的是显示我们试图去建模的系统和角色之间的交互情况。我们把系统看为一个黑盒子。例如:对于“警戒和消除警戒”用例我们可以想到有下述场景:
上图中的场景用消息序列图,显示了一段时间内户主(homeowner)和报警器(HomeAlarm)之间的交互。当建立这样的图时,系统的更多细节问题将被发现。例如在上述场景中,按键的时间间隔信息必须被确定。
这些场景帮助我们更好地理解用户如何使用系统并且帮助我们确切理解什么是需要做的。
一般地,每个用例有几个要注意的场景要捕捉。每个场景实际是用例的一个实例。
领域图
下一步要看看我们如何将模型分解为小一些的子系统和领域。领域可以在UML中通过包来表示,即基本上是类的集合。下图的领域图显示了家用报警器如何被分解为两个基本包:报警器领域(AlarmPkg)和硬件领域(HardwarePkg)。此处的想法是报警器领域完全独立于实际的硬件,并且任何能实现IHardware接口的硬件领域能与报警器领域联系。接口类IHardware描述了所有报警器领域中必须的操作。set操作是报警器领域可以在硬件中调用的操作,而on操作则可被硬件调用。
一旦报警器领域和硬件领域之间的接口被确定了,每个相对领域的更细节的分析就可以同时和独立地进行,因为每个领域都了解和其他领域之间的接口。例如我们现在可以开始分析报警器领域而不必知道将要用到的硬件的更多的任何信息。
对象模型图
对于报警器领域(AlarmPkg),我们可以设想有一个AlarmController类,它包含Keypad、Led和LightController类。这些类如下图:
图中显示AlarmController是一个复合类,包含了其他类的实例。我们也能看到选择显示了Keypad类的操作和属性。复合类是一个显示强聚合关系的有效方式。上例中如果一个AlarmController类的实例生成,就会生成一个叫做theKeypad的Keypad的实例,两个Led的实例分别叫theRedLed和theGreenLed,也生成LightController的一个叫theLightController的实例。如果AlarmController的实例被删除则其包含的实例也要被删除。
IHardware类有一些纯虚操作,为了测试AlarmController必须忽略这些操作。下图中显示了HomeAlarm包含了AlarmController类的一个实例和Hw类(从IHardware类继承且忽略了一些操作)的一个实例。
白盒场景
现在我们发现了几个类,可以开始将每个黑盒场景扩展,生成白盒场景。消息序列图再一次被用来捕捉上述类的实例在不同场景中的交互情况。例如下图中描述了户主设置警戒后离家的场景。
消息的斜率显示消息是同步(水平线)的或者是异步(斜线)的。从上图中我们可以看到这些类中的许多有行为,它们处理超时,对其他类操作调用也接收事件。在UML里,这个行为能用状态图规定。
小键盘的状态图
下面的状态图描述了小键盘的行为:经常在问题复杂时,会被分为几个更简单的问题。这就是对小键盘做的事情。并发状态可被使用,所以“密码输入(codeEntry)”能用一个并发状态图解决,而高一级的功能“戒备,解除戒备和重编程(armingDisarmingReprogramming)”可通过另一个并发状态图解决。可以看到在状态图里可以输入动作的C++代码,也可将不同的操作叫做例如像isCodeEntered(),changeCode(),…也可用到各种属性如count和newCode。
LED的状态图
下图中描述了LED类的行为。同样,在这个状态图中用到复合状态。在on和off状态中,符号>显示这里有进入/退出动作。例如在on状态有如下的动作:
操作和属性
我们可以看到许多类有属性和操作,这些可以规定如下,注意实际的C++代码包含在模型中。
像Rhapsody这样的工具可以根据UML模型自动生成代码,当然经常是其中一些代码要手工修改,如类的操作和状态图的动作,这些可以在工具中完成(如上图)。
产生代码
UML是严格定义的,足以从状态图和对象模型图自动地生成代码。每次做面向对象的项目时,需要建立某些类型的框架。这些框架需要可能实现一个处理状态图的机制,可能有与OS接口的机制,也要描述如何实现关系,不同线程上的类间如何通信,如何处理超时,…
这个框架可以提供实现应用的大约全部代码量的百分之六十到九十。试想开发一个Windows程序不用MFC框架的情况。Rhapsody提供了一个框架(为嵌入实时项目设计和优化)使嵌入实时系统程序员可以专注于应用而不是底层的实现。当使用了这个框架后,自动生成代码变得高效和相对直接。Rhapsody框架也包含了一个抽象的操作系统接口使模型可独立于实际使用的OS。
一旦其余的图画完,并且不同的操作代码体也完成了,我们就可以生成代码和测试模型。Rhapsody里我们需要建立一个组件和配置告诉Rhapsody从什么类中生成代码和应用在什么操作环境中,通常刚开始时,我们使用Microsoft环境(Windows操作系统和Visual
C++编译器)。那时就可以生成代码并在Rhapsody中编译成可执行文件。下图中是AlarmController类生成的代码的一个摘要:
UML模型验证
Rhapsody会在生成的代码中通过一些手段使其在运行时向工具返回一些信息,使模型活动起来。这样就允许程序员可以在设计级就可以进行调试。可以在开发的任何阶段进行,甚至是第一天程序员没有写一行代码的时候。设计级调试可以使问题在设计周期的早期就可以被发现并马上改正,所以可将产品更快地推向市场。
在上图中我们可以看到一个活动的状态图显示了Keypad类的实例在空闲(idle)状态和密码输入(code entry)状态。激活的状态通过高亮显示。
事件可以手工插入进来,象下面这样的活动的顺序图可以用来自动捕获事件发生时实例间的交互情况。
这个活动的顺序图可以检查或者甚至在分析阶段可与手工画的顺序图相比较以保证模型的正确。这种比较当然可以让Rhapsody自动进行。
用简单GUI(图形界面)测试UML模型
警报器领域一经过手工插入事件并观察看有什么事发生的测试后,可以建立一个简单的GUI(图形界面),例如用微软的Visual C++。用于建立这个GUI的类可以从IHardware类中继承,特别是“set”操作和当一个按键按下时调用“on”操作。这个GUI可以用于驱动模型又可以进行设计级的调试。
使用这个GUI也使客户和市场方面验证产品是否如其期望成为可能。模型任何必要的修改可以很容易而且简单地按一下按钮就可以生成新的可执行程序。
第二部分:目标操作系统(VxWorks)
一旦家用报警器通过设计和验证,就可以设定目标为VxWorks操作系统,比如说运行在一块通过I/O板连有小键盘、两个LED、一个警笛、一个移动检测器和门开关的ns486主板上。小键盘、移动检测器和门开关这些硬件都产生中断。
一个新的继承自接口类IHardware并有“set”操作的HwIrq类被建立。这些操作代码体可以在Rhapsody中写。要向I/O板进行写操作,用到了VxWorks的sysOutByte()操作。
构造子建立一个输出端口和两个输入端口。也用到了VxWorks的intConnect()操作。所以当第三中断(IRQ3)发生时调用isr()例程。最后建立一个事件标识给isr例程用。isr()例程是个静态操作,只是简单地在读操作等待时设置事件标识。读操作用到了VxWorks的另一个sysInByte()操作来读入输入信号。
在Rhapsody中我们需要建立一个新的组件和配置来告诉Rhapsody哪些类要生成代码和运行在什么环境中,在这里,我们用VxWorks486的环境(VxWorks
OS for 486和gnu编译器)。然后代码可以在Rhapsody中生成并被编译成可执行文件可以下载到Tornado调试器执行。
如果代码生成时被设置过,Rhapsody就可以像前面描述的一样,允许对模型进行设计级调试。Tornado可用于源代码级调试,同时Rhapsody可以用于设计级调试。
HwIrq类被设置为活动类,所以有自己的线程,这个线程的参数可以在Rhapsody中简单地配置,如图中设置线程名为“tRhpHw”,堆栈大小为4096bytes和180优先级。
最后,可以从模型生成不含任何其他设置的代码,就可以用经典的Tornado方式进行调试,可能在发布前还要用到WindView 来检查系统性能。
文档也可以通过按一下按钮从模型中产生,项目做完后谁会喜欢花时间(或者确实有时间)做文档啊。
结论
本文说明了如下信息:
1. Rhapsody如何用来进行UML建模
2. 如何使模型能够进行设计级调试
3. 如何使模型可以通过外部的GUI进行调试
4. 如何使模型用于运行VxWorks的特定硬件平台
|