选择器
选择器用于标识 HTTP 请求或 WebSocket 命令的目标设备和会话。
设备选择器 — URL 路径
用于所有设备级 HTTP 路由,格式为 /{device_type}/{id_or_index}/…。
| 路由示例 | 选择器含义 |
|---|---|
inverse3/0 | 按索引取第一个 Inverse3(从 0 开始) |
inverse3/A14 | 设备 ID 为 A14 的 Inverse3 |
inverse3/* | 所有 Inverse3 设备(通配符) |
*inverse/* | 所有 Inverse 系列设备(Inverse3、Inverse3x、Minverse) |
inverse3:left/0 | 按索引取第一个左手Inverse3 |
*inverse:right/0 | 按索引取第一个右手 Inverse 系列设备 |
verse_grip/0 | 按索引取第一个有线 VerseGrip |
wireless_verse_grip/* | 所有无线 VerseGrip |
*verse_grip/* | 所有 VerseGrip 系列设备(有线、无线、自定义) |
*verse_grip:custom/0 | 按索引取第一个自定义 VerseGrip(等同于 custom_verse_grip/0) |
类过滤器先于索引解析
<type>:<class>/<N> 选择器会先应用类过滤器,然后再对过滤后的集合建立索引。<N> 指的是过滤后列表中的位置,而不是原始设备列表中的位置:
*inverse:left/0— 第 0 个左手 Inverse 系列设备。如果连接了三个 Inverse 设备(右、左、右),则会解析为第二个设备, 而不是把原始列表的索引0再过滤为“必须为左手”。inverse3:right/0— 第 0 个右手 Inverse3。*inverse:left/3— 如果连接的左手 Inverse 设备不足四个,则返回404,即使系统中 Inverse 设备总数超过四个。
通配符选择器同样遵循此规则:*inverse/* 和 *verse_grip/* 会匹配该系列中的每一个设备,针对这些选择器的 POST / DELETE 请求会将操作应用于每一个匹配项,而不仅仅是第一个。
规则:
GET会拒绝通配符选择器(含义不明确)→400POST和DELETE接受通配符——操作将应用于所有匹配项404响应使用no device matches selector '<sel>'的格式,其中<sel>为收到的原始选择器表达式
支持的 {device_type} 值: inverse3、verse_grip、wireless_verse_grip,
或系列通配符 *inverse、*verse_grip。
会话选择器 — ?session=<expr> 查询参数
会话级 HTTP 端点(basis、mount、preset、filters、navigation、SDF)需要 ?session=<expr> 查询参数,以指定其操作所在的会话上下文。
| 表达式 | 含义 |
|---|---|
| (省略) | 所有会话(仅允许用于 DELETE) |
#123 或 123 | ID 为 123 的会话 |
:0 | 按索引取第一个会话 |
:-1 | 按索引取最后一个会话 |
profile_name | 使用该 profile 的第一个会话(可能存在歧义) |
profile_name:0 | 指定 profile 且索引为 0 的第一个会话 |
:default:0 | profile 为 default 且索引为 0 的第一个会话 |
co.haply.hub::*:0 | profile 以 co.haply.hub:: 开头(glob 通配符)的第一个会话 |
:co.haply.hub::*:-1 | co.haply.hub:: 命名空间中的最后一个会话 |
示例:
GET /inverse3/0/config/navigation?session=:default:0
Profile 名称通配符
profile 模式支持两个 glob 元字符:
| 元字符 | 含义 |
|---|---|
* | 匹配任意字符序列(包括空序列) |
? | 恰好匹配一个字符 |
其他所有字符——包括 :、.、-、[、]——均按字面匹配(不支持字符类,如 [abc])。匹配区分大小写。
单独的 *(或 all)保持其原有含义,即所有会话。若要匹配名称以 * 开头的 profile,请至少包含一个其他字符(例如 *foo)。
常用模式:
co.haply.hub::*—co.haply.hub::命名空间中的任意 profile*-update— 任意以-update结尾的 profileco.haply.*:*— 命名空间以co.haply.开头的任意 profiledefaul?— 任意以defaul开头、共 7 个字符的 profile
通配符模式本身存在歧义(可能匹配多个会话)。若要在 GET / POST 端点上使用,请在末尾添加 :<index> 以消除歧义:
# First session in the Haply Hub namespace
GET /inverse3/0/config/basis?session=co.haply.hub::*:0
# Last session in a Unity namespace
GET /inverse3/0/config/mount?session=:co.haply.unity::*:-1
不带索引的歧义通配符在 GET/POST 上会被 400 拒绝。在明确接受多会话匹配的路由上则允许使用——GET /sessions/<sel>(返回所有匹配项)和 DELETE 端点(应用于所有匹配项)。
会话选择器 — URL 路径
/sessions/{selector} 端点接受与路径参数相同的表达式——适用于查询单个会话或验证其是否存在:
GET /sessions # list all active sessions
GET /sessions/:default:0 # one session by profile + index
GET /sessions/:-1 # last active session
GET /sessions/#42 # session id 42
GET /sessions/co.haply.hub::* # every session in the co.haply.hub:: namespace
GET /sessions/co.haply.hub::*:0 # first session in that namespace
这在功能上等同于 GET /sessions?session=<selector>;两条路由调用的是同一个处理函数。GET /sessions/<sel> 是少数几个允许省略 :<index> 后缀、接受歧义通配符模式的端点之一——它会以与 GET /sessions 相同的列表结构返回所有匹配项。
上述所有选择器形式——包括 #42 以及带 * 的 profile 模式——都会被标准 HTTP 客户端(Python requests、libhv、curl、fetch)原样传输。无需在客户端进行 URL 编码。在 shell 中,请对包含 # 或 * 的参数加引号(例如 --session "#42"、--session "co.haply.hub::*:0"),以防止 shell 将其视为注释,或对其进行本地文件的 glob 展开。
一个例外:在路径中,? glob 元字符是 URL 查询分隔符。在路径形式的选择器中(例如 /sessions/defaul?:0),它必须编码为 %3F,即 GET /sessions/defaul%3F:0。在查询形式的选择器(?session=defaul?:0)中,只有第一个 ? 会分隔查询部分,后续的 ? 字符将原样传递。
在 WebSocket 连接中,你就是会话本身——你的 configure 和 commands 条目会自动指向当前会话的设备。只有 HTTP 请求才需要会话选择器(包括跨会话远程控制,参见 会话)。