SLAM建图话题数据流
一、全景图
═══════════ Pi(车上,碰硬件的唯一进程:backend)═══════════
雷达 A1M8 编码器×4(pigpio) IMU(CH340串口)
│串口帧 │GPIO边沿 │7E23帧
▼ ▼ ▼
lidar.py encoder.py imu.py
720点/圈·物理量 tick增量(带符号) gz(rad/s)
│ └────────┬──────────────┘
▼ ▼
ScanBroadcaster odom_exporter._step() 每1/30s
TCP:5001 NDJSON 调 integrate() → 速度组(v,w) + 位姿组(x,y,θ)
每行一圈JSON │TCP:5003 NDJSON
│ │ 每拍三条: wheel / imu / odom
═══ 容器 ros_env(两座桥:JSON → ROS 话题的翻译官)═══
▼ ▼
lidar_bridge.py odom_bridge.py --mode raw
│/scan (LaserScan) │/wheel_odom(只twist) + /imu/data_raw(只vyaw)
│BEST_EFFORT │TF-free(位姿决定权上交)
└────────── WiFi DDS(domain 0 + 静态对端)──────────┐
▼
═══════════ 笔记本(ROS 世界,全部计算)═══════════════════
EKF(robot_localization) slam_toolbox
/wheel_odom取vx+vyaw、/imu取vyaw ①取/scan + TF放置扫描
──积分+加权──→ (x,y,θ) ──→ TF odom→base_link ②匹配/建节点(阈值0.5)/回环
│ ──→ /map + TF map→odom(改正数表)
└──────────────┬────────────────────────┘
▼
slam_service.py :5002 订阅/map
│HTTP 栅格(gzip ~6KB) 每3s
▼
浏览器「地图」画布 ←—— 点云走的是另一条路:Socket.IO :5000 直连,不过 ROS
这张图本质上是三段地域(Pi / 容器 / 笔记本)、两座桥、一次跨海(WiFi DDS)。
二、逐块讲解
第一块:Pi 硬件层(顶部三个并排的源头)
雷达 A1M8 编码器×4(pigpio) IMU(CH340串口)
│串口帧 │GPIO边沿 │7E23帧
三个传感器,三种完全不同的原始信号:雷达从串口吐字节;编码器是GPIO 引脚上的电平跳变(每秒近万个边沿,pigpio 守护进程捕获);IMU 从 USB 串口吐 7E 23 开头的协议帧。
第二块:三个驱动模块(物理量层)
lidar.py encoder.py imu.py
720点/圈·物理量 tick增量(带符号) gz(rad/s)
各把各的原始信号翻译成有物理意义的数:
lidar.py:字节重组成一圈 720 个 0.5° 方向的距离值——“我看见了什么”;encoder.py:边沿喂进转移表状态机 → 带符号 tick——“轮子转了多少”;imu.py:帧解析 → gz(rad/s,正=逆时针左转)——“车体转多快”。
第三块:两个出口(TCP 广播层)
ScanBroadcaster odom_exporter._step() 每1/30s
TCP:5001 NDJSON 调 integrate() → 速度组(v,w) + 位姿组(x,y,θ)
每行一圈JSON │ 每拍三条: wheel / imu / odom
左边:雷达每转完一圈,打包成一行 JSON 广播到 :5001(每个客户端独立 2 格缓冲,慢消费者丢帧不阻塞采集)。
右边:每 1/30 秒一拍,odom_exporter 去编码器和 IMU 手里取当拍数据,喂给纯函数 integrate()——轮子管走多快、陀螺管转多快,合成速度组 (v, w),顺带积出车上底账 (x, y, θ)。然后广播三条 JSON:wheel(四轮 tick + vL/vR)、imu(gz)、odom(底账位姿)——同一拍三份视图,原始层留着分诊,融合层给遥测。
这一块是国境线:TCP + 每行一个 JSON,nc 就能看。过了这两个端口,数据就不再属于”车”,而属于”任何来取的人”。
:5001 和 :5003 这两个端口是裸 TCP 服务——客户端连上后,服务端就不停地把数据字节流灌过来,仅此而已。它不是网页(没有 HTTP 的请求/响应、没有头部),也不是 Socket.IO 那种带协议握手的通道。任何能建立 TCP 连接的工具都能收,不挑客户端。
补充:nc(netcat)是 Linux 自带的“TCP 直连接工具”:给它一个地址和端口,它就建立连接,把收到的一切原样打印到终端。
第四块:容器里的两座桥(翻译官)
lidar_bridge.py odom_bridge.py --mode raw
│/scan (LaserScan) │/wheel_odom(只twist) + /imu/data_raw(只vyaw)
│BEST_EFFORT │TF-free(位姿决定权上交)
Pi 上没有 ROS 节点直接读硬件——翻译发生在 ros_env 容器里的两个 Python 脚本上。它们读 TCP、把 JSON 行组装成 ROS 消息发出去:
- 左桥:一行圈 JSON → 一个
LaserScan话题消息,发到/scan。BEST_EFFORT= 传感器的正确取舍(要最新,不要重传旧的)——这也是”测频率必须加--qos-reliability best_effort“的根源。 - 右桥(raw 模式):
wheel行 →/wheel_odom(只填速度半边 twist:vx、vyaw;位姿留零);imu行 →/imu/data_raw(只填 gz)。TF-free = 它不发任何”车在哪”的声明——寄生观测不寄结论,位姿的决定权上交给笔记本的 EKF。(为什么:Pi 的融合是二选一硬开关,EKF 的是按质量加权——生数据交上去,让更好的算法做菜。)
跨海:WiFi DDS
└────────── WiFi DDS(domain 0 + 静态对端)──────────┐
ROS 话题是分布式的:桥发到话题上,谁订谁收,不指定收件人。发现机制靠 domain 0 + 静态对端(两端方向相反:笔记本填 Pi 的 IP,Pi 容器填笔记本的 IP)+ SUBNET 范围。桥和消费者隔着一整段 WiFi,但代码里看不见”发送到笔记本”这种字样——这就是 ROS 的解耦:发布者根本不知道笔记本存在。
第五块:笔记本,两个计算节点
左边,EKF——速度变位姿:
/wheel_odom取vx+vyaw、/imu取vyaw
──积分+加权──→ (x,y,θ) ──→ TF odom→base_link
订两路生数据,各取一项(15 位配置里其余 13 位全关)。加权:陀螺 vyaw 置信 0.01、编码器 vyaw 置信 0.09,按 9:1 混合,某路挂了自动塌向另一路;积分:vx、vyaw 攒成每一时刻的 (x, y, θ)。然后以 TF odom→base_link 的形式发布——数值就是位姿 (x, y, θ),意义是”车此刻在里程计坐标系里在哪朝哪”。建图用的那本账,在这站落笔。
右边,slam_toolbox——位姿+扫描变地图:
①取/scan + TF放置扫描
②匹配/建节点(阈值0.5)/回环
──→ /map + TF map→odom(改正数表)
每帧 /scan 到了:① 查 TF 链(map→odom→base_link→laser_frame,GIS 式分步基准转换)知道”这帧时刻雷达在地图里的大概位置朝向”,把点放进地图;② 扫描匹配微调 → 挪够 0.5m 或转够 0.5rad(≈28.6°)设一个新驿站(节点)→ 转回老地方时认出来(回环)→ 位姿图优化平差,误差分摊回整条轨迹。产出两样:/map(占据栅格地图)和 TF map→odom(改正数表——odom 账照漂不改,修正全挂在这条变换上)。
至此 TF 树闭合,三条各司其主:
map ──TF①(slam_toolbox发)── odom ──TF②(EKF发)── base_link ──TF③(静态)── laser_frame
平差改正 死推位姿 车体 雷达安装
| TF | 谁发布 | 数据来源 | 消费者 | 性质 |
|---|---|---|---|---|
| odom→base_link | EKF | vx+vyaw 积分 | slam_toolbox(放扫描) | 实时,30Hz |
| map→odom | slam_toolbox | 回环平差 | 整棵树的修正 | 慢速跳变(回环时) |
| base_link→laser_frame | 静态发布器 | 安装关系(本项目全零) | 所有要用扫描的人 | 永不变 |
第六块:最后一跳,进浏览器
slam_service.py :5002 订阅/map
│HTTP 栅格(gzip ~6KB) 每3s
▼
浏览器「地图」画布 ←—— 点云走的是另一条路:Socket.IO :5000 直连,不过 ROS
slam_service 订阅 /map,攒成 HTTP 服务;浏览器每 3 秒轮询栅格(gzip 后约 6KB,没变不重发),画进”地图”画布。注意最后的分岔:点云画面根本不进 ROS——后端 Socket.IO :5000 直推浏览器。
三、附录
个人理解笔记(10.4 10.5):



