YouDuck.ai

duckctl:从笔记本控制机器人,不需要网络和 ssh

最后更新

duckctl 跑在你的笔记本上,通过蓝牙 LE 和机器人对话。它是手机 App 的替代品,也是触达一台从没见过 wifi 网络的机器人的唯一途径。

这解决了一个先有鸡还是先有蛋的问题:新机器人要连 wifi,但配 wifi 通常需要先能连上它。duckctl wifi connect 走无线电,自己不需要任何网络。

不要在机器人上跑它。发布版里没有任何东西依赖它——机器人上的工具是 robotctl

命名上有个细节:工具名刻意不体现蓝牙mediad 给机器人提供了第二条传输通道,触达的是另一组方法,所以工具按「它跟谁说话」命名,而不是按「它现在用哪种无线电」。它曾经叫 duck-btctl

装它

从仓库克隆直接跑:

cargo run -q -p duckctl -- --name <robot-name> info

或者装一次(代价是它成为一个不再跟随分支的快照):

cargo install --path duckctl
duckctl --name <robot-name> info

下面的命令都按安装后的形式写。想从克隆里跑,前面加 cargo run -q -p duckctl --

这个工具以前会把自己装成 btctl。如果 which btctl 还能找到一个,那是你当年安装时的构建产物,它永远不会再更新:

cargo uninstall btd --bin btctl

找到机器人

duckctl scan
1 robot(s) advertising the duck service:
  aa:bb:cc:dd:ee:ff duck-c51b — 192.168.1.42, 1 service(s)  ← DUCK_ROBOT

7 other device(s) in range, not listed. …

只列机器人,无线电范围内的其它设备只计数不列出。--verbose 会展开——想找的机器人没出现在第一份列表里时,值得看一眼。

每台机器人会广播自己的 IPv4 地址,所以这里也是拿到 ssh 目标地址的地方。整个过程不建立连接、不需要 PIN。

  • 行尾 no address 表示机器人没连网络;
  • 完全没有地址那一行,说明它跑的是“机器人还不广播地址”之前的发布版,这时 duckctl wifi status 依然能报告。

SSID 不在列表里——广播包塞不下。duckctl wifi status 有它,还有信号强度和两个地址。

只要地址

ssh radxa@$(duckctl ip)

ip 只打印地址,所以可以直接做命令替换。它读的是广播包,不建立连接、不需要 PIN、约一秒返回。而且答案不会过期:btd 每五秒重读一次地址,变了就重新广播。

已经和这台电脑绑定过的机器人,往往会停止向它广播该服务,这时 ip 会转而建立连接并调用 net.status——更慢,而且需要 PIN,但它总能给出答案。--verbose 会说明走的是哪条路。

机器人的名字

从没改过名的机器人叫 duck- 加上从序列号推出的四个字符,比如 duck-c51b

macOS 会把一台机器人显示成两个名字合在一起,比如 radxa-zero3 [duck-c51b]——任意一半都能用作 --name

两个常见的名字问题

扫到过 duck-c51b,连过一次之后只剩 radxa-zero3:机器人跑的发布版把名字给了广播包但没给蓝牙适配器,而客户端缓存了适配器那个。更新机器人。但这不会清掉客户端已经缓存的,所以还要手动清:Linux 上 bluetoothctl remove <mac>,macOS 上在蓝牙设置里“忽略此设备”。

两台机器人重名:

2 robots answer to "radxa-zero3": radxa-zero3, radxa-zero3

指定了名字却有两台应答,是错误而不是让你选。这发生在 bootloader 把序列号留空的板子上——它于是按主机名命名,而同一个镜像刷出来的板子主机名都一样。在机器人上改名:

robotctl system set-name ducky

固定用同一台机器人

把名字放进环境变量,不用每条命令都写:

export DUCK_ROBOT=duck-c51b
duckctl info

写进 ~/.zshrc 就能持久化。DUCK_PIN 同理,用于设了 PIN 的机器人:

export DUCK_PIN=418299

临时对另一台机器人执行,--name 优先:

duckctl --name duck-ffff info

临时忽略默认值(比如台架上放着别人的机器人),把它设为空:

DUCK_ROBOT= duckctl scan

改名不会跟着改 DUCK_ROBOT。工具会提醒你,但变量得手动改——否则之后每条命令都在找一个不再应答的名字。

网页控制台

机器人自己提供一个页面,能看摄像头画面并驱动它:

duckctl open

它先找到机器人,然后在浏览器里打开 http://<address>:8080/--print 只给 URL(适合没有浏览器的机器或脚本),--port 用于 mediad --web-port 非默认的情况。

不需要安装任何东西,也不需要起服务——mediad 把这个页面内嵌了,所以跑着这个守护进程的机器人就自带控制台。

页面上有:摄像头画面,旁边是链路的码率、帧率、丢包和往返延迟;两个虚拟摇杆,以及 W/A/S/DQ/E 按键驱动(速度是手柄的 0.3 m/s 和 1.5 rad/s);在画面上拖动可以让它看向某个点;enable / init / relax / stop / shutdown 按钮;技能和音色库做成菜单;以及 2 Hz 的状态流和 robot.health——舵机过热、电池没电、控制循环变慢都在这里报出来。

stop 只是把页面正在发送的指令归零。它不是急停——这个系统里没有任何东西能从浏览器切断舵机电源,按钮做成普通样式就是这个原因。

控制台连不上时

涉及两个端口,而你只输入了其中一个。页面自己会用它被服务的那个主机去连 8443 上的信令服务器。

如果页面能加载,但提示信令端口没有应答——机器人是好的,你和 8443 之间有东西挡着,最常见的是防火墙。

另外:摄像头和驱动控制只走 WebRTC,所以没有网络地址的机器人没有控制台。先用下面的 wifi connect 通过无线电把它接入网络。

配 wifi(不需要自己有网络)

duckctl --name <robot-name> wifi status    # SSID、信号、地址
duckctl --name <robot-name> wifi scan      # 机器人现场扫描,要几秒
duckctl --name <robot-name> wifi connect <ssid> --psk <passphrase>
duckctl --name <robot-name> wifi forget <ssid>

开放网络省掉 --psk

wifi scan机器人现场扫无线电,不是返回上次的结果,所以要等几秒。

wifi connect 有两个行为要有心理准备:

  1. 加入新网络会断开当前网络,所以走 wifi 的 ssh 会话会掉——这是命令在正常工作,不是失败。
  2. 最多可能要 45 秒才返回。

设计上 net.connect 被明确禁止走 WebRTC,所以配网这件事只能走无线电——这也是为什么没有网络地址的机器人会被告知该怎么办,而不是简单报个空。

状态与版本

duckctl --name <robot-name> info      # 名字、序列号、运行时长
duckctl --name <robot-name> health    # 控制循环是否健康
duckctl --name <robot-name> status    # 版本握手与更新状态
duckctl --name <robot-name> version   # API 版本、发布版本、构建时的 git revision
duckctl --name <robot-name> name <new-name>   # 最多 24 字符,几秒生效,不用重启
duckctl --name <robot-name> reboot

versionrevisionnull 意味着这个发布版是在某人的笔记本上构建的,不是 CI 出的。排查诡异问题时这条信息很有用。

更新

用词和 robotctl update 完全一致,所以在机器人上学会的命令这里直接能用。每条都接受 --component <name>,默认 daemon——目前机器人也只有这一个组件。

duckctl --name <robot-name> update check
duckctl --name <robot-name> update status
duckctl --name <robot-name> update versions
duckctl --name <robot-name> update log --limit 20
duckctl --name <robot-name> update apply

安装要几分钟,过程中会打印进度:

· daemon: preflight
· daemon: downloading 12%
· daemon: downloading 47%
· daemon: verifying
· daemon: swapping
· daemon: health_gate
{
  "outcome": "applied",
  "from": "0.5.1",
  "to": "0.6.0"
}

装完连接会断开——机器人立刻重启各守护进程,btd 大约在这个应答之后五秒重启。这是更新在正常工作。重连后跑 duckctl update status 确认。

相关

相关内容