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")