iOS 自动化ESP32蓝牙 HID

ESP32 HID 低成本 iOS 自动化方案:蓝牙 HID 实现 iPhone 免越狱自动化控制

用 ESP32 蓝牙 HID 给 iPhone 注入触控指令:零签名、零描述文件、极低成本(¥12–30/台),从固件编写到中控联调的完整实操教程。

约 19 分钟

在移动设备批量管理领域,「一台电脑控一百台手机」一直是个刚需。安卓端的方案已经比较成熟——通过 USB 或无线 ADB 即可批量接入,成本也低。但 iOS 生态封闭,Apple 不开放任何原生远程控制接口,导致 iPhone/iPad 的批量控制长期是行业痛点。

为什么要选 ESP32,硬件和固件怎么配

过去几年,iOS 自动化有三种主流路线:代理模式(Proxy) 给 Apple 描述文件签名、装一个中间 App 拦截触控事件,有三个硬伤——每次签名有费用、被系统更新打乱频率高、某些机型签名后稳定性差;快捷指令 + 屏幕录制 利用 iOS 内置工作流触发 App 操作,门槛最低但可控性极差,只能执行预定义的有限动作,无法模拟真实点击坐标、无法灵活读界面状态;蓝牙 HID 硬件注入 通过一块外接硬件伪装成键盘或鼠标,把触控指令通过蓝牙发给 iPhone,这就是 ESP32 方案的定位。

ESP32 是目前业界最成熟的低功耗蓝牙(BLE)微控制器之一,价格通常在 10 到 30 元之间,支持蓝牙 5.0,可以完美模拟标准蓝牙键盘和鼠标设备,让 iPhone 把它当成一只普通蓝牙鼠标。

它的核心价值在于三点:零签名、零描述文件,不需要装任何 App,不影响设备原有的安全性,也不会在设备上留下“被控”的痕迹;极低成本,单台只需一片 ESP32-C3 或 ESP32-S3,加几根杜邦线和面包板,整套硬件不超过 30 元,百台投入约 3000 元;可离线运行,ESP32 接着中控电脑的 USB 口或独立供电即可独立完成蓝牙 HID 通信,不需要 iPhone 保持联网,对网络隔离、隐私要求高的场景很关键。

选型:

型号 芯片 闪存 适用场景 参考价
ESP32-C3 RISC-V 单核 448KB SRAM 轻载 HID 注入,预算优先 ¥12–18
ESP32-S3 双核 Xtensa 512KB SRAM 多任务、OTA、日志打印 ¥20–30
ESP32-WROOM 双核 Xtensa 4MB Flash 传统 WiFi+BLE 双模 ¥15–20

纯 HID 触控注入场景,ESP32-C3 已完全够用且性价比最高;要同时处理 WiFi 转发或 OTA 就选 S3,WROOM 适合需要云端联动的企业部署。接线也很简单,ESP32 本身就是一块完整的开发板,USB 线直插电脑即可完成供电和烧录,无需额外接线:单台使用直接用 Micro-USB 线插电脑或充电宝,大批量部署建议用 USB Hub 集中供电;用热熔胶或小扎带固定在设备背后,注意不要挡住 Lightning 充电口;批量组装时建议在防静电工作台操作,ESD 可能对 BLE 天线性能有影响。没有排线、没有破解壳、不需要拆机,这是相比 iPad 解锁器、越狱模块等方案的核心优势。

硬件到位、线也插上了,剩下的是把固件跑起来。

EasyClick HID 设置模式界面展示蓝牙 HID 设备的配对与连接流程

ESP32 用 Arduino IDE 或 PlatformIO 开发都可以,下面用 Arduino IDE 演示,核心是让它运行一个 BLE HID 设备(Bluetooth Human Interface Device Profile),模拟标准的 Bluetooth Mouse。环境搭建分四步:下载 Arduino IDE(2.x 版本以上);打开“文件 → 首选项 → 附加开发板管理器 URL”,填入 https://raw.githubusercontent.com/espressif/arduino-esp32/package_esp32_index.json;打开“工具 → 开发板管理器”,搜索 esp32 并安装最新版本的 ESP32 by Espressif;选择对应的开发板型号,例如 ESP32 Dev Module,处理器选 ESP32,端口选择正确的 COM 口。

核心代码框架:

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "BLE2902.h"
#include "BLEHIDDevice.h"
#include "hid_keys.h"

#include "features/PowerService.h"
#include "features/HIDReport.h"

#define VBAT_PIN      ADC1_CHANNEL_6
#define VBAT_DIV_EN   true
#define ENABLE_CHARGING   false

bool connected;

const char *hidDesc = "\x05\x01"                      // Usage Page (Generic Desktop)
                      "\x09\x02"                      // Usage (Mouse)
                      "\xA1\x01"                      // Collection (Application)
                      "\x85\x01"                      //   Report ID (1)
                      "\x09\x01"                      //   Usage (Pointer)
                      "\xA1\x00"                      //   Collection (Physical)
                      "\x05\x09"                      //     Usage Page (Buttons)
                      "\x19\x01"                      //     Usage Minimum (1)
                      "\x29\x03"                      //     Usage Maximum (3)
                      "\x15\x00"                      //     Logical Minimum (0)
                      "\x25\x01"                      //     Logical Maximum (1)
                      "\x75\x01"                      //     Report Size (1)
                      "\x95\x03"                      //     Report Count (3)
                      "\x81\x02"                      //     Input (Data, Var, Abs)
                      "\x75\x05"                      //     Report Size (5)
                      "\x95\x01"                      //     Report Count (1)
                      "\x81\x03"                      //     Input (Const, Var, Abs)
                      "\x05\x01"                      //     Usage Page (Generic Desktop)
                      "\x09\x30"                      //     Usage (X)
                      "\x09\x31"                      //     Usage (Y)
                      "\x09\x38"                      //     Usage (Wheel)
                      "\x15\x81"                      //     Logical Minimum (-127)
                      "\x25\x7F"                      //     Logical Maximum (127)
                      "\x75\x08"                      //     Report Size (8)
                      "\x95\x03"                      //     Report Count (3)
                      "\x81\x06"                      //     Input (Data, Var, Rel)
                      "\xC0"                          //   End Collection
                      "\xC0";                         // End Collection

BLEHIDDevice* hid;
InputReport* mouseReport;
PowerService* power;

void setup() {
  Serial.begin(115200);

  hid = new BLEHIDDevice();
  power = new PowerService(*hid, VBAT_PIN, VBAT_DIV_EN, ENABLE_CHARGING);
  mouseReport = new InputReport(*hid, 1);

  hid->manufacturer()->SetValueString("EasyClick");
  hid->pnp(0x02, 0xe502, 0xa0ce, 0x0210);
  hid->hidInfo(0x00, 0x01);

  BLEDevice::init("EasyClick-HID");

  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(hid);

  hid->bootInpReport();
  hid->reportMap((uint8_t *)hidDesc, strlen(hidDesc));
  hid->startServices();

  hid->setBatteryLevel(100);

  connected = false;

  advertising->setScanResponse(true);
  advertising->addFlags(FLAGS_BREDR_NOT_MODERABLE);
  advertising->addServiceUUID(hid->hidService()->getUUID());

  advertising->start();
  hid->setAppearance(HID_MOUSE);

  Serial.println("Ready. Pair via Bluetooth settings on your iOS device.");
}

void loop() {
  delay(100);
}

这段代码创建了一个 BLE HID Mouse 设备。关键点:Report Descriptor(报告描述符) 按 HID 协议规范定义报告格式——3 个按钮位 + 1 个 X 轴 + 1 个 Y 轴 + 1 个滚轮,每个值都是相对位移(Rel),符合蓝牙 HID 鼠标的工作方式;服务 UUID 由 BLE 协议栈自动分配,不需要手动指定;Advertising(广播) 设置广播名和标志位,让 iPhone 能在蓝牙列表里看到“EasyClick-HID”这个名字。编译烧录后 ESP32 就进入配对模式。

iOS 端配对是一次性的:打开 iPhone 的 设置 → 蓝牙;在设备列表中找到 EasyClick-HID;点击连接,iOS 会提示“是否允许此设备访问你的应用?”,点允许;配对成功后状态栏会出现一个小键盘图标,表示蓝牙外设已连接。后续只要 ESP32 和 iPhone 都在蓝牙范围内且未断开,连接就自动保持。配对时注意:蓝牙距离建议在 10 米以内、障碍物越少越好;不要频繁切换 Wi-Fi,部分 iOS 版本在 Wi-Fi/BLE 共存时有干扰;配对失败就在 iPhone 上删掉旧设备后重启 ESP32;ESP32-C3/S3 通常能同时维护最多 5–10 个 BLE 连接(含 HID)。

配对完成后,核心工作就是通过 ESP32 向 iPhone 发送实际的触控指令。方式一是 ESP32 本地执行,业务逻辑简单时(比如固定坐标点击、顺序滑动)可以把完整逻辑写在固件里,改上面的 loop() 函数:

void loop() {
  // 移动到坐标 (X, Y) 并点击
  moveAndClick(100, 200, true);
  
  delay(500);
  
  // 向右滑动 50 像素
  scrollRelative(-50, 0);
  
  delay(1000);
}

void moveAndClick(int x, int y, bool click) {
  Mouse.move(x, y);
  if (click) {
    Mouse.press(MOUSE_LEFT);
    delay(50);
    Mouse.release(MOUSE_LEFT);
  }
}

void scrollRelative(int dx, int dy) {
  Mouse.move(dx, dy);
}

这种方式优点是不依赖外部通道,缺点是每改一次逻辑都要重新烧录固件。方式二是中控上位机下发,更实用:中控电脑通过串口/USB/WiFi 发送指令序列,ESP32 接收到后立即翻译成 BLE HID 鼠标动作执行,这样可以在电脑上编写复杂的 Python/Node.js 业务脚本:

# 中控端示例(Python)
import serial
import time

ser = serial.Serial('/dev/cu.SLAB_USBtoUART', 115200)

def send_move(x, y):
    """相对移动"""
    ser.write(f'MOVE {x} {y}\n'.encode())

def send_click():
    """左键单击"""
    ser.write(b'CLICK\n')

def send_scroll(dy):
    """垂直滚动"""
    ser.write(f'SCROLL {dy}\n'.encode())

# 实际业务流程
send_move(100, 50)
send_click()
time.sleep(0.5)
send_scroll(-20)

ESP32 端的串口逻辑负责解析这些指令并调用对应 Mouse API 执行,这是生产环境的标准做法,业务脚本写在电脑上,ESP32 只是一个透明的“遥控器转换器”。无论哪种方式,最终效果都是 ESP32 告诉 iPhone“手指在这里按了一下”,而 iOS 系统和目标 App 毫无感知地接受了这个输入。

实战流程、选型对比与它为什么值得用

以电商多店铺上架为例:唤醒页面(点 Safari 书签或导航到卖家后台);登录验证(点用户名框 → 输账号 → 点密码框 → 输密码 → 点登录);商品编辑(点“新增商品”→ 逐行填标题、分类、规格 → 上传主图);发布上架(点确认提交 → 等待加载 → 跳转商品列表 → 重复下一件)。每一步都通过 HID 指令精确模拟手指操作,配合图像识别(如 OpenCV 模板匹配)判断页面元素是否出现,遇弹窗就模拟关闭,遇异常就记录截图并告警。批量化的关键是并行和差异化:一台电脑通过多串口/USB Hub 同时连接几十片 ESP32,每台对应一台 iPhone,中控程序为每台设备维护独立的指令队列;随机化操作间隔(300–1500ms)、坐标偏移(±5px)、点击时长(50–150ms),模拟真人手感、降低平台风控概率。管理 50 台 iPhone 做电商运营,总硬件成本大约 1500 元(ESP32 × 50 + USB Hub + 线材),再加 3–5 台 macOS 或 Windows 电脑做中控服务器,远低于购买商用群控软件动辄数万的费用。

和其他 iOS 方案比:

维度 ESP32 BLE HID 蓝牙 HID 模块(商业) OTG HID(有线) 代理模式
硬件成本 ¥12–30/台 ¥50–150/台 线缆 ¥5–10/条 零硬件
是否需要签名 否 否 否 是(付费签名)
安装复杂度 低(直插即用) 中 中(需布线) 中
抗系统更新 5/5 5/5 5/5 2/5
并发稳定性 4/5 4/5 5/5 3/5
可扩展性 高(自研二次开发) 受限 受线缆限制 高(纯软件)

ESP32 方案的核心竞争力在于自主可控的扩展能力:固件可以自己改,协议可以自己加,批量烧录可以自己搞;商业 HID 模块开箱即用,但碰到定制需求很被动;OTG 有线方案物理层更稳,但大规模部署的线缆管理成本不容小觑。它之所以值得认真对待,根本原因很简单:它绕开了 iOS 的所有安全机制,却没有任何安全风险,因为从根本上就不存在“绕过”,你用的正是 Apple 自己设计的蓝牙 HID 协议。对从业者来说,这是一套成本最低、风险最小、兼容性最好的 iOS 自动化输入方案,无论跨境电商运营、应用测试还是内部效率工具,都值得纳入技术方案备选库,尤其设备量级到 10 台以上时成本优势很明显。

常见问题与排查

Q1:ESP32 搜不到或连不上 iPhone?

最常见原因是蓝牙配对缓存冲突。在 iPhone 上删除旧的设备后,给 ESP32 断电重启再重新扫描。如果多次失败,尝试刷入最新固件。部分旧版 ESP-IDF 对 BLE 的连接参数有 Bug。

Q2:触控有时不灵敏或有延迟?

先检查 ESP32 的供电是否充足,电压不足会导致 BLE 发射功率下降。其次检查蓝牙信道干扰。微波炉、Wi-Fi 路由器都工作在 2.4GHz,尽量把 ESP32 远离这些源。如果是连续大量指令堆积,加节流控制:每两条指令之间至少间隔 20ms,避免 BLE 缓冲区溢出。

Q3:能不能同时控制多台手机?

可以。每片 ESP32 独立连接一台 iPhone。中控侧通过串口编号区分不同的 ESP32,并发发送指令。典型的上限是一台电脑通过 USB Hub 管理 16–32 台 ESP32,受限于 USB 带宽和 CPU 调度。如果要扩展到 50 台以上,可以拆分成多台中控机。

Q4:iPhone 系统更新后能用吗?

ESP32 HID 方案使用的是 Apple 官方公开的 HID 蓝牙协议栈,属于蓝牙基础功能层。除非 Apple 从操作系统层面禁止 HID 设备(从未发生过),否则 iOS 每次大版本更新都能正常使用。这是它相对于越狱方案和描述文件方案的最大优势,不受系统更新影响。

Q5:会不会被 App 检测到?

不会。ESP32 对 iOS 而言就是一个标准的蓝牙鼠标/键盘设备。App 收到的触控事件和手指触控完全一致,没有任何差异字段可以被用来区分来源。这也是为什么这套方案在风控场景下特别有价值,它是目前市面上最安全的自动化输入方式之一。

Q6:续航和发热怎样?

ESP32-C3 在 BLE HID 待机状态下电流约 3mA,持续发送指令时峰值 50–80mA。接充电宝或 USB 口都不会有明显发热,连续运行几个月没问题。如果做户外或移动场景部署,搭配一个 5000mAh 的充电宝就能保证一周以上的独立工作时间。

Q7:有没有现成的开源项目可以参考?

有的。github.com/nkolban/ESP32_BLE_Arduino 提供了基础 BLE HID 示例,还有专门面向 TouchBar 的 ESP32-BLE-HID-Touchbar 等项目可以作为参考起点。不过实际生产环境建议在此基础上做定制开发,加入心跳保活、断线重连、日志上报等企业级特性。

Q8:鸿蒙设备能用同样的方案吗?

可以。HarmonyOS 同样支持蓝牙 HID 协议栈,ESP32 配对方式与 iOS 完全一致。唯一的差异在于脚本层面的适配。HarmonyOS 的应用 UI 结构(ArkUI)与 iOS/UIKit 不同,需要针对华为的具体页面编写定位和点击逻辑。

Q9:能否做到熄屏操作?

可以。蓝牙 HID 输入不依赖屏幕显示状态,即使 iPhone 处于锁屏状态,ESP32 发送的点击和滑动指令依然能穿透到锁屏界面。但要注意:从锁屏到主屏幕需要先模拟解锁手势,之后才能进入具体 App 内部操作。部分机型需要在“设置 → Face ID / Touch ID → 锁屏时需要按下侧边按钮”关闭相关保护。

Q10:这套方案的极限带机量是多少?

理论上一台电脑通过 USB Hub 挂接的 ESP32 数量没有硬性上限(USB 3.0 Hub 理论上支持 127 个设备,实际受主控芯片驱动能力限制)。实践中常见的规模是 16–32 台单台中控机管理。更大的规模建议采用分布式架构:多台轻量中控机各自管理一批 ESP32,上层统一调度和监控。


关于 EasyClick:手机自动化脚本开发平台,覆盖安卓免 ROOT、iOS 免越狱、鸿蒙三大手机生态,还提供苹果脚本编写、苹果群控、安卓群控、远程云控、AI 群控手机等。→ 了解全部产品


想要真实跑起来?

本文介绍的方案均可在 EasyClick 手机自动化平台落地。官网提供完整文档、开发工具与群控云控产品,免费体验。

访问 EasyClick 官网 →