307 lines
9.5 KiB
Bash
Executable File
307 lines
9.5 KiB
Bash
Executable File
#!/bin/bash
|
||
#=======================================================================
|
||
# FILE: scripts/run_host_sim.sh
|
||
# DESC: 运行与测试 pPowerMangerHost(模拟 pPowerManger 上位机的网页程序)
|
||
#
|
||
# 用法:
|
||
# ./scripts/run_host_sim.sh run 启动 MOOSDB + pPowerManger
|
||
# + pPowerMangerHost(前台运行,
|
||
# Ctrl-C 退出并清理)
|
||
# ./scripts/run_host_sim.sh test 自动化测试 pPowerMangerHost:
|
||
# 网页 HTTP / WebSocket 反馈 /
|
||
# 指令下发到 MOOSDB
|
||
# ./scripts/run_host_sim.sh build 仅编译需要的目标
|
||
# ./scripts/run_host_sim.sh stop 清理残留进程
|
||
# ./scripts/run_host_sim.sh help 显示帮助
|
||
#
|
||
# 环境变量(可覆盖):
|
||
# SERVER_HOST MOOSDB 地址(默认 localhost)
|
||
# SERVER_PORT MOOSDB 端口(默认 9000)
|
||
# WEB_PORT 网页监听端口(默认 18080)
|
||
# WITH_POWERMANGER run 模式是否同时启动 pPowerManger(默认 1)
|
||
#=======================================================================
|
||
set -uo pipefail
|
||
|
||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
BIN_DIR="${ROOT}/bin"
|
||
MOOS_TEMPLATE="${ROOT}/src/pPowerMangerHost/pPowerMangerHost.moos"
|
||
|
||
SERVER_HOST="${SERVER_HOST:-localhost}"
|
||
SERVER_PORT="${SERVER_PORT:-9000}"
|
||
WEB_PORT="${WEB_PORT:-18080}"
|
||
WITH_POWERMANGER="${WITH_POWERMANGER:-1}"
|
||
|
||
MOOSDB_BIN="${MOOSDB_BIN:-$(command -v MOOSDB || true)}"
|
||
POKE_BIN="$(command -v uPokeDB || true)"
|
||
PM_BIN="${BIN_DIR}/pPowerManger"
|
||
HOST_BIN="${BIN_DIR}/pPowerMangerHost"
|
||
|
||
MOOSDB_PID=""
|
||
PM_PID=""
|
||
HOST_PID=""
|
||
WORKDIR=""
|
||
MISSION=""
|
||
|
||
info() { printf '\033[1;36m[host-sim]\033[0m %s\n' "$*"; }
|
||
ok() { printf '\033[1;32m[host-sim]\033[0m %s\n' "$*"; }
|
||
warn() { printf '\033[1;33m[host-sim]\033[0m %s\n' "$*"; }
|
||
err() { printf '\033[1;31m[host-sim]\033[0m %s\n' "$*"; }
|
||
|
||
usage() {
|
||
sed -n '2,23p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
||
exit 0
|
||
}
|
||
|
||
# 生成临时 mission 文件(同时含 pPowerManger 与 pPowerMangerHost 两个配置块)
|
||
make_mission() {
|
||
WORKDIR="$(mktemp -d)"
|
||
MISSION="${WORKDIR}/host_sim.moos"
|
||
cat > "${MISSION}" <<EOF
|
||
ServerHost = ${SERVER_HOST}
|
||
ServerPort = ${SERVER_PORT}
|
||
Community = h100hostsim
|
||
|
||
ProcessConfig = pPowerManger
|
||
{
|
||
AppTick = 4
|
||
CommsTick = 4
|
||
}
|
||
|
||
ProcessConfig = pPowerMangerHost
|
||
{
|
||
AppTick = 4
|
||
CommsTick = 4
|
||
WEB_PORT = ${WEB_PORT}
|
||
}
|
||
EOF
|
||
}
|
||
|
||
kill_proc() {
|
||
[ -n "$1" ] && kill "$1" 2>/dev/null && wait "$1" 2>/dev/null
|
||
}
|
||
|
||
cleanup() {
|
||
kill_proc "${HOST_PID}"
|
||
kill_proc "${PM_PID}"
|
||
kill_proc "${MOOSDB_PID}"
|
||
[ -n "${WORKDIR}" ] && rm -rf "${WORKDIR}"
|
||
}
|
||
|
||
build_targets() {
|
||
info "编译 pPowerManger / pPowerMangerHost ..."
|
||
mkdir -p "${ROOT}/build"
|
||
cd "${ROOT}/build"
|
||
cmake -DCMAKE_BUILD_TYPE=Release "${ROOT}" >/dev/null 2>&1 || { err "cmake 失败"; return 1; }
|
||
make -j2 pPowerManger pPowerMangerHost >/dev/null 2>&1 || { err "make 失败"; return 1; }
|
||
cd "${ROOT}"
|
||
ok "编译完成"
|
||
}
|
||
|
||
check_binaries() {
|
||
local missing=0
|
||
[ -n "${MOOSDB_BIN}" ] || { err "未找到 MOOSDB(请加入 PATH)"; missing=1; }
|
||
[ -x "${HOST_BIN}" ] || { err "未找到 ${HOST_BIN}(请先运行: $0 build)"; missing=1; }
|
||
if [ "${WITH_POWERMANGER}" = "1" ]; then
|
||
[ -x "${PM_BIN}" ] || { err "未找到 ${PM_BIN}(请先运行: $0 build)"; missing=1; }
|
||
fi
|
||
return "${missing}"
|
||
}
|
||
|
||
start_moosdb() {
|
||
make_mission
|
||
info "启动 MOOSDB (${SERVER_HOST}:${SERVER_PORT}) ..."
|
||
"${MOOSDB_BIN}" --moos_no_colour "${MISSION}" > "${WORKDIR}/moosdb.log" 2>&1 &
|
||
MOOSDB_PID=$!
|
||
sleep 1
|
||
kill -0 "${MOOSDB_PID}" 2>/dev/null || { err "MOOSDB 未存活"; return 1; }
|
||
}
|
||
|
||
start_pm() {
|
||
info "启动 pPowerManger ..."
|
||
( cd "${WORKDIR}" && exec "${PM_BIN}" "${MISSION}" ) > "${WORKDIR}/pm.log" 2>&1 &
|
||
PM_PID=$!
|
||
sleep 1
|
||
kill -0 "${PM_PID}" 2>/dev/null || warn "pPowerManger 未存活(无 CCU 硬件属正常)"
|
||
}
|
||
|
||
start_host() {
|
||
info "启动 pPowerMangerHost (网页端口 ${WEB_PORT}) ..."
|
||
( cd "${WORKDIR}" && exec "${HOST_BIN}" "${MISSION}" ) > "${WORKDIR}/host.log" 2>&1 &
|
||
HOST_PID=$!
|
||
sleep 2
|
||
kill -0 "${HOST_PID}" 2>/dev/null || { err "pPowerMangerHost 未存活"; return 1; }
|
||
}
|
||
|
||
#-----------------------------------------------------------------------
|
||
# 测试项
|
||
#-----------------------------------------------------------------------
|
||
test_http() {
|
||
info "测试: 网页 HTTP (http://${SERVER_HOST}:${WEB_PORT}/) ..."
|
||
local code
|
||
code="$(curl -s -o /dev/null -w '%{http_code}' "http://${SERVER_HOST}:${WEB_PORT}/" 2>/dev/null || echo 000)"
|
||
if [ "${code}" = "200" ]; then
|
||
ok "网页 HTTP 200 通过"
|
||
return 0
|
||
else
|
||
err "网页 HTTP 失败 (code=${code})"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 反馈链路:向 MOOSDB 注入一条假的反馈变量 → 宿主订阅后应经 WS 推给浏览器
|
||
test_monitor_loop() {
|
||
info "测试: 反馈链路 (MOOSDB -> pPowerMangerHost -> WebSocket) ..."
|
||
if [ -z "${POKE_BIN}" ]; then
|
||
warn "未找到 uPokeDB,跳过反馈链路测试"
|
||
return 0
|
||
fi
|
||
if ! python3 -c "import websockets" >/dev/null 2>&1; then
|
||
warn "未安装 python3 websockets,跳过反馈链路测试"
|
||
return 0
|
||
fi
|
||
|
||
local fb_key="uPower_pmState_fb"
|
||
local fb_val='{"workCondition_uint8":9,"currentMod_uint8":1,"powerState_uint8":1,"faultLevel_uint8":0,"soc_uint16":88,"sustainableTime_int64":3600,"leakage_uint8":0,"info_str":"host-sim-test"}'
|
||
|
||
# 先让 WS 客户端连接等待快照,再注入反馈
|
||
FB_KEY="${fb_key}" python3 - "${WEB_PORT}" > "${WORKDIR}/ws_fb.log" 2>&1 <<'PY' &
|
||
import asyncio, json, os, sys
|
||
import websockets
|
||
|
||
PORT = int(sys.argv[1])
|
||
KEY = os.environ["FB_KEY"]
|
||
|
||
async def main():
|
||
uri = f"ws://localhost:{PORT}/ws"
|
||
async with websockets.connect(uri) as ws:
|
||
try:
|
||
while True:
|
||
msg = await asyncio.wait_for(ws.recv(), timeout=8)
|
||
snap = json.loads(msg)
|
||
if isinstance(snap, dict) and KEY in snap:
|
||
print(f"FEEDBACK_OK {KEY}")
|
||
return
|
||
except asyncio.TimeoutError:
|
||
pass
|
||
print("FEEDBACK_TIMEOUT")
|
||
|
||
asyncio.run(main())
|
||
PY
|
||
WS_PID=$!
|
||
sleep 1
|
||
|
||
"${POKE_BIN}" --host="${SERVER_HOST}" --port="${SERVER_PORT}" "${fb_key}=${fb_val}" >/dev/null 2>&1
|
||
wait "${WS_PID}" 2>/dev/null
|
||
|
||
if grep -q "FEEDBACK_OK" "${WORKDIR}/ws_fb.log" 2>/dev/null; then
|
||
ok "反馈链路通过(${fb_key} 已推送到浏览器)"
|
||
return 0
|
||
else
|
||
err "反馈链路失败(WS 未收到 ${fb_key})"
|
||
cat "${WORKDIR}/ws_fb.log" >&2
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 指令下发:网页 WS 下发 -> 宿主 Notify -> MOOSDB -> (宿主订阅回环) -> 推回浏览器
|
||
# 利用宿主订阅了 uPower_pmState_fb,下发该变量后经 MOOSDB 回环,能收到即证明
|
||
# 指令确实进入了 MOOSDB。
|
||
test_command_notify() {
|
||
info "测试: 指令下发 (WebSocket -> pPowerMangerHost -> MOOSDB) ..."
|
||
if ! python3 -c "import websockets" >/dev/null 2>&1; then
|
||
warn "未安装 python3 websockets,跳过指令下发校验"
|
||
return 0
|
||
fi
|
||
|
||
python3 - "${WEB_PORT}" > "${WORKDIR}/ws_cmd.log" 2>&1 <<'PY'
|
||
import asyncio, json, sys
|
||
import websockets
|
||
|
||
PORT = int(sys.argv[1])
|
||
KEY = "uPower_pmState_fb"
|
||
TAG = "cmd-loopback-test"
|
||
|
||
async def main():
|
||
uri = f"ws://localhost:{PORT}/ws"
|
||
async with websockets.connect(uri) as ws:
|
||
await ws.send(json.dumps({"key": KEY, "value": {"info_str": TAG, "workCondition_uint8": 7}}))
|
||
try:
|
||
while True:
|
||
msg = await asyncio.wait_for(ws.recv(), timeout=8)
|
||
snap = json.loads(msg)
|
||
if isinstance(snap, dict) and KEY in snap:
|
||
obj = json.loads(snap[KEY])
|
||
if obj.get("info_str") == TAG:
|
||
print("CMD_OK")
|
||
return
|
||
except asyncio.TimeoutError:
|
||
pass
|
||
print("CMD_TIMEOUT")
|
||
|
||
asyncio.run(main())
|
||
PY
|
||
local r=$?
|
||
|
||
if [ ${r} -eq 0 ] && grep -q "CMD_OK" "${WORKDIR}/ws_cmd.log" 2>/dev/null; then
|
||
ok "指令下发到 MOOSDB 通过(uPower_pmState_fb 已回环)"
|
||
return 0
|
||
else
|
||
err "指令下发校验失败:未收到回环快照"
|
||
cat "${WORKDIR}/ws_cmd.log" >&2
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
#-----------------------------------------------------------------------
|
||
# 命令入口
|
||
#-----------------------------------------------------------------------
|
||
cmd_run() {
|
||
check_binaries || return 1
|
||
start_moosdb || return 1
|
||
[ "${WITH_POWERMANGER}" = "1" ] && start_pm
|
||
start_host || return 1
|
||
|
||
info "全部启动完成。"
|
||
info "浏览器访问: http://${SERVER_HOST}:${WEB_PORT}/"
|
||
info "按 Ctrl-C 退出并清理。"
|
||
echo ""
|
||
|
||
trap 'info "退出清理..."; cleanup; exit 0' INT TERM
|
||
while kill -0 "${HOST_PID}" 2>/dev/null; do sleep 1; done
|
||
info "pPowerMangerHost 已退出。"
|
||
cleanup
|
||
}
|
||
|
||
cmd_test() {
|
||
check_binaries || return 1
|
||
start_moosdb || return 1
|
||
start_host || return 1
|
||
|
||
local pass=0 fail=0 r
|
||
|
||
test_http; r=$?; [ $r -eq 0 ] && pass=$((pass+1)) || fail=$((fail+1))
|
||
test_monitor_loop; r=$?; [ $r -eq 0 ] && pass=$((pass+1)) || fail=$((fail+1))
|
||
test_command_notify;r=$?; [ $r -eq 0 ] && pass=$((pass+1)) || fail=$((fail+1))
|
||
|
||
echo ""
|
||
info "==== 测试结果: 通过 ${pass} / 失败 ${fail} ===="
|
||
cleanup
|
||
[ "${fail}" -eq 0 ]
|
||
}
|
||
|
||
cmd_stop() {
|
||
pkill -f "${HOST_BIN}" 2>/dev/null
|
||
pkill -f "${PM_BIN}" 2>/dev/null
|
||
pkill -x MOOSDB 2>/dev/null
|
||
ok "已清理残留进程"
|
||
}
|
||
|
||
case "${1:-run}" in
|
||
run) cmd_run ;;
|
||
test) cmd_test ;;
|
||
build) build_targets ;;
|
||
stop) cmd_stop ;;
|
||
help|-h|--help) usage ;;
|
||
*) usage ;;
|
||
esac
|