Esp32-HID外设 iOS
请前往 HID 外设 页面查看完整的芯片选购、固件下载、烧录教程和视频教程。
iOS 支持蓝牙固件,C3 / S3 / ESP32 Pico 等都能用。
本模块为 HID 模式专用,需要 ESP32 硬件。WDA 模式下请使用 action 模块。
位移模式 (rel / abs)
从 5.x 版开始,固件是 单 bin 双模式,同一个固件包含两套 HID 方案,开机时根据内部 NVS 标志选择其中一种工作:
| 模式 | 原理 | 优势 | 限制 |
|---|---|---|---|
| rel (相对鼠标) | 模拟普通 BLE 鼠标的相对位移 | 全 iOS 版本兼容(12+) | 需要一次性校准;新 iOS (18+) 不允许带按键移动,swipe 在 iOS 18+ 会失效 |
| abs (绝对鼠标) | HID 描述符直接用 0~4095 绝对坐标 | 无需校准,点击/滑动都稳定 | 仅 iOS 18+ 支持 |
简单来说:
- iOS ≥ 18 → 建议用 abs 模式(连上就能用,swipe 稳定)
- iOS < 18 → 只能用 rel 模式(第一次使用要校准一次)
- 固件出厂默认 rel 模式
模式在固件里持久化,切换一次之后每次开机都是新模式,除非再次切换。切换方式有两种:
- 在 iOS App 首页「位移模式切换」按钮里选
- 脚本里调
device.set_mode(...)(见下文)
切换模式会让固件重启,广播名也会变(AS_iOS_REL_xxxx ↔ AS_iOS_ABS_xxxx)。因为 iOS 会缓存旧的 HID 描述符,切换后必须在「设置 → 蓝牙」里把旧设备「忽略此设备」,再重新配对新名字,否则能连上但点击无反应。
使用前准备
手机设置
abs 模式(iOS 18+ 推荐)只需做一件事:
1. 开启辅助触控:设置 → 辅助功能 → 触控 → 辅助触控 → 打开开关
不开启的话鼠标指针能移动但点击无效。这是 iOS 系统要求,rel / abs 两种模式都一样。
rel 模式除上面那一条外,还要把两个滑块拉满:
2. 跟踪灵敏度拉到最右:设置 → 辅助功能 → 触控 → 辅助触控 → 跟踪灵敏度 → 拉到最右
3. 跟踪速度拉到最右:设置 → 通用 → 触控板与鼠标 → 跟踪速度 → 拉到最右
rel 模式靠校准表补偿 iOS 的鼠标加速度。两个滑块都必须在最右(最快),校准和运行才是同一套系数;否则点击会偏移。
abs 模式不需要调这两项——abs 是绝对坐标直接映射,不受跟踪速度/灵敏度影响。
第一次配对
首次运行脚本时,iPhone 上会弹出一个 "蓝牙配对请求" 的弹窗,点 "配对" 就行。配对成功后屏幕上会出现一个小圆点(鼠标指针),这是正常的。
- 配对弹窗只在 App 前台时才显示,看不到时切回 App
- 日常使用不要在 iOS 设置里"忽略此设备",下次要重新配对
- 但切换 rel/abs 模式之后必须忽略旧设备重新配对(见上方警告)
- 也可以在 iOS 系统蓝牙设置里先手动配对,然后脚本里直接连
导入模块
from ascript.ios.esp32hid import BleDevice
快速上手
rel 模式(默认)
第一次使用前:
- 开启辅助触控(设置 → 辅助功能 → 触控 → 辅助触控)
- 跟踪灵敏度拉到最右(设置 → 辅助功能 → 触控 → 辅助触控 → 跟踪灵敏度)
- 跟踪速度拉到最右(设置 → 通用 → 触控板与鼠标 → 跟踪速度)
- 校准一次(在 iOS App 首页点「校准」按钮,走一遍默认流程)
之后直接用:
from ascript.ios.esp32hid import BleDevice
device = BleDevice() # 自动读屏幕大小
device.connect() # 自动扫描、连接、登录
device.click(642, 1389) # 点击屏幕某个位置(物理像素坐标)
device.home() # 回到桌面
abs 模式(iOS 18+ 推荐)
切到 abs 模式之后无需校准,连上就用:
from ascript.ios.esp32hid import BleDevice
device = BleDevice()
device.connect()
device.click(642, 1389)
device.swipe(500, 1500, 500, 500, 400) # 从下往上滑,iOS 18+ 上稳定
connect() 内部会向固件查询 getmode?,自动识别当前是 rel 还是 abs 模式。你的脚本只需要用 device.click/swipe/... 等统一接口,不用关心底层模式。
连接相关
创建设备
device = BleDevice()
会自动从 iOS 系统读屏幕大小。如果自动读取不对可以手动传:
device = BleDevice(screen_width=1284, screen_height=2778)
扫描附近芯片
devices = device.scan(timeout=5) # 返回名字列表
print(devices)
# ['AS_iOS_REL_8CD0CA63B0E4']
连接设备
device.connect() # 自动发现并连接第一个 AS_ 开头的设备
device.connect("AS_iOS_REL_8CD0CA63B0E4") # 或指定设备名
- 连接后自动登录,不需要手动
login() - 如果上次连接还在,会自动复用,速度很快
- 连接成功后 Python 会通过
getmode?确认当前固件模式,打印[BleDevice] 固件模式: rel(或abs)
断开连接
device.disconnect()
检查连接状态
if device.is_connected():
print("已连接")
位移模式切换
查询当前模式
print(device.get_mode()) # "rel" 或 "abs"
常量:
BleDevice.MODE_REL # "rel"
BleDevice.MODE_ABS # "abs"
切换模式
device.set_mode(BleDevice.MODE_ABS)
# 或
device.set_mode("abs")
切换流程:
- Python 发
setmode?abs&给固件 - 固件写 NVS → 自动
ESP.restart() - 板子重启后广播名变成
AS_iOS_ABS_xxxx(之前是AS_iOS_REL_xxxx) - Python 自动断开旧连接
- 需要你手动操作:
- iPhone 设置 → 蓝牙 → 找到旧的
AS_iOS_*设备 → 点右侧 ⓘ → 忽略此设备 - 回到 App 或脚本,重新
device.connect(),这次会自动配对新名字
- iPhone 设置 → 蓝牙 → 找到旧的
不手动忽略旧配对的话,iOS 会用缓存的旧 HID 描述符,结果是能连上但点击无反应。这是 Apple 的 GATT 缓存机制决定的,无法在代码里绕过。
脚本可以在启动时检查一下当前模式,不对就要求用户切换:
device.connect()
if device.get_mode() != BleDevice.MODE_ABS:
print("当前是 rel 模式,脚本需要 abs 模式,请切换后再运行")
# 也可以直接触发切换
# device.set_mode(BleDevice.MODE_ABS)
exit()
点击和滑动
所有坐标都是屏幕物理像素坐标,左上角是原点 (0, 0)。
不管 rel 还是 abs 模式,Python 接口统一用物理像素,不需要你做换算。
移动光标
只移动光标到目标位置,不点击。适 合"先看看那是什么再决定要不要点"的场景。
| 参数 | 类型 | 说明 |
|---|---|---|
| x | int | 横坐标(物理像素) |
| y | int | 纵坐标(物理像素) |
device.move_to(500, 800) # 光标移到 (500, 800), 不点击
- abs 模式:瞬移(~50ms)
- rel 模式:走 mouseHome + walk(时 间取决于距离,如果光标已经在目标位置会自动跳过)
device.move_to(500, 800) # 先移过去
time.sleep(0.5) # 看一下
device.click(500, 800) # 再点(第二次不用重新走位, 很快)
点击
点击屏幕上某个位置。会阻塞到点击完成才返回。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| x | int | - | 横坐标(物理像素) |
| y | int | - | 纵坐标(物理像素) |
| duration | int | 100 | 按住多长时间(毫秒) |
device.click(642, 1389) # 普通点击
device.click(500, 800, 200) # 按住 200ms 再松开
device.click(500, 800, 3000) # 长按 3 秒
长按
语义同 click(x, y, duration),只是默认 duration=1000ms。
device.long_click(500, 800) # 长按 1 秒
device.long_click(500, 800, 2000) # 长按 2 秒
双击
在同一位置快速点击两次。用两次 click 加短暂等待实现:
import time
device.click(642, 1389)
time.sleep(0.08)
device.click(642, 1389)
触摸(低层)
touch 是 slide(x, y, x, y, duration) 的简写,不阻塞等待完成。一般用 click 就够了。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| x | int | - | 横坐标 |
| y | int | - | 纵坐标 |
| duration | int | 100 | 按住多长时间(毫秒) |
device.touch(642, 1389)
滑动
从一个位置滑动到另一个位置。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| x1 | int | - | 起点横坐标 |
| y1 | int | - | 起点纵坐标 |
| x2 | int | - | 终点横坐标 |
| y2 | int | - | 终点纵坐标 |
| duration | int | 300 | 滑动总时间(毫秒) |
| easing_mode | int | 0 | 缓动模式 |
| easing_power | int | 0 | 缓动力度(2=二次方,3=三次方) |
缓动模式(仅 abs 模式下生效 ,让滑动曲线更自然):
| 值 | 效果 | 说明 |
|---|---|---|
| 0 | 匀速 | 从头到尾速度一样 |
| 1 | 先慢后快 | 起步慢,越来越快 |
| 2 | 先快后慢 | 起步快,慢慢停下来 |
| 3 | 两头慢中间快 | 起步慢,中间加速,结尾减速 |
device.swipe(642, 800, 642, 1800, 500)
device.swipe(642, 800, 642, 1800, 500, easing_mode=2, easing_power=3)
iOS 18+ 在 rel 模式下不允许"按键期间移动光标",swipe 会变成一次点击而不是真正的拖动。
- iOS < 18 + rel 模式 → swipe 正常工作
- iOS ≥ 18 + rel 模式 → swipe 失效,会变成"走到终点点一下"
- iOS ≥ 18 + abs 模式 → swipe 正常工作
需要 swipe 功能请切到 abs 模式。
四个方向的快捷滑动
device.swipe_up() # 从下往上滑(默认 300ms)
device.swipe_down() # 从上往下滑
device.swipe_left() # 从右往左滑
device.swipe_right() # 从左往右滑(iOS 上相当于返回上一页)
device.swipe_up(500) # 指定速度
检查是否正在滑动
if device.is_dragging():
print("正在滑动中...")
按键操作
回到桌面
device.home()
返回
device.back()
实际发送的是 Esc 键。在 Safari / 设置 / 邮件等响应 Esc 的 App 里会执行"返回上一级"。
iOS 没有开放"从左边缘滑动返回"对应的 HID 按键。在不响应 Esc 的 App 里,如果想模拟边缘滑动返回,建议用:
device.swipe(0, 1000, 300, 1000, 300) # 从屏幕左边缘往右滑
回车
device.enter()
多任务界面
device.recent() # iOS 上用 Cmd+Tab 模拟,进入任务切换
全选复制
device.copy() # 按 Cmd+A 全选,再按 Cmd+C 复制
粘贴
device.paste() # Cmd+V
清空输入框
device.clear() # Cmd+A 全选 + Delete
键盘输入
输入文字
通过模拟 HID 键盘一字一字打出来。只支持英文、数字和 ASCII 符号,不支持中文输入。
| 参数 | 类型 | 说明 |
|---|---|---|
| text | str | 要输入的文字 |
device.print_text("hello world")
device.print_text("ascript@2024.com")
keyboard_write 是另一个别名:
device.keyboard_write("hello")
按住一个键不松开
需要配合 keyboard_release_all() 使用。
import time
device.keyboard_press("a") # 按住 a 键
time.sleep(1)
device.keyboard_release_all() # 松开
组合键
按修饰键 + 普通键(比如 Cmd+A)。参数是 HID 编码的 16 进制字符串。
| 参数 | 类型 | 说明 |
|---|---|---|
| modifier_hex | str | 修饰键 HID 编码。填 "0" 表示没有修饰键 |
| key_hex | str | 按键 HID 编码 |
device.key("E3", "04") # Cmd+A (全选)
device.key("E3", "06") # Cmd+C (复制)
device.key("E3", "19") # Cmd+V (粘贴)
常用 HID 编码参考:
| 键 | 编码 |
|---|---|
| Cmd (左) | E3 |
| Ctrl (左) | E0 |
| Shift (左) | E1 |
| Alt (左) | E2 |
| A | 04 |
| C | 06 |
| V | 19 |
| Z | 1D |
设备管理
获取芯片 ID
chip_id = device.get_id()
print(chip_id) # 类似 "8CD0CA63B0E4"
查看设备状态
if device.state():
print("设备已登录并运行")
修改蓝牙名称
给芯片起个自定义名字,改完后会自动重启生效。
| 参数 | 类型 | 说明 |
|---|---|---|
| new_name | str | 新名字,最长 30 字符 |
device.set_name("my_hid_01")
改完后 iOS 上需要重新配对(名字变了 iOS 把它当新设备)。
恢复默认名称
device.reset_name()
恢复成 AS_iOS_REL_xxxx 或 AS_iOS_ABS_xxxx(看当前模式)。
重启芯片
device.restart()
高级设置
点击偏移微调
如果点击位置总是固定地偏一点,可以设偏移校正。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| offset_x | int | 0 | 横向偏移(物理像素,正=右移,负=左移) |
| offset_y | int | 0 | 纵向偏移(物理像素,正=下移,负=上移) |
device.set_offset(-10, -5) # 点击总是偏右 10px、偏下 5px 就这样抵消
手动设置屏幕大小
一般不需要。如果自动获取的屏幕尺寸不对:
| 参数 | 类型 | 说明 |
|---|---|---|
| width | int | 竖屏时的屏幕宽度(物理像素) |
| height | int | 竖屏时的屏幕高度(物理像素) |
device.set_screen_size(1284, 2778) # iPhone 12 Pro Max
同步光标坐标系(横竖屏切换时)
iOS 横竖屏切换时,鼠标坐标系可能不会立即同步,导致点击位置偏。调这个方法修复,耗时约 3-5 秒。
device.sync_cursor()
如果 AS App 的录屏扩展正在运行,横竖屏切换时会自动检测并同步,通常不需要手动调用。
设置屏幕方向
手动告知 BleDevice 当前屏幕方向,用于横屏适配。
| 参数 | 类型 | 说明 |
|---|---|---|
| orientation | int | 屏幕方向常量 |
方向常量:
| 常量 | 值 | 说明 |
|---|---|---|
| BleDevice.PORTRAIT | 0 | 竖屏(默认) |
| BleDevice.LANDSCAPE_LEFT | 1 | 左横屏 |
| BleDevice.LANDSCAPE_RIGHT | 2 | 右横屏 |
| BleDevice.PORTRAIT_UPSIDE | 3 | 倒竖屏 |
device.set_orientation(BleDevice.LANDSCAPE_LEFT)
查询/清空校准数据(rel 模式)
仅 rel 模式下有效,abs 模式不需要校准。
if device.is_calibrated():
print("已校准")
else:
print("未校准,请在 App 首页点「校准」")
device.clear_calibration() # 清除校准数据,下次需要重新校准
横屏使用
横屏下坐标直接传当前屏幕方向的物理像素即可,不需要做旋转。
iOS 系统的鼠标坐标系在横竖屏切换后可能不会立即同步,点击位置会偏。解决方式:
- 自动同步:确保 App 的录屏扩展正在运行,切换时会自动同步
- 手动同步:调
device.sync_cursor()(需等待 3-5 秒重连) - 别完全平放:手机稍微倾斜让重力感应能识别方向
device = BleDevice()
device.connect()
device.click(1500, 400) # 横屏下直接用截图坐标点击
# 如果切换方向后点击不准
device.sync_cursor()
device.click(1500, 400)
完整示例
基本操作
from ascript.ios.esp32hid import BleDevice
import time
# 创建设备并连接
device = BleDevice()
device.connect() # 自动扫描、连接、登录
print(f"当前模式: {device.get_mode()}")
# 回到桌面
device.home()
time.sleep(1)
# 点击打开 App
device.click(500, 800)
time.sleep(2)
# 在输入框里打字
device.click(642, 400) # 先点一下输入框
time.sleep(0.5)
device.print_text("hello") # 打字
device.enter() # 回车
# 滑动翻页(注意: rel 模式在 iOS 18+ 下失效)
device.swipe(640, 1500, 640, 500, 500)
time.sleep(1)
# 返回上一页
device.swipe_right()
根据模式写适配代码
from ascript.ios.esp32hid import BleDevice
device = BleDevice()
device.connect()
if device.get_mode() == BleDevice.MODE_ABS:
# abs 模式: 直接用 swipe
device.swipe(500, 1500, 500, 500, 400)
else:
# rel 模式: iOS 18+ 下不能 swipe, 用点击替代
device.click(500, 800, 100)
切换到 abs 模式的完整流程
from ascript.ios.esp32hid import BleDevice
import time
device = BleDevice()
device.connect()
if device.get_mode() != BleDevice.MODE_ABS:
print("当前是 rel 模式, 正在切换到 abs...")
device.set_mode(BleDevice.MODE_ABS)
# 此时固件已重启, Python 已断开连接
# 输出提示: 请到 iOS 设置 → 蓝牙 忽略此设备后重新配对
print("请在 iPhone 上完成:")
print(" 1. 设置 → 蓝牙")
print(" 2. 找到 AS_iOS_REL_xxxx, 点 ⓘ, 选「忽略此设备」")
print(" 3. 等几秒让 iOS 扫到新的 AS_iOS_ABS_xxxx, 点它配对")
# 等用户手动完成, 或者脚本自己轮询重连
while True:
time.sleep(3)
if device.connect():
if device.get_mode() == BleDevice.MODE_ABS:
print("切换成功!")
break
print("等待重新配对...")
# 切换完成, 正常用 abs 模式
device.click(642, 1389)
device.swipe(500, 1500, 500, 500, 400)