diff --git a/docs/API_reference/en/networklib/umqtt.md b/docs/API_reference/en/networklib/umqtt.md index 40e42e56b6450f9133e0343275e661d92ffd9da0..9439f30185aafffeeefa2eb29d7d5861debcb379 100644 --- a/docs/API_reference/en/networklib/umqtt.md +++ b/docs/API_reference/en/networklib/umqtt.md @@ -251,6 +251,8 @@ MQTTClient.wait_msg() Blocks waiting for a message response from the MQTT server. +>If this method is called in a thread, it is necessary to ensure that the thread stack created is >= 16K. For details, please refer to the mqtt reconnection sample code. + * Parameter None @@ -381,6 +383,7 @@ import net import _thread import checkNet import dataCall +import uos from umqtt import MQTTClient PROJECT_NAME = "QuecPython_MQTT_example" @@ -552,7 +555,18 @@ class MqttClient(): return -1 def loop_forever(self): + task_stacksize =_thread.stack_size() + name,platform = uos.uname()[1].split("=",1) + if platform == "EC600E" or platform == "EC800E": + _thread.stack_size(8 * 1024) + elif platform == "FCM362K": + _thread.stack_size(3 * 1024) + else: + _thread.stack_size(16 * 1024) + # Before creating a thread, modify the thread stack space according to the platform. _thread.start_new_thread(self.__listen, ()) + # After the thread is created successfully, the platform thread stack default size is restored. + _thread.stack_size(task_stacksize) if __name__ == '__main__': ''' diff --git a/docs/API_reference/zh/networklib/umqtt.md b/docs/API_reference/zh/networklib/umqtt.md index 5358e766c9e8c588510a704b1de2813b5668d183..00a6b029470667edcbf8fb99078a175ec92517d2 100644 --- a/docs/API_reference/zh/networklib/umqtt.md +++ b/docs/API_reference/zh/networklib/umqtt.md @@ -222,6 +222,7 @@ MQTTClient.wait_msg() 阻塞等待服务器消息响应。 +>该方法如果在线程中调用时,需保证创建线程栈空间>=16K,具体请参照mqtt重连示例代码. ### `MQTTClient.get_mqttsta` @@ -339,6 +340,7 @@ import net import _thread import checkNet import dataCall +import uos from umqtt import MQTTClient PROJECT_NAME = "QuecPython_MQTT_example" @@ -510,7 +512,18 @@ class MqttClient(): return -1 def loop_forever(self): + task_stacksize =_thread.stack_size() + name,platform = uos.uname()[1].split("=",1) + if platform == "EC600E" or platform == "EC800E": + _thread.stack_size(8 * 1024) + elif platform == "FCM362K": + _thread.stack_size(3 * 1024) + else: + _thread.stack_size(16 * 1024) + # 创建线程之前,按照平台,修改线程栈空间。 _thread.start_new_thread(self.__listen, ()) + # 线程创建成功后,恢复平台线程栈默认大小。 + _thread.stack_size(task_stacksize) if __name__ == '__main__': ''' diff --git a/docs/API_reference/zh/networklib/voip.md b/docs/API_reference/zh/networklib/voip.md new file mode 100644 index 0000000000000000000000000000000000000000..02e0c25656d91042939f2adddca1134e271c38ca --- /dev/null +++ b/docs/API_reference/zh/networklib/voip.md @@ -0,0 +1,327 @@ +# class voip - voip电话 + +该类提供 voip 电话功能,是基于 sip 协议实现的网络电话客户端。 + +**示例:** + +```python +# 导入voip模块 +# -*- coding: UTF-8 -*- +#voip目前以源码的形式提供,可自行放/usr目录下 +from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent +import log +import dataCall +import utime as time +import checkNet + +''' +下面两个全局变量是必须有的,用户可以根据自己的实际项目修改下面两个全局变量的值 +''' +PROJECT_NAME = "QuecPython_VoipCall_example" +PROJECT_VERSION = "1.0.0" + +checknet = checkNet.CheckNetwork(PROJECT_NAME, PROJECT_VERSION) + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +voip_log = log.getLogger("VOIP_CALL") + +#voip状态回调函数 +def voip_event(event, call): + if event == VoIPEvent.VOIP_EVENT_REGISTERED: + print('VOIP_EVENT_REGISTERED entry') + elif event == VoIPEvent.VOIP_EVENT_RINGING: + print('VOIP_EVENT_RINGING entry') + try: + call.answer() + call.startAudioService() # pcmu/pcma + except InvalidStateError: + pass + except: + call.hangup() + elif event == VoIPEvent.VOIP_EVENT_CANCELED: + print('VOIP_EVENT_CANCELED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_BYE: + print('VOIP_EVENT_BYE entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ENDED: + print('VOIP_EVENT_ENDED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ANSWERED: + print('VOIP_EVENT_ANSWERED entry') + call.startAudioService() + else: + print('UNKNOWN {} entry'.format(event)) + +if __name__ == '__main__': + stagecode, subcode = checknet.wait_network_connected(30) + if stagecode == 3 and subcode == 1: + voip_log.info('Network connection successful!') + # 获取拨号IP + lte = dataCall.getInfo(1, 0) + if lte[2][0] == 1: + voip_log.info('lte dataCall normal') + # 创建一个VoIPPhone实例 + phone = VoIPPhone('118.31.23.236', 9999, '1007', '1007', callCallback=voip_event, myIP=lte[2][2], sipPort=5061) + # 注册服务器 + phone.start() + + time.sleep(3) + voip_log.info('VOIP start call 1007') + # 拨打电话:XXX + in_call = phone.call('1007') + + time.sleep(20) + voip_log.info('qpy hangup call') + # 主动挂断电话 + in_call.hangup() + + time.sleep(3) + voip_log.info('qpy deregitster voip service') + # 释放VoIPPhone对象 + phone.stop() + else: + raise ValueError("dataCall getInfo error") + else: + voip_log.info('Network connection failed! stagecode = {}, subcode = {}'.format(stagecode, subcode)) +``` + +```python +# 导入voip模块 +# -*- coding: UTF-8 -*- +#voip目前以源码的形式提供,可自行放/usr目录下 +from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent +import log +import dataCall +import utime as time +import checkNet + +''' +下面两个全局变量是必须有的,用户可以根据自己的实际项目修改下面两个全局变量的值 +''' +PROJECT_NAME = "QuecPython_VoipAnswer_example" +PROJECT_VERSION = "1.0.0" + +checknet = checkNet.CheckNetwork(PROJECT_NAME, PROJECT_VERSION) + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +voip_log = log.getLogger("VOIP_ANSWER") + +#voip状态回调函数 +def voip_event(event, call): + if event == VoIPEvent.VOIP_EVENT_REGISTERED: + print('VOIP_EVENT_REGISTERED entry') + elif event == VoIPEvent.VOIP_EVENT_RINGING: + print('VOIP_EVENT_RINGING entry') + try: + call.answer() + call.startAudioService() # pcmu/pcma + except InvalidStateError: + pass + except: + call.hangup() + elif event == VoIPEvent.VOIP_EVENT_CANCELED: + print('VOIP_EVENT_CANCELED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_BYE: + print('VOIP_EVENT_BYE entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ENDED: + print('VOIP_EVENT_ENDED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ANSWERED: + print('VOIP_EVENT_ANSWERED entry') + call.startAudioService() + else: + print('UNKNOWN {} entry'.format(event)) + +if __name__ == '__main__': + stagecode, subcode = checknet.wait_network_connected(30) + if stagecode == 3 and subcode == 1: + voip_log.info('Network connection successful!') + # 获取拨号IP + lte = dataCall.getInfo(1, 0) + if lte[2][0] == 1: + voip_log.info('lte dataCall normal') + # 创建一个VoIPPhone实例 + phone = VoIPPhone('118.31.23.236', 9999, '1007', '1007', callCallback=voip_event, myIP=lte[2][2], sipPort=5061) + # 注册服务器 + phone.start() + #注册完成后,voip会一直处于监听状态,等待服务器拨打电话。 + else: + raise ValueError("dataCall getInfo error") + else: + voip_log.info('Network connection failed! stagecode = {}, subcode = {}'.format(stagecode, subcode)) +``` + + +## 构造函数 + +### `VoIPPhone` + +```python +class VoIPPhone(server, port, username, password, myIP, callCallback, sipPort, domain, displayName, timeout) +``` +创建voip电话对象。 + +**参数描述:** + +- `server` - 服务器地址,字符串类型。 + +- `port` - 服务器端口,整型。 + +- `username` - 注册服务器使用的用户名,字符串类型。 + +- `password` - 注册服务器使用的密码,字符串类型。 + +- `myIP` - 本地使用的IP,字符串类型。 + +- `callCallback` - 注册回调,监听不同的通话状态并通过回调反馈给用户。 + +- `sipPort` - 本地使用的端口(可选参数),整型。 + +- `domain` - 注册服务器使用的域,字符串类型。 + +- `displayName` - 注册服务器使用的显示名称(可选参数),字符串类型。 + +- `timeout` - 超时时间,整型,单位秒,默认为20秒。 + +> domain参数,注册服务器的必须参数,未设置时,默认和server配置服务器地址相同。 +displayName参数,注册服务器的可选参数,未设置时,默认和username配置相同。 + +示例: +```python +#voip目前以源码的形式提供,可自行放/usr目录下 +from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent +phone = VoIPPhone(server: str, port: int, username: str, password: str, myIP: str, callCallback = None, sipPort=5060, domain=server, displayName=username) +``` + +## 方法 + +### `phone.start` + +```python +phone.start() +``` + +启动 voip 电话客户端,进行服务器注册。 + +> 对应注册参数,在申请对象时已配置。正常无返回,返回-1或抛异常,表示注册服务器失败,需检查网络和服务器参数是否正常。 + +### `phone.stop` + +```python +phone.stop() +``` + +关闭 voip 电话客户端,断开与服务器的连接。 + +### `phone.get_answer_number` + +```python +call.get_answer_number() +``` + +该方法用于获取来电号码。 + +**返回值:** + +- `answer_number` - 来电号码,字符串类型。 + +### `phone.call` + +```python +call = phone.call(number) +``` + +该方法用于主动拨打voip电话。 + +**参数描述:** + +- `number` - 接收方电话号码,字符串类型。 + +**返回值:** + +- `call` - 通话对象,用来和接收方电话号进行通信。 + +### `call.hangup` + +```python +call.hangup() +``` + +该方法用于挂断 voip 通话中电话。 + +> 已接听的voip电话,使用 call.hangup 进行挂断电话。 + +### `call.cancel` + +```python +call.cancel() +``` + +该方法用于取消 voip 拨号中电话。 + +> 未接听的voip电话,可使用 call.cancel 进行取消拨号。 + +### `call.answer` + +```python +call.answer() +``` + +该方法用于接听 voip 呼入的电话,需配合call.startAudioService。 + +### `call.set_dtmf` + +```python +call.set_dtmf(data) +``` + +该方法用于设置voip电话DTMF音。 + +**参数描述:** + +- `data` - DTMF字符串,字符串类型,有效字符数有:0-9、A、B、C、D、*、#。 + +### `call.startAudioService` + +```python +call.startAudioService() +``` + +该方法用于voip电话接听后,开启audio。 + +## 常量 + +### `VoIPEvent.VOIP_EVENT_REGISTERED` + +voip电话服务中事件通知:服务器注册成功。 + +### `VoIPEvent.VOIP_EVENT_DEREGISTERED` + +voip电话服务中事件通知:服务器注册异常。 + +### `VoIPEvent.VOIP_EVENT_RINGING` + +voip电话服务中事件通知:电话来电通知。 + +### `VoIPEvent.VOIP_EVENT_CANCELED` + +voip电话服务中事件通知:拨号中被取消。 + +### `VoIPEvent.VOIP_EVENT_BYE` + +voip电话服务中事件通知:电话挂断。 + +### `VoIPEvent.VOIP_EVENT_ENDED` + +voip电话服务中事件通知:电话已终止。 + +### `VoIPEvent.VOIP_EVENT_ANSWERED` + +voip电话服务中事件通知:电话接听。 + + diff --git a/docs/Application_guide/zh/media/network-comm/net-protocols/voip/microVoip.png b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/microVoip.png new file mode 100644 index 0000000000000000000000000000000000000000..033b650df7b02f5eea543e3d08be8d0920bee5e3 Binary files /dev/null and b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/microVoip.png differ diff --git a/docs/Application_guide/zh/media/network-comm/net-protocols/voip/sipInteraction.png b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/sipInteraction.png new file mode 100644 index 0000000000000000000000000000000000000000..e86adc4fc6cd2db9bf009a031182c59851f06200 Binary files /dev/null and b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/sipInteraction.png differ diff --git a/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voip_answer.png b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voip_answer.png new file mode 100644 index 0000000000000000000000000000000000000000..4a52d5c401f6796e2908dcee6034149d55be5590 Binary files /dev/null and b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voip_answer.png differ diff --git a/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voip_call.png b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voip_call.png new file mode 100644 index 0000000000000000000000000000000000000000..52fc675bbf10a68d0fe1a942cec61123f9e0b82a Binary files /dev/null and b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voip_call.png differ diff --git a/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voipprotocolsystem.png b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voipprotocolsystem.png new file mode 100644 index 0000000000000000000000000000000000000000..8339d1cd3311df80051d33a98898bc08524c368c Binary files /dev/null and b/docs/Application_guide/zh/media/network-comm/net-protocols/voip/voipprotocolsystem.png differ diff --git a/docs/Application_guide/zh/network-comm/net-protocols/README.md b/docs/Application_guide/zh/network-comm/net-protocols/README.md index b678e1880dade231caf5f8efef0d58980ed5e76b..2dc7940ace40720d7f10f1bc240cbcaa63d101c4 100644 --- a/docs/Application_guide/zh/network-comm/net-protocols/README.md +++ b/docs/Application_guide/zh/network-comm/net-protocols/README.md @@ -7,4 +7,5 @@ - [TCP/UDP 协议应用指导](./tcp-udp.md) - [HTTP 协议应用指导](./http.md) - [MQTT 协议应用指导](./mqtt.md) +- [VOIP 应用指导](./voip.md) - [FTP 协议应用指导](./ftp.md) diff --git a/docs/Application_guide/zh/network-comm/net-protocols/voip.md b/docs/Application_guide/zh/network-comm/net-protocols/voip.md new file mode 100644 index 0000000000000000000000000000000000000000..8e102dee69b978da7a40dc7906603e6c8e240365 --- /dev/null +++ b/docs/Application_guide/zh/network-comm/net-protocols/voip.md @@ -0,0 +1,345 @@ +# VoIP 通信 + +本章节主要介绍 QuecPython 下 VoIP 通话功能,基于 sip 协议实现的网络电话客户端。 + +## VoIP 简介 + +### VoIP 以及 SIP 协议定义 + +VoIP(Voice over Internet Protocol)是指通过互联网协议(IP)网络(如互联网、企业内部网络等)进行语音通信的技术。是一种将模拟语音信号转换成数字数据包,并通过IP网络进行传输的技术框架,旨在实现基于互联网的语音通话服务。它取代了传统的公共交换电话网络(PSTN),使得语音通信能够利用现有的数据网络基础设施,降低了成本并提供了额外的功能与灵活性。 + +SIP(Session Initiation Protocol,会话初始协议)是由IETF(Internet Engineering Task Force,因特网工程任务组)制定的一种应用层协议,专为管理和控制多媒体通信会话而设计。SIP 是一种基于文本的消息传递协议,其主要目的是在IP网络中实现多媒体通信会话(如语音通话、视频会议、即时消息、文件共享等)的建立、修改和终止。作为会话控制信令协议,SIP 负责协调多个参与者之间的交互,确保他们能够正确地建立、维护和结束通信会话。 + +![](../../media/network-comm/net-protocols/voip/voipprotocolsystem.png) + +![](../../media/network-comm/net-protocols/voip/sipInteraction.png) + +### VOIP 与 SIP 关系 + +SIP是 VoIP 生态系统中的关键组成部分,为 VoIP 通话提供了必要的会话控制和管理能力,而 VoIP 则是 SIP 所控制的多媒体会话中的一种具体应用实例。 + +## VoIP 应用 + + +### VoIP 固件功能确认 + +> 以EC600M_CNLE 为例 +> - [点此下载对应固件 ](https://python.quectel.com/download)。 + +下载固件后,需确认当前固件基础功能以及硬件是否满足 VoIP 通话条件。 +> 此功能是pcm回环测试,可以通过话筒讲话并播放,确认是否正常。 + +```python +#audio pcm 回环测试 +import audio +import G711 +import _thread +import utime + +pcm = audio.Audio.PCM(2, 1, 8000, 2, 1) + +level = pcm.getVolume() +print("level:", level) + +pcm.setVolume(10) + +level = pcm.getVolume() +print("level:", level) + +g711 = G711(pcm) + +def g711_fun_test(para): + while True: + g711_buf = g711.read(1) + if(len(g711_buf) > 0): + write_len = g711.write(g711_buf, 1) + print("G711:", write_len) + +def fun_start(func): + _thread.start_new_thread(func, (1,)) + +fun_start(g711_fun_test) +``` + +### 网络状态检查 + +启动 VoIP 应用之前,首先要确保网络畅通。 + +> 如果网络连接异常,可参考网络拨号相关的应用指导,重新进行网络连接。 + +QuecPython 相关模组在上电后会自动进行蜂窝网络连接。因此,一般情况下开发者只需在应用中检测网络状态以及检查拨号即可,示例代码如下: + +```python +import checkNet +import dataCall + +if __name__ == '__main__': + stage, state = checkNet.waitNetworkReady(30) + if stage == 3 and state == 1: + print('Network connection successful.') + while True: + lte = dataCall.getInfo(1, 0) + if lte[2][0] == 1: + print('lte network normal') + break + print('wait lte network normal...') + time.sleep(3) + else: + print('Network connection failed, stage={}, state={}'.format(stage, state)) + +``` + +### VoIP 语音通话服务器注册 + +VoIP 是基于 ip 的网络通信,首先需要保证模组网络正常。[点此查看VoIP api文档 ](https://gitee.com/qpy-doc-center/teedoc_with_qpydoc/blob/df49f188df041da6358b0cfa5fe776807e02aa24/docs/API_reference/zh/networklib/voip.md)。 + +注册成功后,当前设备就可以正常和服务器进行通信,包括呼叫、接听、退出登陆等。 + +> 一般不同的服务器有不同的特性,需要按照不同服务器的要求进行参数配置。 +> 可使用PC工具:MicroSIP 进行对比&调试。 + +VoIP 注册服务器分为2步骤: +1. 创建一个VoIPPhone实例。 + > `phone=VoIPPhone( server: str, port: int, username: str, password: str, callCallback = cb, myIP="0.0.0.0", sipPort=5060, domain=server, displayName=username)` + 在对接服务器时,服务器一般会对如下参数有要求,需要匹配一致进行服务器接入。 + + - `server`: 服务器地址。 + - `port`: 服务器端口号。 + - `username`: 登录的用户名,用于标识唯一用户身份。在注册、认证以及呼叫请求中,客户端会使用这个用户名。 + - `password`: 用户密码,用于在注册过程中向服务器验证身份。客户端在与服务器交互时需提供此密码以确保合法访问。 + - `callCallback`: 回调函数,用于处理呼叫请求和呼叫响应。 + - `myIP`: 本机ip地址,用于标识本地网络地址。若未特别指定,使用本地默认网卡地址作为默认值。 + - `sipPort`: 本机端口号,用于标识本地端口号。若未特别指定,使用默认值5060。 + - `domain`: SIP域,通常与服务器地址相同。在SIP URI中用于标识用户所在的域。若未特别指定,使用服务器地址作为默认值。 + - `displayName`:显示名,默认值usrname,在呼叫中显示给对方的友好名称,不同于SIP用户名,通常更为用户友好的表述。如果没有特别指定,使用SIP用户名作为默认显示名称。 + - `phone`:返回值,VoIPPhone实例,voip电话对象。 + +2. 服务器注册。 + > `phone.start()` + + 此方法正常无返回,返回-1或抛异常,表示注册服务器失败,需检查网络和服务器参数是否正常。 + +### QuecPython VoIP 实现介绍 + +![](../../media/network-comm/net-protocols/voip/microVoip.png) + +> - [点此查看Voip api文档 ](https://gitee.com/qpy-doc-center/teedoc_with_qpydoc/blob/df49f188df041da6358b0cfa5fe776807e02aa24/docs/API_reference/zh/networklib/voip.md)。 + + +## VoIP 语音通话示例 + +QuecPython 提供 VoIP 语音通话实现源码,用于 VoIP 语音通话,本节分为 VoIP 呼叫和接听两个章节进行说明。 + +具体应用流程,如下描述: + +1. VoIP 目前以源码的形式提供,可自行放/usr目录下,再通过`from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent`方式调用对应模块。 +2. 创建一个VoIPPhone实例`phone=VoIPPhone(server: str, port: int, username: str, password: str)`,`phone`是返回可操作的句柄,Python里面称之为对象,该对象拥有voip所有的API方法,例如注册服务器,主动呼叫等。 +3. 创建对象完成后,需要与 SIP 服务端建立连接,使用`phone.start`方法完成登录请求,这里需要的用户名以及密码,在初始化时需提供。 +4. 连接登录完成后,当前设备就可以通过注册回调返回的对象`call.answer`接听来电,也可以根据需求,调用`phone.call`进行呼叫。 + + +### VoIP 呼叫 + +![](../../media/network-comm/net-protocols/voip/voip_call.png) + +> VoIP 语音通话设备仅通过网络(拨号网络或网卡网络)与服务器进行交互,即sip通话仅可呼叫服务器其他成功注册`username`,如需和运营商进行交互,需要服务器进行对应转发&处理。 + +VoIP 接入运营商呼叫具体应用流程,如下描述: +1. VoIP 目前以源码的形式提供,可自行放/usr目录下,再通过`from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent`方式调用对应模块。 +2. 创建一个VoIPPhone实例`phone=VoIPPhone(server: str, port: int, username: str, password: str)`,`phone`是返回可操作的句柄,Python里面称之为对象,该对象拥有voip所有的API方法,例如注册服务器,主动呼叫,退出登陆等。 +3. 创建对象完成后,需要与 SIP 服务端建立连接,使用`phone.start`方法完成登录请求,这里需要的用户名以及密码,在初始化时需提供。 +4. 连接登录完成后,当前设备就可以通过注册回调返回的对象`call.answer`接听来电,也可以根据需求,调用`phone.call`进行呼叫。 +5. 调用`call = phone.call(number)`进行SIP电话呼叫,SIP 通话仅可呼叫服务器其他的注册`username`(如需呼叫电话号码,需服务器进行转发)。成功呼叫后,返回可操作的句柄`call`,该对象拥有call通话所有的 API 方法,例如`call.cancel`取消 VoIP 拨号中电话,`call.hangup`挂断 VoIP 通话等。 +6. `phone.call`成功返回`call`对象后,当前设备就处于正常通话状态,用户可主动`call.cancel`取消 VoIP 拨号中电话,`call.hangup`挂断 VoIP 通话。 +7. 如成功呼叫后,通话对象取消&挂断 VoIP 通话,在创建`VoIPPhone`注册的回调函数会实时返回状态:电话挂断`VoIPEvent.VOIP_EVENT_BYE`,拨号中被取消`VoIPEvent.VOIP_EVENT_CANCELED`,电话已终止`VoIPEvent.VOIP_EVENT_ENDED`。 +8. 如成功呼叫后,通话对象接听 VoIP 通话,在创建`VoIPPhone`注册的回调函数会实时返回状态:电话接听`VoIPEvent.VOIP_EVENT_ANSWERED`。 +9. 如需退出 VoIP 通话,可调用 `phone.stop`关闭 VoIP 电话客户端,断开与服务器的连接。 + +```python +# 导入voip模块 +# -*- coding: UTF-8 -*- +#voip目前以源码的形式提供,可自行放/usr目录下 +from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent#对应流程1,导入voip相关模块 +import log +import dataCall +import utime as time +import checkNet + +''' +下面两个全局变量是必须有的,用户可以根据自己的实际项目修改下面两个全局变量的值 +''' +PROJECT_NAME = "QuecPython_VoipCall_example" +PROJECT_VERSION = "1.0.0" + +checknet = checkNet.CheckNetwork(PROJECT_NAME, PROJECT_VERSION) + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +voip_log = log.getLogger("VOIP_CALL") + +#voip状态回调函数,具体状态含义可参考wiki。 +def voip_event(event, call): + if event == VoIPEvent.VOIP_EVENT_REGISTERED: + print('VOIP_EVENT_REGISTERED entry') + elif event == VoIPEvent.VOIP_EVENT_RINGING: + print('VOIP_EVENT_RINGING entry') + # 对应流程8,回调返回对端接听,开启voip应答以及audio。 + try: + call.answer() + call.startAudioService() # pcmu/pcma + except InvalidStateError: + pass + except: + call.hangup() + elif event == VoIPEvent.VOIP_EVENT_CANCELED: + print('VOIP_EVENT_CANCELED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_BYE: + print('VOIP_EVENT_BYE entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ENDED: + print('VOIP_EVENT_ENDED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ANSWERED: + print('VOIP_EVENT_ANSWERED entry') + call.startAudioService() + else: + print('UNKNOWN {} entry'.format(event)) + +if __name__ == '__main__': + stagecode, subcode = checknet.wait_network_connected(30) + if stagecode == 3 and subcode == 1: + voip_log.info('Network connection successful!') + # 获取拨号IP + lte = dataCall.getInfo(1, 0) + if lte[2][0] == 1:#用来确认当前拨号网络是否就绪。 + voip_log.info('lte dataCall normal') + #对应流程2,创建一个VoIPPhone实例 + phone = VoIPPhone('118.31.23.236', 9999, '1007', '1007', callCallback=voip_event, myIP=lte[2][2], sipPort=5061) + #对应流程3,请求登录 SIP 服务端 + phone.start() + + time.sleep(3) + voip_log.info('VOIP start call 1007') + # 对应流程5,SIP电话呼叫 + in_call = phone.call('1007') + + time.sleep(20) + voip_log.info('qpy hangup call') + # 对应流程6,当前默认对端接听,执行挂断 VoIP 通话 + # 具体执行,需根据callback返回判断。 + in_call.hangup() + + time.sleep(3) + voip_log.info('qpy deregitster voip service') + # 对应流程9,关闭 VoIP 电话客户端,断开与服务器的连接 + phone.stop() + else: + raise ValueError("dataCall getInfo error") + else: + voip_log.info('Network connection failed! stagecode = {}, subcode = {}'.format(stagecode, subcode)) + +``` + +### VoIP 接听 + +![](../../media/network-comm/net-protocols/voip/voip_answer.png) + +> VoIP 语音通话设备仅通过网络(拨号网络或网卡网络)与服务器进行交互,如需和运营商进行交互,需要服务器进行对应转发&处理。 + +SIP 设备内呼具体应用流程,如下描述: +1. VoIP 目前以源码的形式提供,可自行放/usr目录下,再通过`from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent`方式调用对应模块。 +2. 创建一个 VoIPPhone 实例`phone=VoIPPhone(server: str, port: int, username: str, password: str)`,`phone`是返回可操作的句柄,Python里面称之为对象,该对象拥有voip所有的API方法,例如注册服务器,主动呼叫等。 +3. 创建对象完成后,需要与SIP服务端建立连接,使用`phone.start`方法完成登录请求,这里需要的用户名以及密码,在初始化时需提供。 +4. 连接登录完成后,当前设备就可以通过注册回调返回的对象`call.answer`接听来电,也可以根据需求,调用`phone.call`进行呼叫。 +5. 成功注册服务器后,服务器其他设备就可以主动呼叫当前注册`username`,如服务器其他设备主动呼叫,创建`VoIPPhone`注册的回调函数会实时返回状态:电话来电通知`VoIPEvent.VOIP_EVENT_RINGING`,用户可主动`call.cancel`取消拨号中电话,`call.hangup`挂断通话。 +6. 如需退出VOIP通话,可调用 `phone.stop`关闭 VoIP 电话客户端,断开与服务器的连接。 + +``` +# 导入voip模块 +# -*- coding: UTF-8 -*- +#voip目前以源码的形式提供,可自行放/usr目录下 +from usr.microVoIP.VoIP import VoIPPhone, InvalidStateError, VoIPEvent#对应流程1,导入voip相关模块 +import log +import dataCall +import utime as time +import checkNet + +''' +下面两个全局变量是必须有的,用户可以根据自己的实际项目修改下面两个全局变量的值 +''' +PROJECT_NAME = "QuecPython_VoipAnswer_example" +PROJECT_VERSION = "1.0.0" + +checknet = checkNet.CheckNetwork(PROJECT_NAME, PROJECT_VERSION) + +# 设置日志输出级别 +log.basicConfig(level=log.INFO) +voip_log = log.getLogger("VOIP_ANSWER") + +#voip状态回调函数,具体状态含义可参考wiki。 +def voip_event(event, call): + if event == VoIPEvent.VOIP_EVENT_REGISTERED: + print('VOIP_EVENT_REGISTERED entry') + elif event == VoIPEvent.VOIP_EVENT_RINGING: + # 对应流程5,回调返回对端接听,开启voip应答以及audio。 + print('VOIP_EVENT_RINGING entry') + try: + call.answer() # 对应流程4,当前设备接听来电 + call.startAudioService() # 对应流程4,开启当前设备audio + except InvalidStateError: + pass + except: + call.hangup() + elif event == VoIPEvent.VOIP_EVENT_CANCELED: + print('VOIP_EVENT_CANCELED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_BYE: + print('VOIP_EVENT_BYE entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ENDED: + print('VOIP_EVENT_ENDED entry') + #call.stopAudioService() + elif event == VoIPEvent.VOIP_EVENT_ANSWERED: + print('VOIP_EVENT_ANSWERED entry') + call.startAudioService() + else: + print('UNKNOWN {} entry'.format(event)) + +if __name__ == '__main__': + stagecode, subcode = checknet.wait_network_connected(30) + if stagecode == 3 and subcode == 1: + voip_log.info('Network connection successful!') + # 获取拨号IP + lte = dataCall.getInfo(1, 0)#用来确认当前拨号网络是否就绪。 + if lte[2][0] == 1: + voip_log.info('lte dataCall normal') + #对应流程2,创建一个VoIPPhone实例 + phone = VoIPPhone('118.31.23.236', 9999, '1007', '1007', callCallback=voip_event, myIP=lte[2][2], sipPort=5061) + #对应流程3,请求登录 SIP 服务端 + phone.start() + #注册完成后,voip会一直处于监听状态,等待服务器拨打电话。 + else: + raise ValueError("dataCall getInfo error") + else: + voip_log.info('Network connection failed! stagecode = {}, subcode = {}'.format(stagecode, subcode)) + +``` + +## 常见问题 + +**Q: 存在导入模块失败** + +A:首先确保正确烧录了QuecPython固件,且该固件需包含G711&audio模块,如不包含则需更换固件。 + +**Q:设备连接Voip服务器失败。** + +A:请检查是设备能够正常找网,在确保找网成功后检查voip连接参数是否正确(可使用PC工具:MicroSIP 对比),如工具正常,需提供对应log进行排查。 + +**Q:设备使用call 拨打电话,对应号码无反应(未震铃)** + +A:可使用PC工具:MicroSIP 对比,确认当前服务器转发正常,如工具正常,需提供对应log进行排查。 + +**Q:设备成功登陆号,服务器拨打电话,无对应状态返回 ** + +A:可使用PC工具:MicroSIP 对比,确认当前服务器转发正常,如工具正常,需提供对应log进行排查。