第 01 章|【系统全景与四条铁律】

所属:卷一 · ROS1 全栈 | 状态:已发布(2026-11-06) | 配套代码:src

拿到宇树 G1 的第一天,我什么都没写。不是没想法,是知道只要动错一个地方,这套几十万的设备就可能再也起不来——而宇树出厂的系统并没有你想的那么皮实。

这篇文章是整个系列的开篇。不教你写代码,先把系统边界画清楚:哪些是宇树的、哪些是我们的、哪颗雷达只能归一个人用、哪几个服务绝对不能 systemctl stop。把这些搞明白,后面所有章节才有意义。

这一章要解决什么

  • 设备到手、文档零散、官方 demo 能跑但不知道从何下手
  • 想加自研定位/导航,但不敢动宇树自带的服务,怕把生产环境搞崩
  • 需要一张“地图”:硬件怎么连、软件栈怎么分、雷达归谁、灰度怎么做

读完之后,你应该能说清楚这台 G1 上两套系统的边界在哪、四个共享资源(雷达、网口、时钟、运控)各自归谁,以及为什么后面 32 章的技术选型长成这个样子。

环境与前置

  • 硬件:宇树 G1(29 DoF,机载主机 aarch64 / Ubuntu 20.04)+ Livox MID360(倒装,roll=180°)
  • 出厂软件:宇树原生 unitree_sdk2 / unitree_slam / advanced_slam,由 systemd 托管
  • 我们的目标:在不破坏出厂系统的前提下,叠加一套自研的定位 + 导航 + Web 栈
  • 前置知识:会用 systemctl、知道 ROS 话题是什么即可,其余每一章都会补

系统全景

先把整机拆成四层。后面 32 章,每一章都在这四层里的某一层干活。

┌─────────────────────────────────────────────────────────────┐
│  Web 层   HTTP 8080(页面/API) + WebSocket 9002(位姿/点云)  │
├─────────────────────────────────────────────────────────────┤
│  导航层   move_base(TEB) ──ROS1──▶ Nav2(Smac2D/TEB) ──ROS2──▶ │
│           g1_nav_bridge → loco sport 服务                      │
├─────────────────────────────────────────────────────────────┤
│  定位层   FAST-LIO2 → hdl(BBS) → g1_global_init → open3d_loc   │
│           输出 /localization_3d @10Hz + map→odom TF            │
├─────────────────────────────────────────────────────────────┤
│  驱动层   Livox 官方 livox_ros_driver2(倒装走配置 roll=180)   │
│           MID360 点云 10Hz / IMU 200Hz                         │
├─────────────────────────────────────────────────────────────┤
│  宇树原生 unitree_slam / advanced_slam(出厂 SLAM,独占雷达)    │
│           loco sport 服务(步态/运动控制,我们一直复用)         │
└─────────────────────────────────────────────────────────────┘

硬件拓扑:雷达走网口,这是后面一切麻烦的源头

MID360 不是 USB 设备,它走以太网。机载主机有一块网口专门接雷达,配的是静态 IP(driver.host_ip,出厂网段 192.168.123.x)。这一个事实,在第 14、21、24 章会反复咬人:

  • 开机时 NetworkManager 配网卡可能晚于我们的服务启动 → 雷达驱动 bind 失败 → 收不到数据(第 24 章的 wait_lidar_net 就是为它写的)
  • 雷达是独占资源:同一时间只能有一个进程持有它的数据流

时间也是共享资源:比雷达更隐蔽的第二条命门

雷达之外还有一个被低估的共享资源——时钟。机载主机的 RTC 不准,开机瞬间系统时间是错的,要靠校时服务在头几分钟里把它拽回来。而现场一度同时存在三套校时(ntp-sync.service、rc.local 里的 ntpdate、chronyd),它们互相拉扯,曾在开机约 2 分钟时把时钟跳变 +128 秒。

后果比“网卡晚了”更狠:MID360 报文时间来自雷达内部时基,时钟一跳,FAST-LIO2 的 lidar/IMU 时间对齐被撕裂 → 里程计发散 → open3d_loc 崩溃重启,进入循环。所以 g1-ros2.service 里显式写了 After=chronyd.service,启动脚本另有一道 wait_clock_stable() 兜底。这条线在第 06 章(1840 ms → 2.9 ms)和番外A(200 Hz 被录成 12 Hz)会正面展开。

软件栈边界:宇树的归宇树,我们的归我们

模块归属我们动它吗
unitree_slam / advanced_slam宇树出厂 SLAM不动代码,只做有界切换
loco sport 服务(运动控制)宇树一直复用,从不替换
livox_ros_driver2Livox 官方用官方包,倒装只改 MID360_config.json 的 "roll": 180.0
FAST-LIO2 / hdl / open3d_loc开源深度改造后自研部署
g1_global_init / g1_nav_bridge自研全书主角
web_server / web_bridge自研全书主角

注意 loco sport 这一行:整个系列(第 1-32 章),我们从来没有自己写过步态控制器。运动控制是另一条线(见姊妹篇《G1 运动控制开发心得》),导航线只负责“告诉它往哪走、走多快”,然后调用宇树的运动接口。

运控侧:第三个共享资源是控制权

导航层最后一步是把 cmd_vel 交给宇树运动接口,而那条链路上不止我们一个写者:运控产品侧会下发自己的指令,手动接管、急停也会插进来。两个进程各发一点,叠加起来就会失控——真机上出过一次 max|dq| 从 0.04 rad/s 冲到 9.89 rad/s 的事故,关节保护直接动作。

所以我们不直连宇树接口,而是把运控收进一个网关(TCP 9500,Domain42 网络隔离),由网关统一裁决“现在归谁控制”,并用 FSM 状态码(501 / 802 / 1000)把控制权切换显式化。这条约束在第 30、31 章展开,本章只需要记住一句:运控是单 writer 的,谁写不取决于代码跑在哪个进程里。

四条铁律

这是整个项目用几次惊险换回来的规矩,写在最前面。

铁律一:不动宇树服务

unitree_slam.service 由 systemd 托管,Restart=on-failure + KillMode=control-group。这意味着:

  • 你 kill 它,它会被 systemd 拉起来,而 KillMode=control-group 会连带清掉同组的雷达驱动
  • 你改了它的配置,下次升级/重启可能直接起不来,而且你很难还原

我们唯一允许的操作是 systemctl start/stop,并且必须记住恢复命令。真误停了,恢复路径是:

sudo systemctl stop g1-ros2.service   # 先停我们的栈,释放雷达
sudo systemctl start unitree_slam.service

铁律二:旧栈全程可用

自研栈(v1 ROS1 / v2 ROS2)和宇树出厂 SLAM 互斥但不能互相破坏。任何时刻,停掉我们的栈,出厂系统必须能立刻接管。这决定了:

  • 所有自研组件装在用户目录(~/xxj/fast-lio、~/micromamba),不污染系统
  • 不 root 安装,不修改系统级 ROS 环境
  • systemd 服务里用 Conflicts=unitree_slam.service advanced_slam.service 声明互斥,由启动脚本负责切换,而不是手动乱停

铁律三:每阶段先验收再推进

这个项目跨越 32 章、两次大迁移(ROS1→ROS2、RPP→TEB)。能不走回头路,靠的就是每一步都有量化验收:

  • 录一条基线 bag,把“好不好”变成数字(第 04 章)
  • 每章都有“验证与量化”一节,没有数字不算完
  • 切换窗口(灰度)是有界的:同一个验收窗口内,允许两栈对照,但写雷达的永远只有一个

铁律四:雷达独占

这是最容易被低估的一条。MID360 同一时间只能供一个消费方。所以:

  • 不能让宇树 SLAM 和我们的 FAST-LIO2 同时跑——会抢雷达
  • 不能简单地“并行灰度”——没有第二个雷达
  • 灰度 = 有界切换窗口:停 A、起 B、验收、不行就切回 A。窗口内只允许一个写雷达

这条约束直接塑造了第 17 章(TF 树设计)、第 21 章(systemd 互斥)、第 24 章(一键切换与回退)的所有设计。

三条技术链与全系列路线图

链内容章节
定位驱动 → FAST-LIO2 → 全局初始化 → ICP 跟踪第 03-11 章
导航move_base(TEB) → Nav2(Smac+TEB) → 楼梯通行第 12-13、20-22、26-32 章
Web 同步WebSocket 9002 / web_server / stair-walk第 18-19、32 章

三条链对应卷一到卷六的阶段推进:先用 ROS1 把定位链路跑通 → 迁到 ROS2 → 换成 Nav2/TEB → 接上楼梯与运控 → 部署交付。初始化不算独立一条链,它是定位链的前半段:没有 g1_global_init 把机器人放到地图里,后面 ICP 跟踪无从谈起。读的时候不必按章号顺序,但每一卷都假定上一卷已经可运行。

关键数据(真机实测)

指标数值
点云10 Hz
IMU200 Hz
定位输出 /localization_3d10 Hz
全局定位耗时7.5 s
Web 端口HTTP 8080 / WebSocket 9002
运控网关TCP 9500(Domain42)

验证与量化

开篇没有算法可测,但“边界是否真的画住了”可以验。拿到机器后,按这四条自检,任何一条不成立,后面都不用往下走:

检查项命令 / 手段通过标准
互斥声明生效systemctl cat g1-ros2.service | grep Conflicts同时列出 unitree_slam.service 与 advanced_slam.service
雷达只有一个消费者起自研栈后 ros2 topic hz /livox/lidar;同时看 unitree_slam 状态点云稳定 10 Hz,且 unitree_slam.service 为 inactive
停栈能还权systemctl stop g1-ros2.service 后 systemctl start unitree_slam.service出厂 SLAM 在 1 分钟内恢复,不再需要手动清进程
时钟已稳定timedatectl 观察开机后 3 分钟;chronyc tracking无秒级跳变,System time 偏移持续收敛

这四条就是本章的“数字”。后面每一章的验收口径,都是在这个边界上继续加刻度。

踩坑清单

现象根因解法
误停 unitree_slam 后无法恢复systemd 托管 + KillMode=control-group 连带杀雷达驱动只用 systemctl start/stop,记住恢复命令
自研栈起来后雷达没数据与宇树 SLAM 同时持有雷达Conflicts= 互斥 + 启动脚本切换
开机后整链起不来网卡/雷达就绪晚于服务启动NetworkManager-wait-online + wait_lidar_net(第 24 章)
开机约 2 分钟里程计发散、open3d_loc 崩溃循环三套校时互相拉扯,时钟跳变 +128 s 撕裂 lidar/IMU 对齐只留 chronyd,服务 After=chronyd.service + wait_clock_stable()
关节突然超速、保护动作运控侧多 writer 叠加(0.04 → 9.89 rad/s)收进网关,单 writer + FSM 显式交接(第 30、31 章)
改坏了出厂环境在系统目录/root 下装自研依赖全部装用户目录,不 root

配套代码

  • 真机工程 fast-lio-v2/deploy_humanoid/systemd/g1-ros2.service:互斥、启动依赖(网卡/时钟)与杀进程范围声明
  • 真机工程 fast-lio-v2/deploy_humanoid/start_ros2.sh:一键启停、wait_lidar_net 与 wait_clock_stable 兜底
  • 真机工程 fast-lio-v2/deploy_humanoid/config.json:端口(8080 / 9002 / 9500)与雷达网段配置的单一来源

架构图源文件与端口/话题速查表待补(src/ep01-系统全景)。本系列的 src/ 目录沿用历史 epXX 编号,与章号的对应关系见 src/README.md。

小结

这一章没有一行算法,但它是后面所有章节的约束来源:

  1. 不动宇树服务 → 自研栈必须可共存、可切换、可回退
  2. 旧栈全程可用 → 一切装用户目录、不 root、systemd 互斥
  3. 先验收再推进 → 每章都要有数字
  4. 雷达独占 → 灰度只能是有界切换窗口

四个共享资源各归一人:雷达归当前写者、网口归 NetworkManager、时钟归 chronyd、运控归网关。把这四个“唯一”钉死,两套系统才能在同一台机器上活下去。

下一章开始动手:在不 root、不碰宇树环境的前提下,把 ROS1 Noetic 和依赖装齐。

下章预告

第 02 章:【环境搭建:不 root 装 ROS1 Noetic 与依赖】
GitHub地址

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐