bleHID 蓝牙 HID
bleHID 蓝牙 HID
bleHID 是 ScriptX 里的固定 BLE HID 控制模块。它面向的是一类已经配对好的蓝牙 HID 设备,底层当前按固定协议去找:
- 服务 UUID:
FFE0 - 特征 UUID:
FFE1 - 已配对设备名此前缀开头:
zf_或ff_
你可以把它理解成:
- 先连上一块固定 BLE HID 触控 / 键盘设备
- 再把主屏或虚拟屏坐标映射成 HID 报文坐标
- 最后发触摸、滑动、Home / Back / Recents 或键盘报文
先记住这 12 条
- 实际公开对象名是
bleHID,兼容别名是$bleHID,不是全小写bleHid。 - 这套能力依赖系统里已经配对好的
zf_/ff_前缀 BLE HID 设备。 - Android 12 及以上需要
BLUETOOTH_CONNECT权限。 requestPermission()只有在前台Activity存在且仍然活着时才能拉起权限弹窗。connect(timeout?)默认超时10000ms,实际会夹到500..120000ms。- 当前触摸只支持一个指针,
pointerId只能是0。 touch()会把当前“按下状态”占住,直到你untouch()/up()/release()。- 触摸坐标最终不是直接发原屏幕坐标,而是会映射到固定
800 x 800HID 报文坐标。 setTarget(...)既可以指向主屏,也可以指向一个 shell 型虚拟屏会话。click()的按下时长固定是100ms;press()默认350ms;swipe()默认260ms。writeReport()是高级用法,允许你自己发原始 HID 报文,但每项字节都必须在0..255。home()、back()、recents()本质上都是内置键盘报文序列。
bleHID.requestPermission()
请求蓝牙连接权限。
返回值
boolean
| 返回值 | 含义 |
|---|---|
true | 当前本来就已经有权限 |
false | 已成功发起权限请求,等待系统弹窗结果 |
什么时候它会直接报错
1. 当前系统版本太低
Android 12 以下没有这套运行时蓝牙连接权限,请求接口本身不会走弹窗。
2. 当前没有前台 Activity
会直接报:
bleHID.requestPermission() requires a foreground Activity
3. Activity 已经结束或销毁
会直接报:
bleHID.requestPermission() requires an active foreground Activity
示例
if (!bleHID.hasPermission()) {
const launched = bleHID.requestPermission();
log(`request launched=${!launched}`);
}
bleHID.hasPermission()
判断当前是否已经具备蓝牙连接权限。
返回值
boolean
版本行为
| 系统版本 | 行为 |
|---|---|
| Android 12 以下 | 直接视为有权限 |
| Android 12 及以上 | 检查 android.permission.BLUETOOTH_CONNECT |
示例
if (!bleHID.hasPermission()) {
throw new Error("还没有蓝牙连接权限");
}
bleHID.connect(timeout?)
连接到已配对的固定 BLE HID 设备。
const ok = bleHID.connect(15000);
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
timeout | number | 任意有限数字 | 10000 | 最长等待连接就绪时间,单位毫秒 |
返回值
boolean
| 返回值 | 含义 |
|---|---|
true | 成功连上并进入就绪状态 |
false | 超时、权限缺失、蓝牙关闭、设备不存在等导致失败 |
底层会做什么
- 先检查蓝牙连接权限
- 从已配对设备里找名称以
zf_或ff_开头的 BLE HID 设备 - 建立 GATT 连接
- 查找
FFE0/FFE1服务和特征 - 进入
ready状态后才算连接成功
超时边界
| 传入值 | 实际超时 |
|---|---|
小于 500 | 500 |
500..120000 | 原值 |
大于 120000 | 120000 |
什么时候容易返回 false
BLUETOOTH_CONNECT没权限- 蓝牙没打开
- 设备不支持蓝牙
- 没有配对过
zf_/ff_前缀设备 - 找到了设备,但没有
FFE0/FFE1服务 - 连接或写入过程中断开
示例
if (!bleHID.connect()) {
const state = bleHID.status();
throw new Error(`BLE HID 连接失败: ${state.error}`);
}
bleHID.disconnect() / bleHID.close()
断开当前脚本占用的 BLE HID 连接。
返回值
boolean
真实行为
- 会释放当前脚本持有的连接 owner
- 如果当前脚本还占着原始触摸按下状态,也会先尝试补发释放报文
- 当没有其他脚本 owner 时,底层会真正断开 GATT
示例
bleHID.disconnect();
bleHID.isConnected()
判断底层当前是否已经进入 ready 状态。
返回值
boolean
注意
这个值只表示“当前底层会话 ready 了没有”,不等于:
- 当前脚本一定已经抢到触摸 owner
- 当前目标显示器一定还存在
所以更完整的排查还是看 status()。
示例
log(bleHID.isConnected());
bleHID.status() / bleHID.getState()
读取当前 BLE HID 状态。
log(JSON.stringify(bleHID.status(), null, 2));
返回值
object
返回字段
| 字段 | 类型 | 说明 |
|---|---|---|
state | string | 当前状态 |
connected | boolean | 是否 ready |
deviceName | string | 已连接设备名 |
deviceAddress | string | 设备 MAC 地址 |
serviceUuid | string | 当前固定服务 UUID |
characteristicUuid | string | 当前固定特征 UUID |
reportWidth | number | 报文坐标宽度,固定 800 |
reportHeight | number | 报文坐标高度,固定 800 |
maxPointers | number | 当前固定 1 |
owners | number | 当前底层有多少脚本 owner |
rawTouchActive | boolean | 当前是否处于“按下未释放”状态 |
error | string | 最近错误信息 |
hasPermission | boolean | 当前是否已具备蓝牙连接权限 |
target | string | 当前坐标目标,main 或某个虚拟屏会话 id |
targetWidth | number | null | 当前目标宽度 |
targetHeight | number | null | 当前目标高度 |
targetRotation | number | 目标旋转;虚拟屏固定写 0 |
targetDisplayId | number | null | 当前虚拟屏 displayId |
targetAvailable | boolean | null | 虚拟屏目标是否仍存在 |
state 可能有哪些值
| 值 | 含义 |
|---|---|
disconnected | 未连接 |
connecting | 连接中 |
discovering | 正在查找 GATT 服务 |
ready | 已可发 HID 报文 |
closing | 正在关闭 |
error | 当前处于错误状态 |
示例:连接失败后看错误
if (!bleHID.connect()) {
const state = bleHID.status();
log(JSON.stringify(state, null, 2));
}
bleHID.setTarget(target)
切换坐标映射目标。
bleHID.setTarget("main");
bleHID.setTarget(screen);
支持的写法
| 写法 | 是否支持 | 说明 |
|---|---|---|
null | 支持 | 切回主屏 |
0 | 支持 | 切回主屏 |
"main" | 支持 | 切回主屏 |
"default" | 支持 | 切回主屏 |
screen | 支持 | 直接传 vdisplay.create() 返回的会话对象 |
"vdisplay-1" | 支持 | 传虚拟屏会话 id |
{ target: screen } 一类可解析对象 | 支持 | 会继续走虚拟屏目标解析 |
返回值
object
返回当前目标状态,也就是 status() 里那组和目标有关的字段。
切主屏和切虚拟屏的区别
主屏
- 会读取主屏真实宽高和旋转
- 再把你的脚本坐标映射到
800 x 800HID 坐标
虚拟屏
- 会读取虚拟屏 session 的
width/height - 再把虚拟屏坐标映射到
800 x 800 - 如果这个虚拟屏会话后面被销毁,后续触摸会报目标不存在
示例:切到虚拟屏
const screen = vdisplay.create({ width: 720, height: 1280 });
bleHID.setTarget(screen);
bleHID.click(x, y, pointerId?)
在当前目标上做一次点击。
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
x | number | 有限数字 | 必填 | 目标坐标 X |
y | number | 有限数字 | 必填 | 目标坐标 Y |
pointerId | number | 只能是 0 | 省略时视作 0 | 当前保留兼容参数 |
返回值
boolean
固定行为
- 按下时长固定
100ms - 会先把目标坐标映射到
800 x 800 - 当前如果还存在未释放的原始触摸,会直接失败
示例
bleHID.click(300, 500);
bleHID.press(x, y, duration?, pointerId?)
在当前目标上长按一个点。
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
x | number | 有限数字 | 必填 | 目标坐标 X |
y | number | 有限数字 | 必填 | 目标坐标 Y |
duration | number | 任意有限数字 | 350 | 按下持续时间,实际至少 1ms |
pointerId | number | 只能是 0 | 省略时视作 0 | 当前保留兼容参数 |
返回值
boolean
示例
bleHID.press(360, 900, 800);
bleHID.touch(x, y, pointerId?) / bleHID.down(x, y, pointerId?)
发送原始“按下并保持”的触摸。
参数
和 click() 一样,只是不会自动释放。
返回值
boolean
什么时候适合用它
- 你想自己控制按下、移动、抬起全过程
- 你要做拖拽或分步滑动
一个关键限制
当前原始触摸一旦按下,这个 owner 会被当前脚本占住。没有 up() 前:
- 同脚本再发别的手势会失败
- 其他脚本也不能抢同一条原始触摸通道
示例:手动拖动的第一步
bleHID.touch(200, 600);
bleHID.move(x, y, pointerId?)
把当前保持中的原始触摸移动到另一个点。
参数
和 touch() 一样。
返回值
boolean
前提
必须先有一次当前脚本自己的 touch() / down(),否则会失败。
示例:手动拖拽过程
bleHID.touch(200, 600);
sleep(80);
bleHID.move(260, 600);
sleep(80);
bleHID.move(320, 600);
sleep(80);
bleHID.release();
bleHID.untouch(pointerId?) / bleHID.up(pointerId?) / bleHID.release(pointerId?)
释放当前原始触摸。
参数
pointerId 省略即可;如果传,仍然只能是 0。
返回值
boolean
示例
bleHID.touch(200, 600);
sleep(200);
bleHID.release();
bleHID.swipe(x1, y1, x2, y2, duration?, pointerId?)
在当前目标上做一次滑动。
参数
| 参数 | 类型 | 可填值 | 默认值 | 说明 |
|---|---|---|---|---|
x1 | number | 有限数字 | 必填 | 起点 X |
y1 | number | 有限数字 | 必填 | 起点 Y |
x2 | number | 有限数字 | 必填 | 终点 X |
y2 | number | 有限数字 | 必填 | 终点 Y |
duration | number | 任意有限数字 | 260 | 滑动时长,实际至少 1ms |
pointerId | number | 只能是 0 | 省略时视作 0 | 当前保留兼容参数 |
返回值
boolean
真实行为
- 内部固定按 20 步插值
- 每步至少等待
5ms - 最后会自动补一个 release 报文
示例
bleHID.swipe(360, 1100, 360, 300, 320);
bleHID.home()
发送 Home 键 HID 报文序列。
返回值
boolean
示例
bleHID.home();
bleHID.back()
发送 Back 键 HID 报文序列。
返回值
boolean
示例
bleHID.back();
bleHID.recents() / bleHID.recent()
发送最近任务键 HID 报文序列。
返回值
boolean
示例
bleHID.recents();
bleHID.keyboard(modifier, keyCode)
发送一组键盘 HID 报文。
bleHID.keyboard(0, 40);
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
modifier | number | 0..255 | 键盘修饰键位图 |
keyCode | number | 0..255 | HID 键码 |
返回值
boolean
什么时候适合用它
- 你明确知道 HID 键盘码
- 想发自定义组合键
- 内置的
home()/back()/recents()不够用
示例:直接发一个 HID Home
bleHID.keyboard(8, 40);
bleHID.writeReport(bytes)
直接发送原始 HID 报文。
bleHID.writeReport([1, 1, 0, 20, 0, 30, 0, 0, 0, 0]);
参数
bytes 支持这些输入:
| 写法 | 是否支持 | 说明 |
|---|---|---|
ByteArray | 支持 | 直接发 |
| JS 数组 | 支持 | 每项必须能转成 0..255 |
List / Iterable | 支持 | 会逐项转字节 |
| 普通对象 | 不支持 | 会直接报错 |
返回值
boolean
限制
| 限制 | 说明 |
|---|---|
| 不能为空 | 至少 1 字节 |
| 长度上限 | 最多 512 字节 |
| 每项范围 | 必须在 0..255 |
什么时候才建议你用它
只有当你已经清楚底层 HID 协议报文格式时,再用它。普通点击、滑动、按键脚本没必要直接发原始报文。
示例
const pressPacket = [1, 1, 0, 10, 0, 20, 0, 0, 0, 0];
const releasePacket = [1, 2, 0, 10, 0, 20, 0, 0, 0, 0];
bleHID.writeReport(pressPacket);
sleep(100);
bleHID.writeReport(releasePacket);
bleHID.reportWidth
当前 HID 报文坐标宽度。
类型
number
固定值
800
它的意义
脚本里传的主屏 / 虚拟屏坐标,最终都会被映射到这个宽度范围。
bleHID.reportHeight
当前 HID 报文坐标高度。
类型
number
固定值
800
bleHID.maxPointers
当前最大支持指针数。
类型
number
固定值
1
这就是为什么 pointerId 只能填 0
源码层当前只实现了单指原始触摸。
bleHID.api
bleHID 自己的别名引用。
类型
object
示例
log(bleHID.api === bleHID);
一段完整的新手示例
下面这段示例演示的是:先申请权限、连接设备、切换到虚拟屏、点击、滑动、返回,再断开连接。
if (!bleHID.hasPermission()) {
bleHID.requestPermission();
throw new Error("请先同意蓝牙连接权限后再重试");
}
if (!bleHID.connect(15000)) {
throw new Error(`连接失败: ${bleHID.status().error}`);
}
const screen = vdisplay.create({ width: 720, height: 1280 });
bleHID.setTarget(screen);
bleHID.click(360, 640);
sleep(300);
bleHID.swipe(360, 1100, 360, 280, 320);
sleep(300);
bleHID.back();
bleHID.disconnect();
