# 交互约定(客户端&服务端)

客户端通过指令和事件和iFLYOS云端交互。管理输入输出优先级的规则就是交互模型。

# 事件上报

事件是从设备端发送给iFLYOS的消息,通知服务端设备上发生了某些变化。最常见的事件是来自用户的语音请求。

# 生成事件

事件由两个部分组成,一个是可选的上下文内容,一个是必选的事件内容。

{
    "context": [...],
    "event": {
        "header": {...},
        "payload": {...}
    }
}

# 上下文内容

事件上报时,上下文字段是由若干上下文项组成的数组,用于描述设备当前的状态。例如以下内容用于描述扬声器的相关状态

{
    "header": {
        "namespace": "Speaker",
        "name": "VolumeState"
    },
    "payload": {
        "volume": 78,
        "muted": false
    }
}

多个不同的描述组成了上下文内容。

# 事件内容

事件的样例如下:

header
┣ namespace         事件类型
┣ name              事件具体名称
┣ dialogRequestId   会话 id
┗ messageId         事件消息 id
payload
┣ ...               ...
┗ ...               ...

header用于描述事件

  • 事件的具体分类由namespacename来确定,用于标识事件内容所属分类。
  • 事件内容通过dialogRequestIdmessageId来标识唯一性,dialogRequestId决定了与该事件相关联的会话中所返回指令集的dialogRequestIdmessageId用于标识事件本身。设备端建立事件可通过随机算法来生成dialogRequestIdmessageId,并需要保证其唯一性

payload存放事件的具体数据

# 指令执行

# 概述

设备端在使用过程中会接收到iFLYOS下发的指令,并需要按照约定好的逻辑执行这些指令。所有处于同一会话中的指令有顺序地都包含在一个集合中,称为指令集。

# 内容解析

指令的header包含指令内容描述:

header
┣ namespace         指令接口类型
┣ name              指令接口具体名称
┣ dialogRequestId   会话 id
┗ messageId         指令消息 id

其中,接口类型及接口具体名称用于确定具体的执行指令,例如namespaceAudioPlayernameplay的指令,应当指令音频播放接口的播放能力。 header中携带同一dialogRequestId的指令视为同一指令集的指令。包含dialogRequestId的指令属于用户上报事件(例如SpeechRecognizer中的Recognize事件)触发的指令集,此时dialogRequestId与上报的事件中的dialogRequestId相同;不包含dialogRequestId的指令应当立即执行。 messageId是一个指令的唯一标志。

# 执行顺序

在同一指令集中的指令,应当按照接收顺序依次执行,上一个指令执行完毕才可以开始执行下一个指令。特别地,对于SpeechSynthesizerSpeak指令,应当等待播放完成之后才视为指令执行完毕。 当设备端正在执行尚未完毕的指令集时,如果接收到不同dialogRequestId的指令,应当终止并抛弃尚未执行完毕的指令,并开始执行新的指令集。 不论何时,接收到不带dialogRequestId的指令时,设备端应当立即执行该指令,且在资源不冲突的情况下不影响其他正在执行或待执行的指令。

注意

当指令无法正确解析或者执行的时候,需要上报System接口中的ExceptionEncountered事件。

# 客户端交互

客户端和云端的交互有两种方式:

  1. 一般情况下,交互从客户端开始。客户端发送事件到云端,云端处理事件并返回 0~n 个指令给客户端。例如:用户询问:“现在几点?”,客户端流传用户语音至服务端,iFLYOS处理该事件后,将下发一个播放TTS的指令给客户端。
  2. 有的时候,交互也会从云端开始。客户端会在没有上报事件的情况下收到云端下发的指令。例如:当一个用户通过关联APP调整了音量,虽然客户端(设备)没有发送任何事件给云端,但设备将会收到调整音量的指令,此时客户端也需要根据云端指令做出相应的操作。

注意

  1. 当iFLYOS更新时可能会造成客户端接收到新的/未知的指令。这就要求你的代码需要弹性面对这些变化,当收到未知指令时设备不会出现错误。
  2. 当iFLYOS添加新特性时,可能会在保证原有指令正常运行的情况下,在指令的payload中增加新参数。这就要求你的代码需要弹性面对这些变化,当收到的指令payload中包含未知的参数时,客户端不会出现错误。

# 语音请求的生命周期

你需要确保在任何时间,客户端都只有一个活跃的语音请求。这要求你的客户端在每一次Recognize事件中必须生成一个唯一的dialogRequestId,并跟踪活跃的dialogRequestId,这个dialogRequestId将会出现在服务端下发的对应指令中。

注意

在一个会话中,不要重复使用dialogRequestId,每一次Recognize事件都需要生成新的唯一的dialogRequestId

在下一次上报Recognize事件时(无论是新的请求还是云端下发了expectSpeech指令),都必须生成新的dialogRequestId,与之前活跃的dialogRequestId相关联的指令必须执行或丢弃。

上报事件至云端时,客户端需遵循以下原则:

  • 对每一个Recognize事件,你都需要创建一个唯一的dialogRequestId,并在一个会话内,dialogRequestId不能重用。
  • dialogRequestId必须包含在Recognize事件的header中。
  • dialogRequestId在下一次Recognize事件前都需要是活跃状态。当新的Recognize事件发生时,和旧dialogRequestId相关联的所有指令都需要被执行或丢弃。

接收云端指令时,客户端需要遵循以下原则:

  • 云端下发的指令header中包含dialogRequestId,和当前活跃的dialogRequestId一致,这些指令需要按顺序执行。
  • 没有带dialogRequestId的指令需要被立即执行。
  • 接收到未知指令时,客户端需要上报ExceptionEncountered事件,但客户端本身不应该向用户报错。
  • 接收Speak指令时,需要完成播放关联音频,除非播放过程被新的指令打断。

# 音频通道

设备音频可以分为三个通道:对话通道,提醒通道和内容通道。通道用来管理音频输入输出的优先级,每一个通道都有活跃和非活跃状态。

通道和设备能力接口的对应关系:

通道 设备能力接口
对话通道 语音识别、语音合成
提醒通道 提醒
内容通道 音频播放器

当有语音输入或输出时,对话通道活跃;当闹钟/提醒/计时器开始响铃时提醒通道活跃;当有音频播放时内容通道活跃。

通道分为前景(foreground)状态和背景(background)状态,在任何时间,只能有一个通道在前景。当多个通道都活跃时,按照通道优先级:对话通道 > 提醒通道 > 内容通道,优先级高的通道在前景,优先级低的通道在背景。在最高优先级的播放通道播放完毕并退出活跃状态后,下一优先级的播放通道从背景移到前景。

客户端需要遵循的音频通道原则:

  • 不活跃的通道永远在背景
  • 对话通道活跃时一定在前景。
  • 提醒通道只有在对话通道不活跃时会在前景。
  • 内容通道只有在其他通道都不活跃时才会在前景。
  • 当前景通道变为非活跃时,下一个优先级的活跃通道变为前景。