소스 검색

基础信令实现

刘威 6 달 전
부모
커밋
d7dac565e4

+ 149 - 27
src/core/callcenter/client.py

@@ -14,10 +14,14 @@ import concurrent.futures
 from http.server import BaseHTTPRequestHandler
 from http.server import HTTPServer
 import src.core.callcenter.utils.call_cache_util as CacheUtil
-from src.core.callcenter.constant.constant import AT, CO, SK, saasId
-from src.core.callcenter.constant.esl_constant import SIP_HEADER, SPLIT, SOFIA, ORIGINATE, PARK
+from src.core.callcenter.constant.constant import AT, CO, SK, saasId, EMPTY
+from src.core.callcenter.constant.esl_constant import BRIDGE_VARIABLES, BRIDGE, HANGUP, NORMAL_CLEARING, SIP_HEADER, SPACE, SPLIT, SOFIA, \
+    ORIGINATE, PARK, SET, EAVESDROP, SMF_ALEG, EXECUTE, PLAYBACK, PAUSE, TRANSFER, UUID_TRANSFER, UUID_BROADCAST, UUID_BREAK, UUID_HOLD, \
+    UUID_RECORD, UUID_SETVAR, UUID_GETVAR
 import src.core.callcenter.utils.esl_event_util as EslEventUtil
 import src.core.callcenter.handler as event_handler
+from src.core.callcenter.constant.sip_header_constant import sipHeaderHoldMusic
+from src.core.callcenter.enumeration import CallCauseEnum
 from src.core.callcenter.handler.default_esl_event_handler import DefaultEslEventHandler
 
 
@@ -145,7 +149,7 @@ class InboundClient:
             "{return_ring_ready=true,",
             f"sip_contact_user={display},",
             "ring_asr=true,",
-            "absolute_codec_string=codecs,",  # Assuming codecs is defined somewhere
+            "absolute_codec_string=^^:PCMU:PCMA,",  # Assuming codecs is defined somewhere
             f"origination_caller_id_number={display},",
             f"origination_caller_id_name={display},",
             f"origination_uuid={device_id},",
@@ -169,70 +173,180 @@ class InboundClient:
 
     def send_args(self, device_id, name, arg):
         msg = ESL.ESLevent("sendmsg", device_id)
-        msg.addHeader("call-command", "execute")
+        msg.addHeader("call-command", EXECUTE)
         msg.addHeader("execute-app-name", name)
         msg.addHeader("execute-app-arg", arg)
         self.con.sendEvent(msg)
 
     def bridge_call(self, call_id, device_id1, device_id2):
         """桥接电话"""
-        pass
+        self.multi_set_var(device_id1, BRIDGE_VARIABLES)
+        self.multi_set_var(device_id2, BRIDGE_VARIABLES)
+        self.con.bgapi(BRIDGE, device_id1 + SPACE + device_id2)
 
     def transfer_call(self, _from, _to):
         """转接"""
-        pass
+        builder = [
+            _from,
+            "  -both 'set:hangup_after_bridge=false,set:park_after_bridge=true,park:' inline "
+        ]
+        arg = ''.join(builder)
+        self.con.bgapi(TRANSFER, arg)
 
     def answer(self, device_id):
         """应答"""
-        pass
+        self.con.bgapi('uuid_phone_event', device_id + ' talk')
 
-    def hangup_call(self, call_id, device_id):
+    def hangup_call(self, call_id, device_id, case_enum=CallCauseEnum.DEFAULT):
         """挂机"""
-        pass
-
-    def play_file(self, call_id, device_id, file):
-        """放音"""
-        pass
+        msg = ESL.ESLevent("sendmsg", device_id)
+        msg.addHeader("call-command", EXECUTE)
+        msg.addHeader("execute-app-name", HANGUP)
+        msg.addHeader("execute-app-arg", NORMAL_CLEARING)
+        # log.info("hangupCall挂机 hangup media:{} call: {}, device: {}, ctiCauseEnum:{}", media,callId, deviceId, ctiCauseEnum);
+        self.send_args(device_id, SET, EslEventUtil.SIP_H_P_WDH_HANGUP_CAUSE + "=" + case_enum.description)
+        self.con.sendEvent(msg)
 
     def broadcast(self, uuid, path, smf):
-        pass
+        builder = [
+            UUID_BROADCAST,
+            uuid,
+            path,
+            smf
+        ]
+        command = ' '.join(builder)
+        self.con.bgapi(command, EMPTY)
 
-    def break0(self, uuid, all, sync):
-        pass
+    def break0(self, uuid, all=False, sync=True):
+        builder = [
+            UUID_BREAK,
+            uuid
+        ]
+        if all:
+            builder.append("all")
+        command = ' '.join(builder)
+        if sync:
+            self.con.api(command, EMPTY)
+        else:
+            self.con.bgapi(command, EMPTY)
 
     def hold(self, smf, uuid, display):
-        pass
+        builder = [
+            UUID_HOLD,
+            smf,
+            uuid,
+            display
+        ]
+        if display:
+            builder.append("all")
+        else:
+            builder.append(EMPTY)
+        command = ' '.join(builder)
+        self.con.bgapi(command, EMPTY)
 
     def get_var(self, uuid, var):
-        pass
+        builder = [
+            UUID_GETVAR,
+            uuid,
+            var
+        ]
+        command = ' '.join(builder)
+        self.con.bgapi(command, EMPTY)
 
     def set_var(self, uuid, var, val):
-        pass
+        builder = [
+            UUID_SETVAR,
+            uuid,
+            var,
+            val
+        ]
+        command = ' '.join(builder)
+        self.con.bgapi(command, EMPTY)
 
     def multi_set_var(self, uuid, params):
-        pass
+        builder = [
+            "uuid_setvar_multi " + uuid + " "
+        ]
+        builder1 = []
+        for k, v in params.items():
+            builder1.append(k + "="+v)
+        builder.append(';'.join(builder1))
+        command = ''.join(builder)
+        self.con.bgapi(command, EMPTY)
 
     def record(self, uuid, action, path, limit):
-        pass
+        builder = [
+            UUID_TRANSFER,
+            uuid,
+            action,
+            path,
+            str(limit)
+        ]
+        command = ' '.join(builder)
+        self.con.bgapi(command, EMPTY)
 
     def transfer(self, uuid, smf, dest, dialplan, context):
-        pass
+        builder = [
+            UUID_TRANSFER,
+            uuid,
+            smf,
+            dest,
+            dialplan,
+            context
+        ]
+        command = ' '.join(builder)
+        self.con.bgapi(command, EMPTY)
 
     def insert(self, device_id):
         """强插"""
-        pass
+        builder = [
+            device_id,
+            "  -both 'set:hangup_after_bridge=false,set:park_after_bridge=true,park:' inline "
+        ]
+        arg = ''.join(builder)
+        self.con.api(TRANSFER, arg)
 
     def bridge_break(self, device_id):
         """拆线"""
-        pass
+        builder = [
+            device_id,
+            "  -both 'set:hangup_after_bridge=false,set:park_after_bridge=true,set:" + SIP_HEADER + sipHeaderHoldMusic + "=true,park:' inline "
+        ]
+        arg = ''.join(builder)
+        self.con.api(TRANSFER, arg)
+
+    def play_file(self, call_id, device_id, file, sync):
+        """放音"""
+        if sync:
+            return self.hold_play(device_id, file)
+        else:
+            msg = ESL.ESLevent("sendmsg", device_id)
+            msg.addHeader("call-command", EXECUTE)
+            msg.addHeader("execute-app-name", PLAYBACK)
+            msg.addHeader("execute-app-arg", file)
+            msg.addHeader("async", "true")
+            self.con.sendEvent(msg)
 
     def stop_play(self, device_id):
         """关闭播放音乐"""
-        pass
+        builder = [
+            device_id,
+            " on"
+        ]
+        arg = "".join(builder)
+        return self.con.api(PAUSE, arg)
 
     def hold_play(self, device_id, play):
         """向a-leg插播tts音乐(无限播放)"""
-        pass
+        builder = [
+            device_id,
+            " playback::",
+            play,
+            " ",
+            SMF_ALEG
+        ]
+        arg = "".join(builder)
+        return self.con.api(UUID_BROADCAST, arg)
 
     def play_timeout(self, call_id, timeout):
         """播放超时主动挂机"""
@@ -240,7 +354,15 @@ class InboundClient:
 
     def listen(self, device_id1, device_id2, aleg, bleg):
         """监听"""
-        pass
+        if aleg:
+            self.send_args(device_id1, SET, "eavesdrop_bridge_aleg=true")
+        if bleg:
+            self.send_args(device_id1, SET, "eavesdrop_bridge_bleg=true")
+        self.send_args(device_id1, EAVESDROP, device_id2)
+
+    def show_channel(self, device_id):
+        msg = self.con.api("show", " channels like " + device_id + " as json")
+        print('show_channel::', msg)
 
     def expression(self, template, params):
         for key, value in params.items():

+ 1 - 0
src/core/callcenter/constant/constant.py

@@ -37,6 +37,7 @@ READY_TIMES = "readyTimes"
 #服务次数
 SEREVICE_TIMES = "serviceTimes"
 
+EMPTY = ""
 DEFAULT_KEY = ""
 
 #坐席状态相关

+ 20 - 0
src/core/callcenter/constant/sip_header_constant.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# encoding:utf-8
+
+profile1 = "gateway/#{[gateway]}"
+profile2 = "user/#{[called]}@#{[realm]}"
+
+sipHeaderCallId = "P-LIBRA-CallId"
+sipHeaderCaller = "P-LIBRA-caller"
+sipHeaderCalled = "P-LIBRA-called"
+sipHeaderDirection = "P-LIBRA-direction"
+sipHeaderEavesdrop = "P-LIBRA-is-eavesdrop"
+sipHeaderDeviceId = "P-LIBRA-DeviceId"
+sipHeaderDeviceType = "P-LIBRA-DeviceType"
+sipHeaderServiceId = "P-LIBRA-ServiceId"
+sipHeaderCallSequenceId = "P-LIBRA-CallSequenceId"
+sipHeaderDomain = "P-LIBRA-domain"
+sipHeaderCtiFlowId = "P-LIBRA-CtiFlowId"
+
+sipHeaderClueOutId = "X-EXTID"
+sipHeaderHoldMusic = "P-LIBRA-need-hold-music"

+ 23 - 0
src/core/callcenter/enumeration.py

@@ -103,3 +103,26 @@ class NextType(Enum):
     @property
     def code(self):
         return self._code
+
+
+class CallCauseEnum(Enum):
+    DEFAULT = (0, "默认")
+    RESTART = (2, "服务重启")
+    CALL_TIMEOUT = (3, "呼叫超时")
+    PLAY_TIMEOUT = (5, "播放音超时")
+    WAITING_TIMEOUT = (6, "等待超时")
+    AGENT_HANGUP_CALL = (7, "坐席挂断请求")
+    HANGUP_EVENT = (8, "挂断事件")
+    PLAYBACK_STOP = (9, "等待音结束")
+    ROBOT_MONITOR = (10, "机器人挂断监听")
+    LINE_BREAK = (11, "线路故障")
+    ROBOT_TRANSFER = (12, "机器人转接")
+    ROBOT_FULL = (13, "机器人话路数满额")
+    NO_MUSIC_HANGUP = (14, "无挂断播放音挂断")
+    PLAY_KEY_DONE_HANGUP = (15, "按键播放音播完挂断")
+    PLAY_TIMEOUT_HANGUP = (16, "通知播放超时挂断")
+    WAITING_KEY_TIMEOUT_HANGUP = (17, "等待按键超时挂断")
+
+    def __init__(self, code, description):
+        self.code = code
+        self.description = description

+ 2 - 2
src/core/callcenter/model.py

@@ -268,8 +268,8 @@ class CallDetail:
 class RouteGateway:
     def __init__(self, id=None, saas_id=None, cts=None, uts=None, name=None, media_host=None,
                  media_port=None, caller_prefix=None, called_prefix=None,
-                 profile='gateway/gateway-fxo', sip_header1='P-WDH-CallId=#{[callId]}',
-                 sip_header2='P-WDH-deviceId=#{[deviceId]}', sip_header3=None, status=None):
+                 profile='gateway/gateway-fxo', sip_header1='P-LIBRA-CallId=#{[callId]}',
+                 sip_header2='P-LIBRA-deviceId=#{[deviceId]}', sip_header3=None, status=None):
         self.id = id  # PK
         self.saas_id = saas_id
         self.cts = cts  # 创建时间