WSL / 树莓派 Ubuntu 网络信息查看与 ROS2 分布式通信检查笔记这份笔记用于记录： 如何在 WSL Ubuntu 和树莓派 Ubuntu 中查看 IP、网段、网关、网卡，以及之后如何检查两端是否可以用于 ROS2 分布式通信。1. 常用命令以后主要记住这两个命令：ip -br addr ip route也可以使用完整形式：ip addr ip route其中：ip -br addr：简洁查看网卡状态和 IP 地址ip addr：详细查看网卡信息ip route：查看默认网关和路由信息2. 查看 WSL 的 IP 和网卡例如你之前执行 ip addr 后看到：7: eth4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ... inet 192.168.154.239/24 brd 192.168.154.255 scope global noprefixroute eth4重点看这一行：inet 192.168.154.239/24 brd 192.168.154.255 ... eth4可以得到：信息结果WSL IP192.168.154.239子网前缀/24广播地址192.168.154.255网卡名eth4解释：inet：IPv4 地址 192.168.154.239：当前 WSL 的 IP 地址 /24：子网前缀，表示网段范围 brd 192.168.154.255：广播地址 eth4：这个 IP 所在的网卡3. 怎么判断哪个网卡正在使用？看网卡后面有没有这些标志：UP LOWER_UP例如：eth4: <BROADCAST,MULTICAST,UP,LOWER_UP>说明 eth4 是正在工作的网卡。而像下面这种：eth0: <BROADCAST,MULTICAST> ... state DOWN eth1: <BROADCAST,MULTICAST> ... state DOWN说明这些网卡目前没有启用，不用管。简单判断：有 UP、LOWER_UP，并且下面有 inet IP 的网卡，就是当前主要使用的网卡。4. 查看网关执行：ip route例如你之前看到：default via 192.168.154.114 dev eth4 proto kernel metric 35 192.168.154.0/24 dev eth4 proto kernel scope link metric 291 192.168.154.114 dev eth4 proto kernel scope link metric 35重点看第一行：default via 192.168.154.114 dev eth4含义：字段含义default默认路由，访问外网时默认走这里via 192.168.154.114默认网关dev eth4通过 eth4 这个网卡出去所以可以得到：网关：192.168.154.114 默认网卡：eth45. 查看网段你之前的 WSL IP 是：192.168.154.239/24其中 /24 表示子网掩码是：255.255.255.0所以网段是：192.168.154.0/24这个网段中：网络地址：192.168.154.0 可用地址：192.168.154.1 ~ 192.168.154.254 广播地址：192.168.154.255所以你当前的信息可以总结为：项目内容WSL IP192.168.154.239网段192.168.154.0/24网关192.168.154.114网卡eth46. 为什么不能 ping 192.168.154.0？因为：192.168.154.0是网络地址，代表整个网段，不是一台具体设备。所以不要用：ping 192.168.154.07. 为什么之前 ping 192.168.154.1 失败？很多教程中网关是：192.168.1.1 192.168.0.1 192.168.31.1但你的实际网关不是 .1，而是：192.168.154.114所以应该 ping：ping -c 10 192.168.154.114而不是：ping -c 10 192.168.154.18. 当前网络检查结果你已经测试过：ping -c 10 192.168.154.114 ping -c 5 8.8.8.8 ping -c 5 baidu.com sudo apt update结果：测试项目结果说明ping 网关 192.168.154.114成功，0% 丢包局域网网关正常ping 8.8.8.8成功，0% 丢包外网 IP 访问正常ping baidu.com成功，0% 丢包DNS 域名解析正常sudo apt update成功apt 软件源访问正常结论：WSL 的 mirrored 网络模式目前工作正常，不需要改回 NAT。9. WSL mirrored 模式当前状态你现在的状态可以理解为：WSL IP：192.168.154.239 网段：192.168.154.0/24 网关：192.168.154.114 网卡：eth4 网络模式：mirrored相比默认 NAT 模式：默认 NAT 模式：常见 IP 是 172.xx.xx.xx mirrored 模式：更接近真实局域网设备，更适合 ROS2 分布式通信10. 树莓派到了之后怎么检查？树莓派 Ubuntu 上也执行：ip -br addr ip route假设树莓派显示：wlan0 UP 192.168.154.88/24 default via 192.168.154.114 dev wlan0那么可以得到：项目内容树莓派 IP192.168.154.88树莓派网卡wlan0树莓派网关192.168.154.114树莓派网段192.168.154.0/24如果 WSL 是：192.168.154.239/24树莓派是：192.168.154.88/24说明它们都在：192.168.154.0/24也就是同一个局域网。11. 两端互相 pingWSL 里 ping 树莓派：ping 192.168.154.88树莓派里 ping WSL：ping 192.168.154.239判断：两边都能 ping 通：网络基本没问题，可以继续测试 ROS2。 一边能 ping 通，另一边不行：可能是防火墙或网络隔离问题。 两边都 ping 不通：可能不在同一个局域网，或者网络被隔离。12. ROS2 分布式通信环境变量WSL 和树莓派两端建议设置一致：export ROS_DOMAIN_ID=30 export ROS_LOCALHOST_ONLY=0 export RMW_IMPLEMENTATION=rmw_fastrtps_cpp可以写入 ~/.bashrc：echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc echo "export ROS_DOMAIN_ID=30" >> ~/.bashrc echo "export ROS_LOCALHOST_ONLY=0" >> ~/.bashrc echo "export RMW_IMPLEMENTATION=rmw_fastrtps_cpp" >> ~/.bashrc source ~/.bashrc注意：WSL 和树莓派的 ROS_DOMAIN_ID 必须一致。 ROS_LOCALHOST_ONLY 不能是 1，否则只能本机通信。检查命令：echo $ROS_DOMAIN_ID echo $ROS_LOCALHOST_ONLY echo $RMW_IMPLEMENTATION13. ROS2 本机测试在 WSL 中打开第一个终端：source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp talker再打开第二个终端：source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp listener如果能看到类似：I heard: [Hello World: 1] I heard: [Hello World: 2]说明 ROS2 本机通信正常。14. ROS2 跨设备测试树莓派上运行发布者：source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp talkerWSL 上运行订阅者：source /opt/ros/humble/setup.bash ros2 run demo_nodes_cpp listener如果 WSL 能收到：I heard: [Hello World: 1] I heard: [Hello World: 2]说明树莓派到 WSL 的 ROS2 topic 通信成功。然后反过来测试：WSL 上运行：ros2 run demo_nodes_cpp talker树莓派上运行：ros2 run demo_nodes_cpp listener这样可以确认双向通信。15. 以后换网络时的检查流程每次换 WiFi、换路由器、换学校网络，都按这个流程检查：第一步：查看 WSL 网络ip -br addr ip route记录：WSL IP： WSL 网卡： WSL 网关： WSL 网段：第二步：查看树莓派网络ip -br addr ip route记录：树莓派 IP： 树莓派网卡： 树莓派网关： 树莓派网段：第三步：判断是不是同一个网段例如：WSL：192.168.154.239/24 树莓派：192.168.154.88/24都是：192.168.154.0/24说明在同一个网段。如果是：WSL：192.168.154.239/24 树莓派：192.168.1.88/24那就不是同一个网段，可能无法直接发现 ROS2 节点。第四步：互相 pingWSL ping 树莓派：ping 树莓派IP树莓派 ping WSL：ping WSL_IP第五步：测试 ROS2 talker/listener一端：ros2 run demo_nodes_cpp talker另一端：ros2 run demo_nodes_cpp listener16. 最简记忆版以后只记这几条：ip -br addr ip route ping 对方IP echo $ROS_DOMAIN_ID echo $ROS_LOCALHOST_ONLY ros2 run demo_nodes_cpp talker ros2 run demo_nodes_cpp listener判断逻辑：1. ip -br addr 看 IP 和网卡 2. ip route 看网关 3. 两端 IP 是否在同一网段 4. 两端是否能互相 ping 5. ROS_DOMAIN_ID 是否一致 6. ROS_LOCALHOST_ONLY 是否为 0 7. talker/listener 是否能通信17. 当前你的记录WSL IP：192.168.154.239 网段：192.168.154.0/24 网关：192.168.154.114 网卡：eth4 网络模式：mirrored目前网络测试结果：网关 ping 通 外网 IP ping 通 域名 ping 通 apt update 正常结论：当前 WSL 网络正常，可以保留 mirrored 模式，等待树莓派到货后继续测试跨设备 ROS2 通信。ssh gyh@192.168.3.211 ip -br addr hostname -I
ROS2了解WSL -d Ubuntu-22.04-ROS2-Study1. 节点节点就是 ROS2 中正在运行的一个功能程序。ros2 node list # 查看所有节点 ros2 node info /节点名 # 查看节点详细信息 ros2 run 包名 可执行文件名 # 运行节点例子：ros2 run turtlesim turtlesim_node ros2 node list ros2 node info /turtlesim记忆：node list 看节点 node info 看节点详细信息 ros2 run 运行节点2. 话题话题是单向、持续的数据流。发布者一直发，订阅者一直收。# 发布者 ros2 run learning_topic topic_helloworld_pub # 订阅者 ros2 run learning_topic topic_helloworld_sub 常用命令：ros2 topic list # 查看话题 ros2 topic list -t # 查看话题和类型 ros2 topic echo /话题名 # 查看话题数据 ros2 topic info /话题名 # 查看发布者/订阅者数量 ros2 topic type /话题名 # 查看话题类型 ros2 interface show 类型名 # 查看消息结构 ros2 topic pub /话题名 类型 "数据" # 手动发布话题例子：ros2 topic list -t ros2 topic echo /turtle1/pose ros2 topic type /turtle1/cmd_vel ros2 interface show geometry_msgs/msg/Twist让乌龟动一下：ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0}, angular: {z: 1.0}}"记忆：topic echo 看数据 topic pub 发数据 topic type 看类型 interface show 看结构3. 服务服务是一次请求，一次响应。客户端 Client ---> 请求 Request ---> 服务端 Server 客户端 Client <--- 响应 Response <--- 服务端 Server常用命令：ros2 service list # 查看服务 ros2 service list -t # 查看服务和类型 ros2 service type /服务名 # 查看服务类型 ros2 interface show 服务类型 # 查看请求/响应结构 ros2 service call /服务名 类型 "数据" # 调用服务例子：ros2 service list -t ros2 service type /clear ros2 service call /clear std_srvs/srv/Empty生成一只新乌龟：ros2 service call /spawn turtlesim/srv/Spawn "{x: 2.0, y: 2.0, theta: 0.0, name: 'turtle2'}"记忆：service call 是调用服务 服务是一次请求一次响应4. 动作动作适合长时间任务，可以有目标、反馈、结果。动作包含三部分：Goal：目标 Feedback：执行过程中的反馈 Result：最终结果动作可以理解为：服务 + 话题 的结合它比服务更适合长任务，因为服务一般是“请求后很快返回”，动作可以边执行边反馈。适合：导航到某个点 机械臂移动到某个位置 机器人旋转到某个角度 执行一个耗时任务适合：长时间任务，比如导航、旋转、机械臂运动常用命令：ros2 action list # 查看动作 ros2 action list -t # 查看动作和类型 ros2 action info /动作名 # 查看动作信息 ros2 interface show 动作类型 # 查看 Goal / Result / Feedback ros2 action send_goal /动作名 类型 "目标" # 发送动作目标例子：ros2 action list -t ros2 action info /turtle1/rotate_absolute ros2 interface show turtlesim/action/RotateAbsolute让乌龟旋转，并显示反馈：ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" --feedback记忆：action = 目标 + 反馈 + 结果 send_goal 发送目标 --feedback 显示过程反馈5. 参数参数特点：参数是节点的配置 不是持续通信常用命令：ros2 param list # 查看参数 ros2 param list /节点名 # 查看某个节点参数 ros2 param get /节点名 参数名 # 获取参数 ros2 param set /节点名 参数名 值 # 修改参数 ros2 param dump /节点名 > a.yaml # 保存参数 ros2 param load /节点名 a.yaml # 加载参数例子：ros2 param list /turtlesim ros2 param get /turtlesim background_r ros2 param set /turtlesim background_r 100刷新背景：ros2 service call /clear std_srvs/srv/Empty记忆：param get 看参数 param set 改参数 param dump 保存 param load 加载小结名称一句话理解常见用途Node 节点正在运行的功能程序摄像头节点、电机节点、控制节点Topic 话题持续单向通信图像、速度、传感器数据Service 服务一次请求，一次响应清空、生成、开关、查询Action 动作长时间任务，有反馈导航、旋转、机械臂运动Parameter 参数节点的配置项颜色、阈值、PID、开关6. 分布式通信ROS2 默认靠 DDS 在局域网里自动发现节点，发现过程依赖 UDP/多播。WSL2 默认 NAT 网络经常导致树莓派发现不了 WSL 里的 ROS2 节点。解决方法：Windows 11 22H2 及以上可以开启 WSL 的 mirrored networking提前改成 mirrored 原因是 ROS2 的 DDS 自动发现比较依赖局域网通信、多播、UDP。微软官方说明，WSL 的 mirrored networking 支持 multicast 多播，也支持从局域网直接连接 WSL，并且改善 NAT 架构下的网络兼容性。具体修改可以去查看ROS2的问题总结# 将笔记本和树莓派连接到同一个网络中 ping 192.168.3.211 # 这里换成你的树莓派的ip地址# 在树莓派的ubuntu中打开终端 vi .bashrc # 在最后一行写上： export ROS_DOMAIN_ID=31 # 设置脚本生效 source .bashrc # 这样就生成了一个编号为31号的网络组，这时候电脑端和树莓派应该是不能通信了（因为电脑端没有设置31号的网络组）# 在电脑端 vi .bashrc # 在最后一行写上： export ROS_DOMAIN_ID=31 # 设置脚本生效 source .bashrc
一. 环境搭建1. 软件安装VisualStudio（用于编译和运行AirSim环境）VS安装主要勾选：.NET桌面开发，C++桌面开发，C++游戏开发，C++的Linux开发，以及CMake虚幻引擎v4.27（AirSIM基于虚幻引擎制作）AirSIM（开源工程，基于虚幻引擎，实现模拟环境和无人机。settings.json是无人机配置文件，类型，传感器，ip地址等）WSL-22.04（运行PX4和QGC的ubuntu环境）PX4-Autopilot（模型飞控的开源软件）QGroundControl（地面站开源软件）2. 编译AirSIM使用Developer PowerShell for VS 2022编译AirSIM# 先进入AirSIM文件目录 #运行： build.cmd编译成功之后会在 /Unreal/Environments/Blocks 目录下生成Block.uproject工程文件3. 安装PX4在WSL的Ubuntu22.04环境下执行命令：sudo apt update sudo apt install -y pip libpulse-mainloop-glib0 zip pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ pip config set global.trusted-host mirrors.aliyun.com echo 'export PATH=/home/hw/.local/bin:$PATH' >> ~/.bashrc echo 'export LIBGL_ALWAYS_SOFTWARE=1' >> ~/.bashrc echo 'export DISPLAY=:0' >> ~/.bashrc克隆PX4仓库git clone --recursive https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot编译启动PX4cd px4v1.15.2 make px4_sitl jmavsim # 轻量级的仿真器 make px4_sitl_default none_iris4. 安装QGC# 安装QGC sudo usermod -a -G dialout $USER sudo apt-get remove modemmanager -y sudo apt install gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-gl -y sudo apt install libfuse2 -y sudo apt install libxcb-xinerama0 libxkbcommon-x11-0 libxcb-cursor-dev -y # 运行QGC # 给文件添加可执行权限 chmod +x ./QGroundControl-x86_64.AppImage # 运行 QGC ./QGroundControl-x86_64.AppImage5. IP绑定AirSIM和PX4是通过TCP通讯的，AirSIM运行在Win11上，PX4运行在Ubuntu上，因此需要双方知道对方的IP地址来通信# 在WSL中输入 hostname -I# 在Win11的CMD输入 ipconfig在C:\Users\Arthur\Documents\AirSim目录下的setting.json文件改成Win11的IP地址或者0.0.0.0之后在WSL的Ubuntu环境下链接这个IP地址# 换成自己AirSIM的IP地址（Win11的IP地址） echo 'export PX4_SIM_HOST_ADDR=172.26.208.1' >> ~/.bashrc source ~/.bashrc sudo ufw allow 4560 sudo ufw allow 100496.效果验证启动AirSIM场景，启动PX4，启动QGC地面站可以实现这个QGC地面站来控制Setting的无人机，就是成功了二、自动避障效果1. json文件添加传感器 "DistanceFrontMid": { "SensorType": 5, "Enabled" : true, "MinDistance": 0.2, "MaxDistance": 40, "X": 0.2, "Y": 0, "Z": -0.5, "Yaw": 0, "Pitch": 0, "Roll": 0, "DrawDebugPoints": true, "ExternalController": true }, "DistanceFrontLeft": { "SensorType": 5, "Enabled" : true, "MinDistance": 0.2, "MaxDistance": 40, "X": 0.2, "Y": 0, "Z": -0.5, "Yaw": -25, "Pitch": 0, "Roll": 0, "DrawDebugPoints": true, "ExternalController": true }, "DistanceFrontRight": { "SensorType": 5, "Enabled" : true, "MinDistance": 0.2, "MaxDistance": 40, "X": 0.2, "Y": 0, "Z": -0.5, "Yaw": 25, "Pitch": 0, "Roll": 0, "DrawDebugPoints": true, "ExternalController": true }, "DistanceFrontUp": { "SensorType": 5, "Enabled" : true, "MinDistance": 0.2, "MaxDistance": 40, "X": 0.2, "Y": 0, "Z": -0.5, "Yaw": 0, "Pitch": 25, "Roll": 0, "DrawDebugPoints": true, "ExternalController": true }, "DistanceFrontDown": { "SensorType": 5, "Enabled" : true, "MinDistance": 0.2, "MaxDistance": 40, "X": 0.2, "Y": 0, "Z": -0.5, "Yaw": 0, "Pitch": -25, "Roll": 0, "DrawDebugPoints": true, "ExternalController": true }2. 避障脚本文件import math import asyncio from mavsdk import System from mavsdk import mission_raw from mavsdk.telemetry import (DistanceSensor, PositionNed) from mavsdk.offboard import (OffboardError, AccelerationNed, PositionNedYaw, PositionGlobalYaw, VelocityBodyYawspeed) from mavsdk.mission import (MissionItem, MissionPlan) glb_leave_home = False # 全局变量 保存是否离开了home，在home附近是不会触发避障逻辑 glb_mission_current = 0 # 全局变量 保存最新进度 glb_mission_total = 0 # 全局变量 保存任务总数 glb_distance_front = 1000.0 # 全局变量 保存前方距离 glb_distance_left = 1000.0 # 全局变量 保存左前方距离，航向左转45都 glb_distance_right = 1000.0 # 全局变量 保存右前方距离，航向右转45度 glb_distance_up = 1000.0 # 全局变量 保存上前方距离，航向上转45度 glb_distance_down = 1000.0 # 全局变量 保存下前方方距离，航向下转45度 glb_distance_min = 1000.0 # 全局变量 保存所有传感器中的最小距离 # 定期打印所有信息 ################################################################ async def periodically_print(): while True: print(f"\n\nProgress: {glb_mission_current}/{glb_mission_total}") print(f"Distances: \n" f"Front: {glb_distance_front}m \n" f"Back : {glb_distance_back}m \n" f"Left : {glb_distance_left}m \n" f"Right: {glb_distance_right}m \n" f"Up : {glb_distance_up}m \n" f"Down : {glb_distance_down}m \n") await asyncio.sleep(1) # 每秒打印一次 # 监控是否离开了home ############################################################## async def check_leave_home(drone: System): global glb_leave_home # 等待获取Home点坐标 async for home in drone.telemetry.home(): home_lat = home.latitude_deg home_lon = home.longitude_deg break while True: # 获取当前位置坐标 async for position in drone.telemetry.position(): current_lat = position.latitude_deg current_lon = position.longitude_deg break #print(f"Home坐标: ({home_lat:.9f}, {home_lon:.9f})") #print(f"当前坐标: ({current_lat:.9f}, {current_lon:.9f})\n\n") # 判断是否在home附近 if (abs(current_lat-home_lat) + abs(current_lon-home_lon)) > 0.000015: #print("离开home") glb_leave_home = True else: #print("在home附近") glb_leave_home = False await asyncio.sleep(0.03) # 监控任务进度 ################################################################# async def monitor_progress(drone: System): global glb_mission_current global glb_mission_total async for mission_progress in drone.mission_raw.mission_progress(): glb_mission_current = mission_progress.current glb_mission_total = mission_progress.total print(f"Update Mission progress: " f"{glb_mission_current}/" f"{glb_mission_total}") # 监控距离传感器 ############################################################# async def monitor_distance(drone: System): global glb_distance_front global glb_distance_left global glb_distance_right global glb_distance_up global glb_distance_down global glb_distance_min # 获取所有距离传感器信息 async for distance_sensor in drone.telemetry.distance_sensor(): # 提取传感器姿态角（单位：度） roll = distance_sensor.orientation.roll_deg pitch = distance_sensor.orientation.pitch_deg yaw = distance_sensor.orientation.yaw_deg current_distance = distance_sensor.current_distance_m # 根据姿态角判断方向，后续优化为初始化时判断一下，判断通过就不再监控 if math.isclose(yaw, 0.0) and math.isclose(pitch, 0.0) and math.isclose(roll, 0.0): # 前 glb_distance_front = current_distance elif math.isclose(yaw, 45.0) and math.isclose(pitch, 0.0) and math.isclose(roll, 0.0): # 右 glb_distance_right = current_distance elif math.isclose(yaw, 135.0) and math.isclose(pitch, 0.0) and math.isclose(roll, 0.0): # 左 glb_distance_left = current_distance elif math.isclose(yaw, 45.0) and math.isclose(pitch, 0.0) and math.isclose(roll, 90.0): # 下 glb_distance_down = current_distance elif math.isclose(yaw, 135.0) and math.isclose(pitch, 0.0) and math.isclose(roll, 90.0): # 上 glb_distance_up = current_distance else: print("未知距离他传感器方位\n") # 获取所有传感器中最小的值 if glb_distance_front < glb_distance_left: tmp_distance_min = glb_distance_front else: tmp_distance_min = glb_distance_left if tmp_distance_min > glb_distance_right: tmp_distance_min = glb_distance_right if tmp_distance_min > glb_distance_up: tmp_distance_min = glb_distance_up if tmp_distance_min > glb_distance_down: tmp_distance_min = glb_distance_down glb_distance_min = tmp_distance_min #print(f"最小距离传感器: {glb_distance_min}") # 简单避障逻辑，遇到障碍上升 ############################################################# AVOIDANCE_TRIGGER_DISTANCE = 7.0 # 避障触发距离阈值 AVOIDANCE_RELEASE_DISTANCE = 15.0 # 避障解除距离阈值 # 方法1：速度避障逻辑，遇到障碍物进行爬升，直到和障碍物的距离大于阈值 ------------------------ async def speed_avoidance(drone: System): # 适当后退远离障碍物，避免抵住障碍物导致无法爬升 print("--加载速度避障逻辑") await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0)) await drone.offboard.start() print("--适当倒退") await drone.offboard.set_velocity_body( VelocityBodyYawspeed( -0.1, # X轴速度（前后方向） 0.0, # Y轴速度（左右方向） 0.0, # Z轴速度（上下方向） 0.0 # Yaw角速度（机体旋转绕Z轴旋转，0表示保持航向不变） ) ) #await drone.offboard.set_acceleration_ned(AccelerationNed(0.000000, 0.000000, -1.0)) await asyncio.sleep(0.5) # 让无人机后退0.5秒，远离障碍物 # 设置爬升加速度 print("--稳定姿态") await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0)) await asyncio.sleep(2) print("--开始爬升") await drone.offboard.set_velocity_body(VelocityBodyYawspeed(0.0, 0.0, -1.7, 0.0)) while True: # 判断前方距离 if glb_distance_min > AVOIDANCE_RELEASE_DISTANCE : break # 如果前方距离大于阈值，则停止持续爬升逻辑 await asyncio.sleep(1) await asyncio.sleep(1.7) # 继续爬升1.7秒，留出足够余量 print("--障碍物已远离，停止爬升!") # 停止Offboard模式，继续执行任务 print("--退出避障逻辑") print("恢复执行任务...") await drone.offboard.stop() await drone.mission.start_mission() # 避障主循环 ------------------------ async def obstacle_avoidance(drone: System): while True: #print(f"chcek{glb_distance_front}") if glb_leave_home and glb_distance_min < AVOIDANCE_TRIGGER_DISTANCE : print(f"近距离发现障碍物{glb_distance_min:.2f}，停止前进!") await drone.mission.pause_mission() #await asyncio.sleep(1) await speed_avoidance(drone) # 调用速度避障逻辑 await asyncio.sleep(0.04) # 每0.1秒检查一次 # 主函数 ###################################################################### async def run(): # 连接到飞行器 drone = System() await drone.connect(system_address="udp://:14540") # 在14540端口上等待PX4的连接 print("等待连接...") async for state in drone.core.connection_state(): if state.is_connected: print("已连接!\n") break # 启动所有任务 task_check_leave_home = asyncio.create_task(check_leave_home(drone)) #task_monitor_progress = asyncio.create_task(monitor_progress(drone)) task_monitor_distance = asyncio.create_task(monitor_distance(drone)) task_obstacle_avoidance = asyncio.create_task(obstacle_avoidance(drone)) #task_periodically_print = asyncio.create_task(periodically_print()) await asyncio.gather(task_check_leave_home, task_monitor_distance, task_obstacle_avoidance) # 运行主函数 if __name__ == "__main__": asyncio.run(run()) 3. 运行效果# 1.运行虚幻引擎Block场景 # 2.进入WSL的Ubuntu环境的PX4根目录 cd px4v1.15.2 make px4_sitl_default none_iris # 3.进入脚本文件存放目录并运行脚本文件 chmod +x avoidance-simple.py ./avoidance-simple.py # 4.启动QGC地面站 ./QGroundControlV4.4.4.AppImage在QGC地面站设定路径之后，遇到障碍物会简单的往上避障三、ROS2三维建图1. 安装ROS2# 更新软件源并安装必要工具 sudo apt update sudo apt install -y locales software-properties-common curl sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8 # 添加 ROS2 官方软件源 sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null # 更新软件源列表 sudo apt update # 将 ROS2 环境自动添加到终端启动配置中 echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc source ~/.bashrc # 安装依赖管理工具和编译工具 sudo apt install -y python3-rosdep python3-colcon-common-extensions # 初始化 rosdep sudo rosdep init rosdep update2. json文件 // 设置无人机摄像头拍摄角度 "Gimbal": { "Stabilization": 0, "Pitch": 0, "Roll": 0, "Yaw": 0 }, "X": 0, "Y": 0, "Z": -1, "Pitch": 0, "Roll": 0, "Yaw": 0,3. 脚本文件 # RVIZ2显示配置 lesson4_rviz_path = os.path.join(pkg_share, 'rviz/lesson4.rviz') lensson4_rviz_node = launch_ros.actions.Node( package='rviz2', executable='rviz2', name='depth_rviz2', arguments=['-d', lesson4_rviz_path] ) # 将深度摄像头的信息转换为点云信息，再将点云信息发送给octomap_server lensson4_depth_image_proc_node = launch_ros.actions.Node( package='depth_image_proc', executable='point_cloud_xyz_node', name='depth_to_cloud', remappings=[ ('image_rect', '/airsim_node/PX4/CameraDepth/DepthPlanar'), ('camera_info', '/airsim_node/PX4/CameraDepth/camera_info'), ('points', '/depth_camera/pointers') ] ) # 用上面depth_image_proc转换出的点云信息生成为地图 lensson4_octomap_server_node = launch_ros.actions.Node( package='octomap_server', # 包名 executable='octomap_server_node', # 可执行文件名 name='octomap_server', # 节点名称 parameters=[ {'frame_id': 'world_ned'}, # 地图坐标系，以世界地图为坐标 {'base_frame_id': 'PX4/CameraDepth_optical'}, #无人机参考坐标 # PX4/CameraDepth_optical, PX4/CameraDepth_body, PX4/CameraDepth_body/static {'resolution': 0.25}, # 地图分辨率，单位：米，当前0.25米的分辨率 {'compress_map': False}, # 是否压缩地图，启用后会减少网络数据传输，但会加大CPU计算量。用于分布式协同系统 {'ground_filter': False}, # 过滤地面相关的配置，使用point_cloud_max_z来代替 {'ground_filter.distance': 0.05}, {'ground_filter.plane_distance': 0.06}, {'sensor_model.max_range': 25.0}, # 传感器最大有效距离，也就是深度摄像头的能力 {'publish_free_space': False}, # 是否发布空的地点值(无阻挡的空间)，避障逻辑会使用 {'use_height_map': True}, # 地图是否用不同的颜色标记高度 {'dynamic': False}, # 不启用动态边界扩展。下面的设置就是边界设置，超出边界的数据都会丢弃 {'point_cloud_max_x': 200.0}, {'point_cloud_min_x': -200.0}, {'point_cloud_max_y': 200.0}, {'point_cloud_min_y': -200.0}, {'point_cloud_max_z': -0.17}, {'point_cloud_min_z': -50.0}, {'occupancy_max_z': -0.17}, {'occupancy_min_z': -50.0}, {'min_x_size': 0.0}, {'min_y_size': 0.0}, {'latch': False} # True for a static map, false if no initial map is given。设置为false能够加快处理速度 ], remappings=[ ('cloud_in', '/depth_camera/pointers') # 输入点云话题重映射 ] )4. 运行效果# 1.运行场景 # 2.运行ROS2 cd hw-ros2/ros2 source install/local_setup.sh ros2 launch hw_insight 3dmax.launch.py # 3.运行PX4 cd px4v1.15.2 make px4_sitl_default none_iris #运行QGC ./QGroundControlV4.4.4.AppImage四、YOLO自动识别追踪1. AirSIM配置文件// 相机和拍摄角度，一种深度相机，一种RGB相机 "Cameras": { "CameraDepth1": { "Enabled": true, "CaptureSettings": [ { "ImageType": 0, "Width": 800, "Height": 600, "FOV_Degrees": 120 }, { "ImageType": 1, "Width": 800, "Height": 600, "FOV_Degrees": 120 } ], "X": 0, "Y": 0, "Z": 0.7, "Pitch": -20, "Roll": 0, "Yaw": 0 } },2. 识别脚本文件# 订阅相机的RGB图片和深度图片。RGB图片用来给YOLO识别物体，深度图片用来根据YOLO识别的结果计算坐标值 self.rgb_sub = message_filters.Subscriber(self, Image, f'/airsim_node/PX4/{self.camera_name}/Scene') self.depth_sub = message_filters.Subscriber(self, Image, f'/airsim_node/PX4/{self.camera_name}/DepthPlanar') # 创建一个近似时间同步器，确保RGB图片和DEPTH图片的同步 # 参数：订阅者列表，队列大小，时间戳容差（秒） self.time_synchronizer = message_filters.ApproximateTimeSynchronizer([self.rgb_sub, self.depth_sub], 2, 0.01) # 读取YOLO模型，CvBridge用来在RGB和ROS的image格式间进行转换 pkg_share = get_package_share_directory('hw_insight') yolo_model_path = os.path.join(pkg_share, 'yolo/'+self.yolo_model) # 可换成其他模型 self.model = YOLO(yolo_model_path) self.bridge = CvBridge() # 用来发布YOLO识别后，带识别方框的RGB图片 self.yolo_pub = self.create_publisher(Image, '/yolo/output', 1) # 用来发布识别到的人的位置信息，基于PX4_odom的坐标(转换坐标系) self.point_pub = self.create_publisher(PointStamped, '/yolo/person_position', 1) # 相机内参, 分辨率 800x600，水平视场角 FOV = 120° HFOV_deg = 120.0 HFOV_rad = math.radians(HFOV_deg) W = 800.0 H = 600.0 # 根据公式 fx = W / (2 * tan(FOV/2)) 计算焦距（像素） fx = W / (2 * math.tan(HFOV_rad / 2)) fy = fx # AirSim像素是方形的 cx = W / 2 cy = H / 2 # 像素坐标到相机坐标系转换 x_cam = (u - cx) * depth / fx y_cam = (v - cy) * depth / fy z_cam = float(depth)3. 追踪脚本文件# 追踪逻辑主要数据 ===================================================================== # 经过测试，跟踪的目标人物的速度为6.0米/秒，速度不会变化。以此前提来制定阶梯变速追踪方案 # 从0到15米，每一米的距离给给出对应的速度，因此是15级阶梯 # STEP_DISTANCE 没有实际作用，只是为了让下面的 STEP_SPEED 看起来更方便 # STEP_SCALE 是计算得到的每一米内部的速度变化率 self.STEP_LEVEL = 15 self.STEP_DISTANCE = [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0] self.STEP_SPEED = [-1.0, -0.5, 0.0, 2.0, 5.0, 6.0, 6.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 18.0, 20.0] self.STEP_SCALE = [0.0] * 16 for i in range(self.STEP_LEVEL): self.STEP_SCALE[i] = (self.STEP_SPEED[i+1] - self.STEP_SPEED[i]) self.STEP_SCALE[self.STEP_LEVEL] = 0.0 # 探测逻辑主要数据 ===================================================================== # 当丢失目标超过一段时间后，会触发探测逻辑，无人机会飞到最后一次目标人物的坐标点后，在一定高度进行原地旋转弹出。 # 初始化处于就处于目标丢失状态，无人机会在当前坐标旋转 self.DETECT_YAW_SPEED = 2.0 # 旋转的速度，单位是弧度 self.DETECT_Z_HEIGHT = -5.0 # 旋转的高度，单位米 self.LOST_THRESHOLD = 200 # 丢失目标的时间阈值 self.target_lost_count = self.LOST_THRESHOLD+1 # 丢失目标的计数，初始处于丢失目标状态4. 运行效果五、VINS-Fusion 视觉-惯性导航系统1. 重点介绍内参%YAML:1.0 --- # 定义模型的类型为MEI（Mixed Excitation Model），用于表示相机的投影模型 model_type: PINHOLE camera_name: camera image_width: 400 image_height: 300 # 畸变参数，用于校正径向和切向畸变 distortion_parameters: k1: 0 k2: 0 p1: 0 p2: 0 # 投影参数，用于将3D点投影到图像平面 projection_parameters: fx: 200 fy: 200 cx: 200 cy: 150 外参%YAML:1.0 # 通用参数 # 支持：1个IMU和1个摄像头；1个IMU和2个摄像头：2个摄像头； imu: 1 # IMU数量 num_of_cam: 1 # 摄像头数量 imu_topic: "/airsim_node/PX4/imu/Imu" # IMU数据主题 image0_topic: "/airsim_node/PX4/CameraImage/Scene" # 第一个摄像头图像数据主题 output_path: "/home/hw/hw-ros2/ros2/src/vins/log/" # 输出路径 cam0_calib: "cam0_mei.yaml" # 第一个摄像头的校准文件 image_width: 400 # 图像宽度 image_height: 300 # 图像高度 # IMU和摄像头之间的外参。 estimate_extrinsic: 0 # 0 拥有准确的外参。我们将信任以下imu^R_cam, imu^T_cam，不要改变它。 # 1 对外参有初步估计。我们将在您的初步估计周围进行优化。 # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. body_T_cam0: !!opencv-matrix # IMU到摄像头的变换矩阵 rows: 4 cols: 4 dt: d data: [0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 1] # 多线程支持 multiple_thread: 1 # 特征跟踪参数 max_cnt: 200 # 特征跟踪中最大特征数量 min_dist: 10 # 两个特征之间的最小距离 freq: 10 # 发布跟踪结果的频率（Hz）。至少10Hz才能获得良好的估计。如果设置为0，频率将与原始图像相同 F_threshold: 1.0 # RANSAC阈值（像素） show_track: 1 # 以主题形式发布跟踪图像 flow_back: 1 # 执行前向和后向光流以提高特征跟踪精度 # 优化参数 max_solver_time: 0.04 # 最大求解迭代时间（ms），以保证实时性 max_num_iterations: 8 # 最大求解迭代次数，以保证实时性 keyframe_parallax: 10.0 # 关键帧选择阈值（像素） # IMU参数 提供的参数越准确，性能越好 # IMU PARAMETERS # 根据AirSim settings.json中的IMU配置计算 # AngularRandomWalk = 0.3 => 角速度噪声密度约为 0.3/sqrt(3600) ≈ 0.005 # VelocityRandomWalk = 0.24 => 加速度噪声密度约为 0.24*9.8/1000 ≈ 0.00235 acc_n: 0.1 #0.024 # accelerometer measurement noise standard deviation gyr_n: 0.1 #0.05 # gyroscope measurement noise standard deviation # 随机游走参数需要根据其他参数计算 # GyroBiasStability = 4.6 deg/hr = 4.6/3600 deg/s = 0.00128 deg/s # AccelBiasStability = 36 ug = 36*1e-6*9.8 m/s^2 = 0.000353 m/s^2 acc_w: 0.01 #0.0035 # accelerometer bias random walk noise standard deviation gyr_w: 0.001 #0.00022 # gyroscope bias random walk noise standard deviation g_norm: 9.81007 # 重力大小 # acc_n: 6.6848772813371373e-02 # 加速度计测量噪声的标准差，用于模拟加速度计读数的误差，（m/s²） # gyr_n: 8.7510201471946162e-04 # 陀螺仪测量噪声的标准差，用于模拟陀螺仪读数的误差，（rad/s） # acc_w: 3.4118090066874175e-03 # 加速度计偏差随机游走噪声的标准差，用于模拟加速度计长期使用的偏差变化，（ug，即微重力加速度，1ug = 1 × 10⁻⁶ g） # gyr_w: 4.0083425496425443e-05 # 陀螺仪偏差随机游走噪声的标准差，用于模拟陀螺仪长期使用的偏差变化，（deg/h） # g_norm: 9.81007 # 重力大小 # 非同步参数 estimate_td: 0 # 在线估计摄像头和IMU之间的时间偏移 td: 0.0 # 时间偏移的初始值。单位：秒。读取的图像时钟 + td = 实际图像时钟（IMU时钟） # 回环闭合参数 load_previous_pose_graph: 1 # 加载和重用以前的姿态图；从'pose_graph_save_path'加载 pose_graph_save_path: "/home/hw/hw-ros2/ros2/src/vins/log/pose_graph/" # 保存和加载路径 save_image: 1 # 保存图像到姿态图以供可视化使用；可以通过设置为0来关闭此功能 2. 启动脚本import os import launch import launch_ros.actions from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node from launch.launch_description_sources import PythonLaunchDescriptionSource from ament_index_python.packages import get_package_share_directory from launch.actions import ExecuteProcess def generate_launch_description(): pkg_share = get_package_share_directory('hw_insight') # RVIZ2显示配置 lesson7_rviz_path = os.path.join(pkg_share, 'rviz/lesson7.rviz') lesson7_rviz_node = launch_ros.actions.Node( package='rviz2', executable='rviz2', name='rviz2', arguments=['-d', lesson7_rviz_path] ) # 启动AirSim节点 airsim_node_launch = IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(get_package_share_directory('airsim_ros_pkgs'), 'launch/airsim_node.launch.py') ) ) # AIRSIM的GPS一直是100Hz的频率，太高了不真实，降频到1Hz~10Hz之间 gps_throttle_node = launch_ros.actions.Node( package='topic_tools', executable='throttle', name='gps_throttler', output='screen', arguments=[ 'messages', '/airsim_node/PX4/gps/Gps', # 输入话题 '5.0', # 目标频率 1.0 ~ 10.0 Hz '/airsim_node/PX4/gps/gps_throttled' # 输出话题 ] ) # map to world static_tf_map_to_world = Node( package='tf2_ros', executable='static_transform_publisher', name='static_tf_map_to_world', arguments=[ '0', '0', '0', # x y z 平移量（米） '0', '0', '0', # roll pitch yaw 旋转量（弧度） 'world', # 父坐标系 'map' # 子坐标系 ], output='screen' ) # 启动 vins_estimator 原始数据（绿色轨迹） config_path = "/home/hw/hw-ros2/ros2/src/vins/config/AIRSIM/euroc_mono_imu_config.yaml" vins_estimator_node = launch_ros.actions.Node( package='vins', executable='vins_node', name='vins_estimator', namespace='vins_estimator', output='screen', parameters=[{'config_file': config_path}], arguments=[config_path] ) # 启动 loop_fusion 回环检测（红色轨迹） loop_fusion_node = launch_ros.actions.Node( package='loop_fusion', executable='loop_fusion_node', name='loop_fusion', namespace='loop_fusion', output='screen', parameters=[{'config_file': config_path}], arguments=[config_path] ) # 启动 global_fusion GPS融合（白色轨迹） global_fusion_node = launch_ros.actions.Node( package='global_fusion', executable='global_fusion_node', name='global_fusion', namespace='global_fusion', output='screen', parameters=[ {'gps_type': 0} # 0: GPS数据是经纬度(NavSatFix), 1: 已经是XY坐标 ], remappings=[ ('/gps', '/airsim_node/PX4/gps/gps_throttled') ] ) # Create the launch description and populate ld = LaunchDescription() # Create the launch description and populate ld.add_action(airsim_node_launch) ld.add_action(gps_throttle_node) ld.add_action(static_tf_map_to_world) ld.add_action(lesson7_rviz_node) ld.add_action(vins_estimator_node) ld.add_action(loop_fusion_node) ld.add_action(global_fusion_node) return ld 3. 效果
WSL2中的Echo机器人环境安装1.WSL2内Ubuntu安装首先命令窗口cmd(PowerShell)内输入wsl --list --online这样可以看到微软官方支持的所有 Linux 发行版列表 像这样NAME FRIENDLY NAME Ubuntu Ubuntu Ubuntu-24.04 Ubuntu 24.04 LTS openSUSE-Tumbleweed openSUSE Tumbleweed openSUSE-Leap-16.0 openSUSE Leap 16.0 SUSE-Linux-Enterprise-15-SP7 SUSE Linux Enterprise 15 SP7 SUSE-Linux-Enterprise-16.0 SUSE Linux Enterprise 16.0 kali-linux Kali Linux Rolling Debian Debian GNU/Linux AlmaLinux-8 AlmaLinux OS 8 AlmaLinux-9 AlmaLinux OS 9 AlmaLinux-Kitten-10 AlmaLinux OS Kitten 10 AlmaLinux-10 AlmaLinux OS 10 archlinux Arch Linux FedoraLinux-43 Fedora Linux 43 FedoraLinux-42 Fedora Linux 42 eLxr eLxr 12.12.0.0 GNU/Linux Ubuntu-20.04 Ubuntu 20.04 LTS Ubuntu-22.04 Ubuntu 22.04 LTS OracleLinux_7_9 Oracle Linux 7.9 OracleLinux_8_10 Oracle Linux 8.10 OracleLinux_9_5 Oracle Linux 9.5 openSUSE-Leap-15.6 openSUSE Leap 15.6 SUSE-Linux-Enterprise-15-SP6 SUSE Linux Enterprise 15 SP6之后选择版本命令进行安装wsl --install -d <换成你想安装的版本名称>示例：wsl --install -d Ubuntu-22.04# 更新软件包列表 sudo apt-get update # 升级已安装的软件包（可选，但推荐） sudo apt-get upgrade -y1.安装依赖环境sudo apt-get install repo git ssh make gcc gcc-multilib g++-multilib module-assistant expect g++ gawk texinfo libssl-dev bison flex fakeroot cmake unzip gperf autoconf device-tree-compiler libncurses5-dev pkg-config2.下拉完整项目(SDK和软件demo)git clone https://github.com/No-Chicken/Echo-Mate.git cd Echo-Mate git submodule update --init --recursive3.如果要递归更新所有子模块，请执行：git submodule update --remote --merge --recursive4.由于子仓库有大文件LFS，需要执行：# 下拉lfs git lfs pull # 下拉所有lfs git submodule foreach --recursive 'git lfs pull'2.获取SDK#进入到SDK的目录 cd Echo-Mate cd ./SDK/rv1106-sdk 首先需要在SDK文件夹选择板级配置，这里选择对应的开发板，选择echo mate的配置即可。如果使用[7]custom，会弹出所有的.mk文件./build.sh lunch一键自动编译(会自动下载交叉编译的工具链及相关配置)./build.sh 如果PATH环境变量的目录出错，则换一个临时的PATH目录清理缓存./build.sh clean # 清理编译产物(如果失败则先清理一下缓存)3. 使用开发板demo的修改打开DeskBot_demo目录cd Echo-Mate/Demo/DeskBot_demo① 把conf文件夹中的dev_conf.h中的内容更换#define LV_USE_SIMULATOR 0 (把这个宏定义的1改成0)② 把里面的toolchain.cmake的SDK_PATH目录换一下set(SDK_PATH "/home/arthur/Echo-Mate/SDK/rv1106-sdk") #换成自己的目录# 把这个sysroot也换成自己的目录 # Specify the sysroot (if available) set(CMAKE_SYSROOT ${SDK_PATH}/sysdrv/source/buildroot/buildroot-2023.02.6/output/host/arm-buildroot-linux-uclibcgnueabihf/sysroot)③ 把CMakeLists.txt# Option to choose target architecture option(TARGET_ARM "Build for ARM architecture" ON) // OFF改成ON4. 烧录进开发板之后安装教程进入cd ./Demo cd ./DeskBot_demo mkdir ./build cd ./build cmake .. make make -j4 rm -rf * # 清理缓存把编译好的bin文件使用ADB传输进开发板在Windows的CMD/PowerShell中操作：adb push F:\bin /root/ # 推送整个 bin 文件夹到 /root 目录 adb push C:\Users\Arthur\Downloads\bin /root/ # 前面windows的目录换成自己的，这里是把WSL内编译好的放到了windows中adb push C:\Users\Arthur\Downloads\bin /root/ adb shell chmod +x /root/bin/main reboot# 文件传进开发板 scp -r ./bin root@192.168.0.6:/root/ scp -r ./bin root@172.32.0.93:/root/# 开发板传出文件，注意是传到当前目录下了 scp -r root@192.168.0.6:/root/test ./ scp -r root@172.32.0.93:/root/test ./ # 进入开发板 adb shell ssh root@192.168.0.6 ssh root@172.32.0.93运行adb shell # 进入开发板系统cd ../bin chmod +x main #加入可执行操作 ./main # 进入bin文件后运行这个main可执行程序5. 开机自启动解决需要执行指令才能运行的问题修改启动脚本# 编辑脚本 vi /etc/init.d/S99main添加上这些：#!/bin/sh cd /root/bin && ./main &/etc/init.d/S99main # 重启脚本 chmod +x /etc/init.d/S99main6.开机联网# 1. 配置 WiFi 信息 cat > /etc/wpa_supplicant.conf << EOF ctrl_interface=/var/run/wpa_supplicant ap_scan=1 network={ ssid="your name" psk="your password" } EOF # 2. 恢复网络接口配置（保持干净） cat > /etc/network/interfaces << EOF # interface file auto-generated by buildroot auto lo iface lo inet loopback EOF # 3. 创建 WiFi 自启动脚本 cat > /etc/init.d/S95wifi << 'EOF' #!/bin/sh ( sleep 10 ifconfig wlan0 up wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf sleep 5 udhcpc -i wlan0 -q > /dev/null 2>&1 ) & EOF chmod +x /etc/init.d/S95wifi # 4. 确保 main 自启动脚本正确 cat > /etc/init.d/S99main << 'EOF' #!/bin/sh cd /root/bin && ./main & EOF chmod +x /etc/init.d/S99main # 5. 重启测试 reboot7. 解决双进程问题加入自启动之后，这看不了这个调试信息，再次手动启动main可执行文件会导致双进程切换卡顿的问题在 main.c 开头添加了 自动杀旧进程 的机制：// 1. 查找所有 main 进程 FILE *fp = popen("pidof main", "r"); // 2. 如果找到其他 main 进程 if (找到其他进程) { kill(其他进程PID, SIGTERM); // 杀掉它 usleep(100000); // 等它彻底退出 } // 3. 设置调试输出 if (isatty(STDOUT_FILENO)) { // 手动运行 → 输出调试信息 } else { // 自启动 → 静默运行 freopen("/dev/null", "w", stdout); }//示例： static void kill_other_main_processes(void) { // 获取当前进程PID pid_t my_pid = getpid(); // 执行命令查找所有 main 进程 FILE *fp = popen("pidof main", "r"); if (!fp) { return; // 无法执行命令，直接返回 } char line[128]; if (fgets(line, sizeof(line), fp)) { // 解析出所有PID（可能多个，用空格分隔） char *token = strtok(line, " \n"); while (token) { pid_t other_pid = atoi(token); // 如果是其他进程（不是自己），杀掉它 if (other_pid > 0 && other_pid != my_pid) { // 手动运行时才显示调试信息 if (isatty(STDOUT_FILENO)) { printf("[Main] Killing old process: PID=%d\n", other_pid); } kill(other_pid, SIGTERM); // 发送终止信号 usleep(50000); // 等50ms } token = strtok(NULL, " \n"); } } pclose(fp); // 再等一会儿确保旧进程完全退出 usleep(100000); // 100ms } //在main函数中： // ===== 第一步：杀死其他 main 进程 ===== // 这个函数会杀掉已经存在的 main 进程（包括开机自启动的那个） kill_other_main_processes(); // ===== 第二步：设置调试输出 ===== if (isatty(STDOUT_FILENO)) { // 有终端 = 手动运行 = 调试模式 printf("\n================================\n"); printf("System Monitor (Manual Debug Mode)\n"); printf("================================\n"); // 保持正常输出 } else { // 无终端 = 后台自启动 = 静默模式 // 重定向所有输出到 /dev/null freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); }
WSL相关安装1.WSL2内Ubuntu安装首先命令窗口cmd(PowerShell)内输入wsl --list --online这样可以看到微软官方支持的所有 Linux 发行版列表 像这样NAME FRIENDLY NAME Ubuntu Ubuntu Ubuntu-24.04 Ubuntu 24.04 LTS openSUSE-Tumbleweed openSUSE Tumbleweed openSUSE-Leap-16.0 openSUSE Leap 16.0 SUSE-Linux-Enterprise-15-SP7 SUSE Linux Enterprise 15 SP7 SUSE-Linux-Enterprise-16.0 SUSE Linux Enterprise 16.0 kali-linux Kali Linux Rolling Debian Debian GNU/Linux AlmaLinux-8 AlmaLinux OS 8 AlmaLinux-9 AlmaLinux OS 9 AlmaLinux-Kitten-10 AlmaLinux OS Kitten 10 AlmaLinux-10 AlmaLinux OS 10 archlinux Arch Linux FedoraLinux-43 Fedora Linux 43 FedoraLinux-42 Fedora Linux 42 eLxr eLxr 12.12.0.0 GNU/Linux Ubuntu-20.04 Ubuntu 20.04 LTS Ubuntu-22.04 Ubuntu 22.04 LTS OracleLinux_7_9 Oracle Linux 7.9 OracleLinux_8_10 Oracle Linux 8.10 OracleLinux_9_5 Oracle Linux 9.5 openSUSE-Leap-15.6 openSUSE Leap 15.6 SUSE-Linux-Enterprise-15-SP6 SUSE Linux Enterprise 15 SP6之后选择版本命令进行安装wsl --install -d <你想安装的版本名称>示例：wsl --install -d Ubuntu-24.042.WSL2内安装Linux环境下的ESP32环境# 更新软件包列表 sudo apt-get update # 升级已安装的软件包（可选，但推荐） sudo apt-get upgrade -y # 安装必要的依赖包 sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 -y 创建工作目录并下载ESP-IDFmkdir -p ~/esp cd ~/esp #这里下载的是v5.4.1版本 git clone -b v5.4.1 --recursive https://github.com/espressif/esp-idf.git #这个过程可能耗时5-10分钟安装ESP32-S3工具链cd ~/esp/esp-idf ./install.sh esp32s3 #这个过程可能耗时5-10分钟设置环境变量echo "alias get_idf='. \$HOME/esp/esp-idf/export.sh'" >> ~/.bashrc source ~/.bashrc设置完环境变量之后，就可以直接用这个名字激活ESP-IDF环境# 打开终端，激活 ESP-IDF 环境 get_idf测试是否环境安装成功# 测试编译一下 cd ~/esp/workspace cp -r ~/esp/esp-idf/examples/get-started/hello_world . cd hello_world idf.py set-target esp32s3 idf.py buildWSL2 无法直接访问 Windows 主机的 USB 设备所以要需要配置 USB 转发才能让 WSL2 识别你的 ESP32-S3 开发板在 Windows 端安装 usbipd（以管理员身份打开 PowerShell）# 安装 usbipd-win winget install --interactive --exact dorssel.usbipd-win #或者找github安装下载之后在管理员模式下的命令串口输入usbipd list # 查看 BUSID会看到 C:\Windows\System32>usbipd list Connected: BUSID VID:PID DEVICE STATE 2-3 1a86:55d3 USB-Enhanced-SERIAL CH343 (COM10) Not shared 2-7 04f2:b7b8 Integrated Camera Not shared 2-9 048d:c982 USB 输入设备 Not shared 2-10 8087:0033 英特尔(R) 无线 Bluetooth(R) Not shared Persisted: GUID DEVICE找到你的esp32开发板接口这里是2-3在 Windows PowerShell（管理员）中执行：# 1. 绑定设备（共享） usbipd bind --busid 2-3 # 2. 附加到 WSL（使用 --auto-attach 防止烧录时断开） usbipd attach --wsl --busid 2-3 --auto-attach在 WSL2 Ubuntu 中执行：如果失败的话，可以用ch341 驱动 兼容 CH343 芯片# 1. 安装驱动（你的 ESP32-S3 用的是 CH343 芯片） sudo apt update sudo apt install linux-modules-extra-$(uname -r) # 2. 加载 CH343 驱动 sudo modprobe ch341 # 3. 设置永久加载驱动（可选） echo "ch341" | sudo tee -a /etc/modules # 4. 检查设备是否识别 lsusb # 应该看到：ID 1a86:55d3 QinHeng Electronics # 5. 查看串口设备 ls /dev/ttyUSB* # 应该显示：/dev/ttyUSB0 # 6. 给串口设备添加权限 sudo chmod 666 /dev/ttyUSB0 # 7. 创建 udev 规则（永久生效） echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d3", MODE="0666"' | sudo tee /etc/udev/rules.d/99-ch343.rules sudo udevadm control --reload-rules3.修缮完成之后指令① 在 PowerShell（管理员）中# 1. 打开管理员 PowerShell # 按 Win + X → 选择"Windows PowerShell(管理员)" 或 "终端(管理员)" # 2. 连接 USB 设备到 WSL2 usbipd attach --wsl --busid 2-3 --auto-attach #记得同时打开WSL # 看到成功提示： # usbipd: info: Device 2-3 is now attached # 这个窗口保持打开，不要关！ # 查看设备状态 usbipd list② 第二步：在 WSL2 终端中# 1. 检查设备是否连接成功 ls -l /dev/ttyACM0 # 应该显示：crw-rw-rw- 1 root dialout ... /dev/ttyACM0 # 2. 激活 ESP-IDF 环境 get_idf # 看到提示：Done! You can now compile ESP-IDF projects. # 3. 进入你的项目目录 cd ~/esp/esp-idf/hello_world # 4. 编译项目（如果代码有修改） idf.py build # 5. 烧录到开发板 idf.py -p /dev/ttyACM0 flash # 6. 查看运行结果 idf.py -p /dev/ttyACM0 monitor # 7. 退出监视器（按 Ctrl + ]）③ 一键完成（最常用）# 1. 先进入你的项目目录 cd ~/esp/esp-idf/hello_world # 编译 + 烧录 + 监视 一步到位 idf.py -p /dev/ttyACM0 flash monitor工作流程示例# 1. 插上开发板 # 2. 打开管理员 PowerShell，输入： usbipd attach --wsl --busid 2-3 --auto-attach # 3. 回到 WSL2 终端 get_idf cd ~/esp/esp-idf/hello_world idf.py -p /dev/ttyACM0 flash monitor
Docker 学习这个是基于WSL中使用命令行下载的Docker，作用是管理WSL下特定版本的Ubuntu系统下载docker# 1. 更新系统包索引 sudo apt update # 2. 安装必要的依赖 sudo apt install -y ca-certificates curl # 3. 创建密钥目录并添加 Docker 官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # 4. 添加 Docker 官方仓库 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 5. 再次更新包索引（这次会从 Docker 仓库获取信息） sudo apt update # 6. 安装 Docker 及相关组件 sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # 7. 启动 Docker 服务 sudo service docker start # 8. 验证安装 sudo docker run hello-worldDocker中有三种身份宿主机身份（你Windows/WSL里的用户），默认身份（root），容器内的普通用户（可以自己创建）把你加入 docker 组，这样就不需要使用sudo就能执行docker指令了sudo usermod -aG docker $USER # 加入docker组就可以使用docker命令使组权限生效# 退出当前 WSL 会话 exit wsl --terminate Ubuntu-24.04-learn wsl -d Ubuntu-24.04-learn使用dockerdocker images # 查看docker有哪些镜像 docker ps -a # 查看docker的容器（包括已停止的） docker ps #查看docker正在运行的容器docker rmi <镜像名字或者id> # 把服务器上的指定的镜像删除 docker rm <容器的名字或者id> # 把服务器上的镜像创建的容器删除 docker rm -f <容器的名字或者id> # 强制删除容器docker pull nginx # 从 Docker Hub 下载 Nginx 镜像到本地 docker run nginx # 使用镜像创建一个容器，包含了docker pull 命令，如果发现本地不存在则会先拉取 docker run -d nginx # 表示容器在后台执行，不阻塞当前窗口，会打印出容器的id docker run -p 80:80 nginx # 端口映射 docker run -d --name <自定义名字> # docker 容器自定义名字 docker run -d -p 80:80 -v /website/html:/usr/share/nginx/html nginx # -v 代表吧宿主机的目录和容器内的目录做绑定 docker volume create nginx_html docker run -d -p 80:80 -v nginx_html:/usr/share/nginx/html nginx docker volume list # 列出所有创建过的卷 docker volume rm nginx_html # 删除一个卷 docker volume prune -a # 删除所有没有任何容器使用的卷
CmakeCMake 是一个构建系统生成器，它把你的构建意图（CMakeLists.txt）转换成具体的编译命令，让你可以用任何编辑器、在任何平台、用任何编译器构建项目，同时自动处理头文件路径、库依赖等复杂问题安装wsl --install -d Ubuntu-24.04 # 先安装ubuntu版本 sudo apt-get update sudo apt-get upgrade -y sudo apt install build-essential cmake -y # 安装cmake工具简易指令cmake_minimum_required(VERSION 3.15) # VERSION 指定Cmake的版本号 project(test) # 该项目的名称，可以自定义 add_executable(app add.cpp div.cpp mult.cpp main.cpp sub.cpp) # 生成的可执行文件+要用到的文件# 之后使用cmake + CMakeLists所在路径 进行编译 cmkae . # 表示当前目录 cmake .. # 表示CMakeLists在上一级目录 make # 生成可执行文件 #同样可以先创建build文件夹，之后使用cmake .. 这样生成的编译文件都在build中 mkdir build # 在项目目录下创建build文件夹 cmake .. # 在build中生成编译文件 make cmake .. 应该叫： ✅ 配置（Configure） ✅ 生成构建系统（Generate Build System） make 应该叫： ✅ 构建（Build） ✅ 编译链接（Compile & Link） ✅ 生成可执行文件（Generate Executable） ✅ 构建项目（Build Project）引入变量cmake_minimum_required(VERSION 3.15) project(test) set(SRC add.cpp div.cpp mult.cpp main.cpp sub.cpp) # 设置变量名字为SRC,SRC所指的是后面的文件 set(EXECUTABLE_OUTPUT_PATH /home/arthur/Cmake_Learn/Test1/aa/bb/cc) # 指定生成可执行文件的目录 set(CMAKE_CXX_STANDARD 11) # 设定C++ 标准为11 add_executable(app ${SRC})搜索文件cmake_minimum_required(VERSION 3.15) project(test) # 自动收集指定目录下的所有源文件 放到SRC这个变量，不会进入子目录（可能也会搜到测试文件进行编译） # 这个宏PROJECT_SOURCE_DIR目录是执行cmake 后面对应的目录 aux_source_directory(${PROJECT_SOURCE_DIR} SRC) set(EXECUTABLE_OUTPUT_PATH /home/arthur/Cmake_Learn/Test1/aa/bb/cc) set(CMAKE_CXX_STANDARD 11) add_executable(app ${SRC})cmake_minimum_required(VERSION 3.15) project(test) # GLOB就是不递归搜索（不进入子目录） # CMAKE_CURRENT_SOURCE_DIR 宏是CMakeLists.txt所在的目录和PROJECT_SOURCE_DIR一样 # /*.cpp 指的是在该目录下找.cpp文件 file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) set(EXECUTABLE_OUTPUT_PATH /home/arthur/Cmake_Learn/Test1/aa/bb/cc) set(CMAKE_CXX_STANDARD 11) add_executable(app ${SRC})指定头文件路径cmake_minimum_required(VERSION 3.15) project(test) # 指定cpp文件所在位置 file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) # 指定头文件(.h)文件所在位置 include_directories(${PROJECT_SOURCE_DIR}/inc) set(EXECUTABLE_OUTPUT_PATH /home/arthur/Cmake_Learn/Test1/bin) set(CMAKE_CXX_STANDARD 11) add_executable(app ${SRC})通过Cmake制作库文件Linux中静态库名字分为三部分：lib+库名字+.a动态库名字分为三部分：lib+库名字+.so静态库cmake_minimum_required(VERSION 3.15) project(test) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) include_directories(${PROJECT_SOURCE_DIR}/inc) set(LIBRARY_OUTPUT_PATH /home/arthur/Cmake_Learn/Test2/lib) # 指定生成库文件的位置 add_library(calc STATIC ${SRC}) # STATIC 生成静态库动态库cmake_minimum_required(VERSION 3.15) project(test) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) # 指定源文件 include_directories(${PROJECT_SOURCE_DIR}/inc) # 指定头文件 set(LIBRARY_OUTPUT_PATH /home/arthur/Cmake_Learn/Test2/lib) add_library(calc SHARED ${SRC}) # SHARED 生成动态库程序中链接静态库cmake_minimum_required(VERSION 3.15) project(test) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include_directories(${PROJECT_SOURCE_DIR}/inc) link_libraries(calc) # 链接到对应的库文件 link_directories(${PROJECT_SOURCE_DIR}/lib1) # 链接到对应库文件的目录 # set(CMAKE_CXX_STANDARD 11) add_executable(app ${SRC}) 程序中链接动态库cmake_minimum_required(VERSION 3.15) project(test) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include_directories(${PROJECT_SOURCE_DIR}/inc) link_directories((${PROJECT_SOURCE_DIR}/lib2) # 指定动态库目录 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # set(CMAKE_CXX_STANDARD 11) add_executable(app ${SRC}) target_link_libraries(app calc) # 把calc这个动态库以默认的Public模式链接到app上 在cmake 中打印日志信息(无) ：重要消息STATUS ：非重要消息WARNING ：CMake 警告, 会继续执行AUTHOR_WARNING ：CMake 警告 (dev), 会继续执行SEND_ERROR ：CMake 错误, 继续执行，但是会跳过生成的步骤FATAL_ERROR ：CMake 错误, 终止所有处理过程# 输出一般日志信息 message(STATUS "source path: ${PROJECT_SOURCE_DIR}") # 输出警告信息 message(WARNING "source path: ${PROJECT_SOURCE_DIR}") # 输出错误信息 message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}") 变量操作# 字符串的拼接 set(tmp hello world) set(tmp1 ${tmp} ${SRC}) message(${tmp}) message(${tmp1}) list(APPEND tmp "xx1" "sss2" ${SRC}) message(${tmp}) # 字符串的删除 file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) message("============") message(${SRC}) list(REMOVE_ITEM SRC ${PROJECT_SOURCE_DIR}/src/main.cpp) message("============") message(${SRC}) message("============")