#!/bin/bash #======================================================================= # FILE: scripts/build-board.sh # DESC: 把源码同步到目标板卡 (RK3588 / Ubuntu 22.04),在板卡原生编译 # (aarch64 直编,比本地 QEMU 模拟快约 10 倍),并把产物部署到 # /root/work/h100/bin/,可选重启 systemd 服务。 # # 用法: # ./scripts/build-board.sh # 同步+编译+部署(不重启) # ./scripts/build-board.sh --start # 编译后重启全部服务(含 pCanBridge/pMotor/pELoad) # ./scripts/build-board.sh --clean # 板卡上重新 cmake 再编译 # ./scripts/build-board.sh --jobs 4 # 指定并行度(默认板卡 nproc) # # 参数: # --host 目标主机(默认 192.168.0.223) # --user 登录用户(默认 root) # --board-src 板卡源码目录(默认 /root/work/h100/src) # --board-bin 板卡部署目录(默认 /root/work/h100/bin) # --jobs 编译并行度(默认板卡 nproc) # --start 编译部署后重启服务 # --clean 先重新 cmake(配置变更时用) # --ccu-host 覆盖 mission 中 pPowerManger 的 CCU 地址 (ccuhost) # --ccu-port 覆盖 mission 中 pPowerManger 的 CCU 端口 (ccuport) # -h, --help 显示帮助 # # 前置: 板卡已配免密 SSH,且有编译环境(cmake/g++/MOOS/jsoncpp), # 源码已能本地编译(本脚本只负责同步+板卡编译)。 # # !!!! 板卡网络配置(2026-08-26 联调定型,勿随意修改) !!!! # # 板卡与 FC 的组网为"单网卡 + /23 掩码"方案(依据《超滑FC和复合管控器 # 通讯协议-0821》1.1.1:CCU 192.168.0.140/23 <-> FC 192.168.1.162): # # enP3p49s0 192.168.0.223/23 (255.255.254.0) <- 唯一业务网口,接交换机 # /23 使 192.168.0.x 与 192.168.1.x 同子网直达, # FC(192.168.1.162) 无需网关即可互通。 # NM 配置档案: "有线连接 1"(静态 manual,已持久化) # enP4p65s0 已弃用(旧双网卡方案的 FC 专口,物理链路已断) # 回环 lo pPowerManger(5001) <-> pCCU(7000) 同机 UDP 互通 # # 关联端口: # FC 状态: FC:7000 -> 板卡:6000 (pCCU 监听) # FC 控制: 板卡:6000 -> FC:7000 (pCCU 发送) # PM 指令: pPowerManger:5001 -> pCCU:7000 (127.0.0.1, # 见 missions/h100.moos ccuhost/ccuport,默认值在 PowerManger.h CCUHOST) # PM 状态: pCCU:7000 -> pPowerManger:5001 (127.0.0.1) # # 排障提示: # - 若收不到 FC 状态,先在板卡上 `ethtool enP3p49s0` 看 Link detected, # 再 `ping 192.168.1.162`;ping 不通通常是物理链路/交换机问题, # 不要改回 /24 掩码或启用 enP4p65s0 来"修"网络。 # - 改 IP/掩码须用 `nmcli con mod "有线连接 1" ...` 持久化, # 并保持掩码 /23。 #======================================================================= set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")" HOST="192.168.0.223" USER="root" BOARD_SRC="/root/work/h100/src" BOARD_BIN="/root/work/h100/bin" JOBS="" DO_START=0 DO_CLEAN=0 CCU_HOST="" CCU_PORT="" SERVICES=(moosdb pPowerManger pPowerMangerHost pCCU pCanBridge pMotor pELoad) info() { printf '\033[1;36m[build-board]\033[0m %s\n' "$*"; } ok() { printf '\033[1;32m[build-board]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[build-board]\033[0m %s\n' "$*"; } err() { printf '\033[1;31m[build-board]\033[0m %s\n' "$*"; } usage() { sed -n '2,31p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' exit 0 } #------------------------------------------------------------------- # 参数解析 #------------------------------------------------------------------- while [ $# -gt 0 ]; do case "$1" in --host) [ $# -ge 2 ] || { err "--host 需要参数"; exit 1; }; HOST="$2"; shift 2 ;; --user) [ $# -ge 2 ] || { err "--user 需要参数"; exit 1; }; USER="$2"; shift 2 ;; --board-src) [ $# -ge 2 ] || { err "--board-src 需要参数"; exit 1; }; BOARD_SRC="$2"; shift 2 ;; --board-bin) [ $# -ge 2 ] || { err "--board-bin 需要参数"; exit 1; }; BOARD_BIN="$2"; shift 2 ;; --jobs) [ $# -ge 2 ] || { err "--jobs 需要参数"; exit 1; }; JOBS="$2"; shift 2 ;; --start) DO_START=1; shift ;; --clean) DO_CLEAN=1; shift ;; --ccu-host) [ $# -ge 2 ] || { err "--ccu-host 需要参数"; exit 1; }; CCU_HOST="$2"; shift 2 ;; --ccu-port) [ $# -ge 2 ] || { err "--ccu-port 需要参数"; exit 1; }; CCU_PORT="$2"; shift 2 ;; -h|--help) usage ;; *) err "未知参数: $1(见 --help)"; exit 1 ;; esac done SSH_TARGET="${USER}@${HOST}" SSH="ssh -o BatchMode=yes -o ConnectTimeout=5" case "${CCU_PORT}" in ""|*[!0-9]*) [ -z "${CCU_PORT}" ] || { err "--ccu-port 必须是数字端口: ${CCU_PORT}"; exit 1; } ;; esac #------------------------------------------------------------------- # 1. 前置检查 #------------------------------------------------------------------- if ! ${SSH} "${SSH_TARGET}" 'true' 2>/dev/null; then err "无法免密 SSH 到 ${SSH_TARGET}" err "请先配置免密登录: ssh-copy-id ${SSH_TARGET}" exit 1 fi ok "SSH 连接正常: ${SSH_TARGET}" #------------------------------------------------------------------- # mission 中插入/替换 "key = value"(键已存在则替换,否则插到第一个 # 顶层 '}' 之前,即 pPowerManger 配置块末尾) #------------------------------------------------------------------- upsert_moos_param() { local f="$1" key="$2" val="$3" if grep -qE "^[[:space:]]*${key}[[:space:]]*=" "${f}"; then sed -i -E "s|^([[:space:]]*)${key}[[:space:]]*=.*|\1${key} = ${val}|" "${f}" else awk -v kv=" ${key} = ${val}" \ '!d && /^[[:space:]]*}[[:space:]]*$/ { print kv; d=1 } { print }' \ "${f}" > "${f}.tmp" && mv "${f}.tmp" "${f}" fi } #------------------------------------------------------------------- # 2. 同步源码到板卡(排除构建产物 / git / 数据) #------------------------------------------------------------------- info "同步源码到板卡 ${BOARD_SRC} ..." rsync -az --delete \ --exclude='build/' \ --exclude='build-arm64/' \ --exclude='build-native/' \ --exclude='build-nosa-test/' \ --exclude='bin/' \ --exclude='.git/' \ --exclude='.cache/' \ --exclude='.kilo/' \ --exclude='.vscode/' \ --exclude='docs/' \ --exclude='reports/' \ --exclude='*.log' \ --exclude='*.db' \ --exclude='CMakeCache.txt' \ "${PROJECT_ROOT}/" "${SSH_TARGET}:${BOARD_SRC}/" || { err "rsync 源码失败"; exit 1; } ok "源码已同步" if [ -f "${PROJECT_ROOT}/missions/h100.moos" ]; then info "同步 mission h100.moos ..." local_mission="${PROJECT_ROOT}/missions/h100.moos" tmp_moos="" if [ -n "${CCU_HOST}" ] || [ -n "${CCU_PORT}" ]; then tmp_moos="$(mktemp)" cp "${local_mission}" "${tmp_moos}" [ -n "${CCU_HOST}" ] && upsert_moos_param "${tmp_moos}" ccuhost "${CCU_HOST}" [ -n "${CCU_PORT}" ] && upsert_moos_param "${tmp_moos}" ccuport "${CCU_PORT}" local_mission="${tmp_moos}" info "CCU 地址覆盖: ccuhost=${CCU_HOST:-<保持原值>} ccuport=${CCU_PORT:-<保持原值>}" fi rsync -az "${local_mission}" \ "${SSH_TARGET}:/root/work/h100/missions/h100.moos" || { err "rsync mission 失败"; [ -n "${tmp_moos}" ] && rm -f "${tmp_moos}"; exit 1; } [ -n "${tmp_moos}" ] && rm -f "${tmp_moos}" ok "mission 已同步" fi #------------------------------------------------------------------- # 3. 板卡原生编译 #------------------------------------------------------------------- if [ "${DO_CLEAN}" = "1" ]; then info "重新 cmake(--clean)..." ${SSH} "${SSH_TARGET}" "rm -rf ${BOARD_SRC}/build-native && mkdir -p ${BOARD_SRC}/build-native && \ cd ${BOARD_SRC}/build-native && cmake -DCMAKE_BUILD_TYPE=Release .." \ || { err "cmake 失败"; exit 1; } ok "cmake 完成" else ${SSH} "${SSH_TARGET}" "mkdir -p ${BOARD_SRC}/build-native" # 若首次未配置过则先 cmake if ! ${SSH} "${SSH_TARGET}" "[ -f ${BOARD_SRC}/build-native/Makefile ]"; then info "首次编译,先 cmake ..." ${SSH} "${SSH_TARGET}" "cd ${BOARD_SRC}/build-native && cmake -DCMAKE_BUILD_TYPE=Release .." \ || { err "cmake 失败"; exit 1; } ok "cmake 完成" fi fi if [ -z "${JOBS}" ]; then JOBS="$(${SSH} "${SSH_TARGET}" nproc)" fi info "板卡编译 (make -j${JOBS}) ..." if ! ${SSH} "${SSH_TARGET}" "cd ${BOARD_SRC}/build-native && make -j${JOBS}"; then err "板卡编译失败(见上方输出)" exit 1 fi ok "板卡编译完成" #------------------------------------------------------------------- # 4. 部署产物到板卡运行目录 #------------------------------------------------------------------- info "部署产物到 ${BOARD_BIN}/ ..." ${SSH} "${SSH_TARGET}" "mkdir -p ${BOARD_BIN} && \ cp -f ${BOARD_SRC}/bin/pPowerManger ${BOARD_BIN}/pPowerManger && \ cp -f ${BOARD_SRC}/bin/pPowerMangerHost ${BOARD_BIN}/pPowerMangerHost && \ cp -f ${BOARD_SRC}/bin/pCCU ${BOARD_BIN}/pCCU && \ cp -f ${BOARD_SRC}/bin/pCanBridge ${BOARD_BIN}/pCanBridge && \ cp -f ${BOARD_SRC}/bin/pMotor ${BOARD_BIN}/pMotor && \ cp -f ${BOARD_SRC}/bin/pELoad ${BOARD_BIN}/pELoad && \ chmod +x ${BOARD_BIN}/pPowerManger ${BOARD_BIN}/pPowerMangerHost ${BOARD_BIN}/pCCU ${BOARD_BIN}/pCanBridge ${BOARD_BIN}/pMotor ${BOARD_BIN}/pELoad" \ || { err "部署产物失败"; exit 1; } ok "产物已部署" #------------------------------------------------------------------- # 4.5 安装/刷新 pCanBridge systemd unit(幂等,其它 5 个 unit 已装) #------------------------------------------------------------------- info "安装 pCanBridge.service ..." ${SSH} "${SSH_TARGET}" "cat > /etc/systemd/system/pCanBridge.service" </dev/null || true" ok "pCanBridge.service 已安装" #------------------------------------------------------------------- # 4.6 安装/刷新 pMotor systemd unit(幂等) #------------------------------------------------------------------- info "安装 pMotor.service ..." ${SSH} "${SSH_TARGET}" "cat > /etc/systemd/system/pMotor.service" </dev/null || true" ok "pMotor.service 已安装" #------------------------------------------------------------------- # 4.7 安装/刷新 pELoad systemd unit(幂等) # 电子负载(IT6000C)控制程序:以太网直连设备,不依赖 pCanBridge #------------------------------------------------------------------- info "安装 pELoad.service ..." ${SSH} "${SSH_TARGET}" "cat > /etc/systemd/system/pELoad.service" </dev/null || true" ok "pELoad.service 已安装" echo "" ok "产物: ${BOARD_BIN}/pPowerManger (aarch64)" ${SSH} "${SSH_TARGET}" "file ${BOARD_BIN}/pPowerManger | cut -c1-60" #------------------------------------------------------------------- # 5. 可选重启服务 #------------------------------------------------------------------- if [ "${DO_START}" = "1" ]; then info "重启服务 ..." ${SSH} "${SSH_TARGET}" "systemctl restart ${SERVICES[*]}" \ || { err "重启服务失败"; exit 1; } sleep 3 ok "服务状态: $(${SSH} "${SSH_TARGET}" "systemctl is-active ${SERVICES[*]}")" else echo "" info "未重启服务。需要重启请加 --start 或手动:" echo " systemctl restart ${SERVICES[*]}" fi echo "" ok "完成。"