2025-02-15  2025-02-17    1611 字  4 分钟

苦于国内的短信平台都需要指定发送模板, 没法发送高度自定义的消息, 且模板审核过于麻烦, 因此想要自己开发一套用于发送短信的平台

此文章 的启发, 决定使用Air780E和树莓派制作一个能够接收发送短信的程序

功能

  • 发送短信
  • 接收短信
  • Web界面, 支持查看发送和接收历史
  • API接口, 直接通过调用API接口发送短信

所需材料

  • Air780E
  • 一张手机卡(最好按照自身需求开通短信包,这样发短信便宜)
  • 树莓派

实现逻辑

  1. 在树莓派上运行编写好的程序, 通过GPIO和Air780E通信, 提供HTTP的API接口来发送或查看收到的短信
  2. Air780E收到短信后通过GPIO发送给树莓派, 交由树莓派进一步处理
  3. 树莓派通过GPIO将需要发送的短信内容和手机号发送给Air780E, Air780E将短信发送给指定手机号
  • 由于我还有一个海外手机卡, 因此你会在代码和配置中看到cn和us两个结尾的配置, 他们分别代表了国内和国外手机卡
  • 对于Air780E如何上电自启以及GPIO定义可以看 这篇博客
  • 如何刷写固件及代码可以看 官方文档

整体预览

由于我用了两张卡, 两个手机号, 因此使用了两个Air780E, 如果你只用一个手机号, 那么可以去掉一个

图中蓝色部分是串口数据线, 红色部分是5v供电线, Air780E上除了供电还有一个用于检测树莓派是否在线(由于本身供电就来自树莓派,因此无意义,不过最好还是连上)

连线

Air780E使用的GPIO

Air780E使用的GPIO

整体

Web界面

界面

收到和发出的短信的历史

短信历史

注意事项

由于当初开发时没想过要开源出来, 因此很多地方的代码非常简陋, 部分功能需要的配置写死在文件中, 因此如果想获取除收发短信外的功能, 需要一定的代码知识, 手动修改代码

首先看配置文件

文件: config.ini

  • serial-cnserial-us这里定义了国内和国外手机卡使用的那个串口和Air780E通信, 具体树莓派中每个串口使用的是那个GPIO需要看自己树莓派版本的GPIO定义
  • server定义了程序提供服务的端口
  • database定义了数据库文件存放位置
  • log定义了是否将log输出到文件, 以及log文件存放位置
  • security定义了访问Web界面需要输入的用户名和密码, 以及访问API接口时使用的key

Air780E中收到短信后自身执行的命令

文件: air780e/main.lua

这是写入Air780E的文件, 也就是Air780E用的代码

搜索代码中的手机号12345678900替换为自己日常使用的手机号(并非插在Air780E的手机号)

例如在收到help短信内容时Air780E会直接通过短信返回信息, 不再发送给树莓派处理

if txt == "help" then
    sms.send("+8612345678900", "[SMS][HELP]\nreboot - Reboot SMS\nstatus - SMS Status\ncstatus - SMS Current Status")
    return
end

同时Air780E在上电后也会向这个手机号发送一条短信, 表示程序正常

树莓派和Air780E的数据交换部分

文件: serial/serial_cn.go

下面代码和main.lua中的手机号一样, 都是自己日常使用的手机号

const selfPhoneCN = "12345678900"

readCallbackCN函数中有下面代码

主要说明最后一个else if, 由于树莓派本身运行了HA(Home Assistant这)是收到特定短信后, 通过HA重启路由器, 这样当家里断网时可以尝试通过短信重启路由器看看能否恢复正常

if sms.Message == "hello" {
    if strings.Contains(sms.Phone, selfPhoneCN) {
        SendCN("sms", model.NewMSG(model.MsgTagSmsSend, model.NewSMSLong(sms.Phone, "Hello Akvicor! here is sms")))
    } else {
        SendCN("sms", model.NewMSG(model.MsgTagSmsSend, model.NewSMSLong(sms.Phone, "Hello! here is sms")))
    }
} else if sms.Message == "你好" {
    if strings.Contains(sms.Phone, selfPhoneCN) {
        SendCN("sms", model.NewMSG(model.MsgTagSmsSend, model.NewSMSLong(sms.Phone, "你好Akvicor!这里是sms")))
    } else {
        SendCN("sms", model.NewMSG(model.MsgTagSmsSend, model.NewSMSLong(sms.Phone, "你好!这里是sms")))
    }
} else if sms.Message == "ha.help" && strings.Contains(sms.Phone, selfPhoneCN) {
    SendCN("sms", model.NewMSG(model.MsgTagSmsSend, model.NewSMSLong(sms.Phone, "[HA][HELP]\nha.op.reboot - Reboot OP")))
} else if sms.Message == "ha.op.reboot" && strings.Contains(sms.Phone, selfPhoneCN) {
    _ = util.HttpPost("http://127.0.0.1/api/services/script/reboot_router", nil, util.HTTPContentTypeJson, map[string]string{"Authorization": "Bearer xxxxxxx"})
    SendCN("sms", model.NewMSG(model.MsgTagSmsSend, model.NewSMSLong(sms.Phone, "Reboot OP")))
}

编译运行

将代码下载到本地, 进入代码根目录执行

go mod tidy
go build -trimpath -ldflags "-s -w" -o sms

假设执行文件目录: path/to/exec/sms 假设数据文件目录: path/to/data 假设配置文件目录: path/to/data/config.ini

那么可以编写以下systemd脚本/etc/systemd/system/sms.service

[Unit]
Description=sms
After=network.target auditd.service
ConditionFileIsExecutable=path/to/exec/sms

[Service]
User=root
StartLimitInterval=5
StartLimitBurst=10
ExecStart=path/to/exec/sms -c path/to/data/config.ini
WorkingDirectory=path/to/data
Restart=always
RestartSec=15
LimitNPROC=32768
LimitNOFILE=1048576
# CAP_NET_ADMIN:允许执行网络管理任务
# CAP_NET_BIND_SERVICE:允许绑定到小于1024的端口
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

启用服务

systemctl enable sms
systemctl start sms