使用Raspberry Pi转发国内短信到Slack


人在国外,虽然大部分时间不需要国内的服务,但是一旦需要用国内的公共服务或者银行相关的服务,基本每次都需要输入短信验证码。如果你有一台闲置的旧手机,一直插着电源,再装个短信转发服务理论上可以满足需求。我自己没试过,因为在那之前我的旧手机太旧了,型号Galaxy S2,电源键反应不良,不插电电池容量估计也就撑个几个小时,随时都有可能报废。另外还有一台旧手机不知道为什么无法接入网络和接收短信。如果为了“闲置的旧手机”再去买一台新手机或者中古手机非常不划算,想了想还是用自己闲置的Raspberry Pi做一个短信转发,一方面网上资料比较多,另一方面不用担心电池的问题。

以下是最终成果

需要的东西

  • Raspberry Pi 3 Model B+
  • AK020

Raspberry Pi 3 Model B+是一个自带有线和无线网络的版本。如果你用相对旧的版本的话,你可能需要自己处理网络接入的问题。

AK020是我在Soracom Starter Kit中看到的3G Dongle,理论上可以单独买

用Raspberry Pi转发短信时第一个要确认你的SIM卡是否可以被GMS/3G/4G模块或者3G/4G Dongle识别。老实说兼容性相关的资料很少,你只能碰运气。网上的很多资料里的模块或者Dongle有可能比较早,你已经买不到了。或者在国内淘宝上,不一定支持发送到国外。不过有一点你需要知道GMS/3G/4G模块肯定比3G/4G Dongle贵。比如说3G模块30000日元,3G Dongle只要3000日元。

作为参考,我的SIM卡是联通的3G卡,最终确认AK020支持我的SIM卡并且能在日本接收国内短信。

选AK020的主要原因是,AK020被Soracom,日本的一家做IoT比较有名的公司用作Starter Kit(AK020 + Soracom SIM),说明质量应该不错。顺便说一句,我是在Mercari上买的中古Starter Kit,里面的Soracom SIM已经过期了,Starter Kit附送了SIM卡适配和手机针。

短信转发所需要知道的知识

  • gammu-smsd
  • udev
  • systemd

gammu-smsd用来转发SMS的主要工具。前半部分的gammu是一个控制手机的工具。所谓的控制就是用USB连接你的手机,控制你的手机打电话,收短信等。如果你愿意的话,你可以在命令行输入gammu的命令来获取短信。gammu-smsd是专门用来收短信的。在收到短信之后,可以调用某个脚本。所谓转发就是用这个脚本转发短信。

很多Raspberry Pi转发短信教程都是把SIM卡插进Dongle,装个gammu-smsd就完事。事实上可能没那么简单,根据你的Dongle,你可能要做很多配置。而这些配置和udev和systemd相关。udev负责在你的USB接入Raspebrry Pi之后执行某些操作,比如说创建一个符号链接 /dev/ak020 -> /dev/ttyUSB0 。一些教程略过了这一步。如果你不配置符号链接,把你的3G Dongle接入另外一个USB口的话,你的gammu-smsd就不工作了,因为你在gammu-smsd指定的是 /dev/ttyUSB0 而不是 /dev/ak020

systemd是一个服务管理的工具,用来替代 /etc/init.d 下的shell脚本。你可以用systemd启动gammu-smsd,也可以用systemd包装一些一次性的脚本。我主要用systemd做Raspberry Pi启动后发送当前IP到Slack,以及gammu-smsd的延迟启动。

除了工具,你还需要知道,接受短信不需要连接3G数据网络,所以你不需要安装wvdial等工具,也不需要配置Raspberry Pi的ppp0等网络。一些AK020的教程是针对Soracom SIM卡的数据网络的,并不完全适合我们的短信接收。

AK020

AK020支持zero install。也就是这个3G Dongle同时也是一个只读的USB Flash。这个USB Flash里面包含驱动。这样当你丢失了随带的包含驱动的CD时也可以从3G Dongle本身读取。这对于Windows来说很友好,但是对于Linux,Mac来说就麻烦了。Linux和Mac只会识别AK020为一个CD,而不是一个3G Dongle。AK020的说明书提供了如何让Linux识别它为一个3G Dongle的命令,把命令转成udev rule,这样插上AK020之后Raspberry Pi会自动识别为3G Dongle而不需要每次都手动输入命令。

/etc/udev/rules.d/30-ak020.rules

ACTION=="add", ATTRS{idVendor}=="15eb", ATTRS{idProduct}=="a403", RUN+="/usr/sbin/usb_modeswitch --std-eject --default-vendor 0x15eb --default-product 0xa403 --target-vendor 0x15eb --target-product 0x7d0e"

ACTION=="add", ATTRS{idVendor}=="15eb", ATTRS{idProduct}=="7d0e", RUN+="/sbin/modprobe usbserial vendor=0x15eb product=0x7d0e"

KERNEL=="ttyUSB*", ATTRS{../idVendor}=="15eb", ATTRS{../idProduct}=="7d0e", ATTRS{bNumEndpoints}=="03", ATTRS{bInterfaceNumber}=="02", SYMLINK+="ak020" ENV{SYSTEMD_WANTS}="ak020-smsd.service"

说明一下这些规则

第一条指示在vendor为15eb同时product为a403的设备接入(即ACTION为add)时,执行usb_modswitch转换为另一个product。第二条则是在转换完之后执行modprobe,即加载驱动。第三条匹配名字起始为ttyUSB的设置,并过滤vendor和product,增加符号链接ak020。这样我们可以用 /dev/ak020 访问AK020这个3G Dongle。 ENV{SYSTEMD_WANTS}  是随带启动的systemd服务。一般来说不推荐gammu-smsd开机启动,除非你的手机或者3G Dongle一开始就接在Raspberry Pi上。当你的设备接上Raspberry Pi并且就绪之后开启gammu-smsd才是一个比较安全的做法。之后具体介绍ak020-smsd.service这个服务。

AK020的Linux版说明书(日文)

AK020还有另外一个配置需要注意:AK020接上Raspberry Pi之后默认是飞行模式。AK020设备上的灯,如果一直都是绿色,那么就表示是飞行模式。说明书上的其他模式

  • 1.5秒间隔点亮绿色:idle
  • 0.5秒间隔点亮绿色:数据通讯中
  • 红色:没有找到服务

如果要解除飞行模式,按照说明书需要发送AT命令 AT+CFUN=1 给AK020。AT命令是用来控制手机等的通用命令。你可以用cu,screen等来交互式地发送AT命令,也可以直接使用

echo -e "AT+CFUN=1\r\n" > /dev/ak020

发送。缺点是你不知道结果。

整理一下AK020需要做的事情

  1. 使用usb_modswitch切换product
  2. 创建符号链接 /dev/ak020
  3. 发送AT命令 AT+CFUN=1 给AK020解除飞行模式

前两者可以用udev的rule来处理,最后一个因为可能比较耗时,我放在一个systemd服务  ak020-smsd.service  去了。

ak020-smsd.service

这个systemd服务只是一个脚本 ak020-smsd.sh 的包装。

/etc/systemd/system/ak020-smsd.service

[Unit]
Description=Start Gammu smsd for AK020

[Service]
Type=simple
ExecStart=/home/pi/ak020-smsd.sh

服务配置很简单,即使你不熟悉systemd也没关系。ExecStart 指定脚本的位置, Type 使用默认的 simple 。如果你对systemd熟悉的话你也可以用 oneshot

/home/pi/ak020-smsd.sh

#!/bin/sh

echo -e "AT+CFUN=1\r\n" > /dev/ak020

sleep 10

systemctl start gammu-smsd

注意设置ak020-smsd.sh为可执行脚本

chmod +x /home/pi/ak020-smsd.sh

ak020-smsd.sh 脚本首先用echo给AK020发送AT命令。理论上这里应该用cu加expect的组合来确保AT命令执行完成,但是实际中AT命令有响应并不代表网络就绪。设备解除飞行模式之后还需要搜寻服务商,再登入网络,所以这里我设置sleep 10秒左右,保证gammu-smsd启动时AK020已经就绪。如果没有这个间隔的话,AK020和gammu-smsd可能会反复重启。

gammu-smsd

安装gammu-smsd和禁用开机启动。之前也说过,gammu-smsd需要等待设备就绪后启动。

sudo apt-get install gammu-smsd
sudo systemctl disable gammu-smsd

gammu-smsd的配置如下

/etc/gammu-smsdrc

# Configuration file for Gammu SMS Daemon
# Gammu library configuration, see gammurc(5)
[gammu]
port = /dev/ak020
connection = at
# Debugging
#logformat = textall
gammucoding = utf8

# SMSD configuration, see gammu-smsdrc(5)
[smsd]
service = files
logfile = syslog
# Increase for debugging information
debuglevel = 1
RunOnReceive = /home/pi/sms-forwarder.sh

# Paths where messages are stored
inboxpath = /var/spool/gammu/inbox/
outboxpath = /var/spool/gammu/outbox/
sentsmspath = /var/spool/gammu/sent/
errorsmspath = /var/spool/gammu/error/

大部分都是默认配置,几个修改过的和需要注意的配置项

port = /dev/ak020

port之后是之前配置过的符号链接

connection = at

使用默认的at,at表示使用AT命令控制,可以不指定频率(比如at19200)

gammucoding = utf8

不指定的话短信消息会变成乱码。测试下来,短信的编码为UTF-8。实际可能会有不同,你可以先用gammu读取已有的短信进行测试。

RunOnReceive = /home/pi/sms-forwarder.sh

短信转发脚本位置

短信转发

前面所有事情做完之后,终于到了可以自由发挥的步骤了。

我使用最简单的Slack Bot的incoming webhook做短信转发。专门设置一个叫做 #sms-forwarder 的channel,把短信往这个channel里发送。本文不会介绍如何申请Slack Bot和配置incoming webhook,不过提醒一下这个脚本因为有nested environment variable所以处理起来有点麻烦。以下是我的脚本。

/home/pi/sms-forwarder.sh

#!/bin/sh

for i in `seq $SMS_MESSAGES` ; do
    # use eval to get nested environment variable
    # otherwise you will get a bad substitution error
    SMS_NUMBER=$(eval echo "\${SMS_${i}_NUMBER}")
    SMS_TEXT=$(eval echo "\${SMS_${i}_TEXT}")
    PAYLOAD="{
            \"type\": \"plain_text\",
            \"text\": \"${SMS_NUMBER}\n ${SMS_TEXT}\"
        }"
    curl -X POST -H "Content-type: application/json" --data "$PAYLOAD" https://hooks.slack.com/services/xxx
done

gammu-smsd传入的环境变量名为 SMS_1_NUMBER, SMS_1_TEXT, SMS_2_NUMBER, SMS_2_TEXT… 这是一种内嵌的环境变量名。如果你用shell写脚本的话,你需要用 $(eval echo "\${SMS_${i}_NUMBER}") 这种方式来读取,否则你会得到一个 bad substitution error

最后注意设置这个脚本为可执行脚本

chmod +x /home/pi/sms-forwarder.sh

小结

整体来说,用Raspberry Pi转发短信到Slack还是有些复杂的。建议还是一点一点配置,特别注意你的3G/4G Dongle有没有特殊配置,有的话需要udev/systemd做相应的配置。希望本文能帮到你。