1. 项目概述用树莓派搭建一个低成本短信服务器如果你手头有一台闲置的树莓派又恰好有一些需要自动发送短信提醒的场景比如服务器宕机报警、家庭安防通知或者只是想折腾点有趣的物联网项目那么这个用树莓派配合GPRS模块搭建的短信服务器绝对值得一试。它本质上是一个能通过网页或命令行向任意手机号码发送短信的微型系统。整个方案的核心成本极低硬件部分主要就是一块几十块钱的GPRS模块和一个USB转串口工具软件则完全基于开源生态。我最初做这个是为了给家里的智能家居系统增加一个不依赖网络的离线报警通道实测下来非常稳定已经连续运行了两年多。下面我就把从硬件选型、组装、软件配置到最终部署的完整过程以及我踩过的那些坑毫无保留地分享出来。2. 硬件选型与组装详解2.1 核心硬件清单与选型逻辑这个项目的硬件清单非常精简但每一件都有其不可替代的作用。选择它们不仅仅是图便宜更是基于稳定性和易用性的综合考虑。GPRS/GSM模块约30-50这是项目的“嘴巴”负责与蜂窝网络通信。原文提到的“GPRS Shield 900/1800MHz”是一种典型的2G模块。选择它是因为2G网络覆盖广、模块价格低廉、功耗相对可控且AT指令集成熟稳定。在国内你需要确保模块支持中国移动/联通的900MHz和1800MHz频段。现在市面上更常见的是SIM800系列模块如SIM800C它们功能类似且文档丰富是更好的选择。USB转TTL串口模块约5-15这是连接树莓派和GPRS模块的“翻译官”。为什么必须用它因为树莓派自带的硬件串口UART位于GPIO的14/15引脚默认被系统蓝牙占用且电平是3.3V。而多数GPRS模块需要5V供电且其串口通信电平可能不兼容。一个独立的USB转串口模块如CH340、CP2102芯片的可以完美解决供电和电平转换问题并提供稳定的/dev/ttyUSB0设备节点让软件配置变得简单统一。树莓派任意型号作为系统的“大脑”。Model B2512MB RAM或更新型号如3B、4B当然更好但即使是Zero W也完全够用。系统资源消耗很小主要运行一个轻量的Web服务器和后台命令。预付费SIM卡一张能正常发送短信的手机卡。注意有些物联网卡或特定套餐可能关闭了短信发送功能务必先用手机测试一下。同时确保卡已插入模块并识别通常会有网络状态指示灯。注意供电是关键GPRS模块在发送短信瞬间的峰值电流可能达到2A仅靠USB转串口模块的5V引脚供电可能不足会导致模块重启或发送失败。强烈建议为GPRS模块单独供电。最稳妥的方法是使用一个5V/2A以上的电源适配器其正极VCC同时连接到USB转串口模块的VCC引脚和GPRS模块的VCC引脚所有设备的GND地线连接在一起。这样可以避免因电流不足导致的不稳定。2.2 硬件连接与焊接要点连接原理很简单让树莓派通过USB转串口模块与GPRS模块进行串口对话。具体引脚对应关系如下USB转串口模块的TX-GPRS模块的RX(数据从树莓派发送到模块)USB转串口模块的RX-GPRS模块的TX(数据从模块发送到树莓派)USB转串口模块的VCC (5V)-GPRS模块的VCC(供电如前所述建议外接电源)USB转串口模块的GND-GPRS模块的GND(共地必须连接)实际操作中我强烈建议你使用杜邦线进行连接并在连接前用万用表确认USB转串口模块的VCC引脚输出确实是5V。有些劣质模块输出可能不稳。焊接时如果模块有排针可以先焊接好排针再插杜邦线如果直接焊接线材注意做好绝缘防止短路。一个我踩过的坑早期我曾尝试跳过USB转串口直接将树莓派3.3V的GPIO串口连接到模块结果通信极其不稳定时好时坏。原因是电平不匹配和驱动能力不足。所以不要省掉USB转串口模块它能省去你无数调试时间。3. 软件环境配置与Gammu调试硬件连接好后我们进入软件层面。核心软件是Gammu它是一个功能强大的手机通讯工具箱支持数百款手机和模块我们用它来驱动GPRS模块。3.1 系统准备与Gammu安装首先确保你的树莓派系统如Raspbian是最新的。通过SSH或直接连接显示器键盘操作。# 更新软件源列表和升级现有软件包 sudo apt update sudo apt upgrade -y # 安装Gammu及其图形前端Wammu用于初始调试很方便 sudo apt install gammu wammu -ywammu是一个图形界面在桌面环境下可以直观地查看信号、读取短信等但对于无头服务器无显示器模式我们主要使用命令行工具gammu。3.2 配置Gammu识别硬件这是最关键的一步告诉Gammu你的GPRS模块连接在哪里。查找设备将USB转串口模块插入树莓派USB口然后执行ls /dev/ttyUSB*通常你会看到/dev/ttyUSB0。如果连接了多个USB串口设备可能会有ttyUSB1等。记下你的设备号。创建配置文件Gammu的配置文件是~/.gammurc用户目录或/etc/gammurc全局。我们先在用户目录创建nano ~/.gammurc输入以下内容[gammu] # 端口改为你实际的设备比如 /dev/ttyUSB0 port /dev/ttyUSB0 # 连接速度SIM800系列通常是115200 connection at115200 # 名称可自定义 name my_gprs_module # 型号可以留空Gammu会自动检测 model 按CtrlO保存CtrlX退出nano编辑器。测试连接运行以下命令如果配置正确你会看到模块的详细信息。gammu --identify成功的输出类似这样Device : /dev/ttyUSB0 Manufacturer : SIMCOM INCORPORATED Model : SIMCOM_SIM800C (c) Firmware : Revision:1418B02SIM800C32 IMEI : 862991234567890 SIM IMSI : 460001234567890这表示Gammu已经成功与你的GPRS模块“握手”并读取到了IMEI和SIM卡信息。如果这一步失败请按以下顺序排查权限问题当前用户可能没有读写/dev/ttyUSB0的权限。可以尝试sudo gammu --identify或者将用户加入dialout组sudo usermod -a -G dialout $USER然后注销重新登录。端口错误确认ls /dev/ttyUSB*的输出修改配置文件中的port。波特率错误尝试其他常见波特率如at9600、at19200等。硬件连接问题检查TX/RX是否接反这是最常见错误检查供电是否充足SIM卡是否插好模块天线是否连接。4. 构建网页版短信发送接口让短信服务器通过网页操作是最方便的使用方式。我们将创建一个简单的PHP页面。4.1 安装Web服务器与PHP树莓派上最轻量的是lighttpd但Apache更常见。这里以Apache为例sudo apt install apache2 php php-cli -y安装完成后在浏览器访问树莓派的IP地址如http://192.168.1.100应该能看到Apache的默认页面。4.2 解决权限问题让Web服务器能执行Gammu这是安全且关键的一步。Apache以www-data用户身份运行它默认无权直接访问硬件设备(/dev/ttyUSB0)或执行gammu命令。我们通过sudo授权但要用安全的方式——无需密码地运行特定命令。使用visudo命令安全地编辑sudo配置sudo visudo在文件末尾添加下面这行www-data ALL(ALL) NOPASSWD: /usr/bin/gammu这行配置的意思是用户www-data可以在任何主机上以任何用户身份无需密码地执行/usr/bin/gammu这个命令。 按CtrlO保存CtrlX退出。4.3 创建PHP短信发送脚本现在在Apache的网页目录通常是/var/www/html/下创建我们的短信发送页面例如sms.phpsudo nano /var/www/html/sms.php将以下PHP代码粘贴进去。这段代码不仅实现了基本功能还增加了安全检查、错误处理和更友好的反馈!DOCTYPE html html head title树莓派短信服务器/title meta charsetutf-8 style body { font-family: sans-serif; margin: 30px; background: #f5f5f5; } .container { background: white; padding: 25px; border-radius: 10px; max-width: 600px; margin: auto; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h2 { color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; } label { display: block; margin-top: 15px; font-weight: bold; color: #555; } textarea, input[typetext] { width: 100%; padding: 10px; margin-top: 5px; border: 1px solid #ccc; border-radius: 5px; box-sizing: border-box; font-size: 14px; } button { background-color: #4CAF50; color: white; padding: 12px 25px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; margin-top: 20px; } button:hover { background-color: #45a049; } .result { margin-top: 25px; padding: 15px; background-color: #e8f5e9; border-left: 4px solid #4CAF50; white-space: pre-wrap; font-family: monospace; font-size: 13px; } .error { background-color: #ffebee; border-left-color: #f44336; } /style /head body div classcontainer h2 树莓派短信发送平台/h2 form methodpost action label fornumbers收信号码多个用英文逗号隔开:/label input typetext idnumbers namenumbers placeholder例如13800138000,13912345678 required label formessage短信内容最多200字符:/label textarea idmessage namemessage rows4 placeholder请输入要发送的短信内容... maxlength200 required/textarea div字符数: span idcharCount0/span/200/div button typesubmit namesend发送短信/button /form ?php if ($_SERVER[REQUEST_METHOD] POST isset($_POST[send])) { $numbers trim($_POST[numbers]); $message trim($_POST[message]); // 基础验证 if (empty($numbers) || empty($message)) { echo div classresult error错误号码和内容不能为空。/div; exit; } if (strlen($message) 200) { echo div classresult error错误内容超过200字符限制。/div; exit; } // 清理号码字符串分割成数组 $numberArray array_map(trim, explode(,, $numbers)); $validNumbers []; foreach ($numberArray as $num) { // 简单的手机号格式验证11位数字以1开头 if (preg_match(/^1[3-9]\d{9}$/, $num)) { $validNumbers[] $num; } else { echo div classresult error警告号码 $num 格式不正确已跳过。/div; } } if (empty($validNumbers)) { echo div classresult error错误没有有效的手机号码。/div; exit; } echo div classresultstrong发送报告/strong\n; echo \n; // 对消息进行安全转义防止shell注入 $escapedMessage escapeshellarg($message); $successCount 0; $failCount 0; foreach ($validNumbers as $index $phone) { // 构建Gammu命令。使用sudo并以www-data用户执行。 // -c 指定配置文件避免依赖全局配置。 $command sudo -u www-data /usr/bin/gammu -c /etc/gammurc --sendsms TEXT . escapeshellarg($phone) . -text . $escapedMessage . 21; $output []; $returnCode 0; exec($command, $output, $returnCode); echo \n--- 尝试发送给 $phone ---\n; echo implode(\n, $output); if (strpos(implode( , $output), OK) ! false $returnCode 0) { echo ✅ 发送成功\n; $successCount; } else { echo ❌ 发送失败\n; $failCount; } // 在每条短信之间添加延迟避免模块或网络处理不过来 // 根据你的网络状况调整6秒是个保守值 if ($index count($validNumbers) - 1) { sleep(6); } } echo \n\n; echo 总结成功 $successCount 条失败 $failCount 条。/div; } ? /div script // 实时显示文本域字符数 document.getElementById(message).addEventListener(input, function() { document.getElementById(charCount).textContent this.value.length; }); /script /body /html保存并退出。现在通过浏览器访问http://你的树莓派IP/sms.php你应该能看到一个简洁的发送界面。输入号码和内容点击发送下方会实时显示Gammu的命令行输出。关键点解析与避坑escapeshellarg()函数这是安全的重中之重。它将用户输入的手机号和消息内容用单引号包裹确保即使输入中包含特殊字符如;、|、也不会被shell误解为命令从而有效防止命令注入攻击。sudo -u www-data我们虽然配置了www-data可以无密码运行gammu但在PHP的exec()中默认用户可能不是www-data。使用sudo -u www-data显式地以www-data用户身份执行命令确保权限一致。-c /etc/gammurc指定Gammu配置文件的绝对路径避免因环境变量问题找不到配置。我们需要把之前创建的配置文件复制到/etc/下sudo cp ~/.gammurc /etc/gammurc。延迟sleep(6)连续快速发送短信可能导致模块或SIM卡网络响应不过来。添加延迟是保证成功率的重要措施。你可以根据实际网络情况调整这个时间。5. 进阶功能实现短信定时发送任务调度网页发送是即时的但有时我们需要定时发送比如每天早上的天气提醒、每周的备份报告。我们可以利用Linux自带的at命令来实现任务调度。5.1 安装并配置at命令首先安装at守护进程sudo apt install at -y确保atd服务已启动sudo systemctl enable --now atd。然后我们需要扩展www-data用户的sudo权限让它也能无密码使用at命令sudo visudo找到之前添加的那行修改为www-data ALL(ALL) NOPASSWD: /usr/bin/gammu, /usr/bin/at这样PHP脚本就能通过sudo调用at命令来安排任务了。5.2 创建定时发送PHP脚本新建一个文件例如sms_scheduler.php!DOCTYPE html html head title短信定时发送/title style/* 样式参考上一个文件此处省略以节省篇幅 *//style /head body div classcontainer h2⏰ 短信定时发送调度器/h2 form methodpost action label fornumbers收信号码仅第一个号码生效:/label input typetext idnumbers namenumbers placeholder13800138000 required label formessage短信内容:/label textarea idmessage namemessage rows4 required/textarea label forschedule_time发送时间at命令格式:/label input typetext idschedule_time nameschedule_time placeholder例如now10 minutes, 或 1430 (表示14:30), 或 tomorrow 9:00 AM required small提示now10 minutes (10分钟后), 1430 (今天14:30), tomorrow 9:00 AM, next friday/small button typesubmit nameschedule安排定时发送/button /form ?php if ($_SERVER[REQUEST_METHOD] POST isset($_POST[schedule])) { $phone trim($_POST[numbers]); $message trim($_POST[message]); $timeExpr trim($_POST[schedule_time]); // 基础验证 if (!preg_match(/^1[3-9]\d{9}$/, $phone)) { echo div classresult error错误手机号格式不正确。/div; exit; } if (empty($message) || empty($timeExpr)) { echo div classresult error错误内容和时间不能为空。/div; exit; } // 构建echo命令将短信内容通过管道传递给gammu再通过管道给at // 注意at命令从标准输入读取要执行的命令 $escapedMessage escapeshellarg($message); // 这里使用第一个号码因为at调度单条命令时多号码处理复杂 $gammuCommand /usr/bin/gammu -c /etc/gammurc --sendsms TEXT . escapeshellarg($phone) . -text . $escapedMessage; // 完整的shell命令将gammu命令echo出来通过管道交给at在指定时间执行 $fullCommand echo . escapeshellarg($gammuCommand) . | sudo -u www-data /usr/bin/at . escapeshellarg($timeExpr) . 21; $output []; exec($fullCommand, $output, $returnCode); echo div classresult; echo strong调度结果/strong\n; echo \n; echo 目标号码: $phone\n; echo 发送时间表达式: $timeExpr\n; echo --- 命令输出 ---\n; echo implode(\n, $output); if ($returnCode 0) { // 从at的输出中提取job id例如“job 12 at Mon May 16 14:30:00 2023” preg_match(/job\s(\d)\sat/, implode( , $output), $matches); $jobId $matches[1] ?? 未知; echo \n✅ 定时任务安排成功任务ID: $jobId\n; echo 你可以使用 sudo atq 查看队列或 sudo atrm $jobId 删除该任务。; } else { echo \n❌ 安排定时任务失败。请检查时间格式是否正确。; } echo /div; } ? /div /body /html5.3 at命令时间格式详解与实战技巧at命令的时间格式非常灵活但也是容易出错的地方。上面表单的提示只是一部分这里详细列一下表达式示例含义now 30 minutes30分钟后now 1 hour1小时后now 2 days2天后17:30今天下午5点30分如果已过则为明天17:30 tomorrow明天下午5点30分noon今天中午12点midnight今晚午夜12点teatime下午4点英式茶时间next week下周的此刻next monday下周一上午10点at的默认时间2024-12-252024年圣诞节上午10点管理定时任务查看队列在树莓派终端执行sudo atq会列出所有等待执行的任务。查看任务内容任务详情保存在/var/spool/at/目录下但直接查看不便。更简单的方法是邮件at命令的输出会邮寄给提交者。可以安装mailutils来查看sudo apt install mailutils然后看邮件mail。删除任务sudo atrm 任务ID任务ID从atq命令中获取。一个重要的限制与变通方案如原文所述通过echo “command” | at TIME这种方式只能处理一条简单的命令。如果你想像即时发送那样处理多个号码直接在at里用循环会比较麻烦。一个实用的变通方案是将发送逻辑写成一个独立的Shell脚本然后在at命令中调用这个脚本并传递参数。例如创建/usr/local/bin/send_bulk_sms.sh#!/bin/bash # 脚本接收两个参数号码列表逗号分隔和消息 NUMBERS$1 MESSAGE$2 IFS, read -ra ADDR $NUMBERS for i in ${ADDR[]}; do /usr/bin/gammu -c /etc/gammurc --sendsms TEXT $i -text $MESSAGE sleep 6 done赋予执行权限sudo chmod x /usr/local/bin/send_bulk_sms.sh。 然后在PHP中安排任务的命令就变成了echo /usr/local/bin/send_bulk_sms.sh 13800138000,13912345678 你的消息 | at now1 hour这样就能实现多号码的定时群发了。6. 稳定性优化与日常维护让这个短信服务器7x24小时稳定运行还需要一些优化和维护工作。6.1 开机自启动与看门狗我们希望树莓派开机后GPRS模块能自动就绪。可以创建一个systemd服务。创建服务文件sudo nano /etc/systemd/system/gprs-module-setup.service[Unit] DescriptionPrepare GPRS Module on USB Serial Afternetwork.target [Service] Typeoneshot # 有时USB设备加载需要时间等待2秒 ExecStartPre/bin/sleep 2 # 关键设置USB转串口设备的权限让www-data用户能读写 ExecStart/bin/chmod 666 /dev/ttyUSB0 RemainAfterExityes [Install] WantedBymulti-user.target启用并启动服务sudo systemctl daemon-reload sudo systemctl enable --now gprs-module-setup.service这个服务会在每次启动时确保/dev/ttyUSB0设备对所有人可读写解决可能的权限问题。6.2 网络状态监控与自动重启GPRS模块可能会因为信号不稳、SIM卡欠费等原因“死掉”。我们可以写一个简单的监控脚本定期检查模块是否还能响应AT指令。创建监控脚本/usr/local/bin/check_gprs.sh#!/bin/bash # 尝试通过Gammu发送一个空查询命令超时设为10秒 TIMEOUT10 if timeout $TIMEOUT gammu -c /etc/gammurc --identify /dev/null 21; then echo $(date): GPRS模块状态正常 else echo $(date): GPRS模块无响应尝试重启服务... # 记录日志 echo $(date): GPRS模块故障 /var/log/gprs_monitor.log # 可以在这里加入更激进的操作比如重启树莓派USB总线或系统 # sudo hub-ctrl -h 0 -P 2 -p 0 # 示例使用hub-ctrl关闭再打开USB电源需安装 # sleep 5 # sudo hub-ctrl -h 0 -P 2 -p 1 fi赋予执行权限sudo chmod x /usr/local/bin/check_gprs.sh。 然后通过cron定时任务每5分钟检查一次sudo crontab -e添加一行*/5 * * * * /usr/local/bin/check_gprs.sh6.3 安全加固建议这个网页接口暴露在局域网甚至互联网上如果你做了端口转发安全必须考虑。基础认证最简单的在Apache中为sms.php和sms_scheduler.php目录添加密码保护。使用htpasswd创建密码文件并在Apache虚拟主机配置中添加Require valid-user。使用HTTPS如果从外网访问务必配置SSL证书Let‘s Encrypt免费避免信息明文传输。可以使用certbot轻松为Apache配置HTTPS。限制访问IP在Apache配置中使用Require ip 192.168.1.0/24来只允许局域网IP访问。输入验证我们前面的PHP脚本已经做了基础验证但可以更严格比如限制每天发送条数、验证手机号归属地等。日志审计记录所有发送请求的IP、时间、号码和内容摘要注意隐私。可以将PHP脚本中的发送记录追加到一个日志文件中。7. 常见问题排查与解决实录在实际搭建和使用过程中你几乎一定会遇到下面这些问题。我把它们和解决方案整理成了表格方便你快速对照排查。问题现象可能原因排查步骤与解决方案gammu --identify无输出或报错1. 设备节点不对2. 波特率不匹配3. 权限不足4. TX/RX接反1.ls /dev/ttyUSB*确认设备名更新.gammurc。2. 尝试修改connection at9600或at19200。3. 执行sudo gammu --identify或将用户加入dialout组。4.重点检查交换USB转串口模块上的TX和RX线与GPRS模块的连接。发送短信失败提示“No response in specified timeout”1. 模块供电不足2. SIM卡无信号/欠费3. 短信中心号码(SCA)未设置1.外接5V/2A电源给模块单独供电这是最常见原因。2. 将SIM卡插入手机确认有信号、能发短信、未欠费。3. 用gammu --getsmsc查看短信中心号若无用gammu --setsmsc设置号码咨询运营商。网页点击发送后长时间无反应或报500错误1. PHP执行权限问题2. sudoers配置错误3. Gammu配置文件路径错误1. 在PHP脚本开头加error_reporting(E_ALL); ini_set(display_errors, 1);显示具体错误。2. 测试命令sudo -u www-data /usr/bin/gammu --identify看能否成功。3. 确认/etc/gammurc文件存在且内容正确。定时任务 (at命令) 未执行1.atd服务未运行2. 时间格式错误3. 命令语法在at环境中出错1.sudo systemctl status atd确保服务是active (running)。2. 用at now1 minute加一个简单的测试命令如echo test /tmp/test.txt看是否执行。3. 在at命令中尽量使用绝对路径环境变量可能与shell不同。模块偶尔能识别经常掉线1. USB接口或线缆接触不良2. 电源纹波干扰3. 模块自身故障或天线问题1. 更换USB口、USB线、杜邦线试试。2. 为树莓派和GPRS模块使用质量好的电源避免共用劣质插排。3. 确保天线已牢固连接尝试将模块和天线放在信号更好的位置。发送中文短信乱码字符编码问题Gammu默认可能使用GSM 7-bit编码。尝试在发送命令中指定编码gammu --sendsms TEXT 号码 -unicode -text “中文内容”。注意Unicode短信长度限制为70字符。最后关于硬件本身如果你发现手头的GPRS模块实在不稳定可以考虑升级到4G Cat.1或NB-IoT模块。它们的功耗更低、网络更稳定但价格会稍高且配置方式通常通过USB CDC-ACM或网口有所不同需要寻找对应的驱动和软件如wvdial、ppp或模块商提供的SDK。但对于绝大多数短信报警、通知类应用一个稳定的2G模块加上本文的配置已经足够可靠且极具性价比了。我的第一个版本就是用SIM800C做的至今还在车库的角落里默默工作提醒我车辆进出从未掉过链子。