1、数据采集 _气象监测站,先启,1,精化,2,构造,3,后移交,4,1.先启,什么是数据采集系统? 自动收集数据通常使用传感器或设备实现。这种数据采集通常涉及处理信号和波形来获得所要的信息。数据采集系统的组件包括适当的传感器,用来转换任意被测量的参数为电子信号,电子信号由数据采集硬件获取。所开发的控制软件解释信号,以便分析和显示。,1.1气象监测站需求,测量数据:风速和风向温度气压湿度导出数据:风冷度露点温度温度趋势气压趋势,1.2定义问题的边界,为了限定问题要做以下策略性的假定:处理器时钟传感器标柱键盘显示器定时器,先启,用下面的部署图来说明硬件平台,先启,在这个问题上,我们已经选择抛弃一些硬
2、件,这样就可以更好地聚焦在系统软件上。显然,去掉一些硬件(如去掉一些用户输入和图形设备的硬件)就可能需要较多的软件。下面我们要进行分析有些对象的行为,先启,可以设计出一个简单的类来访问当前的时间日期。这个类负责跟踪当前的时间和日期,包括时、分、秒、月、曰和年。我们的分析可能会决定将这些责任转变为两个服务, 分别表示为操作currentTime和currentDate。操作currentTime返回以下格式的 字符串。 13:56:42 表示当前的时、分和秒。操作currentDate返回以下格式的字符串: 6-10-93 表示当前的月、日和年。,先启,进一步分析可以得出一个更加完善的抽象,允许
3、客户选择12小时制或24小时制的时间格式,我们可以为这种抽象提供一个另外的更改操作setFormat。 因此,时间和日期类的责任必须包括设定日期和时间。完成这个责任需要新的服务集来设置时间和日期,我们通过以下操作提供: setHour、setMinute、setSecond、setDay、 setMonth和setYear。,先启,下面总结一下时间日期类的抽象。 类名:TimeDate 责任:跟踪当前的时间日期。 操作: 属性: currentTime time currentDate date setFormat setHour setMinute setSecond setMonth se
4、tDay setYear,先启,初始化之后类的实例重新设置它的time和date属性,然后无条件地进入Running 状态,运行在24-hourmode状态下。一旦在Running状态,setFormat操作可以将对象的运行模式在12-hourmode和24-hourmode之间切换。,先启,类TemperatureSensor相当于一个硬件温度传感器的模拟。孤立的类分析可以得到这个抽象初步的外部视图。 类名:TemperatureSensor 责任:跟踪当前温度。 操作:currentTemperature setLowTemperature setHighTemperature 属性:te
5、mperature,先启,假定每个温度传感器值用一个定点数表示,它的低点和高点可以校正到适合已知的实际值,在这两点之间用简单的线性内插法将中间的数字转换为实际的温度,如图11-3所示。,先启,气压传感器的抽象: 类名:Pressure Sensor 责任:跟踪当前气压。 操作:currentPressure setLowPressure setHighPressure 属性:pressure,先启,湿度传感器的抽象: 类名:HumiditySensor 责任:跟踪当前湿度,表示为饱和度百分比,范围是 0%100%。 操作:currentHumidity setLowHumidity setHi
6、ghHumidity 属性:humidity,先启,系统需求可以发现,一些行为是类TemperatureSensor、Pressure Sensor和HumiditySensor共有的。所以我们建议创建一个公共的超类HistoricalSensor,负责提供这个公共的行为。 类名:HistoricalSensor 责任:报告过去24小时内的最高和最低值。 操作:highValue lowValue timeOfHighValue timeOfLowValue,先启,风速传感器的抽象:类名:WindSpeedSensor 责任:跟踪当前风速。 操作:currentSpeed setLowSpee
7、d setHighSpeed 属性:speed,先启,对上面4个具体类 (TemperatureSensor、Pressure Sensor、HumiditySensor 和WindSpeedSensor)做快速的领域分析,创建一个更高一级的超类CalibratingSensor来负责这个行为,它的规格说明如下。 类名:CalibratingSensor 责任:给定两个已知数据点,提供线性插值的值。 操作:currentValue setHighValue setLowValue,先启,风向传感器的抽象: 类名:WindDirectionSensor 责任:跟踪当前风向,表达为罗盘图上的点。
8、操作:currentDirection 属性:direction 图11-4说明了这个完整的层次结构。,图11-4说明了传感器类完整的层次结构,先启,显示面板的抽象: 类名:LCDDevice 责任:管理LCD设备,为显示某些图形元素提供服务。 操作: drawText drawLine drawCircle setTextSize setTextStyle setPenSize,先启,最后一个需要考虑的边界类是关于定时器的。由于要截取定时事件,Timer类应当是一个主动抽象,这就意味着它处于控制线程的根部。下面是这个类抽象的规格说明。 类名:Timer 责任:截取定时事件,相应分派回调函数。
9、 操作:setCallback(),1.3场景,我们已经在系统边界处建立了抽象,现在通过研究几个使用场景来继续分析。首先列出一些主要用例 (参见图11-7)监测基本测量数据;监测导出测量数据;显示最高值和最低值; 设置时间和日期; 校正传感器; 启动系统; 电源故障; 传感器故障;,先启,2.精化,2.1气象监测系统用例 监测基本的气象测量数据是气象监测系统的首要功能点。其中一个系统约束是:不可能在1秒内测量60次以上。我们提出了以下采样速率,这些速率能够充分地捕获气象状况的改变: 风向:每0.1秒 风速:每0.5秒 温度、气压和湿度:每5分钟,精化,精化,图11-8所示的交互图阐述了这个场景
10、。从图中可以看出,当代理开始采样时,它依次查询每一个传感器,但为了降低采样速率故意跳过了一些传感器。我们采取轮询传感器而不是让传感器作为一个控制线程,这样系统的执行就是可预测的,因为代理可以控制事件流。这个名字也反映了它在系统行为中的位置,所以我们让这个代理成为类Sampler的一个实例。,精化,必须通过询问交互图的对象中哪一个对象负责将采样值显示在类LCDDevice的一个实例上,来继续这个场景。最终,创建一个分离的对象负责这个行为,因为它将所有关于显示布局的设计决策封装到一个类中。这样,就可把下面的类规格说明加进分析产物中。,精化,类名:DisplayManager 责任:管理LCD设备上
11、项目的布局。 操作:drawStaticItems displayTime displayDate displayTemperature displayHumidity displayPressure displayWindChill displayDewPoint displayWindSpeed displayWindDirection displayHighLow,精化,图11-9提供了一个类图来说明这些抽象必须协作完成这个场景。同时,图中也显示了每一个抽象在与其他类关联时所扮演的角色,精化,精化,为什么将WindChill和DewPoint定义为类,而不是通过一个简单的非成员函数来完成
12、它们的计算? WindChill和DewPoint的实例提供某种行为,并封装一些状态,它们各自都有唯一的标识 。通过 “对象化”这些貌似算法的抽象,可以得出一个更加可重用的架构,WindChill和DewPoint可以从这个特定应用中被提升,因为对于客户它们呈现出清晰的契约,相对于其他抽象,每一个都提供清晰的分离关注。,精化,下一步要考虑的是涉及用户与气象监测系统交互的不同场景。软件分析人员提供的基本信息是原型化工作,而且这确实是基本的,它有助于减轻设计用户界面的风险。而且,根据面向对象架构来实现我们的决策,使得改变这些用户界面决策要相对容易一些,不用破坏设计的机理。考虑一些可能的用户交互用例
13、场景。,精化,用例名:DisplayMaxandMinValueofMeasurements 叙述:这个用例显示所选测量数据的最高值和最低值 基本流: (1)当用户按下SELECT键时,用例开始; (2)系统显示SELECTING; (3)用户按下WIND、TEMP、PRESSURE或HUMIDITY键 中的任何一个,其他按键 (除RUN外)被忽略; (4)系统闪烁相应的标签; (5)用户按下UP或DOWN键来分别选择显示24小时中的最高 值或最低值,其他的按键(除RUN外)被忽略; (6)系统显示所选值,同时显示该值出现时的时间; (7)控制返回步骤(3)或步骤(5)。,精化,设置时间和日期
14、的场景与上面的场景相似。 用例名:SetDateandTime 叙述:这个用例设置日期和时间。 基本流: (1)当用户按下SELECT键时,用例开始; (2)系统显示SELECTING; (3)用户按下TIME或DATE键中的任一个,其他按键(除RUN 和上面场景的步骤(3)所列出的键外)被忽略; (4)系统闪烁相应的标签,同时闪烁选择项的第一个字(即 时间的小时字段和日期的 月份字段): (5)用户按下LEFT或RIGHT键来选择另外的字段 (选择可以 来回移动),用户按下UP或DOWN键来升高或降低被选 中的字段的值; (6)控制返回步骤(3)或步骤(5)。,精化,使用以下用户动作的相关模
15、式来校正特定的传感器。 用例名:CalibrateSensor 叙述:这个用例用于校正传感器。 基本流: (1)当用户按下CALIBRATE键时,用例开始; (2)系统显示CALIBRATING; (3)用户按下WIND、TEMP、PRESSURE或HUMIDITY键中 的任何一个,其他按键 (除RUN外)被忽略; (4)系统闪烁相应的标签; (5)用户按下UP或DOWN键来选择高校正点或低校正点: (6)显示器闪烁相应值; (7)用户按下UP或DOWN键来调整选中的值; (8)控制返回步骤(3)或步骤(5)。,精化,最后一个涉及用户界面的主要场景和设置测量单位如下。 用例名:SetUnito
16、fMeasurement 叙述:这个用例用于设置温度和风速的测量单位。 基本流: (1)当用户按下MODE键时,用例开始; (2)系统显示MODE; (3)用户按下WIND、TEMP键中的任何一个,其他按 键 (除RUN外)被忽略: (4)系统闪烁相应的标签; (5)用户按下UP或DOWN键来切换当前的测量单位; (6)系统更新选中项的测量单位 (7)控制返回步骤(3)或步骤(5)。,精化,通过研究这些场景,可以确定面板上按钮的排列图11-11所 示 。设计一个新类InputManager来 负责 完 成下 面 契 约 性 的规 格 。 类名 :InputManager 责任:管理和分派用户输
17、入。 操作:processKeyPress,精化,如图11-12所示,这个类的最外层状态机图包括4个状态:Running、Calibrating、Selecting和Mode。这些状态直接对应于前述的场景,根据Runmng状态时截获的第一个按键来转换各自的状态,当最后的按键是Run时返回Rlmning状态。每次进入Running时,显示板上的消息被清除。,精化,最后一个主要场景涉及系统启动,下面是针对这个场景作出分析后编写的脚本。 用例名:PowerOn 叙述:启动系统。 基本流: (1)当电源接通时,用例开始; (2)构造每一个传感器。有历史数据的传感器清除其历史数据,趋势 传感器准备好它们
18、的斜率计算算法: (3)初始化用户输入缓冲区,无用按键 (由噪音引起)被抛弃; (4)绘制静态的显示元素; (5)初始化采样过程。 后置条件:每一个主要测量数据的过去高低值被设置成首次采样的值和间。 温度和气压趋势斜率为O。 InputManager处于Running状态。,2.2架构框架,在数据采集和过程控制领域,有许多可以遵循的架构模式,其中两个最普遍的模式是自动执行者同步和基于时间帧的处理。 虽然气象监测系统不是硬实时系统,但是它确实需要少量可预测和有序的行为。出于这个原因,我们转向另外一种基于时间帧的处理模式。,精化,如图11-13所示,这个模型是基于时间的,它将时间分成若干帧 ,帧又
19、可以更进一步被分成子帧,每个子帧包含一些功能行为。从一个帧到另外一个帧的活动可能不同。例如,可以每隔10个帧进行一次风向采样,每隔30个帧进行一次风速采样。这种架构模式的主要优点是能够更严格地控制事件的顺序。,精化,图11-14提供了一张类图,表达了气象监测系统的架构。从图中可以看到我们在早期分析时发现的大多数类,主要的不同点是,现在是展示所有关键抽象之间是怎样协作的。如同产品系统类图中常见的,我们不 (也不能)展示每个类和每个关系。,3.构造,3.1帧机制 我们从精化类Timer的接口开始介绍,该类分派回调函数。图11-15展示了类设计。,构造,在转到Sampler类之前,先引入一个新的声明
20、,用来命名这个特定系统中的不同传感器。枚举类SensorName包含了系统里的所有传感器的名称列举。图11-16展示了Sampler类的接口。,构造,为了将Timer和Sampler类粘在一起,我们需要少许的C+粘合代码。首先声明一个Sampler类的实例和一个非成员函数,代码如下。 Samplersampler; voidacquire(Tick t) Sampler.sample(t); 现在我们可以写一个主函数片段,它仅仅将回调函数加到定时器并启动采样过程代码如下。 main() Timer:setCallback(acquire); Timer:startTiming(); ,构造,继
21、续讨论系统架构,下一步为Sensors类提供一个接口(见图11-17)。我们假定存在不同的具体传感器类。,构造,在Sampler类里指定与Sensors和DisplayManager类的关联,并修改Sampler类的一个实例的声明如图11-18所示。,构造,我们已经精化了架构的这个元素,图11-19所示为突出显示这个框架机制的新类图。,3.2发布计划,首先,我们提出一个发布版本序列,它们中每一个都是建立在以前的版本之上: 开发一个具有最小功能的发布版本; 完成传感器的层次结构; 完成负责管理显示的各个类; 完成负责管理用户界面的各个类。,构造,在构造系统架构的过程中,我们已经看到如何迭代和增量
22、地演化从分析时就开始的传感器类的抽象。在这个演化的发布版本中,我们期望在早期最小功能系统的基础上完成这个类层次的细节。我们可以完成Sensor类的设计(见图11-20)。,构造,直接子类CalibratingSensor的声明建立在Sensor基类之上(见图11-21)。,构造,下面考虑子类HistoricalSensor的声明,它基于类CalibratingSensor(见图11-22)。,构造,TrendSensor继承自HistoricalSensor,并增加了一个新责任 (见图11-23)。,构造,最后,得出具体的子类,如TemperatureSensor(见图11-24)。,3.4显
23、示机制,下一个发布版本的实现将完成类DisplayManager和LCDDevice的功能。实际上,这不需要新的设计工作,只需要对某些函数的签名和语义做出一些战术决策。结合在分析第一个架构原型时做出的决策 ,得到图11-25所示的具体接口。,3.5用户界面机制,最后一个主要发布版本的焦点是类Keypad和InputManager的战术设计和实现。我们以一个声明作为开始,这个声明的作用是用问题空间的词汇来命名物理键。一个枚举类Key的定义如图11-26所示我们用k作为前缀来避免同SensorName中定义的名字发生冲突。接下来,可以捕获Keypad类的抽象,如图11-27所示。,构造,构造,类I
24、nputManager有一个类似的稀疏接口如图11-28所示。正如图11-14中所说明的那样,Sampler、InputManager和Keypad类的实例协作响应用户输入。为了集成这三个抽象,必须巧妙地修改Sampler类的接口,使其包含一个新的对象repInputManager(见图11-29)。,4.后移交,创建一个新类RainFallSensor更新枚举SensorName; 更新DisplayManager; 更新InputManager; 系统的Sensors集合中增加这个类的实例。 创建一个新类SerialPort; 创建一个新类ReportManager; 修改Sampler:sample的实现;,Thank You !,