这台中兴F50放在车里当热点已经用了很久,说实话我一开始完全没把它的“短信功能”当回事。直到有一次抢票,手机里死活收不到银行验证码,我才意识到:一台常年插电、塞在角落里的随身WiFi,短信是最容易被忽略的盲区。既然F50本身跑的是安卓底层,又支持ADB调试,那完全可以自己动手做一条“收到短信→实时转发到微信”的链路,而且门槛没有想象中那么高。这篇保姆级教程就是干这个的:从ADB开关开始,到PushPlus配置,再到一个能长期挂着跑的转发脚本,我会把每一步怎么操作、为什么这么操作、踩过哪些坑都写清楚。适合完全没接触过ADB的新手,也适合想把手头安卓设备短信能力盘活的朋友。
1. 先说清楚:这条链路到底怎么工作的
1.1 F50值得折腾短信转发的几类场景
中兴F50这类随身WiFi,本质是一台带蜂窝网络的安卓设备。它插上SIM卡之后能发WiFi、能上网,但大多数人都忽略了它其实也能收短信。放在车里、工位上、甚至老家当“固定热点”长期插电使用时,短信就成了一个没人看的盲区。
我见过最典型的几个场景:一是车载WiFi,车停在地库,SIM卡收的验证码、通知全堆在设备里;二是双卡用户把副卡塞进F50,副卡绑定的银行、App验证码根本收不到;三是把F50当“公网IP代理工具”用的朋友,更需要第一时间知道SIM卡是否欠费、是否被运营商限制。这些场景下,一个能自动把短信推到微信的方案,价值就体现出来了。
1.2 整条链路的技术原理拆解
这条链路不复杂,核心就三段:F50系统收到短信之后,短信会写入系统自带的短信数据库;电脑或小主机通过ADB(Android Debug Bridge)连接F50,定期查询短信数据库;脚本把新增短信提取出来,调用PushPlus的HTTP接口推送到你的微信。
很多人一听“ADB”就觉得是搞机党才碰的东西,其实ADB就是一个调试通道,安卓系统都内置支持。你要做的无非是:把设备上的调试开关打开,让电脑能通过命令行访问它,然后执行一条查询短信的命令。PushPlus更简单,它是一个微信公众号推送服务,注册之后拿到一个token,往它的接口发一个请求,微信就能收到消息。整体思路和“用爬虫监控网页变化”是同一个套路,只是把监控对象换成了F50的短信数据库。
之所以选ADB而不是在F50上装App,主要原因是F50这类设备的系统是精简过的,想装一个安卓App反而麻烦,而且官方固件也不一定开放安装入口。ADB是系统级能力,不需要在设备上装任何东西,稳定性更高,也更符合“长期无人值守”的需求。整个方案的维护成本,基本只有那一台负责跑脚本的电脑或NAS。
2. 环境准备与ADB工具安装
2.1 下载ADB Platform-Tools
ADB不是Windows自带的,需要单独下载Google官方的Platform-Tools。打开Android开发者官网的Platform-Tools下载页面(developer.android.com/tools/releases/platform-tools),选Windows版本,解压到一个路径,比如D:\platform-tools。
解压之后你会看到一堆exe和dll文件,其中adb.exe就是核心程序。可以先进目录打开命令行,输入adb version验证一下能不能跑。这一步不需要安装任何东西,解压即用,但要注意路径里别带中文和空格,后续写脚本调用时省心很多。
2.2 配置环境变量,让系统全局认识adb
如果不配环境变量,每次使用ADB都得cd到解压目录,或者写全路径,很容易出问题。配环境变量是为了让Windows在任何目录下都能直接执行adb命令。
操作步骤是:右键“此电脑”选“属性”,点“高级系统设置”,在“高级”标签页里点“环境变量”,在下方的“系统变量”里找到Path,双击编辑,新建一行,填D:\platform-tools,确定保存。然后重新打开一个cmd窗口,执行adb version,如果能看到版本号就说明配置成功。这里有个小坑:环境变量改完之后,之前已经打开的cmd窗口不会生效,必须新开窗口。
2.3 安装Python:用什么跑脚本
后面的转发脚本我会用Python写,因为语法简单、轮询和HTTP调用都很方便,Windows、Linux都能跑。去python.org下载Python 3.x版本,安装时务必勾选“Add Python to PATH”,不然命令行里python会提示找不到命令。装完后新开cmd执行python --version确认。
如果你手头已经有Node.js或者其他语言环境,脚本逻辑也可以移植,但没必要折腾。教程里的脚本是完整可跑的,直接用Python最稳妥。跑脚本的机器可以是普通电脑、旧笔记本、软路由、NAS,只要能和F50处在同一个局域网、能访问外网就行。
3. 中兴F50开启ADB调试的完整操作
3.1 进入开发者选项,找到ADB开关
中兴F50的ADB开关不在默认菜单里,一般藏在开发者选项里。先把电脑或手机连上F50的WiFi,浏览器打开它的管理后台,常见地址是http://192.168.0.1,部分固件版本是http://192.168.1.1,以设备背面的标签为准。
后台登录进去之后,找“设置”或者“系统设置”菜单,里面一般能看到“开发者选项”。如果找不到,别急着放弃,很多中兴设备需要连续点击“固件版本号”或者“关于本机”里的版本号5到7次,才会解锁开发者选项,这招和手机解锁开发者模式是一个逻辑。部分固件还会把“ADB调试”和“USB调试”分开命名,本质上都是同一个开关,把它打开就行。如果后台实在找不到,也可以试一下中兴官方的“中兴智联”App,工具箱里偶尔会有调试相关入口,但不同固件差异较大,以你手头那台的界面为准。
3.2 USB连接电脑与驱动处理
开启ADB开关之后,用USB数据线把F50连到电脑上。这里强烈建议先试USB连接,因为无线连接经常因为F50没配对码而卡住,USB是最直接的通道。
连接后打开cmd,执行adb devices。如果列表里出现设备的序列号,状态是device,那就是连接成功,可以直接跳到下一节开无线ADB。如果提示adb不是内部或外部命令,是你环境变量没配好;如果设备列表为空,优先考虑换一根数据线——很多线只能充电不能传数据。再不行就右键“此电脑”进设备管理器,看有没有带黄色感叹号的未知设备,有的话需要装中兴的USB驱动,或者手动更新驱动指向你解压的Platform-Tools目录里的驱动文件。F50一般没有屏幕,所以插上USB后不会像手机那样弹出“允许USB调试吗”的对话框,它会默认允许,这一点和手机不太一样。
3.3 用tcpip模式开启无线ADB
USB能连上之后,要长期跑脚本总不能每次都插着线。ADB支持通过WiFi连接,也就是无线调试模式,把设备转到TCP/IP监听模式就行。
USB连接状态下,先执行:
adb tcpip 5555这条命令告诉F50在5555端口上监听ADB。然后拔掉USB数据线,让电脑连上F50的WiFi热点(或者路由器下同一局域网),执行:
adb connect 192.168.0.1:5555这里的IP是F50的局域网IP,一般就是管理后台那个地址。执行完再adb devices,能看到类似192.168.0.1:5555 device的输出就说明无线ADB已经通了。
需要注意一点:如果F50的系统版本是Android 11以上,“无线调试”功能可能会有配对码机制,直接用adb connect可能报错。但F50这类精简设备通常用adb tcpip 5555的方式就能绕开配对限制,这也是为什么建议先用USB连接。如果遇到cannot connect,先确认电脑和F50在同一个WiFi下,再试试重启ADB服务端:adb kill-server然后adb start-server。
4. 验证ADB能读到短信
4.1 常用ADB命令速查
连上ADB之后,先用几条常用命令确认设备状态和系统环境,顺便也能排查后续问题。这里整理了一个速查表,都是整个方案里最常用的:
| 命令 | 作用 |
|---|---|
adb devices | 查看已连接的ADB设备列表 |
adb connect 192.168.0.1:5555 | 无线连接指定设备 |
adb shell content query --uri content://sms/inbox | 查询收件箱短信 |
adb shell dumpsys telephony.registry | 查看SIM卡状态、信号等 |
adb logcat -d | 抓取系统日志,排错很常用 |
adb shell uiautomator dump | 导出当前界面UI层级,后面权限受限时会用到 |
dumpsys和logcat在正常运行时不需要,但一旦遇到“读不到短信”“设备连不上”这类问题,它们就是排查利器。完整命令记不住也没关系,先把adb devices和content query这两条记住就够用了。
4.2 核心读取命令与输出解析
验证能不能读到短信,直接执行:
adb -s 192.168.0.1:5555 shell content query --uri content://sms/inbox --projection _id,address,body,date这条命令的意思是:查询系统短信数据库的收件箱,只取_id(短信ID)、address(发件号码)、body(短信内容)、date(时间戳)四个字段。如果F50里已经收到了短信,输出会是类似这样的格式:
row: 0 _id=12, address=10086, body=您的验证码是123456, date=1700000000000 row: 1 _id=11, address=95588, body=消费提醒:支出58元, date=1699999999000每行是一条短信,row: 0表示第0条,后面的键值对就是字段内容。注意date是毫秒级Unix时间戳,需要除以1000才能转换成正常时间。只要能看到这类输出,说明ADB权限没问题,可以进入下一步。
4.3 一个必须绕开的坑:命令传参与中文乱码
如果你是在Windows的cmd里直接手敲这条命令,大概率会遇到两个问题。
第一个问题是--sort_order参数。很多人想用date DESC按时间倒序排列,但直接在命令行里写--sort_order date DESC,adb会把date和DESC当成两个独立参数传给设备的shell,查询会直接报错。正确姿势是加反斜杠转义空格:--sort_order date\ DESC。但这里不建议用排序参数,后面Python脚本里改成只按ID增量判断新短信,反而更简单可靠。
第二个问题是中文乱码。F50的短信内容默认是UTF-8编码,Windows的cmd默认是GBK编码,直接打印很容易显示成乱码。临时解决办法是在cmd里执行chcp 65001切换到UTF-8代码页,治本的办法是用Python脚本读取,运行时指定UTF-8编码。后面脚本部分会专门处理这个问题,手敲命令时看到乱码别慌,不代表数据坏了。
5. PushPlus配置与测试
5.1 注册获取token
PushPlus是一个微信公众号消息推送服务,它的逻辑是:你扫码关注它的公众号,然后它给你一个token,你调用它的HTTP接口,消息就会通过公众号推送到你的微信。对个人来说,这就是最简单的“免费服务器推送通知”方案,不需要自己搭公众号,也不需要考虑微信接口的签名算法。
打开PushPlus官网(pushplus.plus),用微信扫码登录,登录后控制台首页就能看到你的token,一串字符复制保存好。整个教程只需要一对一推送功能,也就是把消息推给你自己,不需要建群组。其实PushPlus还支持一对多群组、微信模板消息等高级功能,但对短信转发场景来说,一对一token就完全够用了。
5.2 用浏览器直接测试推送
拿到token之后,先不要急着写脚本,用浏览器手动测一次,确认推送链路通不通。把下面URL里的token换成你自己的,直接粘贴到浏览器地址栏访问:
https://www.pushplus.plus/send?token=你的token&title=测试短信&content=HelloWorld如果一切正常,页面会返回一串JSON,类似{"code":200,"msg":"请求成功","data":"发送成功"},同时你的微信会收到一条来自PushPlus公众号的消息。这一步的目的是把故障范围缩小:只要推送通,后面脚本出问题就只可能是读取短信或传参环节。
5.3 关于推送频率和参数格式的提醒
PushPlus免费版是有调用频率限制的,具体限流策略会动态调整,但长期高频请求确实有被限的风险。短信转发场景本身频率很低,一天撑死几十条,完全在合理范围内。但如果脚本逻辑有bug,比如把同一批历史短信反复推送,就可能触发限制。
还有一点要注意:content参数里如果有中文或换行,在URL里直接拼接可能出问题,需要做URL编码。浏览器手动测试时内容简单无所谓,Python脚本里会用urllib.parse.urlencode自动处理,这属于“代码帮你避坑”的部分。另外每次调用后检查返回的code字段是否等于200,这是判断推送是否成功的标准。
6. 用Python脚本实现自动转发
6.1 脚本整体逻辑与轮询策略
脚本的思路很直接:每隔几秒用ADB查一次短信数据库,把新出现的短信ID和之前见过的ID做对比,有变化就推送,最后把最新ID保存下来。为什么用ID判断而不是时间?因为同一秒内可能收到多条短信,只用时间戳对比会漏掉;短信数据库的_id是单调递增的,用ID做增量判断最可靠。
轮询间隔我建议设置成3到5秒。太短会频繁唤醒ADB通道,增加设备功耗和通道不稳定风险;太长又会明显延迟短信到达微信的时间,验证码场景会急死人。3到5秒基本是“感受不到延迟”的平衡点。
初次运行时的历史短信处理也是个关键点。F50里可能已经存了几十条甚至上百条旧短信,如果脚本启动时一股脑全部推送,微信会炸。这里用一个配置项SKIP_EXISTING控制,第一次运行时自动把当前数据库里所有短信ID都标记为“已读”,从下一次轮询开始才推送新增短信。这个细节很重要,第一次跑脚本之前一定要确认开关是打开的状态。
6.2 完整可运行的转发脚本
下面这个脚本可以直接保存为sms_forward.py运行。我尽量把注释写详细,配置项都集中在最上面,方便你根据自己的环境改。
# -*- coding: utf-8 -*- import subprocess import time import re import json import os import urllib.request import urllib.parse from datetime import datetime # ============ 配置区 ============ DEVICE_IP = "192.168.0.1" # F50的局域网IP,改成你实际的 ADB_PORT = "5555" TOKEN = "你的pushplus_token" # pushplus.plus 后台复制 POLL_INTERVAL = 5 # 轮询间隔,单位秒 SKIP_EXISTING = True # True=启动时跳过历史短信,只推新增 STATE_FILE = "seen_ids.txt" # 已处理短信ID的存档文件 # ================================ SEEN_IDS = set() if os.path.exists(STATE_FILE): with open(STATE_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.isdigit(): SEEN_IDS.add(int(line)) # 正则解析 content query 输出 LINE_RE = re.compile(r"_id=(\d+),\s*address=([^,]*),\s*body=(.*),\s*date=(\d+)") def run_adb(args, timeout=10): cmd = ["adb", "-s", f"{DEVICE_IP}:{ADB_PORT}"] + args try: res = subprocess.run( cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout, ) return res.stdout except subprocess.TimeoutExpired: return "" except FileNotFoundError: print("[错误] 找不到 adb 命令,请检查环境变量") return "" def is_connected(): try: out = subprocess.run( ["adb", "devices"], capture_output=True, text=True, timeout=5 ).stdout except Exception: return False return f"{DEVICE_IP}:{ADB_PORT}" in out def query_sms(): out = run_adb([ "shell", "content", "query", "--uri", "content://sms/inbox", "--projection", "_id,address,body,date", ], timeout=15) return out def parse_sms(text): events = [] blocks = re.split(r"row: \d+ ", text) for block in blocks: m = LINE_RE.search(block) if not m: continue sms_id = int(m.group(1)) address = m.group(2).strip() body = m.group(3).strip() date_ms = int(m.group(4)) events.append((sms_id, address, body, date_ms)) return events def send_pushplus(title, content): params = urllib.parse.urlencode({ "token": TOKEN, "title": title, "content": content, }) url = "https://www.pushplus.plus/send?" + params try: with urllib.request.urlopen(url, timeout=10) as resp: data = resp.read().decode("utf-8") obj = json.loads(data) if obj.get("code") == 200: return True, "" return False, data except Exception as e: return False, str(e) def save_state(): with open(STATE_FILE, "w", encoding="utf-8") as f: for sid in sorted(SEEN_IDS): f.write(str(sid) + "\n") def mark_existing_sms(): # 首次运行时把当前所有短信ID都标记为已读,不推送历史记录 out = query_sms() for sms_id, _addr, _body, _date in parse_sms(out): SEEN_IDS.add(sms_id) save_state() print(f"[初始化] 已标记 {len(SEEN_IDS)} 条历史短信为已读") def poll_once(): out = query_sms() if "row:" not in out: print(f"[{datetime.now():%H:%M:%S}] 未读短信为空或ADB连接异常") return False events = parse_sms(out) new_events = [e for e in events if e[0] not in SEEN_IDS] if not new_events: return True # 按ID升序推送,避免同批次多条短信顺序错乱 for sms_id, address, body, date_ms in sorted(new_events, key=lambda x: x[0]): title = f"F50短信 {address}" content = f"来自 {address} 的短信:\n{body}" ok, err = send_pushplus(title, content) if ok: print(f"[{datetime.now():%H:%M:%S}] 已推送短信 {sms_id} 来自 {address}") else: print(f"[{datetime.now():%H:%M:%S}] 推送失败 {sms_id}: {err}") SEEN_IDS.add(sms_id) time.sleep(0.5) # 多条短信之间稍作间隔,避免触发限流 save_state() return True def main(): if SKIP_EXISTING and not os.path.exists(STATE_FILE): mark_existing_sms() print("开始监控短信...") while True: try: if not is_connected(): print("设备不在线,尝试重连...") run_adb(["connect", f"{DEVICE_IP}:{ADB_PORT}"]) time.sleep(3) continue poll_once() time.sleep(POLL_INTERVAL) except KeyboardInterrupt: print("\n手动退出") save_state() break except Exception as e: print("发生异常:", e) time.sleep(POLL_INTERVAL * 2) if __name__ == "__main__": main()直接运行之前,先把你自己的DEVICE_IP和TOKEN替换进去。在项目目录执行:
python sms_forward.py正常启动后,控制台会先提示已标记历史短信,然后每隔几秒静默轮询。此时给F50里的SIM卡发一条短信,几秒钟内微信就会被推送。
6.3 部署成后台服务,让它长期挂着跑
如果你只是想试一下,开着cmd窗口跑就行。但长期用,总不能让电脑一直挂着窗口。这里给几种简单的部署思路。
Windows用户最简单的做法是写一个start.bat,内容只有两行:
chcp 65001 python D:\sms_forward\sms_forward.py把这个bat放进开机启动文件夹(shell:startup),开机就自动跑。想更规范的话,可以用nssm把Python注册成Windows服务,故障自动重启,但说实话对个人场景有点杀鸡用牛刀了。家里有NAS或者软路由的话,把脚本和Python环境放到机器上,用systemd管理就够了,示例单元文件和脚本同名即可,路径按自己环境改,典型的守护配置是Restart=always。关键逻辑都一样:让脚本进程在后台持续运行,断了能自动拉起。
7. 常见问题与排错实录
7.1 adb devices 为空或一直显示 unauthorized
adb devices什么都没输出,先想三件事:线是不是数据线、驱动装没装、F50的ADB开关是不是真的开了。线的问题我前面强调过,很多Type-C线只能充电,换根数据线瞬间就好。驱动的问题去设备管理器看有没有未知设备,有就装官方ADB驱动。F50这类随身WiFi没有屏幕,一般不会出现手机上那种需要点“允许调试”的弹窗,但如果你的固件比较特殊,连上USB后还是需要确认,那只能找一台有显示器的环境解决。
unauthorized状态是授权没通过,主要是设备端拒绝了调试请求。但F50精简系统里,授权弹窗可能被隐藏了,这时候可以尝试adb kill-server再adb start-server重启ADB服务端,或者关掉开发者选项里的ADB开关再重新打开。如果还是不行,检查一下F50系统时间是否准确,时间偏差过大会导致授权证书校验失败。
7.2 无线连接被拒绝或掉线
adb connect 192.168.0.1:5555返回cannot connect,大概率是设备没在监听5555端口。有些固件重启之后会恢复成默认状态,USB调试开关和tcpip监听都会失效,需要重新插一次USB执行adb tcpip 5555。所以脚本里我加了断线自动重连逻辑,但connect命令对“端口没监听”的情况是无效的,该插线重来还是得来一次。
还有一种情况是F50开启了休眠省电导致WiFi/ADB断掉。随身WiFi为了续航,空闲时会进入低功耗状态,ADB通道自然就断了。解决思路是尽量保持充电状态,同时在后台设置里把“自动休眠”关掉,或者把省电模式关掉。这个坑在长时间挂机场景下很常见,我一度以为脚本写错了,后来才发现是设备自己睡着了。
7.3 读不到短信,提示Permission Denial
终端输出类似SecurityException: Permission Denial的时候,说明当前shell用户没有访问短信ContentProvider的权限。中兴F50原厂固件我实测大多数能直接读,但个别定制固件确实锁了这块。如果遇到,先检查命令里的uri是不是content://sms/inbox,有时精简固件把收件箱uri改成了content://sms,去掉/inbox再试一次。
兜底方案就是之前提到的adb shell uiautomator dump。原理很简单:通过ADB把系统短信App当前界面的UI层级导出来,然后从XML文本里匹配短信内容。前提是F50上有短信App界面,而且你得先把界面切到短信列表页,操作起来相对笨重,但面对权限锁死的固件,这可能是唯一不刷机的路子。我的建议是优先折腾固件里的开发者选项权限,兜底方案只作为备选。
7.4 短信内容解析乱码或字段错位
脚本读取短信时用encoding="utf-8"处理adb输出,Windows控制台用chcp 65001,这两步做到位基本不会乱码。如果短信body里本身包含逗号,我的正则解析用的是贪婪匹配,只要date字段在行尾,body里的逗号不会影响结果。但万一某条短信正文里也含有, date=这种字符串,就会截断,概率极低,真遇到可以在正则上继续加限制。
字段错位多半是因为某些短信的address为空,比如一些运营商服务短信发送号码显示为null,命令行输出可能直接省略这个字段,导致正则没匹配上。所以脚本里如果看到某条短信没推,先打开ADB手动查一次原始输出,看看是哪一行字段缺失。
7.5 PushPlus收不到消息或返回异常
浏览器测试推送成功,但脚本推送失败,最常见原因是你改了token之后脚本没重启,还拿着旧token在跑。另一个坑是content参数没编码,脚本里用urlencode已经处理了,但如果你手写curl测试,记得对中文内容做URL编码。返回400或429则表示请求参数错误或触发频率限制,先看返回的msg字段具体内容,再针对性调整。
从PushPlus服务器到微信完成展示会有几秒延迟,这是正常现象,只要脚本没报错,耐心等一会儿就行。如果长时间收不到,去PushPlus公众号的推送记录里翻一下,能看到历史推送详情,这个功能排查很管用。
7.6 一定要留意的安全底线
ADB调试是一个高权限通道,开启后局域网内的其他设备理论上都能尝试连接你的F50。虽然连接ADB通常需要授权,但adb tcpip 5555模式下如果设备端宽松授权,风险还是存在的。建议只在可信内网里开启,不要为了远程管理把F50放到公网可达的环境,更不要把5555端口映射出去。
PushPlus的token等同于是你的“微信推送钥匙”,任何人拿到都能往你微信发消息,不要上传到公开的代码仓库。脚本里保存的短信内容可能包含验证码、账单、个人隐私,运行脚本的机器要做好基本的安全防护,不用的历史短信数据定期清理。这套方案的本质是把设备的私密信息转发到你的私人微信,安全这根弦绷紧一点没坏处。
最后聊一点我自己的实际体会。这套转发方案跑起来之后,最大的感受是“存在感极低”,它不会像微信通知那样频繁打扰你,只在真正有短信的时候响一下,这一点非常符合我的预期。但折腾过程中真正花掉我最多时间的不是脚本逻辑,而是排查F50无线连接掉线的问题——设备一休眠,所有自动化都白搭,所以后来我把F50固定插电,又关掉省电模式,才算真正稳定下来。如果你也打算长期依赖这套方案,我的建议是:第一次部署多花一点时间把状态保存、断线重连、历史短信跳过这几个环节测透,后面就真的可以做到“忘了它的存在”了。