C#开发短信发送应用如何将安卓手机作为GSM Modem使用
安卓手机作为GSM Modem对接C#短信发送程序实现方案
核心问题说明
安卓系统默认不会对外暴露基带的调制解调器AT指令端口,直接连接电脑后无法在设备管理器的调制解调器分类下识别到设备属于正常现象,按以下方案配置即可完成对接。
第一步:配置安卓端暴露调制解调器串口
选择以下任意一种方案即可:
- 系统原生调制解调器模式:部分华为、三星、搭载联发科芯片的老机型在USB连接电脑后,下拉通知栏的USB偏好设置,选择「调制解调器」「USB拨号」类选项,系统会自动加载对应驱动,电脑端设备管理器的「端口(COM和LPT)」分类下会直接生成对应的COM口号,部分机型需要提前在开发者选项中开启USB调试。
- 第三方串口透传App:全机型通用方案,在安卓端安装支持AT指令透传的串口工具类App,这类App会直接调用手机基带的调制解调器能力,通过USB映射为标准串口,电脑端安装App提供的对应驱动后即可识别到COM口。
- 蓝牙虚拟串口:免USB方案,将手机与电脑完成蓝牙配对,在蓝牙服务设置中开启手机的「串行端口」服务,配对完成后电脑端会自动生成一个蓝牙虚拟COM口,可直接用于AT指令通信。
第二步:优化现有C#串口通信逻辑
现有代码的AT指令流程基本正确,但存在几个容易导致发送失败的问题,需要调整:
- 必须显式设置串口通信参数,绝大多数GSM调制解调器的默认参数为波特率9600、8位数据位、1位停止位、无校验位、无硬件流控,原代码未设置这些参数,大概率会出现通信乱码、无响应的问题。
- 不要使用固定时长的
Thread.Sleep()等待响应,不同手机的AT指令响应速度差异较大,固定等待100ms可能出现响应未返回就读取、或者等待过长的问题,建议通过轮询读取直到收到OK/ERROR/>等预期标识。 - 发送
AT+CMGS指令后,必须等待设备返回>提示符,再发送短信内容和结束符(0x1A,即Ctrl+Z标识),否则设备会判定指令超时直接返回错误。 - 可以增加自动扫描端口的逻辑,不需要用户手动输入COM口号:枚举所有可用串口,逐个发送AT指令测试,返回
OK的端口即为连接了GSM设备的有效端口。
优化后的核心发送逻辑示例:
// 自动扫描可用GSM端口 private string ScanGsmPort() { string[] allPorts = SerialPort.GetPortNames(); foreach (string port in allPorts) { try { using (SerialPort testSp = new SerialPort(port, 9600, Parity.None, 8, StopBits.One)) { testSp.ReadTimeout = 500; testSp.Open(); testSp.WriteLine("AT\r\n"); Thread.Sleep(300); string res = testSp.ReadExisting(); if (res.Contains("OK")) { return port; } } } catch { continue; } } throw new Exception("未检测到可用的GSM调制解调器设备"); } private void button1_Click(object sender, EventArgs e) { SerialPort sp = null; try { // 优先自动扫描端口,也可保留手动输入端口的逻辑 string portName = string.IsNullOrEmpty(textBox3.Text) ? ScanGsmPort() : textBox3.Text; sp = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One) { ReadTimeout = 2000, WriteTimeout = 2000 }; sp.Open(); // 等待AT指令响应的通用方法 string WaitForResponse(SerialPort port, int timeout = 1000) { StringBuilder sb = new StringBuilder(); int startTick = Environment.TickCount; while (Environment.TickCount - startTick < timeout) { sb.Append(port.ReadExisting()); string resStr = sb.ToString(); if (resStr.Contains("OK") || resStr.Contains("ERROR") || resStr.Contains(">")) { return resStr; } Thread.Sleep(20); } return sb.ToString(); } sp.WriteLine("AT\r\n"); if (!WaitForResponse(sp).Contains("OK")) throw new Exception("设备AT握手失败"); sp.WriteLine("AT+CMGF=1\r\n"); if (!WaitForResponse(sp).Contains("OK")) throw new Exception("设置短信文本模式失败"); sp.WriteLine("AT+CSCS=\"GSM\"\r\n"); if (!WaitForResponse(sp).Contains("OK")) throw new Exception("设置GSM字符集失败"); sp.WriteLine($"AT+CMGS=\"{textBox1.Text}\"\r\n"); if (!WaitForResponse(sp).Contains(">")) throw new Exception("设备未就绪接收短信内容"); sp.Write(textBox2.Text); // textBox2为短信内容输入控件 sp.Write(new byte[] { 0x1A }, 0, 1); // 发送Ctrl+Z标识结束短信内容输入 string sendRes = WaitForResponse(sp, 3000); if (sendRes.Contains("ERROR")) { MessageBox.Show("SMS failed!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("SMS Sent!!!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); } } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { if (sp != null && sp.IsOpen) sp.Close(); } }
注意:部分深度定制的安卓系统对基带AT指令有权限限制,如果配置完成后指令持续返回ERROR,可以在安卓端的串口工具中给AT通道授予Root权限,即可解决权限拦截问题。
内容的提问来源于stack exchange,提问作者Sajjad Ahmad
相关产品推荐
相关产品推荐

