跳到主要内容

选择器

选择器用于标识 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 会拒绝通配符选择器(含义不明确)→ 400
  • POST 和 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 或 123ID 为 123 的会话
:0按索引取第一个会话
:-1按索引取最后一个会话
profile_name使用该 profile 的第一个会话(可能存在歧义)
profile_name:0指定 profile 且索引为 0 的第一个会话
:default:0profile 为 default 且索引为 0 的第一个会话
co.haply.hub::*:0profile 以 co.haply.hub:: 开头(glob 通配符)的第一个会话
:co.haply.hub::*:-1co.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 结尾的 profile
  • co.haply.*:* — 命名空间以 co.haply. 开头的任意 profile
  • defaul? — 任意以 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 会话无需选择器

在 WebSocket 连接中,你就是会话本身——你的 configure 和 commands 条目会自动指向当前会话的设备。只有 HTTP 请求才需要会话选择器(包括跨会话远程控制,参见 会话)。