跳到主要内容

二次开发

二次开发先从独立项目目录、dry-run、日志和停止方式开始,不要从修改系统核心文件开始。

Notebook 验证想法

脚本封装功能

Web / API 接入交互

日志记录结果

需要时再部署自启动

涉及底盘、云台、串口、ROS 2 或开机自启动的修改,都应先保留停止方式和恢复方式。

注意

不建议第一次二次开发就修改 app.pybase_ctrl.pycv_ctrl.py、ROS 2 驱动或下位机固件。先复制示例到自己的项目目录,用 dry-run、日志和低风险硬件验证思路。

二次开发原则

  1. 先确认当前功能运行在浏览器、JupyterLab、Jetson 主机、ROS 2 Docker 还是 ESP32 下位机。
  2. 先复制示例,不在原始文件中长期堆积实验代码。
  3. 运动类逻辑默认 dry-run,先输出计划,不发送真实运动指令。
  4. 接入真实控制前,先完成限幅、超时、停止方式和日志记录。
  5. 修改自启动前,先确认脚本可手动运行、可停止、可禁用。
  6. 修改前备份文件,记录命令、参数、日志和回退方法。

开发能力地图

开发对象所在环境常用入口适合改什么不建议改什么
Web 控制端Jetson 主机ugv_jetson/app.pytemplates/页面、按钮、状态显示、独立 API第一次开发就替换主控制流程
Notebook 示例Jetson 主机 / JupyterLabtutorial_cn/视觉识别、参数实验、图像显示长时间无人值守或连续控制底盘
下位机 JSONJetson → ESP32Web 命令框、Python 串口调用LED、OLED、云台、低风险动作未限幅的底盘运动
ROS 2 节点ROS 2 Dockerugv_wstopic 订阅、状态监控、导航扩展直接修改驱动核心
自启动任务Jetson 主机systemdcrontab数据记录、状态监控、独立 API开机后自动驱动底盘
前端二次开发浏览器 / Flasktemplates/、独立端口状态面板、参数表单、低风险按钮删除原页面安全逻辑

开发环境分层

场景推荐环境说明
访问 Web 页面、查看视频流Windows / 浏览器不在这里运行 ROS 2 命令
编写 Notebook、视觉脚本、普通 Python 任务Jetson 主机 / JupyterLab适合上位机视觉和数据记录
检查 /dev/ttyTHS* 串口占用Jetson 主机不建议在 ROS 2 Docker 中做主机串口释放判断
运行 ros2 topicros2 launch、Nav2、SLAMROS 2 Docker 容器每个终端先加载 ROS 2 环境
修改或烧录 ESP32 固件下位机开发环境不属于普通上位机脚本修改

先确认自己当前在哪个环境,再执行命令。不要把 ROS 2 Docker 命令、Jetson 主机命令和 Windows 命令混用。

产品初始化与系统恢复

产品初始化用于恢复出厂系统、系统更新、替换 NVMe 固态硬盘,或自备 Jetson Orin Nano 核心板后重新配置环境。普通二次开发不需要先执行初始化。

注意

系统烧录、镜像恢复和软件重装可能覆盖现有文件。操作前先备份项目代码、地图、Notebook、日志和自定义服务配置。

初始化分为两类:

场景推荐处理方式说明
恢复到官方预配置环境使用 资料下载 中的系统镜像适合恢复整机软件环境,通常需要拆下 NVMe 并使用硬盘烧录器
自备 Jetson Orin Nano 核心板先通过 NVIDIA SDK Manager 安装系统,再安装上位机和 ROS 2 程序适合开发者自备核心板或需要重新配置系统的情况
只修改了少量脚本或页面优先使用备份文件或 Git 回退不建议直接重装系统

初始化前备份

开始初始化前,至少备份以下内容:

  • ~/ugv_jetson 中修改过的 Web、Notebook、脚本和配置;
  • /home/ws/ugv_ws 中修改过的 ROS 2 包、地图和脚本;
  • map.pgmmap.yaml~/.ros/rtabmap.db 等地图文件;
  • ~/ugv_projects/ 中的自定义项目、日志和数据;
  • 自定义 systemdcrontab、网络配置和账号信息。

核心板系统安装概览

给 Jetson Orin Nano 安装系统时,需要一台 Ubuntu 22.04 主机或虚拟机、NVIDIA 账号、黄色跳线帽、USB 数据线和稳定供电。SDK Manager 下载入口见 资料下载

  1. 关闭 UGV 电源,用跳线帽或杜邦线短接 FC RECGND,让核心板进入 Recovery Mode。

    Jetson Orin Nano Recovery Mode 跳线位置

  2. 打开 UGV 电源,用 USB 数据线连接 Jetson 主板的 Type-C 数据口和 Ubuntu 22.04 主机。

  3. 在 Ubuntu 主机中运行 lsusb,确认能识别到 NVIDIA 设备。

  4. 安装并打开 NVIDIA SDK Manager,登录 NVIDIA 账号。

    SDK Manager 登录界面

    SDK Manager 登录完成

  5. 如果 SDK Manager 没有自动识别 Jetson 设备,先确认 Recovery Mode 和 USB 连接,再点击刷新。

    SDK Manager 刷新设备

  6. 选择 Jetson Orin Nano 对应设备和 JetPack 版本。开发套件场景选择 Developer Kit;Host Machine 组件按需取消勾选。

    SDK Manager 设备和 JetPack 配置

  7. 在组件选择页面保留 Jetson Linux,并按实际需要选择 Runtime / SDK 组件。确认协议后继续。

    SDK Manager 组件和许可配置

  8. 首次使用 SDK Manager 烧录时,按提示创建安装配置并继续。

    SDK Manager 创建安装配置

  9. 在预配置页面选择开发板类型,OEM Configuration 可选择 Pre-Config,并填写系统用户名和密码。

    SDK Manager 预配置用户名和密码

  10. 等待系统烧录完成。完成后再进行上位机软件和 ROS 2 工作空间恢复。

    SDK Manager 系统安装完成

上位机软件恢复概览

上位机软件恢复有两种方式:

  1. 使用 资料下载 中的系统镜像,将已配置好的系统写入 NVMe 固态硬盘。该方式会覆盖硬盘原有数据,写入前先备份。
  2. 在已安装 Ubuntu 的 Jetson 系统中手动配置 ugv_jetsonugv_ws、依赖、服务和工作目录。该方式适合需要保留自定义系统环境的开发场景,操作前先记录当前镜像版本、Python 依赖、ROS 2 环境和服务配置。

恢复完成后,按以下顺序检查:

  1. Web 页面和 JupyterLab 是否可访问;
  2. 摄像头、云台、LED、OLED 是否能执行低风险动作;
  3. 底盘串口是否正常,停止方式是否可用;
  4. ROS 2 Docker、ugv_ws、雷达、TF、SLAM 和 Navigation 是否按 ROS 2 教程 验证。

开发前检查清单

  • 当前功能运行在 Jetson 主机、ROS 2 Docker、浏览器还是 ESP32 下位机?
  • 是否会占用底盘串口?
  • 是否会占用摄像头?
  • 是否会发布 /cmd_vel 或 JSON 运动指令?
  • 是否已经有 dry-run?
  • 是否已经有限幅、超时和停止逻辑?
  • 是否有日志路径?
  • 是否知道如何停止当前程序?
  • 是否知道如何恢复原 Web / ROS 2 / 自启动配置?
  • 是否已经备份要修改的文件?

源码结构导览

常见结构如下,以当前设备中的实际目录为准。

ugv_jetson/
├─ app.py
├─ base_ctrl.py
├─ cv_ctrl.py
├─ audio_ctrl.py
├─ templates/
├─ static/
├─ tutorial_cn/
├─ sounds/
└─ config.yaml

ugv_ws/
├─ src/ugv_main/ugv_bringup
├─ src/ugv_main/ugv_tools
├─ src/ugv_main/ugv_nav
├─ src/ugv_main/ugv_slam
├─ src/ugv_main/ugv_vision
├─ src/ugv_main/ugv_gazebo
└─ install/
文件或目录推荐先看什么适合扩展什么不建议直接改什么
app.pyWeb 路由、命令入口、自启动日志独立接口、状态查询主控制流程和安全停止逻辑
base_ctrl.pyJSON 发送方式、串口初始化上层封装调用串口协议核心和停止逻辑
cv_ctrl.py视觉模式、摄像头入口识别参数和结果显示相机初始化和线程核心
audio_ctrl.py音频播放入口语音提示、状态播报音频设备初始化逻辑
templates/页面结构、按钮和表单状态卡片、参数面板删除原控制页面安全逻辑
config.yaml按钮、参数和反馈配置复制后做参数实验未备份时修改原配置
tutorial_cn/Notebook 示例复制到个人目录后实验在原示例中长期保存项目代码
sounds/默认音频文件新增提示音删除默认音频
ugv_ws/src/ugv_main/ugv_tools行为控制和工具节点自定义工具节点参考直接改已验证工具入口
ugv_ws/src/ugv_main/ugv_nav导航参数和 launch 入口导航扩展参考未验证时修改 Nav2 参数
ugv_ws/src/ugv_main/ugv_visionROS 2 视觉节点只读识别、结果发布默认控制底盘

常用接口索引

类型接口 / 文件用途风险
Web API/send_command调用已有 Web 命令
下位机 JSON{"T": ...}控制底盘、LED、OLED、云台等
ROS 2 topic/voltage电压监控
ROS 2 topic/scan雷达数据
ROS 2 topic/odom里程计
ROS 2 topic/cmd_vel底盘速度控制
ROS 2 topic/robot_pose当前地图位姿
ROS 2 topic/goal_pose发布导航目标
ROS 2 action/behavior行为控制、保存点、发布点
文件map.pgm / map.yamlNavigation 地图
日志~/ugv_projects/<project>/logs/自定义项目日志

高风险接口在接入前必须先完成停止方式、限幅、低速和小范围验证。

按目标选择开发路径

想实现的功能推荐入口先看哪个教程二次开发重点风险
修改颜色识别、运动检测、手势识别等视觉逻辑Notebook 原型开发上位机教程先可视化结果,再整理成脚本
给机器人增加一个 Web 按钮或参数入口Web 命令与接口调用入门教程上位机教程先调用已有命令,再新增独立接口
制作自动拍照、巡检、数据记录任务自定义 Python 任务脚本上位机教程YAML 配置、dry-run、日志、停止方式
接入 ROS 2、订阅话题或写自定义节点ROS 2 二次开发ROS 2 教程先只读 topic,再发布控制
扩展底盘、灯光、OLED、云台 JSON 指令下位机 JSON / 硬件扩展下位机教程先测停止、LED、OLED,再测运动
开机运行自己的功能部署、自启动与恢复部署、自启动与恢复systemd / crontab、日志、禁用方式
记录长时间运行数据Notebook 或任务脚本上位机教程CSV / JSONL 日志、时间戳、异常记录
处理突然断电、电压过低问题电压记录与监控入门教程上位机教程先观察 V_IN,再做日志或监控扩展

推荐开发流程

  1. 先完成基础验证。 确认 Web、JupyterLab、相机、底盘停止、低速运动和 ROS 2 基础功能是否正常。
  2. 复制示例,不直接改原文件。 将 Notebook、脚本或配置复制到自己的项目目录。
  3. 先 dry-run。 运动类功能先只输出将要执行的命令。
  4. 再低风险硬件验证。 先 LED、OLED、拍照,再云台,最后底盘。
  5. 记录参数和结果。 使用 CSV、JSONL 或 Markdown 记录时间、参数、现象和错误。
  6. 整理成脚本。 将验证通过的 Notebook 逻辑封装为 .py 文件。
  7. 再接入 Web 或 ROS 2。 不要一开始就改 Web 主程序或 ROS 2 驱动。
  8. 最后考虑自启动。 确认有停止方式、日志和恢复方案后再加入 systemd 或 crontab。

路径 1:Notebook 原型开发

Notebook 适合算法验证、图像显示、参数调试和日志记录。摄像头读取、OpenCV 与 MediaPipe 的基础流程见 上位机教程

适合场景:

  • 视觉识别、手势识别、颜色阈值、图像显示和数据曲线调试;
  • 单次动作建议、dry-run 输出、参数对比;
  • 将实验结果记录为截图、CSV 或 Markdown。

不适合场景:

  • 长时间无人值守任务;
  • 开机自动运行任务;
  • 直接连续控制底盘。

推荐做法:

  1. 将示例 Notebook 复制到 ~/ugv_projects/<project>/notebooks/
  2. 第一格写清楚实验目标、硬件状态、是否会控制底盘。
  3. 先显示图像、关键点、Mask、曲线或日志,不发送运动指令。
  4. 涉及底盘或云台时,先按 Notebook 中的安全底盘联动规则 处理。
  5. 验证通过后,把核心逻辑提取为 .py 脚本。
DRY_RUN = True


def send_plan(name, command):
if DRY_RUN:
print("[DRY-RUN]", name, command)
return
raise RuntimeError("接入真实控制前,先完成停止方式和低速验证。")


send_plan("stop", {"T": 1, "L": 0, "R": 0})

观察点:

  • Notebook 中能看到图像、识别框、关键点、曲线或文本状态;
  • dry-run 能输出将要发送的 JSON 或速度参数;
  • 运动类逻辑只在完成 底盘串口确认与占用排查 后再接入真实动作。

路径 2:Web 命令与接口调用

Web 命令和接口适合把已经验证过的功能接入按钮、参数框或远程调用入口。Web 控制页面、命令输入框和接口说明见 上位机教程

适合场景:

  • 给已有功能增加一个入口,例如语音提示、状态显示、拍照或参数切换;
  • 通过 /send_command 调用已有命令;
  • 用独立接口验证自己的参数和返回值。

不适合场景:

  • 第一次开发就改原始 app.py 主流程;
  • 在没有停止按钮和参数限幅时接入底盘运动;
  • 让 Web 页面和 Notebook 同时打开同一个底盘串口。

推荐做法:

  1. 先使用低风险命令验证交互,例如语音、状态显示、视频质量、OLED 或 LED。
  2. 底盘 JSON、巡线参数和云台跟随参数放到进阶验证。
  3. 新增接口先运行在独立端口,不覆盖原 Web 主程序。
  4. 接入控制前增加参数限幅、dry-run、停止按钮和异常返回。
import requests

ROBOT_IP = "192.168.50.5"
WEB_PORT = 5000
DRY_RUN = True


def post_command(command):
url = f"http://{ROBOT_IP}:{WEB_PORT}/send_command"
if DRY_RUN:
print("[DRY-RUN] POST", url, {"command": command})
return {"dry_run": True, "command": command}
response = requests.post(url, data={"command": command}, timeout=2)
response.raise_for_status()
return response.text


post_command("audio -s hello")

观察点:

  • 先验证接口返回值和日志;
  • 再验证低风险外设;
  • 运动类命令只输出预览,完成停止方式验证后再接入真实控制。

路径 3:自定义 Python 任务脚本

任务脚本适合把 Notebook 中已经验证的逻辑整理成可重复运行的应用,例如自动拍照、数据记录、巡检、识别结果保存或定时状态监控。

适合场景:

  • 固定流程任务;
  • 需要配置文件、日志和错误记录的任务;
  • 需要在终端、SSH 或自启动服务中运行的任务。

不适合场景:

  • 仍在频繁改参数和观察图像的算法调试;
  • 缺少停止方式的连续运动任务;
  • 未记录日志的无人值守任务。

推荐项目目录:

mkdir -p ~/ugv_projects/my_project/{scripts,configs,logs,data,assets}

配置文件示例:

project_name: my_project
dry_run: true
log_dir: ~/ugv_projects/my_project/logs
max_speed: 0.05
duration_sec: 0.3

脚本模板:

import json
import logging
import time
from pathlib import Path

import yaml

CONFIG_PATH = Path("~/ugv_projects/my_project/configs/config.yaml").expanduser()


def load_config(path):
with path.open("r", encoding="utf-8") as f:
config = yaml.safe_load(f) or {}
config.setdefault("dry_run", True)
config.setdefault("log_dir", "~/ugv_projects/my_project/logs")
return config


def setup_logging(log_dir):
log_dir.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
filename=log_dir / "task.log",
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
logging.getLogger().addHandler(logging.StreamHandler())


class RobotClient:
def __init__(self, config):
self.config = config

def send_json(self, command):
if self.config["dry_run"]:
logging.info("[DRY-RUN] %s", json.dumps(command, ensure_ascii=False))
return
raise RuntimeError("接入真实发送接口前,先完成停止和低速验证。")

def safe_stop(self):
self.send_json({"T": 1, "L": 0, "R": 0})


def run_task(robot, config):
robot.send_json({"T": 1, "L": 0, "R": 0})
time.sleep(float(config.get("duration_sec", 0.3)))


def main():
config = load_config(CONFIG_PATH)
setup_logging(Path(config["log_dir"]).expanduser())
robot = RobotClient(config)
try:
run_task(robot, config)
except Exception:
logging.exception("task failed")
raise
finally:
robot.safe_stop()


if __name__ == "__main__":
main()

观察点:

  • dry_run: true 时只输出命令和日志;
  • 日志文件中能看到启动、参数、命令、异常和停止;
  • 确认脚本可手动停止后,再接入 Web、ROS 2 或自启动。

路径 4:自定义 Web 页面

自定义 Web 页面适合把任务脚本或已验证功能整理成按钮、状态卡片和参数输入框。先做独立页面或独立端口,验证后再考虑合入原 Web 主程序。

适合场景:

  • 给任务脚本增加远程启动、停止和状态查看;
  • 为参数调试增加表单;
  • 把日志、状态或识别结果展示到页面。

不适合场景:

  • 替换原始 Web 页面;
  • 没有停止按钮的运动控制页面;
  • 将未验证的视觉、底盘和自启动逻辑一次性接入。

独立 Flask API 模板:

from flask import Flask, jsonify, request

app = Flask(__name__)

DRY_RUN = True
MAX_SPEED = 0.10
MAX_DURATION = 0.5


def clamp(value, low, high):
return max(low, min(high, value))


@app.get("/api/status")
def status():
return jsonify({"ok": True, "dry_run": DRY_RUN})


@app.post("/api/stop")
def stop():
command = {"T": 1, "L": 0, "R": 0}
return jsonify({"dry_run": DRY_RUN, "command": command})


@app.post("/api/move")
def move():
data = request.get_json(silent=True) or {}
speed = clamp(float(data.get("speed", 0.0)), -MAX_SPEED, MAX_SPEED)
duration = clamp(float(data.get("duration", 0.2)), 0.0, MAX_DURATION)
command = {"T": 1, "L": speed, "R": speed, "duration": duration}
return jsonify({"dry_run": DRY_RUN, "command": command})


if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)

观察点:

python flask_dev_api.py
curl http://127.0.0.1:5001/api/status
curl -X POST http://127.0.0.1:5001/api/stop
curl -X POST http://127.0.0.1:5001/api/move -H "Content-Type: application/json" -d '{"speed":0.05,"duration":0.2}'

确认独立接口、限幅和 dry-run 正常后,再考虑增加前端页面或合入主程序。

路径 5:ROS 2 二次开发

ROS 2 二次开发适合订阅传感器数据、编写自定义节点、记录 rosbag、扩展导航或与外部系统联动。ROS 2 的启动、topic、SLAM 和 Navigation 流程见 ROS 2 教程

适合场景:

  • 订阅 /voltage、雷达、IMU、图像、里程计或 TF 数据;
  • 编写只读监控节点;
  • 记录 rosbag 复现问题;
  • 在完成基础验证后发布控制消息。

不适合场景:

  • 未完成 ROS 2 基础启动就修改 launch 或驱动;
  • 在未确认底盘安全时发布速度;
  • 同时让 Web 主程序、Notebook 和 ROS 2 抢占同一串口或摄像头资源。

推荐做法:

  1. ROS 2 教程 进入 Docker 或 ROS 2 终端。
  2. 先运行 ros2 topic list 和只读 echo。
  3. 自定义节点先订阅状态,不发布控制。
  4. 发布速度前,先确认底盘停止、低速测试和测试场地。
  5. 修改 launch、参数或驱动前,先复制文件并记录回退方式。

创建只读 ROS 2 节点

第一版自定义节点建议只订阅 topic,不发布控制消息,不发布 /cmd_vel

cd /home/ws/ugv_ws/src
ros2 pkg create ugv_custom_monitor --build-type ament_python --dependencies rclpy std_msgs nav_msgs sensor_msgs

只读电压监控节点示例可命名为 voltage_monitor。如果当前 /voltage 的消息类型不同,先用 ros2 topic info /voltage 核对后再修改订阅类型。

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32


class VoltageMonitor(Node):
def __init__(self):
super().__init__("voltage_monitor")
self.create_subscription(Float32, "/voltage", self.on_voltage, 10)

def on_voltage(self, msg):
self.get_logger().info(f"voltage: {msg.data:.2f} V")


def main(args=None):
rclpy.init(args=args)
node = VoltageMonitor()
try:
rclpy.spin(node)
finally:
node.destroy_node()
rclpy.shutdown()

在包的 setup.py 中配置 console script 后,构建并运行:

cd /home/ws/ugv_ws
colcon build --packages-select ugv_custom_monitor
source install/setup.bash
ros2 run ugv_custom_monitor voltage_monitor

观察点:

  • ros2 topic list 能看到预期 topic;
  • 只读订阅节点能输出数据;
  • rosbag 能记录并回放;
  • 速度发布逻辑先使用 dry-run 或仿真输出。

ROS 2 Web 可视化与 API 接入

ROS 2 Web App / Vizanti 适合做浏览器端 ROS 2 可视化、远程状态查看、教学演示和轻量调试。它可以显示 RobotModel、TF、LaserScanMapOdometryPath 和 Navigation 状态,也可以作为自定义 ROS 2 面板的入口。

它不替代上位机 Web 主页面,不替代 JupyterLab,也不替代 RViz 的完整调试能力。第一次调通 SLAM / Navigation 时仍建议优先使用 RViz。

启动 Vizanti

cd /home/ws/ugv_ws
source /opt/ros/humble/setup.bash
source install/setup.bash

ros2 launch ugv_web_app bringup.launch.py host:=0.0.0.0

浏览器访问:

http://<UGV_IP>:5100

Vizanti 页面通常使用 5100 端口;rosbridge websocket 通常使用 5001。页面能打开但没有内容时,先检查是否有 ROS 2 topic,以及页面中是否已经添加显示组件。

目标需要先启动Vizanti 中添加
查看机器人模型robot_state_publisher / robot_descriptionRobotModel
查看 TF/tf/tf_staticTF
查看雷达bringup / lidarLaserScan /scan
查看 2D 地图SLAM 或 NavigationMap /map
查看里程计bringup / base nodeOdometry /odom
查看导航路径NavigationPath / Goal / Navigation 相关组件

ROS 2 topic 到 Web API 的接入边界

二次开发中可以把 ROS 2 topic、action 或 service 封装成 Web API,但建议按“只读 → dry-run → 限幅 → 真实控制”的顺序推进。

优先从只读状态开始,例如 /voltage/odom/robot_pose/scan 的摘要信息。可先设计以下只读接口:

/api/status
/api/voltage
/api/pose
/api/ros_topics

涉及 /cmd_vel/goal_pose/behavior/send_command 的 API 属于高风险接口。不要第一次就接真实底盘。必须先完成 dry-run、参数限幅、超时停止、日志记录和人工停止方式。

接口风险接入要求
/cmd_vel限速、限时、finally 停止、人工断电方式
/goal_pose先确认 Navigation 地图、AMCL、costmap 正常
/behavior先验证 save / pub 链路,不把回点作为默认保证
/send_command中 / 高先低风险命令,再运动命令
/voltage可作为只读状态接口

Web AI / 外部 API 接入边界

Web AI 属于可选高级二次开发功能,依赖额外 AI 服务配置。AI 输出不能直接无保护地转换成底盘运动、导航目标或下位机 JSON 指令。

推荐顺序:

  1. 只做问答或状态解释;
  2. 只读查询 ROS 2 状态;
  3. dry-run 输出将要执行的动作;
  4. 人工确认后执行低风险动作;
  5. 涉及底盘或导航时必须有限幅、超时、停止和日志。
内容ROS 2 教程二次开发
ugv_web_app 是什么简短说明详细说明
启动 Vizanti最小命令可重复测试流程
页面空白为什么正常简短说明组件和 topic 对应关系
添加 RobotModel / TF / LaserScan / Map最小观察点完整用途表
自定义 ROS 2 Web 面板不展开展开
ROS 2 topic 接 Web API不展开展开
/cmd_vel/goal_pose/behavior 接 API不展开高风险开发说明
Web AI / ugv_chat_ai只跳转可选高级功能

路径 6:下位机 JSON / 硬件扩展

下位机 JSON 和硬件扩展用于控制底盘、LED、OLED、云台或新增外设。先完成 下位机教程 中的低风险指令和停止方式验证,再做扩展。

建议顺序:

  1. 先测试状态、灯光、OLED 或音频提示。
  2. 再测试云台小角度动作。
  3. 最后测试底盘短时低速动作。
  4. 每一步都保留停止方式、日志和串口占用检查。

路径 7:部署、自启动与恢复

部署适合已经手动运行稳定的脚本。部署前,先确认脚本可手动运行、可停止、可记录日志、可禁用。

适合场景:

  • 开机初始化状态;
  • 自动启动数据记录;
  • 启动独立 Web API;
  • 周期性检查电压或系统状态。

不适合场景:

  • 开机后自动驱动底盘;
  • 无日志、无停止方式、无禁用方式的脚本;
  • 依赖相机、串口或 ROS 2 但没有占用检查的脚本。

crontab 模板:

@reboot sleep 10 && /home/jetson/ugv_jetson/ugv-env/bin/python /home/jetson/ugv_projects/my_project/scripts/task.py >> /home/jetson/ugv_projects/my_project/logs/autorun.log 2>&1

禁用方法:执行 crontab -e,在对应行前添加 #,保存后重启。

systemd 模板:

[Unit]
Description=UGV custom task
After=network-online.target

[Service]
Type=simple
User=jetson
WorkingDirectory=/home/jetson/ugv_projects/my_project
ExecStart=/home/jetson/ugv_jetson/ugv-env/bin/python /home/jetson/ugv_projects/my_project/scripts/task.py
Restart=on-failure
RestartSec=5
StandardOutput=append:/home/jetson/ugv_projects/my_project/logs/systemd.log
StandardError=append:/home/jetson/ugv_projects/my_project/logs/systemd.err.log

[Install]
WantedBy=multi-user.target

常用管理命令:

sudo systemctl daemon-reload
sudo systemctl enable ugv-custom-task.service
sudo systemctl start ugv-custom-task.service
sudo systemctl status ugv-custom-task.service
sudo systemctl stop ugv-custom-task.service
sudo systemctl disable ugv-custom-task.service
注意

systemd 和 crontab 二选一即可。不要让同一个脚本被两种方式重复启动。

观察点:

  • 先手动运行脚本;
  • 再用 systemd 或 crontab 启动一次;
  • 检查日志路径、进程状态和停止命令;
  • 重启后确认可禁用、可恢复。

最小可运行项目示例

项目 A:电压状态记录器

目标:定时读取电压状态并写入 CSV 或 JSONL 日志。

入口:JupyterLab、Jetson 主机 Python 脚本,或 ROS 2 只读节点。

不做什么:不控制底盘,不占用摄像头,不修改 Web 主程序。

观察点:日志文件按时间追加;电压字段可读;异常时有错误记录。

下一步:加入电压阈值提示或 Web 状态接口。

项目 B:独立 Web 状态接口

目标:使用独立端口输出自定义状态,例如电压、磁盘空间、任务状态或最近日志。

入口:Flask 独立 API,建议先使用 5001 等独立端口验证。

不做什么:不替换原 Web;不接入运动控制;不占用底盘串口。

观察点:/api/status 返回 JSON;终端有访问日志;异常时返回错误信息。

下一步:确认接口稳定后,再考虑增加只读状态卡片。

项目 C:拍照并保存结果

目标:读取摄像头画面,保存原图、时间戳和识别结果。

入口:Notebook 原型或 Jetson 主机 Python 脚本。

不做什么:不和 Web 视频、其它 Notebook 同时占用摄像头;不控制底盘。

观察点:图片文件可打开;文件名包含时间戳;摄像头被占用时有明确日志。

下一步:增加识别框、分类结果或定时采集。

项目 D:安全底盘动作模板

目标:整理一个带 dry-run、限幅、超时和停止逻辑的运动任务框架。

入口:任务脚本或独立 API。

不做什么:默认不发送真实底盘指令;未验证停止方式前不接入串口或 /cmd_vel

观察点:dry-run 输出完整计划;速度和时间被限制在配置范围内;异常时会执行停止逻辑。

下一步:完成低速短时验证后,再接入真实控制入口。

项目 E:ROS 2 只读监控节点

目标:订阅 /voltage/scan/odom 或其它状态 topic,输出日志或保存 rosbag。

入口:ROS 2 Docker 容器中的 ugv_custom_monitor 包。

不做什么:不发布 /cmd_vel,不发布 /goal_pose,不修改 Nav2 参数。

观察点:节点能启动;topic 有输出;日志中有时间戳和状态值。

下一步:增加阈值提示、文件记录或 Web 只读状态显示。

修改前备份与版本管理

修改系统文件前,先创建备份目录并复制原文件。

mkdir -p ~/ugv_projects/backups

cp /home/jetson/ugv_jetson/app.py \
~/ugv_projects/backups/app.py.$(date +%Y%m%d_%H%M%S)

如果当前目录是 Git 仓库,先查看变更范围:

git status
git diff
git diff -- <file>

仅在当前目录是 Git 仓库、且确认要回退该文件时,才使用:

git checkout -- <file>

如果当前目录不是 Git 仓库,不要执行 Git 回退命令;先用复制备份的方式保护原文件。

Python 依赖与系统包管理

不要为了一个二次开发示例直接升级整套系统包。不要随意执行 apt upgrade。不要在不了解影响的情况下替换 OpenCV、NumPy、ROS 2 或系统 Python 相关依赖。

建议在个人项目目录中使用虚拟环境:

cd ~/ugv_projects/my_project
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip freeze > requirements.lock.txt

如果示例依赖系统摄像头、GPIO、ROS 2 或 Jetson 特定库,虚拟环境可能还需要额外配置;以当前项目实际依赖为准。

Web 前端二次开发边界

操作建议
新增状态卡片、参数表单、低风险按钮可以先在独立页面或独立端口验证
新增任务启动 / 停止接口必须有停止按钮、日志和异常返回
修改底盘控制按钮谨慎处理,必须保留限幅、停止和超时
删除原始控制页面或替换主程序不建议
修改串口发送核心、停止逻辑、安全限幅不建议第一次二次开发就修改

可以进行前端二次开发,但不应破坏原有停止逻辑、限幅逻辑和恢复方式。

外设和硬件扩展注意事项

新增外设前先确认供电、电流、接线和固定方式。

  • 新增 USB 相机、OAK-D Lite 或其它 USB 外设时,注意供电和 USB 带宽;
  • 新增外设不要遮挡雷达、相机或云台;
  • 新增较重外设后,底盘重心、轮子打滑和 Navigation 表现可能变化;
  • 如果外设改变了车体外形或遮挡雷达,可能需要重新建图或重新检查 Navigation;
  • 不要在不确认引脚、电压和电流能力的情况下直接接外设。

常见风险与回退方法

风险常见原因回退方法
串口被占用Web、Notebook、ROS 2 同时打开底盘串口停止多余程序,只保留一个控制入口
摄像头打不开Web 主程序或其它 Notebook 占用摄像头释放摄像头资源后再运行视觉程序
底盘停不下缺少 finally 停止、脚本异常退出先发送停止指令,保留断电方式,之后补 safe_stop()
Web 页面异常修改原始 app.py 或模板后无法恢复用备份恢复,先在独立端口验证
ROS 2 无 topic未进入容器、未加载环境、节点未启动回到 ROS 2 教程完成基础检查
自启动循环失败systemd / crontab 重复启动或路径错误禁用自启动,查看日志,手动运行脚本
电压偏低长时间视觉、ROS 2 或底盘运动耗电停止高负载任务,充电或更换电池
依赖升级后功能异常直接升级系统包或核心 Python 依赖使用依赖记录回退,避免全局升级

二次开发验收模板

部署或合入主程序前,先用该模板记录验证结果。

项目内容
功能名称
运行环境Jetson 主机 / ROS 2 Docker / 浏览器 / ESP32 下位机
是否控制底盘是 / 否
是否占用摄像头是 / 否
是否占用串口是 / 否
dry-run 输出
日志路径
停止方式
异常恢复方式
通过标准
不通过时回退