bot.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. #!/usr/bin/env python3
  2. # encoding:utf-8
  3. import os
  4. import time
  5. import json
  6. import wave
  7. import queue
  8. import threading
  9. import traceback
  10. import sys
  11. import pjsua2 as pj
  12. from enum import Enum
  13. from src.core.callcenter.cache import Cache
  14. from src.core.datasource import SIP_SERVER, SERVE_HOST
  15. from src.core.voip.constant import *
  16. import requests
  17. from src.core.callcenter.api import BotChatRequest,ChatMessage
  18. from src.core import singleton_keys
  19. from src.core.callcenter.snowflake import Snowflake
  20. from src.core.callcenter.data_handler import *
  21. calls = {}
  22. # recording_file = '/code/src/core/voip/incoming_call.wav'
  23. # player_file1 = '/code/src/core/voip/test111.wav'
  24. # player_file2 = '/code/src/core/voip/test222.wav'
  25. class BotStatus(Enum):
  26. # 等待用户讲话,未超时
  27. wait_speaking_not_timeout = 1
  28. # 等待用户讲话,已超时但用户仍在持续讲话
  29. wait_speaking_timeout_user_speaking = 2
  30. # 机器人讲话过程中
  31. speaking = 3
  32. # 等待用户讲话结束
  33. wait_user_speaking_end = 4
  34. # 等待大模型输出
  35. wait_gpt_return = 5
  36. class UserStatus(Enum):
  37. # ASR返回值为sentenceBegin后将状态置为Speaking
  38. speaking = 1
  39. # ASR返回值为sentenceEnd后,将状态置为Silence
  40. silence = 2
  41. class MyAudioMediaPort(pj.AudioMediaPort):
  42. def __init__(self, call, aud_med=None, asr=None):
  43. pj.AudioMediaPort.__init__(self)
  44. # # 打开一个 .pcm 文件来保存音频流(可选:保存为 .wav)
  45. # self.wav = wave.open(f"{recording_file}", "wb")
  46. # self.wav.setnchannels(1)
  47. # self.wav.setsampwidth(2) # 假设每个样本是 16 位(2 字节)
  48. # self.wav.setframerate(16000)
  49. print("MyAudioMediaPort::come in, debugger")
  50. self.call = call
  51. self.aud_med = aud_med
  52. self.asr = asr
  53. self.user_asr_texts = []
  54. def onFrameRequested(self, frame):
  55. print("Request audio frame:", frame)
  56. def onFrameReceived(self, frame):
  57. # self.wav.writeframes(bytes(frame.buf))
  58. # print("Received audio frame:", frame.buf, frame.size)
  59. if self.asr: # 如果ASR实例存在,则发送音频数据
  60. self.asr.send_audio(frame.buf)
  61. try:
  62. asr_text = self.get_asr_text()
  63. play_complete = self.call.is_play_complete()
  64. current_time = time.time() # 实时当前时间
  65. if self.call.inputType == '1.0':
  66. time_difference = int(current_time - self.call.inputLongStart)
  67. # print('current_time - self.call.inputLongStart:',time_difference > 35, self.call.txtLock , play_complete)
  68. if time_difference > 35 and play_complete:
  69. self.user_asr_texts.append(f"DTMF({self.call.digit})DTMF")
  70. user_asr_text = self.user_asr_texts[0] if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
  71. self.user_asr_texts.clear()
  72. self.call.chat(user_asr_text)
  73. # print("测试超长", user_asr_text)
  74. elif asr_text:
  75. self.user_asr_texts.append(asr_text)
  76. if time_difference > int(self.call.wait_time):
  77. self.call.reset_wait_time()
  78. else:
  79. if asr_text and not play_complete:
  80. self.user_asr_texts.append(asr_text)
  81. if (asr_text and play_complete) or (play_complete and self.user_asr_texts):
  82. if asr_text:
  83. self.user_asr_texts.append(asr_text)
  84. user_asr_text = self.user_asr_texts[0] if len(self.user_asr_texts) == 1 else '###'.join(self.user_asr_texts)
  85. self.user_asr_texts.clear()
  86. self.call.chat(user_asr_text)
  87. if self.call.wait_time and self.call.wait_time != "0" and play_complete and not asr_text:
  88. self.call.wait_time_check(current_time, self.call.wait_time)
  89. message_queue_size = self.call.message_queue.qsize()
  90. if (message_queue_size > 0 and not self.call.cur_player_file) or (message_queue_size > 0 and play_complete):
  91. print('onFrameReceived:message_queue_size=', message_queue_size, 'play_complete=', play_complete, asr_text)
  92. self.call.cur_player_file, self.call.wait_time, self.call.inputType,self.call.action, self.call.node_id = self.get_player_file()
  93. # 重置播放完成标志和超时计时器,确保新的播放从头开始计时
  94. self.call.reset_wait_time()
  95. self.call.send_bot_speaker(self.call.cur_player_file)
  96. except:
  97. pass
  98. def get_asr_text(self):
  99. try:
  100. asr_text = self.call.user_asr_text_queue.get(block=False)
  101. print('get_asr_text:', asr_text)
  102. return asr_text
  103. except:
  104. pass
  105. def get_player_file(self):
  106. try:
  107. message = self.call.message_queue.get(block=False)
  108. player_file = [item.voice_url for item in message.contents if item.content_type == 'voice']
  109. print('get_player_file:', player_file,message.wait_time, message.inputType, message.action, message.node_id)
  110. return player_file, message.wait_time, message.inputType, message.action, message.node_id
  111. except Exception as e:
  112. traceback.print_exc()
  113. class MyAudioMediaPlayer(pj.AudioMediaPlayer):
  114. def __init__(self, player_id, sink, on_complete=None):
  115. pj.AudioMediaPlayer.__init__(self)
  116. self.player_id = player_id
  117. self.sink = sink
  118. self.on_complete = on_complete
  119. def onEof2(self):
  120. # self.stopTransmit(self.sink)
  121. if self.on_complete:
  122. self.on_complete(self.player_id)
  123. # Subclass to extend the Account and get notifications etc.
  124. class Account(pj.Account):
  125. def __init__(self, agent, user_part, **kwargs):
  126. pj.Account.__init__(self)
  127. self.agent = agent
  128. self.user_part = user_part
  129. self.calls = {}
  130. self.kwargs = kwargs
  131. def onRegState(self, prm):
  132. ai = self.getInfo()
  133. print("***OnRegState: " + prm.reason, ai.id,ai.isDefault,ai.uri,ai.regIsConfigured,ai.regIsActive,ai.regExpiresSec,ai.regStatus,ai.regStatusText,ai.regLastErr,ai.onlineStatus,ai.onlineStatusText)
  134. def onIncomingCall(self, prm):
  135. ai = self.getInfo()
  136. print("***onIncomingCall: ", prm.callId, ai.id,ai.isDefault,ai.uri,ai.regIsConfigured,ai.regIsActive,ai.regExpiresSec,ai.regStatus,ai.regStatusText,ai.regLastErr,ai.onlineStatus,ai.onlineStatusText)
  137. call = MyCall(self.agent, self, self.user_part, prm.callId, **self.kwargs)
  138. call_op_param = pj.CallOpParam(True)
  139. call_op_param.statusCode = pj.PJSIP_SC_OK
  140. call.answer(call_op_param)
  141. self.calls[prm.callId] = call
  142. class MyCall(pj.Call):
  143. def __init__(self, agent, acc, user_part, call_id, **kwargs):
  144. pj.Call.__init__(self, acc, call_id)
  145. self.agent = agent
  146. self.user_part = user_part
  147. self.call_id = call_id
  148. self.kwargs = kwargs
  149. self.aud_med = None
  150. self.audio_port = None
  151. self.player = None
  152. self.asr = None
  153. self.session_id = kwargs.get('variable_sip_h_P-LIBRA-CallId')
  154. self.device_id = kwargs.get('variable_sip_h_P-LIBRA-DeviceId')
  155. print("self.session_id:", self.session_id)
  156. # self.scripts = build_demo_script()
  157. self.user_asr_text_queue = queue.Queue(maxsize=100)
  158. self.message_queue = queue.Queue(maxsize=3)
  159. self.player_complete_dict = {}
  160. self.wait_time = None
  161. self.inputType = None #记录按键类型 1为长按键类型
  162. self.digit = '' # 存储长按键内容
  163. self.action = None
  164. self.node_id= 'start'
  165. self.cur_player_file = None #当前播放的文件
  166. from src.core.voip.asr import TestSt
  167. self.asr = TestSt(call_id, message_receiver=self.on_receiver_asr_result) # 创建ASR实例
  168. self.asr.start() # 启动ASR线程
  169. self.call_phone, self.callIdString = self.get_phone()
  170. # 超时设置
  171. self.play_start_time = time.time() # 倒计时开始时间
  172. self.play_complete_flag = False # 倒计时开始标志
  173. self.txtLock = False
  174. self.inputLongStart = time.time() #长按键开始时间
  175. def wait_time_check(self, current_time, wait_time):
  176. try:
  177. # 确保 wait_time 是整数类型
  178. wait_time = int(wait_time)
  179. # 如果播放尚未完成,重置标志并返回
  180. if not self.play_complete_flag:
  181. self.play_complete_flag = True
  182. self.play_start_time = current_time
  183. # print(f"开始计时: {self.play_start_time}")
  184. elapsed_time = int(current_time - self.play_start_time)
  185. # print(f"当前时间: {current_time}, 已过时间: {elapsed_time}, 最大等待时间: {wait_time}")
  186. if elapsed_time > wait_time:
  187. # self.user_asr_text_queue.put("ASR408error")
  188. self.chat('ASR408error')
  189. except ValueError as e:
  190. print(f"无效的等待时间参数: {wait_time}, 错误: {e}")
  191. self.reset_wait_time()
  192. def reset_wait_time(self):
  193. self.play_complete_flag = False # 重置播放完成标志
  194. self.play_start_time = None # 重置开始计时时间
  195. def get_phone(self):
  196. import re
  197. call_info = self.getInfo()
  198. match = re.match(r'"(\d+)" <sip:(\d+)@', call_info.remoteUri)
  199. if match:
  200. print("Phone Number:", match.group(1))
  201. return match.group(1), call_info.callIdString # 假设显示名称部分是手机号
  202. else:
  203. return "", call_info.callIdString
  204. def is_play_complete(self): #语音机器人是否播放结束
  205. if self.cur_player_file:
  206. player_id = murmur3_32(self.cur_player_file)
  207. return self.player_complete_dict.get(player_id)
  208. def onDtmfDigit(self, prm):
  209. # 判断是否播放完成 否则不记录用户说的内容
  210. if not self.is_play_complete():
  211. return
  212. digit = prm.digit
  213. self.reset_wait_time()
  214. # 假设为超长类型按键 把用户输入的按键进行拼接 如果为# 则把用户输入所有按键放入队列并发送文本机器人
  215. # 如果为非正常按键服务 输入以后直接发送文本机器人
  216. if self.inputType == '1.0':
  217. if digit != '#':
  218. self.digit += digit
  219. elif digit == '#':
  220. # self.user_asr_text_queue.put(f"DTMF({self.digit})DTMF")
  221. self.chat(f"DTMF({self.digit})DTMF")
  222. else:
  223. self.user_asr_text_queue.put(f"DTMF({digit})DTMF")
  224. def onCallState(self, prm):
  225. call_info = self.getInfo()
  226. print("Call state: %s, call id: %s, callcallIdString: %s ", call_info.state, call_info.id, call_info.callIdString)
  227. # pj.PJSIP_INV_STATE_NULL
  228. # pj.PJSIP_INV_STATE_CALLING
  229. # pj.PJSIP_INV_STATE_INCOMING
  230. # pj.PJSIP_INV_STATE_EARLY
  231. # pj.PJSIP_INV_STATE_CONNECTING
  232. # pj.PJSIP_INV_STATE_CONFIRMED
  233. # pj.PJSIP_INV_STATE_DISCONNECTED
  234. if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
  235. # 当呼叫状态为已确认(即接通)
  236. self.bot_say_hello()
  237. if call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
  238. print("通话结束", self.user_part)
  239. if self.audio_port:
  240. self.audio_port = None # 或调用相关销毁方法
  241. if self.player:
  242. self.player = None # 或调用播放器停止方法
  243. # 远程挂机之后要将分机号回收
  244. self.agent.release(self.user_part)
  245. def onCallMediaState(self, prm):
  246. call_info = self.getInfo()
  247. # print("Call Media state: ", call_info.stateText)
  248. for media in call_info.media:
  249. if media.type == pj.PJMEDIA_TYPE_AUDIO and \
  250. (media.status == pj.PJSUA_CALL_MEDIA_ACTIVE):
  251. print("Call Media state 111: ", call_info.stateText)
  252. self.aud_med = self.getAudioMedia(media.index)
  253. try:
  254. # 建立双向通道
  255. self.receive_user_speaker()
  256. # self.bot_say_hello()
  257. except Exception as e:
  258. traceback.print_exc()
  259. def receive_user_speaker(self):
  260. self.audio_port = MyAudioMediaPort(self, self.aud_med, self.asr)
  261. self.audio_port.createPort("Incoming Call Port", build_audio_format())
  262. self.aud_med.startTransmit(self.audio_port)
  263. def send_bot_speaker(self, player_file):
  264. if not player_file :
  265. return
  266. player_id = murmur3_32(player_file)
  267. self.player_complete_dict[player_id] = False
  268. # print('self.player_complete_dict[player_id]D:', player_id, player_file, self.player_complete_dict[player_id])
  269. print(f"[DEBUG] Sending bot speaker, player_file: {player_file}, player_id: {player_id}")
  270. self.player = MyAudioMediaPlayer(player_id, self.aud_med, on_complete=self.on_media_player_complete)
  271. # self.player.createPlayer(player_file[0], pj.PJMEDIA_FILE_NO_LOOP)
  272. self.player.createPlaylist(player_file, f'my_hello_playlist{player_id}', pj.PJMEDIA_FILE_NO_LOOP)
  273. self.player.startTransmit(self.aud_med)
  274. def on_receiver_asr_result(self, message, *args):
  275. # 判断是否播放完成 否则不记录用户说的内容
  276. if not self.is_play_complete():
  277. return
  278. message = json.loads(message)
  279. if message["header"]["status"] == 20000000:
  280. if message["header"]["name"] == "SentenceEnd":
  281. result = message["payload"]["result"]
  282. print("asr返回内容Result:", result)
  283. self.user_asr_text_queue.put(result)
  284. elif message["header"]["name"] == "TranscriptionResultChanged":
  285. self.reset_wait_time()
  286. else:
  287. print(f"Status is not {message['header']['status']}")
  288. def on_media_player_complete(self, player_id):
  289. print('player complete')
  290. self.player_complete_dict[player_id] = True
  291. self.digit = ''
  292. self.inputLongStart = time.time()
  293. #播放完毕执行的动作
  294. self.say_end_action(self.action)
  295. def bot_say_hello(self):
  296. # print('bot_say_hello, come in ')
  297. self.chat(user_asr_text="start")
  298. def chat(self, user_asr_text=None):
  299. # 调用文本机器人接口
  300. ToTextBotAgent(user_asr_text,self)
  301. def say_end_action(self, action):
  302. print('handling_release', action.action_code)
  303. action_code = action.action_code
  304. if action_code == 'hang': # 挂断
  305. action_content = action.action_content
  306. print(f'todo 挂电话:{action_content}')
  307. self.agent.hangup(self.user_part)
  308. elif action_code == 'transfer': # 转人工
  309. print('todo 转人工')
  310. self.agent.transfer(user_part=self.user_part, call_id=self.session_id, device_id=self.device_id)
  311. #更新通话记录机器人意图
  312. self.agent.dataHandleServer.update_call_record_bussiness_type(self.session_id)
  313. class ToTextBotAgent:
  314. def __init__(self, user_asr_text, call_agent):
  315. if not user_asr_text or (call_agent.action and call_agent.action.action_code != 'normal'):
  316. # print("ASR文本为空,终止执行。")
  317. return
  318. self.call_agent = call_agent
  319. self.request_data = BotChatRequest(
  320. nodeId=self.call_agent.node_id,
  321. userId=self.call_agent.call_phone,
  322. sessionId= self.call_agent.session_id,
  323. recordId="",
  324. taskId="10001",
  325. asrText=user_asr_text,
  326. ext= None
  327. )
  328. print("user_asr_text发送结果:", user_asr_text)
  329. # 发送请求并处理响应
  330. # self.test_request(self.request_data)
  331. self.to_quest(self.request_data)
  332. def to_quest(self, request: BotChatRequest):
  333. # if self.call_agent.txtLock:
  334. # return
  335. # 将实体类转换为JSON字符串
  336. headers = {'Content-Type': 'application/json'}
  337. request_data = request.to_json_string()
  338. url = f"http://{SERVE_HOST}:40072/botservice"
  339. # self.call_agent.txtLock = True
  340. # 发送POST请求
  341. print(f"请求数据:{request_data},url:{url}")
  342. try:
  343. response = requests.post(url=url, json=json.loads(request_data), headers=headers, timeout=10) # 使用占位URL
  344. print(f"原始响应内容:{response.text}")
  345. if response.status_code == 200:
  346. response_data = response.json()
  347. if "data" in response_data and response_data["code"]==0:
  348. data = response_data["data"]
  349. parsed_response = ChatMessage.from_json(data)
  350. self.call_agent.message_queue.put(parsed_response)
  351. sys.stdout.flush() # 强制刷新输出缓冲区
  352. else:
  353. print("响应中没有 'data' 字段")
  354. else:
  355. # 错误处理
  356. print(f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}")
  357. except requests.RequestException as e:
  358. print(f"请求发生异常: {e}")
  359. # 模拟接口请求返回
  360. def test_request(self, params: BotChatRequest):
  361. # if self.call_agent.txtLock:
  362. # return
  363. print("test_request::params=", params)
  364. # self.call_agent.txtLock = True
  365. response_data = {
  366. "node_id": "1.0",
  367. "contents": [],
  368. "wait_time": "6",
  369. "action": {
  370. "action_code": "normal",
  371. "action_content": "正常通话"
  372. },
  373. "inputType": "0"
  374. }
  375. print("asrText:", params.asrText)
  376. if params.asrText == 'start': #欢迎语
  377. response_data['contents'].append({
  378. "content_type": "voice",
  379. "content": "",
  380. "voice_url": '/code/src/core/voip/scripts/1_00.wav',
  381. "voice_content": "五一北京到上海的高铁票还有吗?"
  382. })
  383. response_data['inputType'] = '1.0'
  384. elif params.asrText == 'ASR408error': #超时执行
  385. response_data['contents'].append({
  386. "content_type": "voice",
  387. "content": "",
  388. "voice_url": '/code/src/core/voip/scripts/4_00.wav',
  389. "voice_content": "waitTime超时"
  390. })
  391. elif "DTMF" in params.asrText and self.call_agent.inputType =='1.0': #长按键超30s执行
  392. response_data['contents'].append({
  393. "content_type": "voice",
  394. "content": "",
  395. "voice_url": '/code/src/core/voip/scripts/2_00.wav',
  396. "voice_content": "sds"
  397. })
  398. else :
  399. response_data['contents'] = [
  400. {
  401. "content_type": "voice",
  402. "content": "",
  403. "voice_url": '/code/src/core/voip/test111.wav',
  404. "voice_content": "测试第二个录音文件"
  405. },
  406. {
  407. "content_type": "voice",
  408. "content": "",
  409. "voice_url": '/code/src/core/voip/test222.wav',
  410. "voice_content": "五一北京到上海的高铁票还有吗?"
  411. }
  412. ]
  413. try:
  414. print(json.dumps(response_data['contents']))
  415. parsed_response = ChatMessage.from_json(response_data)
  416. self.call_agent.message_queue.put(parsed_response)
  417. except Exception as e:
  418. print(f"Error in test_request: {e}")
  419. traceback.print_exc() # 打印完整的错误信息
  420. return None
  421. @singleton_keys
  422. class BotAgent:
  423. def __init__(self, app, user_part_range=range(1001, 1011), host=SIP_SERVER, port="5060", password="slibra@#123456"):
  424. self.logger = app.logger
  425. self.user_part_range, self.host, self.port, self.password = user_part_range, host, port, password
  426. self.user_part_pool = queue.Queue(maxsize=len(user_part_range))
  427. self.accounts = {}
  428. self.calls = {}
  429. self.ep = pj.Endpoint()
  430. self.is_stopping = False
  431. self.acd_service = None
  432. self.cache = Cache(app)
  433. self.dataHandleServer = DataHandleServer(app)
  434. threading.Thread(target=self.create_pjsua2, daemon=True).start()
  435. def create_pjsua2(self):
  436. # Create and initialize the library
  437. ep_cfg = pj.EpConfig()
  438. ep_cfg.uaConfig.threadCnt = 12
  439. ep_cfg.uaConfig.mainThreadOnly = False
  440. ep_cfg.uaConfig.maxCalls = 20
  441. ep_cfg.uaConfig.maxAccounts = 20
  442. ep_cfg.medConfig.noVad = True
  443. ep_cfg.logConfig.level = 4
  444. ep_cfg.logConfig.consoleLevel = 4
  445. self.ep.libCreate()
  446. self.ep.libInit(ep_cfg)
  447. aud_dev_mgr = self.ep.audDevManager()
  448. aud_dev_mgr.setNullDev() # 使用虚拟音频设备(如果没有实际设备)
  449. # Set up media configuration, particularly jitter buffer
  450. media_cfg = pj.MediaConfig()
  451. media_cfg.jbMinPre = 4 # Minimum pre-fetch frames
  452. media_cfg.jbMaxPre = 16 # Maximum pre-fetch frames
  453. media_cfg.noVad = True # Disable Voice Activity Detection if needed
  454. self.ep.medConfig = media_cfg # Apply media config to endpoint
  455. # Create SIP transport. Error handling sample is shown
  456. sipTpConfig = pj.TransportConfig()
  457. sipTpConfig.port = 30506
  458. self.ep.transportCreate(pj.PJSIP_TRANSPORT_UDP, sipTpConfig)
  459. # Start the library
  460. self.ep.libStart()
  461. for user_part in self.user_part_range:
  462. acfg = pj.AccountConfig()
  463. acfg.idUri = f"sip:{user_part}@{self.host}:{self.port}"
  464. acfg.regConfig.registrarUri = f"sip:{self.host}:{self.port}"
  465. cred = pj.AuthCredInfo("digest", "*", f"{user_part}", 0, self.password)
  466. acfg.sipConfig.authCreds.append(cred)
  467. acfg.regConfig.timeoutSec = 86400 # 注册超时时间(秒)
  468. acfg.regConfig.retryIntervalSec = 10 # 重试间隔时间(秒)
  469. acfg.regConfig.firstRetryIntervalSec = 10 # 首次重试间隔时间(秒)
  470. acfg.natConfig.iceEnabled = True
  471. acfg.natConfig.turnEnabled = True
  472. acfg.natConfig.turnServer = "stun:pbx.fuxicarbon.com:3478"
  473. # acfg.natConfig.turnUsername = "username"
  474. # acfg.natConfig.turnPassword = "password"
  475. # Create the account
  476. acc = Account(self, user_part=user_part)
  477. acc.create(acfg)
  478. self.user_part_pool.put(user_part)
  479. self.accounts[user_part] = acc
  480. while not self.is_stopping:
  481. self.ep.libHandleEvents(100)
  482. def transfer(self, user_part, call_id, device_id, service_id='00000000000000000'):
  483. if self.acd_service:
  484. self.acd_service.transfer_to_agent(call_id, device_id, service_id)
  485. # sip_headers = {'P-LIBRA-HangUpReason': 'transferToAgent', 'P-LIBRA-ServiceId': service_id}
  486. try_count = 100
  487. while try_count >0:
  488. if self.cache.get_after_play_hold_music(call_id):
  489. self.hangup(user_part)
  490. break
  491. time.sleep(0.1)
  492. def hangup(self, user_part, reason="NORMAL_CLEARING", **sip_headers):
  493. call_op_param = pj.CallOpParam(True)
  494. call_op_param.statusCode = pj.PJSIP_SC_OK
  495. call_op_param.reason = reason
  496. call_op_param.txOption = pj.SipTxOption()
  497. sip_header_vector = pj.SipHeaderVector()
  498. for k, v in sip_headers.items():
  499. _sip_header = pj.SipHeader()
  500. _sip_header.hName = str(k)
  501. _sip_header.hValue = str(v)
  502. print('hangup, header_name=%s, header_value=%s'%(k, v))
  503. sip_header_vector.push_back(_sip_header)
  504. call_op_param.txOption.headers = sip_header_vector
  505. acc = self.accounts.get(user_part)
  506. if acc:
  507. for k, v in acc.calls.items():
  508. v.hangup(call_op_param)
  509. # 机器人主动挂机回收分机号
  510. self.release(user_part)
  511. def register(self, **kwargs):
  512. user_part = self.user_part_pool.get()
  513. acc = self.accounts.get(user_part)
  514. self.logger.info('register, user_part :%d, pool.size :%d', user_part, self.user_part_pool.qsize())
  515. if acc:
  516. print('register==========>', acc.getId())
  517. # ps = pj.PresenceStatus()
  518. # ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE
  519. # ps.activity = pj.PJRPID_ACTIVITY_AWAY
  520. # ps.note = "Away"
  521. # acc.setOnlineStatus(ps)
  522. # acc.setRegistration(renew=True)
  523. acc.kwargs = kwargs
  524. return user_part
  525. def unregister(self, user_part):
  526. acc = self.accounts.get(user_part)
  527. if acc:
  528. acc.setRegistration(renew=False)
  529. # 用户远程挂机回收分机号
  530. self.release(user_part)
  531. def release(self, user_part):
  532. if not user_part:
  533. self.logger.info("release, user_part is None")
  534. return
  535. def element_in_queue(q, element):
  536. with q.mutex: # 确保线程安全
  537. for item in list(q.queue): # 将队列转换为列表进行遍历
  538. if item == element:
  539. return True
  540. return False
  541. if element_in_queue(self.user_part_pool, user_part):
  542. self.logger.info("release, already exists, user_part :%d, pool.size :%d", user_part, self.user_part_pool.qsize())
  543. return
  544. self.user_part_pool.put(user_part)
  545. self.logger.info("release, user_part :%d, pool.size :%d", user_part, self.user_part_pool.qsize())
  546. def destroy(self):
  547. self.is_stopping = True
  548. # Destroy the library
  549. self.ep.libDestroy()
  550. def __del__(self):
  551. self.destroy()
  552. # if __name__ == '__main__':
  553. # import logging
  554. # logger = logging.getLogger('voip')
  555. # bot = BotAgent(logger)