数字电话正在迅速普及,这为语音通信提供了新的技术。在本文里,我将探讨一种用于电信行业的新xml语言。
State Chart xml(SCXML)是(W3C语音浏览器工作组目前正在开发的)VoiceXML 3.0里的控制语言的一个候选对象。Voicexml 3.0包括CCxml 2.0(预计将由语音浏览器工作组在2006年进行开发)和多模编辑语言(正在由多模交互工作组开发)。
它是一种通用状态机语言,能够用在多种环境下,包括Voicexml 3.0、CCxml 2.0和多模交互框架(MultiModal Interaction framework)。
首先,我们将简要介绍这个开发项目的目的,然后再仔细看看SCxml批注,最后我们将探讨Harel State Table批注逻辑及其在SCxml中的体现。
项目目的
新语言所提供的状态机批注结合了CCxml和Harel State Table的概念。
CCXML是一门基于事件的状态机语言,它设计用来支持语音应用程序(尤其包括但不仅限于Voicexml)的呼叫控制功能。CCxml 1.0规范同时定义了状态机和事件处理句法,以及呼叫控制元素的标准化集合。CCxml语言不是本文讨论的范围,我将在另外的文章里专门讨论这门语言。
Harel State Table就是状态机批注,它们也是统一建模语言(UML)的一部分。它们为复杂的构建,比如并行状态,提供了一个清晰、成熟的语义。但是,它们被定义为一门图形规范语言,因此没有xml表示。事件处理模型以及状态和转换的语义都在UML规范有具体的定义。虽然UML也不是本文讨论的范围;但是我们还是要回顾一下UML最基本的一些概念以及SCxml中与之相对应的概念。
SCxml规范的目标是将Harel语义与一种xml句法结合在一起,这种句法是CCxml的状态和事件批注的一个逻辑扩展。SCxml的使用方法有很多种,包括但不仅限于下面几种:
Harel State Table批注和语义
为了简化说明过程,让我们来看看UML定义的状态机的最重要特性,以及它在SCxml中的体现。下面所有的示例都取自SCxml规范。
图A显示的是一个相当简单的状态机,它有三个状态S1、S2和S3。它对应的SCxml也很直观简单,相当容易理解(example1.txt)。这些状态由转换来连接,由事件来触发。
|
图A |
|
|
|
简单的状态机 |
示例1
<?xml version="1.0" encoding="us-ascii"?>
<scxml version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initialstate="S1">
<state id="S1">
<transition event="Event1" target="S2"/>
</state>
<state id="S2">
<transition event="Event2" cond="X>0" target="S1"/>
<transition event="Event2" cond ="X<0" target next="S3"/>
</state>
<state id="S3">
</state>
</scxml>
在这个例子里,状态会在Event1发生的时候从S1转换到S2。这是系统从S1转换到S2的唯一途径,因为它是这两个状态间唯一的转换。当系统处于S1状态时,其它所有事件会被忽略。
S2状态里的逻辑稍微复杂一点。它有两个转换,都是由事件Event2触发。两个转换都有检查变量X的值的警戒条件。如果X < 0,那么当Event2发生的时候,状态S2就会转换为S3。如果X > 0,状态就会转换回S2。要注意的是,没有条件来考虑X = 0的情况。因此如果当Event2发生的时候X = 0,那么系统仍然会保持在S2状态。就在状态S1里一样,所有在转换过程中没有涉及到的事件都会被忽略。在上面的例子里,只用到了<state>和<transition>这两个元素。
Harel State Table可以嵌入代码,用于执行进入或者退出状态。在图B里,我们看到了与图1相同的三个状态,但是S1有一个用来减少X的OnEntry处理程序,而S2也有一个用来减少X的OnExit处理程序。S3同时拥有OnEntry和OnExit处理程序,两个都用来增加X。
|
图B |
|
|
|
新添加的OnEntry和OnExit |
现在假设X当前的值是2,系统进入S1状态。当Event1发生的时候,系统会转换到S2状态,这就和前面一个例子一样。现在,当Event2发生的时候,X等于1,所以系统将转换回S1状态。当系统退出S2状态的时候,OnExit处理程序就把X的值减为0。要注意的是,这发生在转换被选择、警戒条件被计算之后。示例2里是相应的SCxml代码(example2.txt)。我们可以看到<onexit>和<onentry>这两个新元素提供了状态转换处理程序,而<datamodel>元素提供了用在图表里的数据模型。这些数据模型将在下面讨论。
示例2
<?xml version="1.0" encoding="us-ascii"?>
<scxml version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initialstate="S1">
<datamodel>
<data name="X" expr="2"/>
</datamodel>
<state id="S1">
<onentry>
<assign name="X" expr="X--"/>
</onentry>
<transition event="Event1" target="S2"/>
</state>
<state id="S2">
<transition event="Event2" cond= "X>0" target="S1"/>
<transition event="Event2" cond ="X><0" target="S3"/>
<onexit>
<assign name="X" expr="X--"/>
</onexit>
</state>
<state id="S3">
<onentry>
<assign name="X" expr="X++"/>
</onentry>
<onexit>
<assign name="Y" expr="0"/>
</onexit>
</state>
</scxml>
Harel State Table还包括复合状态的概念,也就是有亚状态(example3.txt里的substate)。在这个例子里,S1有连续的亚状态。当状态机处于带有亚状态的状态时,它必须也只能处于其中的一种亚状态。亚状态有一种“Or”语义,可以被看作代表父状态的分解。将系统变到S1状态的转换可以直接将S11或者S12指定为其“目标(target)”。然而,为了处理它无法处理的情况,S1带有一个<initial>元素,用来定义默认的亚状态,当转换将S1指定为其目标时,它就会开始起作用。
示例3
<?xml version="1.0" encoding="us-ascii"?>
<scxml version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initialstate="S1">
<state id="S1">
<state id="S11">
</state>
<state id="s12">
</state>
<initial>
<transition target next="S11"/>
</initial>
</state>
</scxml>
除了连续的亚状态,Harel State Chart还提供了并发亚状态(concurrent substate),代表一种“And”逻辑。当状态机处于一种带有并发亚状态的状态时,它必须同时处于每一种并发子状态。
根据基本的Harel语义,S1和S2会独立运行,所以S1会从S11转到S12再到S13而不管S2在做什么,反之亦然。但是现在假设我们想要确保S2不会从S22转到S23,除非S1离开S11。图C显示了我们可以如何使用<join>状态达到这个目的。相应的SCxml代码见example4.txt。
|
图C |
|
|
|
添加<join>状态 |
示例4
<?xml version="1.0" encoding="us-ascii"?>
<scxml version="1.0" xmlns="http://www.w3.org/2005/07/scxml" initialstate="S1">
<state>
<parallel>
<state id="S1">
<state id="S11">
<transition target next ="S12 synch1"/>
</state>
<state id="S12">
<transition target next="S13"/>
</state>
<state id="S13"/>
</state>
<state id="S2">
<state id="S21">
<transition target="S12"/>
</state>
<state id="S22>
<transition target="j1"/>
</state>
<state id="S23"/>
<join id="j1">
<transition target="S23"/>
</join>
</state>
</parallel>
</state>
</scxml>
我们在S22和S23之间插入一个<join>伪状态,同时还加入了一个从S22的无条件向内转换(incoming transition)和一个向S23的无条件向外转换(outgoing transition)。(<join>伪状态用一个带有多个向内转换和一个向外转换的竖线来表示。)然后我们用转换离开S11,向它加入分支,以便让<join>状态成为另外一个目标。(在图表里,这用一个‘带分叉’的竖线来表示,用来表示一个向内转换和多个向外转换。在我们的SCxml批注里,我们在“目标”属性里用了多个值。)
现在当状态机达到S22状态时,它会等待,直到<join>状态被激活(这样就准备好进行转换),然后进入<join>状态并转换到S23。类似的,当S1准备离开S11状态时,它会等待,直到S2达到S22状态,然后才进入<join>状态。在上面两种情况里,<join>状态的结果导致S1和S2相互等待,然后才同时进入S12和S13状态。