XCC API

XCC入门

在使用XCC接口控制时,请先确保本地安装的系统已经启动了 NATS,使用以下命令可以启动 NATS。默认端口:4222。调用地址:nats://xswitch-nats:4222。

make nats

NATS 简介

NATS[^nats]是一个消息队列,它实现了 Pub/Sub(生产者-消费者)消息机制,通过它也可以实现“请求-响应”式的 RPC(Remote Procedure Call,远程过程调用)交互。

[^nats]: 参见 https://nats.io/

系统配置检查

在使用该模块时需要先确认XCC 模块是否已经开启,点击【高级】⇨【模块配置】⇨【XCC】,点进去默认配置页面,如果页面详情显示“模块未加载”,则点击右侧“重载”按钮,启用该模块。

了解基本参数

XCC模块配置主要包含以下几个部分。

XCC-SETTINGS

主要参数说明

  • nats-url:程序调用的NATS 的地址,如 nats://127.0.0.1:4222,如果不配置则无法连接 NATS。
  • publish-events-subject-prefix:事件的 Subject 前缀,如 cn.xswitch.ctrl.event,比如,程序调用应答事件将会变为 cn.xswitch.ctrl.event.channel_answer 之类。
  • cdr-subject:CDR 专门的 Subject,用于将 CDR 送到专门的 Subject 上,比如,程序获取话单事件可直接订阅变为 cn.xswitch.cdr
  • debug:调试日志级别,0~7。
  • cdr-format:CDR的格式,可以在CDR中选择一种格式,目前支持CUSTOM。
  • xml‑handler‑bindings:XML 绑定,不同的Section以│分隔,如directory│config。

XCC-BINDINGS

事件绑定。绑定的事件会向 NATS 发送,ALL 会绑定所有事件。程序如果想要订阅事件时,可如下所示:

  • cn.xswitch.ctrl.event.channel_answer

通话相关事件可绑定如下事件:

  • CHANNEL_CREATE //通话创建事件
  • CHANNEL_STATE //状态
  • CHANNEL_ANSWER //应答
  • CHANNEL_HANGUP_COMPLETE //挂机结束

XCC-SUBS

向 NATS 订阅。事件订阅可以直接使用 NATS 提供的 subscribe 功能实现,可以订阅 FreeSWITCH 中原生的事件,如,订阅所有原生事件:cn.xswitch.ctrl.event.>。事件的 Subject 以及所需的事件类型可以在系统的 xcc 配置界面上指定。原生事件以 JSON-RPC 格式发出,可以在各种语言中很简单的解析其中的内容。

XCC-CHANNEL

在此页面添加更多通道变量。

  • user_uuid //当前 User 的 UUID
  • user_domain //当前 User 的 Domain

XCC-DIALPLAN

XSwitch 的mod_xcc模块中实现一个XCC Dialplan 接口,使用该 Dialplan 后,XSwitch 在每次查询 Dialplan 时都会向 Ctrl 发送request请求。

  • subject:cn.xswitch.ctrl
  • method:XCtrl.Dialplan

配置页面字段参数说明:

  • cid_name //主叫名称
  • cid_number //主叫号码
  • dst_number //被叫号码
  • context //呼叫源
  • direction //方向,inbound 为向内,outbound 向外

XCC-CDR

订阅 cn.xswitch.cdr时可获取到的字段,如果想要更多字段,可在此页面添加。比如开启了 cdr-subject,则订阅 cn.xswitch.cdr 即可收到话单事件。

在通话完毕后发出。每一个 Channel 都会有一个 CDR 事件,如果参与通话的是两条腿(alegbleg),则会有两个 CDR 事件,并分别有 leg 标志。CDR 事件一般会在 Event.Destroy 之前发出。

CDR 事件默认会送到与 Event.Channel 相同的 Subject 上,但也可以通过全局配置参数 cdr-subject 配置单独的 Subject。

挂机结束后话单指定事件,默认如下字段参数说明:

  • caller_id_number //主叫号码
  • destination_number //被叫号码
  • uuid //通话 uuid
  • start_stamp //开始时间
  • answer_stamp //应答时间
  • end_stamp //结束时间
  • duration //总时长
  • billsec //计费时长
  • hangup_cause //挂机原因
  • context //呼叫源
  • direction //方向,inbound 为向内,outbound 向外
  • caller_id_name //主叫名称

呼入示例

场景描述:

新建一条xcc路由,执行ivr.js代码,代码无报错,打开软电话,登录已注册后的本地分机用户的账号,输入被叫号码1234,点击呼叫,呼入电话进来,开始应答,播放欢迎音"hello,你好,欢迎致电,请按1"。根据提示音进行操作,会返回相对应的json数据。

创建路由:

进入【xswitch】页面,到【呼叫】⇨【路由】中,点击【新建】

  • 名称:xcc

  • 被叫字冠:1234

  • 目的地类型选择:系统

  • 目的地:xcc

点击提交,路由就创建好了。当呼叫1234时,路由到xcc,它会固定往cn.xswitch.ctrl上发Event.Channel消息。如果想让消息发到特定的 Subject 上,目的地中可以填 xcc cn.xswitch.ctrl.你自己的Ctroller的UUID 。

呼入工作流程:

  • 在有电话呼入时,Ctrl 在收到START消息后应该在 10 秒内调用Accept或Answer接口接管呼叫。

  • 除了一些特殊应用(如回彩铃等),应该尽快应答。某些 PSTN 网管在应答前不允许与对方语音交互,未应答的呼叫一般也会被对方超时拆线(如 60 秒)。

  • 应答后,在有延迟的中继网管情况下,可能出现放音的前几个字对方听不到的情况,这时可以在应答后延迟 1-2 秒再发送后续指令

  • 为了防止 Node 崩溃时来不及发送注销消息,Node 每 20 秒发一个Event.NodeUpdate消息,该消息可以做为心跳保活消息使用。此外,该消息还携带节点当前的活动 Channel 数以及负载信息。Ctrl 可以根据该信息向“负载最轻”的 Node 发送新任务。

  • Ctrl 监听cn.xswitch.ctrl

  • Ctrl 收到Event.Channel(state = START)

  • Ctrl 执行Accept或Answer

  • Ctrl 调用 Play 或 TTS 播放欢迎音

  • Ctrl 调用ReadDTMF或DetectSpeech检测按键或语音

  • Ctrl 检查到语音后调用后续操作

在 xui/xcc-examples/nodejs 路径下,执行一个简单的来话 ivr.js 脚本

如:

js xui/xcc-examples/nodejs/ivr.js

输入node ivr.js,执行代码,确保代码执行没有错误,打开软电话,登录有本地分机用户的账号。输入1234,点击呼叫,就打出呼入的电话了。

呼入返回数据示例:

{
        "jsonrpc":      "2.0",
        "method":       "Event.Channel",
        "params":       {
                "node_uuid":    "dd1d43df-71d8-4182-9054-9c2695cca17d",
                "uuid": "018ec6a3-216d-7906-a873-31d36115300b",
                "state":        "START",
                "domain":       "xswitch.cn",
                "cid_name":     "1001",
                "cid_number":   "1001",
                "dest_number":  "1234",
                "bridged":      false,
                "answered":     false,
                "held": false,
                "video":        false,
                "direction":    "inbound",
                "create_epoch": 1712729563,
                "ring_epoch":   0,
                "caller_source":        "mod_sofia",
                "context":      "context-1",
                "params":       {
                        "create_epoch": "1712729563",
                        "ring_epoch":   "0"
                }
        }
}

{
        "jsonrpc":      "2.0",
        "id":   "fake-uuid-answer",
        "result":       {
                "node_uuid":    "dd1d43df-71d8-4182-9054-9c2695cca17d",
                "uuid": "018ec6a3-216d-7906-a873-31d36115300b",
                "code": 200,
                "message":      "OK"
        }
}

{
        "jsonrpc":      "2.0",
        "id":   "fake-tts",
        "result":       {
                "node_uuid":    "dd1d43df-71d8-4182-9054-9c2695cca17d",
                "code": 202,
                "message":      "OK",
                "uuid": "018ec6a3-216d-7906-a873-31d36115300b",
                "dtmf": ""
        }
}

ASR测试

场景描述:

如上xcc路由已建好,就不需要建,执行asr.js代码,代码执行无报错,打开软电话,登录已注册后的本地分机用户的账号,输入被叫号码1234,点击呼叫,呼入电话进来,开始应答,播放欢迎音"您好,欢迎致电,请直接说出您想咨询的业务,或按键选择"。根据提示音进行操作,会返回相对应的json数据。

在 xui/xcc-examples/nodejs 路径下,执行 asr.js 脚本

如:

js xui/xcc-examples/nodejs/asr.js

测试ASR:输入node asr.js,确保代码执行没有错误,执行代码,打开软电话,登录有本地分机用户的账号。输入1234,点击呼叫,听到提示音后,对着话筒说"今天天气怎么样",返回如下数据。

返回数据示例:

{
        "jsonrpc":      "2.0",
        "id":   "fake-tts",
        "result":       {
                "data": {
                        "engine":       "ali",
                        "text": "今天天气怎么样",
                        "confidence":   0.863,
                        "segment_recording_path":       "/usr/local/freeswitch/storage/recordings/segment-recording-2020-ce3261b3e3c146f1832028708902749a.wav",
                        "segment_recording_duration":   3540,
                        "engine_data":  {
                                "header":       {
                                        "namespace":    "SpeechTranscriber",
                                        "name": "SentenceEnd",
                                        "status":       20000000,
                                        "message_id":   "ce3261b3e3c146f1832028708902749a",
                                        "task_id":      "a426f3d4618447519c9d85d1a0d15bf6",
                                        "status_text":  "Gateway:SUCCESS:Success."
                                },
                                "payload":      {
                                        "index":        2,
                                        "time": 23500,
                                        "result":       "今天天气怎么样",
                                        "confidence":   0.863,
                                        "words":        [],
                                        "status":       0,
                                        "gender":       "",
                                        "begin_time":   21880,
                                        "fixed_result": "",
                                        "unfixed_result":       "",
                                        "stash_result": {
                                                "sentenceId":   3,
                                                "beginTime":    23500,
                                                "text": "",
                                                "fixedText":    "",
                                                "unfixedText":  "",
                                                "currentTime":  23500,
                                                "words":        []
                                        },
                                        "audio_extra_info":     "",
                                        "sentence_id":  "2444da4bbb4a496f95e7f0b93e01faab",
                                        "gender_score": 0
                                },
                                "segment_recording_path":       "/usr/local/freeswitch/storage/recordings/segment-recording-2020-ce3261b3e3c146f1832028708902749a.wav",
                                "segment_recording_duration":   3540
                        },
                        "is_final":     true,
                        "uuid": "018ea277-d6ea-7d47-936e-c71259695ceb",
                        "type": "Speech.End"
                },
                "code": 200,
                "message":      "OK",
                "node_uuid":    "72f48b59-520c-4b93-89b7-4898adf4810f",
                "node_ip":      "172.18.0.2",
                "uuid": "018ea277-d6ea-7d47-936e-c71259695ceb"
        }
}

呼出示例

场景描述:

更换代码里面的分机号和分机,更换为自己本地已注册的分机和分机号。更换完后,执行call.js代码,代码执行无报错,会弹出应答框,有三个选项,视频应答、应答、拒绝,选择其中一个,外呼接通。

注意事项:

  • 在外呼时(尤其是在对接 PSTN 网关时),Dial 接口会在收到媒体时返回(如 SIP 中的 183 消息),如果需要在应答后返回,收需要加ignore_early_media=true参数。

更改代码内容:

分机注册到 FreeSWITCH,FreeSWITCH 可以直接呼叫。需要更换自己本地的分机

user/分机号

示例:

  • user/1001

更改分机号

举例: cid_number: '1001'

const dial_string = 'user/1001';  //更改为本地的分机
var rpc = default_rpc();
rpc.method = "XNode.Dial"
rpc.id = 'call1'; 
rpc.params.destination = {
    global_params: {
        ignore_early_media: "true"
    },
    call_params: [{
        uuid: uuid,
        dial_string: dial_string,
        cid_number: '1001',           //加一个分机号
        params: {
            leg_timeout: "20",
        }
    }]
}

在 xui/xcc-examples/nodejs 路径下,执行一个 call.js 脚本

如:

js xui/xcc-examples/nodejs/call.js

输入node call.js,执行代码,代码执行没有错误,外呼接通,返回如下json数据。

呼出返回数据示例:

{
  "jsonrpc": "2.0",
  "method": "XNode.Dial",
  "id": "call1",
  "params": {
    "ctrl_uuid": "4cf999d4-777c-429d-ad52-be758e8e001e",
    "destination": {
      "global_params": {
        "ignore_early_media": "true"
      },
      "call_params": [
        {
          "uuid": "a78bac98-1c96-4d24-b631-efba23c5803a",
          "dial_string": "user/1001",
          "cid_number": "1001",
          "params": {
            "leg_timeout": "20"
          }
        }
      ]
    }
  }
}

{
        "jsonrpc":      "2.0",
        "id":   "call1",
        "result":       {
                "node_uuid":    "981adb0e-9776-4cb2-b61d-f4c5b86a65e7",
                "code": 200,
                "message":      "OK",
                "cause":        "SUCCESS",
                "uuid": "a78bac98-1c96-4d24-b631-efba23c5803a"
        }
}

呼出场景示例

场景描述:

更换本地分机号,执行call-and-bridge2.js代码,代码执行无报错,出现"Bridge Success",代表桥接成功。

注意事项:

桥接两个uuid,两个 Channel 必须为未Bridge状态,且处于PARK状态(没有执行其它 App)。 当桥接完成后马上返回 api 调用的回复消息

  • uuid:当前uuid,为a-leg。
  • peer_uuid:对方uuid,为b-leg。
  • flow_control:桥接成功后是否自动挂机

桥接:将两条独立的腿接到一起,使双方可以通话。

更改代码内容:

填下两个桥接的分机的分机号

const dial_string = "null/1000";
const dial_string2 = "null/1001";

在 xui/xcc-examples/nodejs 路径下,执行一个 call-and-bridge2.js 脚本

如:

js xui/xcc-examples/nodejs/call-and-bridge2.js

输入node call-and-bridge2.js,执行代码,代码没有报错

返回数据示例:

{"jsonrpc":"2.0","method":"XNode.Dial","id":"call1","params":{"ctrl_uuid":"bbae36ef-62f4-476c-a8ed-c3bffef6586c","sync":true,"destination":{"global_params":{"ignore_early_media":"true"},"call_params":[{"uuid":"dfce488d-039b-4676-8064-a2e1abaa7c16","dial_string":"null/1000"}]}}}

{
        "jsonrpc":      "2.0",
        "id":   "call1",
        "result":       {
                "node_uuid":    "082b2994-48ff-4a29-85e8-8bdfe38d229e",
                "code": 200,
                "message":      "OK",
                "cause":        "SUCCESS",
                "uuid": "dfce488d-039b-4676-8064-a2e1abaa7c16"
        }
}

{"jsonrpc":"2.0","method":"XNode.ChannelBridge2","id":"channel-bridge","params":{"ctrl_uuid":"bbae36ef-62f4-476c-a8ed-c3bffef6586c","uuid":"dfce488d-039b-4676-8064-a2e1abaa7c16","peer_uuid":"4e37c96c-1173-4cb5-b8ed-f9f0b89e89af"}}

{
        "jsonrpc":      "2.0",
        "id":   "channel-bridge",
        "result":       {
                "node_uuid":    "082b2994-48ff-4a29-85e8-8bdfe38d229e",
                "code": 200,
                "message":      "Bridge Success"
        }
}

发起一个呼叫,队列分配坐席,接收话单事件,获取话单信息

场景描述:

更换本地分机号和服务器,执行call-and-acd.js代码,代码执行无报错,出现"Success",代表呼出成功。

更改代码内容:

分机注册到 FreeSWITCH,FreeSWITCH 可以直接呼叫。需要更换自己本地的分机

user/分机号

示例:

user/1001

更改服务器

点击【XCC】⇨【XCC-SUBS】,选择订阅 FreeSWITCH 中原生的事件。

subs

向 NATS 订阅。

  • name:Topic,可以是任意合法的 NATS Subject 或 Kafka Topic。其中 NATS 支持queue订阅。
  • queue:如果参数存在则在 Queue 方式订阅,即在集群订阅时,同一个 Queue 的订阅只会有一个订阅者收到。

示例:

service = "cn.xswitch.node.test"

在 xui/xcc-examples/nodejs 路径下,执行一个 call-and-acd.js 脚本

如:

js xui/xcc-examples/nodejs/call-and-acd.js

输入node call-and-acd.js,执行代码,代码没有报错

返回数据示例:

{
        "jsonrpc":      "2.0",
        "id":   "call1",
        "result":       {
                "node_uuid":    "e6378c49-fdcd-4bc1-8593-b586b86c7c32",
                "code": 200,
                "message":      "OK",
                "cause":        "SUCCESS",
                "uuid": "a1"
        }
}
快速上手