Skip to main content

Esp32-HID外设 iOS

芯片选购、固件下载、烧录教程

请前往 HID 外设 页面查看完整的芯片选购、固件下载、烧录教程和视频教程。

iOS 支持蓝牙固件,C3 / S3 / ESP32 Pico 等都能用。

仅 HID 模式

本模块为 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 模式

模式在固件里持久化,切换一次之后每次开机都是新模式,除非再次切换。切换方式有两种:

  1. 在 iOS App 首页「位移模式切换」按钮里选
  2. 脚本里device.set_mode(...)(见下文)
切换模式后必须重新配对

切换模式会让固件重启,广播名也会变(AS_iOS_REL_xxxxAS_iOS_ABS_xxxx)。因为 iOS 会缓存旧的 HID 描述符,切换后必须在「设置 → 蓝牙」里把旧设备「忽略此设备」,再重新配对新名字,否则能连上但点击无反应。

使用前准备

手机设置

abs 模式(iOS 18+ 推荐)只需做一件事:

1. 开启辅助触控:设置 → 辅助功能 → 触控 → 辅助触控 → 打开开关

必须开启辅助触控!

不开启的话鼠标指针能移动但点击无效。这是 iOS 系统要求,rel / abs 两种模式都一样。

rel 模式除上面那一条外,还要把两个滑块拉满:

2. 跟踪灵敏度拉到最右:设置 → 辅助功能 → 触控 → 辅助触控 → 跟踪灵敏度 → 拉到最右

3. 跟踪速度拉到最右:设置 → 通用 → 触控板与鼠标 → 跟踪速度 → 拉到最右

仅 rel 模式需要!

rel 模式靠校准表补偿 iOS 的鼠标加速度。两个滑块都必须在最右(最快),校准和运行才是同一套系数;否则点击会偏移

abs 模式不需要调这两项——abs 是绝对坐标直接映射,不受跟踪速度/灵敏度影响。

第一次配对

首次运行脚本时,iPhone 上会弹出一个 "蓝牙配对请求" 的弹窗,点 "配对" 就行。配对成功后屏幕上会出现一个小圆点(鼠标指针),这是正常的。

tip
  • 配对弹窗只在 App 前台时才显示,看不到时切回 App
  • 日常使用不要在 iOS 设置里"忽略此设备",下次要重新配对
  • 但切换 rel/abs 模式之后必须忽略旧设备重新配对(见上方警告)
  • 也可以在 iOS 系统蓝牙设置里先手动配对,然后脚本里直接连

导入模块

from ascript.ios.esp32hid import BleDevice

快速上手

rel 模式(默认)

第一次使用前:

  1. 开启辅助触控(设置 → 辅助功能 → 触控 → 辅助触控)
  2. 跟踪灵敏度拉到最右(设置 → 辅助功能 → 触控 → 辅助触控 → 跟踪灵敏度)
  3. 跟踪速度拉到最右(设置 → 通用 → 触控板与鼠标 → 跟踪速度)
  4. 校准一次(在 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")

切换流程

  1. Python 发 setmode?abs& 给固件
  2. 固件写 NVS → 自动 ESP.restart()
  3. 板子重启后广播名变成 AS_iOS_ABS_xxxx(之前是 AS_iOS_REL_xxxx
  4. Python 自动断开旧连接
  5. 需要你手动操作
    • iPhone 设置 → 蓝牙 → 找到旧的 AS_iOS_* 设备 → 点右侧 ⓘ → 忽略此设备
    • 回到 App 或脚本,重新 device.connect(),这次会自动配对新名字

不手动忽略旧配对的话,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 接口统一用物理像素,不需要你做换算。

移动光标

只移动光标到目标位置,不点击。适合"先看看那是什么再决定要不要点"的场景。

参数类型说明
xint横坐标(物理像素)
yint纵坐标(物理像素)
device.move_to(500, 800)   # 光标移到 (500, 800), 不点击
  • abs 模式:瞬移(~50ms)
  • rel 模式:走 mouseHome + walk(时间取决于距离,如果光标已经在目标位置会自动跳过)
配合 click 使用
device.move_to(500, 800)     # 先移过去
time.sleep(0.5) # 看一下
device.click(500, 800) # 再点(第二次不用重新走位, 很快)

点击

点击屏幕上某个位置。会阻塞到点击完成才返回。

参数类型默认值说明
xint-横坐标(物理像素)
yint-纵坐标(物理像素)
durationint100按住多长时间(毫秒)
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)

触摸(低层)

touchslide(x, y, x, y, duration) 的简写,不阻塞等待完成。一般用 click 就够了。

参数类型默认值说明
xint-横坐标
yint-纵坐标
durationint100按住多长时间(毫秒)
device.touch(642, 1389)

滑动

从一个位置滑动到另一个位置。

参数类型默认值说明
x1int-起点横坐标
y1int-起点纵坐标
x2int-终点横坐标
y2int-终点纵坐标
durationint300滑动总时间(毫秒)
easing_modeint0缓动模式
easing_powerint0缓动力度(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)
rel 模式的 swipe 限制

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 里会执行"返回上一级"。

tip

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 符号,不支持中文输入。

参数类型说明
textstr要输入的文字
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_hexstr修饰键 HID 编码。填 "0" 表示没有修饰键
key_hexstr按键 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
A04
C06
V19
Z1D

设备管理

获取芯片 ID

chip_id = device.get_id()
print(chip_id) # 类似 "8CD0CA63B0E4"

查看设备状态

if device.state():
print("设备已登录并运行")

修改蓝牙名称

给芯片起个自定义名字,改完后会自动重启生效。

参数类型说明
new_namestr新名字,最长 30 字符
device.set_name("my_hid_01")

改完后 iOS 上需要重新配对(名字变了 iOS 把它当新设备)。

恢复默认名称

device.reset_name()

恢复成 AS_iOS_REL_xxxxAS_iOS_ABS_xxxx(看当前模式)。

重启芯片

device.restart()

高级设置

点击偏移微调

如果点击位置总是固定地偏一点,可以设偏移校正。

参数类型默认值说明
offset_xint0横向偏移(物理像素,正=右移,负=左移)
offset_yint0纵向偏移(物理像素,正=下移,负=上移)
device.set_offset(-10, -5)   # 点击总是偏右 10px、偏下 5px 就这样抵消

手动设置屏幕大小

一般不需要。如果自动获取的屏幕尺寸不对:

参数类型说明
widthint竖屏时的屏幕宽度(物理像素)
heightint竖屏时的屏幕高度(物理像素)
device.set_screen_size(1284, 2778)    # iPhone 12 Pro Max

同步光标坐标系(横竖屏切换时)

iOS 横竖屏切换时,鼠标坐标系可能不会立即同步,导致点击位置偏。调这个方法修复,耗时约 3-5 秒。

device.sync_cursor()
自动同步

如果 AS App 的录屏扩展正在运行,横竖屏切换时会自动检测并同步,通常不需要手动调用。

设置屏幕方向

手动告知 BleDevice 当前屏幕方向,用于横屏适配。

参数类型说明
orientationint屏幕方向常量

方向常量:

常量说明
BleDevice.PORTRAIT0竖屏(默认)
BleDevice.LANDSCAPE_LEFT1左横屏
BleDevice.LANDSCAPE_RIGHT2右横屏
BleDevice.PORTRAIT_UPSIDE3倒竖屏
device.set_orientation(BleDevice.LANDSCAPE_LEFT)

查询/清空校准数据(rel 模式)

仅 rel 模式下有效,abs 模式不需要校准。

if device.is_calibrated():
print("已校准")
else:
print("未校准,请在 App 首页点「校准」")

device.clear_calibration() # 清除校准数据,下次需要重新校准

横屏使用

横屏下坐标直接传当前屏幕方向的物理像素即可,不需要做旋转。

横竖屏切换

iOS 系统的鼠标坐标系在横竖屏切换后可能不会立即同步,点击位置会偏。解决方式:

  1. 自动同步:确保 App 的录屏扩展正在运行,切换时会自动同步
  2. 手动同步:调 device.sync_cursor()(需等待 3-5 秒重连)
  3. 别完全平放:手机稍微倾斜让重力感应能识别方向
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)