# 消息格式

当设备和 IVS 建立连接之后,设备和 IVS 云端之间通过 HTTP/2 进行通讯。一个典型的交互流程是,设备向云端发出 event(事件)表示当前发生了什么事,云端向设备返回 directive(指令)以告诉设备应该做什么。下图为发送给 IVS 的识别事件,其中 Multipart 消息由两个部分组成:一个 JSON 格式的事件本体,以及一个从设备麦克风捕获的音频数据。

关于创建和维护连接的文档,请参阅传输协议章节。

# HTTP/2 消息头部

每一个向 IVS 发起的请求,都要求以下头部:

Method: {{verb}}
Scheme: https
Path: /{{events or directives}}
Authorization: Bearer {{access_token}}
Content-Type: multipart/form-data; boundary={{boundary_term}}

# Method

iFLYOS支持以下请求方法

  • POST 向云端发送事件采用POST
  • GET 用指令建立下行通道采用GET

# Scheme

访问 IVS API 的协议头部为 HTTPS

# Path

所有的事件上报通过以下路径:

Path: /v20180810/events

所有的下行指令订阅通过以下路径:

Path: /v20180810/directives

# 鉴权

当用户授权你的产品代表他们访问 IVS 时,使用讯飞账号登录会返回一个 access_token。设备调用 IVS 的每一个请求都必须在请求头包含 access_token

Authorization: Bearer {{access_token}}

# Content Type

Content-Type描述的是包含在请求体中的内容。这个描述让 IVS 可以正确地处理请求数据。Content-Type 应当始终为 multipart/form-data,请求体通过边界符(boundary term)来区分 Multipart 消息的不同部分。在同一个请求中需要保证只使用一个边界符,且不会出现在消息内容中。

Content-Type: multipart/form-data; boundary={{boundary_term}}

# HTTP/2 Multipart 消息

IVS 能接收并处理 HTTP/2 编码的 Multipart 消息。Multipart 消息由一个或多个 JSON 格式的指令/事件以及相关的二进制音频附件(如果需要)组成。

# JSON 头部

每个 Multipart 请求的 JSON 部分都需要以下头部:

Content-Disposition: form-data; name="metadata"
Content-Type: application/json; charset=UTF-8

# JSON 内容

所有发送到iFLYOS的 JSON 格式的事件都按照以下格式:

{
    "event":{
        "header": {
            "namespace": "SpeechRecognizer",
            "name": "Recognize",
            "messageId": "e52e7a4c...",
        },
        "payload": {
        }
    }
}

每一个事件都有唯一的头部(header)和内容(payload)。在头部中,namespacename 标识接口和事件,messageId 是设备必须为每个请求生成的唯一标识符。

# 二进制音频头部

每一个携带二进制音频附件的 Multipart 请求需要以下头部:

Content-Disposition: form-data; name="audio"
Content-Type: application/octet-stream

# 二进制音频内容

Multipart 消息的这一部分代表二进制音频,只有识别事件需要携带音频附件。更多相关信息可以查阅 语音识别 接口。

# HTTP/2 响应

IVS 会向你的设备发送两种类型的响应。第一个类型,类似请求,由一个或多个 JSON 格式的指令及其相关的二进制音频附件(如果需要)组成的 Multipart 消息;第二种是异常消息,异常消息不是 Multipart 消息,并且在发送错误时才会返回给设备。每个异常信息包含一个错误代码及一个描述。

# 示例

# 示例 1

该示例展示了一个 Recognize 事件,并且响应内容为一个 Speak 指令。

# 请求

// 注意: HTTP/2 是一种非人类可读的二进制协议,以下消息体仅作示范

Method: POST
Scheme: https
Path: /v20180810/events
Authorization: Bearer {{access_token}}
Content-Type: multipart/form-data; boundary=this-is-a-boundary

--this-is-a-boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json; charset=UTF-8

{
    "context": [
        {
            "header": {
                "namespace": "AudioPlayer",
                "name": "PlaybackState"
            },
            "payload": {
                "token": "abcd1234",
                "offsetInMilliseconds": 7000,
                "playerActivity": "PLAYING"
            }
        },
        {
            "header": {
                "namespace": "Alerts",
                "name": "AlertsState"
            },
            "payload": {
                "allAlerts": [
                    {
                        "token": "foo-bar",
                        "type": "ALARM",
                        "scheduledTime": "2015-10-30T22:34:51+00:00"
                    }
                ],
                "activeAlerts": [
                ]
            }
        },
        {
            "header": {
                "namespace": "Speaker",
                "name": "VolumeState"
            },
            "payload": {
                "volume": 25,
                "muted": false
            }
        },
        {
            "header": {
                "namespace": "SpeechSynthesizer",
                "name": "SpeechState"
            },
            "payload": {
                "token": "zxcv8523",
                "offsetInMilliseconds": 0,
                "playerActivity": "FINISHED"
            }
        }
    ],
    "event": {
        "header": {
            "namespace": "SpeechRecognizer",
            "name": "Recognize",
            "messageId": "messageId-123",
            "dialogRequestId": "dialogRequestId-321"
        },
        "payload": {
            "profile": "CLOSE_TALK",
            "format": "AUDIO_L16_RATE_16000_CHANNELS_1"
        }
    }
}

--this-is-a-boundary
Content-Disposition: form-data; name="audio"
Content-Type: application/octet-stream

{{音频二进制数据}}
--this-is-a-boundary--

# 响应

Status: 200
Content-Type: multipart/related; boundary=this-is-a-boundary; type="application/json"

--this-is-a-boundary
Content-Type: application/json; charset=UTF-8

{
    "directive": {
        "header": {
            "namespace": "SpeechSynthesizer",
            "name": "Speak",
            "messageId": "ewq-321",
            "dialogRequestId": "dialogRequestId-321"
        },
        "payload": {
            "url": "cid:1234-5678-0123-4567-8901",
            "format": "AUDIO_MPEG"
            "token": "kr17447380422"
        }
    }
}

--this-is-a-boundary
Content-Type: application/octet-stream
Content-ID: <1234-5678-0123-4567-8901>

{{binary audio attachment}}
--this-is-a-boundary--

# 示例 2

该示例展示了一个 Recognize 事件,并且响应了一个 Speak 指令和一个 ExpectSpeech 指令。

# 请求

// 注意: HTTP/2 是一种非人类可读的二进制协议,以下消息体仅作示范

Method: POST
Scheme: https
Path: /v20180810/events
Authorization: Bearer {{access_token}}
Content-Type: multipart/form-data; boundary=this-is-a-boundary

--this-is-a-boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json; charset=UTF-8

{
    "context": [
        {
            "header": {
                "namespace": "AudioPlayer",
                "name": "PlaybackState"
            },
            "payload": {
                "token": "1234",
                "offsetInMilliseconds": "15874",
                "playerActivity": "PLAYING"
            }
        },
        {
            "header": {
                "namespace": "Alerts",
                "name": "AlertsState"
            },
            "payload": {
                "allAlerts": [
                    {
                        "token": "foo-bar",
                        "type": "ALARM",
                        "scheduledTime": "2015-10-30T22:34:51+00:00"
                    }
                ],
                "activeAlerts": [
                ]
            }
        },
        {
            "header": {
                "namespace": "Speaker",
                "name": "VolumeState"
            },
            "payload": {
                "volume": 25,
                "muted": false
            }
        },
        {
            "header": {
                "namespace": "SpeechSynthesizer",
                "name": "SpeechState"
            },
            "payload": {
                "token": "zlkv8723",
                "offsetInMilliseconds": 0,
                "playerActivity": "FINISHED"
            }
        }
    ],
    "event": {
        "header": {
            "namespace": "SpeechRecognizer",
            "name": "Recognize",
            "messageId": "bnm-123",
            "dialogRequestId": "dialogRequestId-201"
        },
        "payload": {
            "profile": "CLOSE_TALK",
            "format": "AUDIO_L16_RATE_16000_CHANNELS_1",
        }
    }
}

--this-is-a-boundary
Content-Disposition: form-data; name="audio"
Content-Type: application/octet-stream

{{音频二进制数据}}
--this-is-a-boundary--

# 响应

Status: 200
Content-Type: multipart/related; boundary=this-is-a-boundary; type="application/json"

--this-is-a-boundary
Content-Type: application/json; charset=UTF-8

{
    "directive": {
        "header": {
            "namespace": "SpeechSynthesizer",
            "name": "Speak",
            "messageId": "lkj-321",
            "dialogRequestId": "dialogRequestId-201"
        },
        "payload": {
            "url": "cid:1234-5678-0123-4567-8901",
            "format": "AUDIO_MPEG",
            "token": "sn2716057"
        }
    }
}

--this-is-a-boundary
Content-Type: application/octet-stream
Content-ID: <1234-5678-0123-4567-8901>

{{音频二进制数据}}
--this-is-a-boundary

Content-Type: application/json; charset=UTF-8

{
    "directive": {
        "header": {
            "namespace": "SpeechRecognizer",
            "name": "ExpectSpeech",
            "messageId": "fyr-212",
            "dialogRequestId": "dialogRequestId-201"
        },
        "payload": {
            "timeoutInMilliseconds": 8000
        }
    }
}
--this-is-a-boundary--