能否通过SIM800模块经GPRS实现SIP呼叫?ESP32对接Asterisk咨询
问题描述
我有一台云端Asterisk服务器,希望ESP32设备通过SIM800模块经GPRS网络注册为SIP客户端并发起VoIP呼叫。目前已通过SIM800的AT指令建立GPRS连接,可发送HTTP请求和MQTT消息,也能通过GSM发起通话,但尚未找到连接到VoIP服务器的可行方法。以下是我当前的ESP32代码,已实现GPRS下的MQTT连接及GSM通话功能,但无法对接Asterisk:
当前代码
// ESP32 Configuration #define TINY_GSM_MODEM_SIM800 #define SerialMon Serial #define SerialAT Serial2 #define TINY_GSM_DEBUG SerialMon #define GSM_AUTOBAUD_MIN 9600 #define GSM_AUTOBAUD_MAX 38400 #define TINY_GSM_USE_GPRS true #define PhoneNumber "xxxxxxxxxxxxxxx" #define rcSwitchInterruptPin 35 #define ENABLE_SIM800C 22 #define BUTTON_PIN 33 #define SOS_BUTTON_ID0 4236140 #define SOS_BUTTON_ID1 4236132 #define SOS_BUTTON_PIN 27 // GPRS internet settings, specific to your SIM card const char apn[] = "internet"; const char gprsUser[] = ""; const char gprsPass[] = ""; // MQTT settings const char *broker = "broker.emqx.io"; const int mqttPort = 1883; const char * clientID = "esp32-client"; const char * mqttUser = "acido"; const char * mqttPassword = "12345678"; #include <TinyGsmClient.h> #include <PubSubClient.h> #include <SPI.h> #include <Wire.h> #include <PubSubClient.h> #include <elapsedMillis.h> #include <RCSwitch.h> RCSwitch mySwitch = RCSwitch(); TinyGsm modem(SerialAT); TinyGsmClient client(modem); PubSubClient mqtt(client); uint32_t lastReconnectAttempt = 0; void mqttCallback(char *, byte *, unsigned int); boolean mqttConnect(); void DRE_switch(); elapsedSeconds serialLogSeconds; unsigned int serialLog_Interval = 3; elapsedSeconds sinyalBekletme; unsigned int sinyalBekletme_Interval = 3; elapsedSeconds sosDelay; unsigned int sosDelay_Interval = 3; elapsedSeconds kapiacikDelay; unsigned int kapiacikDelay_Interval = 3; elapsedSeconds kapiKapaliDelay; unsigned int kapiKapaliDelay_Interval = 3; elapsedSeconds gazDelay; unsigned int gazDelay_Interval = 3; elapsedSeconds pirDelay; unsigned int pirDelay_Interval = 3; elapsedSeconds dumanDelay; unsigned int dumanDelay_Interval = 3; void setup() { mqtt.setKeepAlive(60); // Set console baud rate SerialMon.begin(9600); while (!SerialMon) delay(10); // !!!!!!!!!!! mySwitch.enableReceive(35); pinMode(BUTTON_PIN, INPUT_PULLDOWN); pinMode(ENABLE_SIM800C, OUTPUT); digitalWrite(ENABLE_SIM800C, HIGH); delay(3000); digitalWrite(ENABLE_SIM800C, LOW); // !!!!!!!!!!! TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); delay(6000); // Restart takes quite some time // To skip it, call init() instead of restart() // if (!modem.restart()) { if (!modem.init()) { DBG("Failed to restart modem, delaying 10s and retrying"); // restart autobaud in case GSM just rebooted TinyGsmAutoBaud(SerialAT, GSM_AUTOBAUD_MIN, GSM_AUTOBAUD_MAX); return; } String modemInfo = modem.getModemInfo(); SerialMon.print("setup: Modem Info: "); SerialMon.println(modemInfo); SerialMon.print("setup: Waiting for network..."); if (!modem.waitForNetwork()) { SerialMon.println("setup: fail, wait 5 seconds"); delay(5000); return; } SerialMon.println("setup: success"); if (modem.isNetworkConnected()) SerialMon.println("setup: Network connected"); #if TINY_GSM_USE_GPRS // GPRS connection parameters are usually set after network registration SerialMon.print(F("setup: Connecting to apn: ")); SerialMon.print(apn); if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { SerialMon.println("setup: GPRS Connecting fail, wait 5 seconds"); delay(5000); return; } SerialMon.println("setup: GPRS success"); if (modem.isGprsConnected()) { SerialMon.println("setup: GPRS connected"); } #endif // MQTT Broker setup mqtt.setServer(broker, mqttPort); mqtt.setCallback(mqttCallback); } void loop() { bool result; // check if we're connected to network if (!modem.isNetworkConnected()) { SerialMon.println("loop: modem disconnected"); if (!modem.waitForNetwork(180000L, true)) { SerialMon.println("loop: modem fail, wait 10 sec"); delay(10000); return; } if (modem.isNetworkConnected()) { SerialMon.println("loop: Modem re-connected"); } // And make sure GPRS/EPS is still connected if (!modem.isGprsConnected()) { SerialMon.println("loop: GPRS disconnected!"); SerialMon.print(F("loop: Connecting to apn:")); SerialMon.print(apn); if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { SerialMon.println("loop: GPRS fail, wait 10 sec"); delay(10000); return; } if (modem.isGprsConnected()) { SerialMon.println("loop: GPRS reconnected"); } } } // check if we're connected to MQTT if (!mqtt.connected()) { SerialMon.println("loop: === mqtt not connected ==="); // Reconnect every 10 seconds uint32_t t = millis(); if (t - lastReconnectAttempt > 10000L) { lastReconnectAttempt = t; if (mqttConnect()) { lastReconnectAttempt = 0; } } return; } if (digitalRead(BUTTON_PIN) == HIGH) { Serial.println("BUTONA BASILDI"); result = modem.callNumber(PhoneNumber); DBG("Call:", result ? "OK" : "fail"); } // Check RF signals and publish to MQTT if (mySwitch.available()) { int num = mySwitch.getReceivedValue(); Serial.print("Num: "); Serial.print(num); Serial.print(" Protocol: "); Serial.println(mySwitch.getReceivedProtocol()); if (sosDelay >= sosDelay_Interval) { if (num == 4236132) // SOS { Serial.println("SOS Butonuna Basıldı."); DRE_switch(); mqtt.publish("CMR/ESP32s/SENSOR/SOS", "SOS"); // Publish message. result = modem.callNumber(PhoneNumber); DBG("Call:", result ? "OK" : "fail"); } else { mqtt.publish("CMR/ESP32s/SENSOR/SOS", "0"); // Publish message. } sosDelay = 0; } if (pirDelay >= pirDelay_Interval) { if (num == 8330345) { DRE_switch(); Serial.println("HAREKET ALGILANDI."); mqtt.publish("CMR/ESP32s/SENSOR/PIR", "HAREKET ALGILANDI"); // Publish message. } else { mqtt.publish("CMR/ESP32s/SENSOR/PIR", "0"); // Publish message } pirDelay = 0; } if (kapiacikDelay >= kapiacikDelay_Interval) { if (num == 5729285) // Kapi acilinca gelen sinyal { DRE_switch(); Serial.println("KAPI AÇILDI."); mqtt.publish("CMR/ESP32s/SENSOR/KAPIACIK", "1"); // Publish message. } else { mqtt.publish("CMR/ESP32s/SENSOR/KAPIACIK", "0"); // Publish message } kapiacikDelay = 0; } if (kapiKapaliDelay >= kapiKapaliDelay_Interval) { if (num == 5729294) // Kapi kapaninca gelen sinyal { DRE_switch(); Serial.println("KAPI KAPANDI."); mqtt.publish("CMR/ESP32s/SENSOR/KAPIKAPALI", "1"); // Publish message. } else { mqtt.publish("CMR/ESP32s/SENSOR/KAPIKAPALI", "0"); // Publish message } kapiKapaliDelay = 0; } if (gazDelay >= gazDelay_Interval) { if (num == 12607491) { DRE_switch(); Serial.println("Gas Algılandı."); mqtt.publish("CMR/ESP32s/SENSOR/GAS", "GAS ALGILANDI"); // Publish message. } else { mqtt.publish("CMR/ESP32s/SENSOR/GAS", "0"); // Publish message } gazDelay = 0; } if (dumanDelay >= dumanDelay_Interval) { if (num == 79974) { DRE_switch(); Serial.println("Duman Algılandı ."); mqtt.publish("CMR/ESP32s/SENSOR/DUMAN", "DUMAN ALGILANDI"); // Publish message. } else { mqtt.publish("CMR/ESP32s/SENSOR/DUMAN", "0"); // Publish message } dumanDelay = 0; } } // end if mySwitch.available modem.maintain(); mqtt.loop(); // Handling MQTT on the background. } // end of loop boolean mqttConnect() { // Connect to MQTT Broker SerialMon.print("mqttConnect: MQTT trying to connect: "); SerialMon.print(broker); // Connect to MQTT Broker with password protected boolean status = mqtt.connect(clientID, mqttUser, mqttPassword); // boolean status = mqtt.connect(clientID); // without password if (status == false) { SerialMon.println("mqttConnect: MQTT Connection Fail"); return false; } SerialMon.println("mqttConnect: *** MQTT CONNECTED *** "); mqtt.publish("topic/akes", "Client connection started"); mqtt.subscribe("topic/akes"); // subscribe to topic return mqtt.connected(); } void mqttCallback(char *topic, byte *payload, unsigned int len) { // Handle message arrived SerialMon.print("mqttCallback: Message arrived ["); SerialMon.print(topic); SerialMon.print("]: "); SerialMon.write(payload, len); SerialMon.println(); } void DRE_switch() { // Disable Reset Enable function for RF module mySwitch.disableReceive(); mySwitch.resetAvailable(); mySwitch.enableReceive(rcSwitchInterruptPin); }
解决方案
要实现ESP32通过SIM800+GPRS对接Asterisk SIP服务器,核心是在ESP32上运行轻量SIP客户端栈,利用已有的GPRS网络连接与Asterisk通信,具体步骤如下:
1. 选择合适的SIP库
ESP32可用的轻量SIP库推荐:
- ESP32-SIP-Client:专为ESP32设计的轻量SIP客户端库,配置简单,资源占用低,适合当前场景。
- PJSIP:功能完整的开源SIP栈,支持复杂VoIP功能,但资源占用较高,适合对功能要求高的场景。
以下以ESP32-SIP-Client为例说明集成流程:
2. 集成SIP库到现有代码
步骤1:安装库
通过Arduino Library Manager搜索并安装ESP32-SIP-Client库。
步骤2:添加SIP配置参数
在代码开头新增Asterisk服务器的SIP配置:
// SIP配置 const char* sip_server = "你的Asterisk服务器IP/域名"; const int sip_port = 5060; // 默认SIP端口 const char* sip_username = "Asterisk上创建的SIP账号"; const char* sip_password = "SIP账号密码"; const char* sip_display_name = "ESP32_VoIP";
步骤3:初始化SIP客户端
在setup()函数中,完成GPRS连接后添加SIP初始化与注册逻辑:
#include <SIPClient.h> SIPClient sipClient; void setup() { // ... 保留原有初始化代码 ... #if TINY_GSM_USE_GPRS // ... 原有GPRS连接代码 ... // 初始化SIP客户端,传入已建立的GPRS客户端 sipClient.begin(sip_server, sip_port, sip_username, sip_password, sip_display_name, &client); // 注册到Asterisk服务器 if(sipClient.registerSIP()){ SerialMon.println("SIP注册成功"); } else { SerialMon.println("SIP注册失败"); } #endif }
步骤4:维护SIP连接并发起呼叫
在loop()函数中添加SIP连接维护逻辑,替换原有GSM呼叫为VoIP呼叫:
void loop() { // ... 原有网络、MQTT维护代码 ... // 维护SIP连接,处理服务器消息 sipClient.loop(); // 示例:SOS触发时发起VoIP呼叫 if (mySwitch.available()) { int num = mySwitch.getReceivedValue(); // ... 原有其他传感器逻辑 ... if (sosDelay >= sosDelay_Interval) { if (num == 4236132) // SOS { Serial.println("SOS Butonuna Basıldı."); DRE_switch(); mqtt.publish("CMR/ESP32s/SENSOR/SOS", "SOS"); // 发起SIP呼叫,目标为Asterisk上的账号或号码 if(sipClient.call("目标SIP账号/号码")){ SerialMon.println("VoIP呼叫发起成功"); } else { SerialMon.println("VoIP呼叫发起失败"); } } sosDelay = 0; } } }
3. Asterisk服务器配置注意事项
- 在Asterisk的
sip.conf中创建对应SIP账号,设置type=friend、host=dynamic(允许动态IP注册),并确保allow字段包含G.711等低带宽音频编码。 - 开启NAT穿透支持:若Asterisk部署在公网,ESP32处于GPRS内网,需在账号配置中添加
nat=yes、externip=你的公网IP,或配置STUN服务器。 - 开放服务器端口:确保Asterisk的5060端口(UDP/TCP)对外开放,允许GPRS网络的流量接入。
4. 常见问题排查
- SIP注册失败:用
modem.ping(sip_server)测试GPRS是否能连通服务器,检查账号密码、服务器地址是否正确,Asterisk是否开启了对应账号的注册权限。 - 呼叫无声音:确认ESP32连接的音频输入输出设备接线正确,SIP库与Asterisk的音频编码设置一致(优先
相关产品推荐
相关产品推荐

