-
Notifications
You must be signed in to change notification settings - Fork 12
Remote Config
遥控器配置文件:
src/remote_controller/config/xbox_default.yaml
remote_controller 只在启动时读取一次 YAML,运行期间不会扫描或重新加载配置。修改 src/remote_controller/config/xbox_default.yaml 后,执行:
colcon build --packages-select remote_controllerbuild 更新 install/ 目录后,重启 remote_controller。YAML 解析失败会使新进程启动失败,并直接报告配置错误。
推荐从上到下按这条线理解:
inputs / sources -> curves -> controls -> outputs -> system
设备选择与输入声明 曲线校准 统一控制量 写 MotionCommands 执行系统命令
配置目标是:业务层不要关心具体是 Xbox、键盘、CRSF 还是其他遥控器。业务层只看统一后的 control 和状态机 event。
每个 sources.<group> 都是一个候选输入设备。任意时刻严格只会激活一个设备;sources 同时负责把 driver 产生的 raw source 命名成业务可读 source。
设备选择策略:
inputs:
selection:
scan_interval_ms: 100
promote_stable_ms: 500-
scan_interval_ms:调用 driveris_available()的周期,必须大于 0。 -
promote_stable_ms:高优先级设备连续可用多久后才允许抢占,0表示立即允许。 - 切换时框架会停止旧驱动、清空旧 signal、发布一次零运动命令、等待新驱动
is_ready(),最后才接受新输入。 - 切换期间 edge 命令被抑制;必须先松开、再按下
start/stop等边沿按键才能重新触发。
通用结构:
sources:
<group_name>:
type: <driver_type>
priority: <integer>
signals:
<semantic.source.name>:
from: <raw.source.name>读法是从左到右:
semantic source 由 raw source 产生
例如:
gamepad.left_y: {from: js.axis.3}表示:
gamepad.left_y 由 js.axis.3 产生
不要反过来写成 js.axis.3: gamepad.left_y,因为那会让人误解为 raw source 被业务含义反向定义。
节点参数 --DEBUG 会每秒调用一次每个 input driver 的 debug()。CRSF 会打印最近一帧
CRC 正确的 16 路归一化通道值;joystick 和 keyboard 会打印自己的可用、就绪状态。
ros2 run remote_controller remote_controller --DEBUG --config /path/to/xbox_default.yamllaunch 的自定义参数必须写成 DEBUG:=true:
ros2 launch remote_controller remote_controller.launch.py DEBUG:=trueros2 launch --debug 只影响 ROS 2 的 launch 系统,不会传递给遥控器 driver。
示例:
sources:
gamepad:
type: joystick
device: /dev/input/by-id/usb-...-joystick
priority: 50
ready_timeout_ms: 1000
loss_timeout_ms: 300
cooldown_ms: 1000
signals:
gamepad.left_y: {from: js.axis.3}
gamepad.left_x: {from: js.axis.0}
gamepad.yaw: {from: js.axis.6}
gamepad.a: {from: js.button.0}
gamepad.b: {from: js.button.1}字段:
-
type: joystick或type: gamepad:使用 Linux joystick driver。 -
device:设备路径,例如/dev/input/js0。 -
priority:候选设备优先级,值越大越优先。 -
ready_timeout_ms:启动后等待 driver 进入is_ready()的最长时间,默认1000。 -
loss_timeout_ms:普通 driver 的is_available()连续失败多久才判定断连,默认300。CRSF 这类按有效帧新鲜度自行判定的 driver 会声明并直接使用该值。 -
cooldown_ms:断连或就绪超时后多久不参与重新选择,默认1000。 -
signals:业务 source 声明。 -
signals.<name>.from:raw source,例如js.axis.3或js.button.0。
driver 行为:
-
js.axis.N会归一化到[-1, 1]。 -
js.button.N按下为1.0,松开为0.0。 - 打开、读取错误或设备拔出会使 driver 变为不可用;设备管理器在
loss_timeout_ms后做安全切换。
示例:
sources:
keyboard:
type: keyboard
poll_timeout_us: 20000
hold_ms: 200
stop: space
signals:
keyboard.vx: {from: keyboard.axis, negative: s, positive: w}
keyboard.vy: {from: keyboard.axis, negative: d, positive: a}
keyboard.yaw: {from: keyboard.axis, negative: e, positive: q}
keyboard.normal: {from: keyboard.key, key: "1", hold_ms: 200}字段:
-
type: keyboard:使用终端键盘 driver。 -
poll_timeout_us:轮询终端输入的超时时间,单位微秒。 -
hold_ms:键盘 source 触发后保持多久。键盘终端没有真实 release 事件,所以需要这个字段。 -
stop:清零运动轴的按键,常用space。 -
from: keyboard.axis:声明键盘模拟轴。 -
negative:按下后输出-1.0的键。 -
positive:按下后输出1.0的键。 -
from: keyboard.key:声明普通按键。 -
key:触发普通按键 source 的键。 -
signals.<name>.hold_ms:覆盖单个 source 的保持时间。
特殊键名:
space
tab
esc
escape
普通键写单字符:
keyboard.sin_wave: {from: keyboard.key, key: "0"}type: crsf 已内置。它使用 POSIX 串口(默认 460800, 8N1, 无流控),
解析 CRC-8/DVB-S2 正确的 RC_CHANNELS_PACKED (0x16) 帧,并输出 16 个
归一化通道。设备拔出或停止收到有效帧后可自动重开 /dev/ttyCRSF。
sources:
crsf:
type: crsf
device: /dev/ttyCRSF
priority: 100
baud_rate: 460800
ready_timeout_ms: 1000
loss_timeout_ms: 300
cooldown_ms: 1000
signals:
crsf.left_x: {from: crsf.channel.1} # CH1 / 左摇杆 X
crsf.left_y: {from: crsf.channel.2} # CH2 / 左摇杆 Y
crsf.trigger_right: {from: crsf.channel.3} # CH3 / RT
crsf.right_x: {from: crsf.channel.4} # CH4 / 右摇杆 X
crsf.right_y: {from: crsf.channel.5} # CH5 / 右摇杆 Y
crsf.trigger_left: {from: crsf.channel.6} # CH6 / LT
crsf.button_group_a: {from: crsf.channel.7} # A/B/X/Y 编码组
crsf.button_group_b: {from: crsf.channel.8} # LB/RB/Back/Start/D-pad 编码组- 默认
xbox_default.yaml把 CH1..CH8 映射为虚拟 Xbox。CH1/CH2 是左摇杆 X/Y, CH3/CH6 是 RT/LT,CH4/CH5 是右摇杆 X/Y;CH2 在move.vx绑定处反向,CH5 使用时也应添加direction: -1。CH7 是按键组 A:200/400/600/800分别代表 A/B/X/Y,992为空闲;CH8 是按键组 B:200/400/600/800分别代表 LB/RB/Back/Start,1180/1380/1580/1780分别代表 D-pad 上/下/左/右,992为空闲。 - CH7/CH8 在默认 YAML 中会先解为
crsf.button_group_a、crsf.button_group_benum,再通过inputs[].when派生 A/B/X/Y/LB/RB/Start/Back 的 bool control。D-pad 左、右 分别以+1、-1映射到 CRSF yaw;上、下仍保留为 enum,可按需绑定输出。 -
crsf.channel.1到crsf.channel.16是 driver 写入的原始通道,默认按旧工程的174..1811映射并限制到[-1, 1]。业务含义只能由 YAML 中的语义 source 和 control 决定。 -
baud_rate:可覆盖默认波特率;兼容旧字段名baudrate。 -
channel_min、channel_max:可选的通道归一化校准值。 - 未激活时,driver 做非阻塞串口协议 probe;只有近期收到 CRC 正确的通道帧才会
成为可用候选。激活后
loss_timeout_ms由 CRSF driver 自己按有效帧新鲜度执行, 不会被候选管理器重复计时。
SBUS、UDP/TCP、蓝牙 HID 或其他串口协议仍按同一套 sources -> controls -> outputs
接入,但需要相应 driver。其他标量参数会保存到 InputDeviceConfig.options,由
对应 C++ driver 读取。具体看 自定义遥控器驱动参考。
只有持续流式协议、且字段可能独立停止更新时才设置:
udp.vx: {from: udp.vx, timeout_ms: 500, failsafe: 0.0}含义是该 raw signal 超过 500 ms 未更新时写入 failsafe。它不会切换设备,也不能替代 driver 的 is_available()。
- Linux joystick:默认不设置。摇杆静止不产生事件是正常行为,设备级断连由 driver 和
loss_timeout_ms判断。 - CRSF:应由 driver 根据“最近一次 CRC 正确完整帧”实现
is_available();不要用每通道timeout_ms作为主要断连保护。 - UDP/TCP:可按协议需要保留,用于某一个字段停止更新的额外保护。
curves 负责输入曲线处理。它可以在 control source 上引用。
expo 曲线:
curves:
stick:
type: expo
deadzone: 0.03
expo: 0.2
limit: [-1.0, 1.0]
calibration:
input: [-1.0, 0.0, 1.0]
output: [-1.0, 0.0, 1.0]字段:
-
type: expo:指数曲线。 -
deadzone:死区,小于等于该绝对值时输出 0。 -
expo:指数强度,范围[0, 1]。 -
limit: [min, max]:输出限幅。 -
min/max:等价的限幅字段。 -
calibration.input:输入校准点[min, center, max]。 -
calibration.output:输出校准点[min, center, max]。 -
calibration.clamp:是否限制输入到校准范围内,默认 true。
分段曲线:
curves:
three_position_curve:
type: piecewise
points:
- [-1.0, -1.0]
- [-0.2, 0.0]
- [0.2, 0.0]
- [1.0, 1.0]piecewise 适合非线性旋钮、三档开关的预处理。points 必须按 input 从小到大排序。
controls 是配置里最重要的一层。后面的 outputs.when 应该判断 control,而不是直接判断 raw source。
controls:
move.vx:
type: analog
mix: max_abs
inputs:
- source: gamepad.left_y
direction: -1
curve: stick
- source: keyboard.vx
deadzone: 0.03
min: -1.0
max: 1.0
alpha: 0.03字段:
-
type: analog:连续模拟量。 -
inputs:唯一的输入规则列表;旧source、sources、expr不再支持。 -
mix: max_abs:取绝对值最大的输入。 -
mix: sum:求和;之后由 control 的min/max限制范围。 -
mix: first_active:按顺序取第一个非零输入。 -
direction:方向,-1表示反向。 -
scale:缩放。 -
offset:偏移。 -
curve:引用curves中的曲线。 -
deadzone:control 层死区。 -
min/max:输出范围。 -
alpha:低通滤波系数,1.0表示不滤波。 -
expo:control 层指数曲线。 -
invert:是否反向。
按钮:
controls:
button.south: {type: bool, inputs: [{source: gamepad.a}]}模拟轴当按钮:
controls:
trigger.left:
type: bool
threshold: 0.85
hysteresis: 0.05
inputs: [{source: gamepad.trigger_left}]字段:
-
threshold:大于阈值时认为按下。 -
hysteresis:迟滞,避免阈值附近抖动。 -
invert:反向。
三档开关或多档模式推荐用 enum:
controls:
switch.group_b:
type: enum
default: idle
hysteresis: 0.03
inputs: [{source: crsf.button_group_b}]
positions:
lb: [-1.0, -0.846]
rb: [-0.845, -0.602]
back: [-0.601, -0.358]
start: [-0.357, -0.118]
idle: [-0.117, 0.114]
dpad_up: [0.115, 0.351]
dpad_down: [0.352, 0.596]
dpad_left: [0.597, 0.841]
dpad_right: [0.842, 1.0]字段:
-
default:没有匹配区间时使用的值。 -
positions.<value>: [min, max]:枚举值对应的输入范围。 -
hysteresis:当前枚举值保持范围扩展。
判断 enum:
when: [switch.group_b=dpad_right]或:
when:
- equals:
control: switch.group_b
value: dpad_right复杂组合键可以先定义成 command.*:
controls:
command.sin_wave:
type: bool
inputs:
- when:
any:
- [trigger.right, button.west]
- [keyboard.sin_wave]
value: true再在 outputs 里复用:
outputs:
edge:
- output: btn_10=5
when: [command.sin_wave]inputs 还可把 enum 直接映射为模拟量,并使用 priority 抢占:
crsf.yaw:
type: analog
mix: sum
inputs:
- when: [crsf.button_group_b=dpad_left]
value: 1.0
priority: 100
- when: [crsf.button_group_b=dpad_right]
value: -1.0
priority: 100每次只有最高 priority 的激活规则组参与 mix;同组 sum 可让正负值抵消。when 支持递归
all/any/not,并支持 raw_range: {source: ..., min: ..., max: ...}。默认 analog alpha 为 1.0;
只有显式设置才平滑。
如果某个组合只用一次,也可以直接写在 outputs.when 里。
outputs 是最后一层。
outputs:
conflict_policy: first_wins
publish_on_change: true
analog:
vel_des.x: move.vx
vel_des.y: move.vy
yawdot_des: move.yaw
edge:
- output: system.start
when: [system.start]
level:
- output: btn_1=1
when:
any:
- [shoulder.right, button.west]
- [keyboard.normal]顶层字段:
-
conflict_policy: first_wins:同一个btn_N被多个 binding 写入不同值时,保留先匹配的。 -
conflict_policy: last_wins:后匹配的覆盖先匹配的。 -
conflict_policy: error:冲突时报错。 -
publish_on_change: true:只有 payload 变化时才发布MotionCommands。 -
publish_on_change: false:固定频率发布。
analog 写连续字段,推荐直接写 MotionCommands 字段路径:
outputs:
analog:
vel_des.x: move.vx
vel_des.y: move.vy
vel_des.z: move.vz
yawdot_des: move.yaw
height_des: body.height当前支持的简写:
vx -> vel_des.x
vy -> vel_des.y
vz -> vel_des.z
yaw -> yawdot_des
height -> height_des
高级写法:
outputs:
analog:
vel_des.x:
controls: [move.vx, autonomous.vx]
mix: first_active
scale: 1.0
offset: 0.0
limit: [-1.0, 1.0]level 是电平输出。条件满足时持续写值,条件不满足时自动回 0。
outputs:
level:
- output: btn_1=1
when: [keyboard.normal]适合:
- 按住期间持续有效的按钮。
- 旧逻辑里需要保持
btn_N=1的命令。
当前设计不需要 release_outputs。level 每次刷新都会重新计算所有 binding,组合键不满足时对应 btn_N 自然回 0。
edge 是上升沿输出。条件从 false 变 true 时输出一帧。
outputs:
edge:
- output: btn_10=5
when: [keyboard.sin_wave]适合:
- 状态切换。
- toggle / pause。
-
system.start、system.stop。
对于 btn_N=value,下一帧自动回 0。对于 system.*,只执行一次命令。
单个 control:
when: [keyboard.normal]多个 control 同时满足:
when: [shoulder.right, button.west]任意一组满足:
when:
any:
- [shoulder.right, button.west]
- [keyboard.normal]显式 pressed:
when:
- pressed: button.west显式 released:
when:
- button.west
- released: shoulder.left
- released: shoulder.right枚举等于:
when:
- switch.mode=high范围判断:
when:
- range:
control: throttle
min: 0.2
max: 1.0outputs.edge 可以触发 system.*:
outputs:
edge:
- output: system.start
when: [system.start]然后在 system: 下定义命令:
system:
start:
- "<prepare_log_or_environment_command>"
- "<start_robot_stack_command>"
stop:
- "<stop_robot_stack_command>"互斥锁:
system_mutexes:
launch:
acquire: start
release: stop清零运动输入:
system_reset_motion_after:
- stop目标:把一个新的键盘或手柄输入映射到一个状态机事件。
- 在
sources.keyboard.signals添加:
keyboard.sin_wave: {from: keyboard.key, key: "0"}- 在
controls添加:
keyboard.sin_wave: {type: bool, inputs: [{source: keyboard.sin_wave}]}- 在
outputs.edge添加一个btn_N=value输出:
- output: btn_N=value
when: [keyboard.sin_wave]- 如果这是状态切换,去
elf3_state_machine.yaml添加对应remote_events和transitions。
下一步看 状态机 YAML 配置。