From de298414ffc52b1392f12f9899149875863bdc8f Mon Sep 17 00:00:00 2001
From: zjk <1553836110@qq.com>
Date: Fri, 14 Aug 2026 21:29:57 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=EF=BC=9AGoogleTest=20?=
=?UTF-8?q?=E5=85=A8=E9=9D=A2=E5=8D=95=E6=B5=8B=EF=BC=8810=E5=B7=A5?=
=?UTF-8?q?=E5=86=B5=E8=AE=BE=E5=A4=87=E8=A1=A8/=E6=95=85=E9=9A=9C?=
=?UTF-8?q?=E6=A3=80=E6=B5=8B/=E5=8D=8F=E8=AE=AE/=E6=B6=88=E6=81=AF/SQLite?=
=?UTF-8?q?=E8=AF=BB=E5=9B=9E=EF=BC=89+=20UDP=E2=86=92SQLite=20=E9=9B=86?=
=?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95=EF=BC=8Csrc=20=E9=9B=B6=E6=94=B9?=
=?UTF-8?q?=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 4 +
README.md | 19 +++
ci/Dockerfile | 15 ++
ci/build-test.sh | 87 +++++++++--
test/CMakeLists.txt | 88 ++++++++++-
test/integration/check_db.py | 27 ++++
test/integration/udp_feeder.cpp | 54 +++++++
test/until/DriverTest.cpp | 252 +++++++++++++++++++++++++++++++
test/until/FaultMapTest.cpp | 45 ++++++
test/until/PmSysvariableTest.cpp | 138 +++++++++++++++++
test/until/SqliteTest.cpp | 149 ++++++++++++++++++
test/until/SystemDataTest.cpp | 203 +++++++++++++++++++++++++
test/until/UdpCommTest.cpp | 111 ++++++++++++++
test/until/UpmsgTest.cpp | 107 +++++++++++++
14 files changed, 1286 insertions(+), 13 deletions(-)
create mode 100644 test/integration/check_db.py
create mode 100644 test/integration/udp_feeder.cpp
create mode 100644 test/until/DriverTest.cpp
create mode 100644 test/until/FaultMapTest.cpp
create mode 100644 test/until/PmSysvariableTest.cpp
create mode 100644 test/until/SqliteTest.cpp
create mode 100644 test/until/SystemDataTest.cpp
create mode 100644 test/until/UdpCommTest.cpp
create mode 100644 test/until/UpmsgTest.cpp
diff --git a/.gitignore b/.gitignore
index 35143d4..9a673a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -253,3 +253,7 @@ bin/pPMtest
# CI test reports
reports/
+
+# CI test binaries (built into bin/)
+bin/PowerMangerTests
+bin/udpFeeder
diff --git a/README.md b/README.md
index 979cd16..fcceb6e 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,25 @@ H100 能源管理程序
- [最新测试报告 (Markdown)](https://gitea2.zhaojingkui.xyz/zjk/H100PowerManger/src/branch/test-reports/test-report.md)
- [JUnit XML](https://gitea2.zhaojingkui.xyz/zjk/H100PowerManger/raw/branch/test-reports/junit.xml)
+## 自动化测试(PowerManger)
+
+CI 在 aarch64 (QEMU) 环境下运行两类测试,**不改动任何 `src/` 生产代码**:
+
+**单元测试(GoogleTest,`PowerMangerTests`,56 例)**
+- `DriverTest`:10 个工况的设备表生成(STANDBY/岸基备航/水中备航/巡航/高速/上浮下潜/浮调/水下侦查/水面侦查/DJ)+ 设备状态读写 + JSON 往返
+- `SystemDataTest`:4 类配电/CCU 反馈的故障检测(断路器 0x55/0xAA/0x5A 映射、绝缘低、电源失电、漏水、DCDC、继电器位提取、缩放)+ 故障码集合/等级 + 漏水位
+- `FaultMapTest`:锂电池故障表(54) / 配电故障表(45) 完整性
+- `UpmsgTest`:上位机/外部通信 JSON 消息解析与序列化往返
+- `UdpCommTest`:UDP 校验和 / 消息头识别 / 校验解析 / 日期
+- `PmSysvariableTest`:`#pragma pack(1)` 协议结构体大小/偏移 + 枚举完整性
+- `SqliteTest`:用裸 sqlite3 读回校验 10 个 `insertData` 写入
+
+**集成测试**
+- 冒烟:MOOSDB + `pPowerManger` 拉起存活
+- UDP 回环:`udpFeeder` 发送真实 CCU 反馈 → 校验 `power_data.db` 落行(验证 UDP→SystemData→SQLite 全链路)
+
+本地跑测试:需要 GoogleTest(`git clone https://gitea2.zhaojingkui.xyz/zjk/googletest.git` 后 `cmake && make && make install`),然后 `./build.sh` 即可。
+
## 构建产物(Gitea Releases,手动部署)
CI 构建的 aarch64 二进制(RK3588 / Ubuntu 20.04)会发布到 Gitea Releases,供手动下载部署:
diff --git a/ci/Dockerfile b/ci/Dockerfile
index d0aa9a0..a68bb9a 100755
--- a/ci/Dockerfile
+++ b/ci/Dockerfile
@@ -63,4 +63,19 @@ RUN ./build-moos.sh -m -j"$(nproc)" && \
RUN apt-get update && apt-get install -y --no-install-recommends netbase \
&& rm -rf /var/lib/apt/lists/*
+#-----------------------------------------------------------------------
+# GoogleTest (for unit tests) from the internal Gitea mirror.
+# Cloned via the same BuildKit secret as MOOS-IvP; installed to /usr/local
+# so `find_package(GTest)` works both inside the container and locally.
+#-----------------------------------------------------------------------
+RUN --mount=type=secret,id=gitea_token \
+ set -eux; \
+ TOKEN="$(cat /run/secrets/gitea_token)"; \
+ git clone --depth 1 "https://zjk:${TOKEN}@gitea2.zhaojingkui.xyz/zjk/googletest.git" /opt/googletest; \
+ mkdir -p /opt/googletest/build && cd /opt/googletest/build && \
+ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_GMOCK=ON .. && \
+ make -j"$(nproc)" && \
+ make install && \
+ ldconfig
+
WORKDIR /src
diff --git a/ci/build-test.sh b/ci/build-test.sh
index 45a9ae4..67b45be 100755
--- a/ci/build-test.sh
+++ b/ci/build-test.sh
@@ -26,8 +26,8 @@ fi
#-----------------------------------------------------------------------
# Result trackers
#-----------------------------------------------------------------------
-BUILD_OK=0; SQLITE_OK=0; SMOKE_OK=0
-BUILD_TIME=0; SQLITE_TIME=0; SMOKE_TIME=0
+BUILD_OK=0; SQLITE_OK=0; PMTESTS_OK=0; SMOKE_OK=0; INTEG_OK=0
+BUILD_TIME=0; SQLITE_TIME=0; PMTESTS_TIME=0; SMOKE_TIME=0; INTEG_TIME=0
now_ms() { date +%s; }
@@ -72,7 +72,23 @@ if [ "${BUILD_OK}" = "1" ]; then
fi
#-----------------------------------------------------------------------
-# 3. Startup smoke test: MOOSDB + pPowerManger liveness
+# 3. PowerMangerTests (gtest) — 全部纯逻辑单测(工况设备表/故障检测/协议等)
+#-----------------------------------------------------------------------
+if [ "${BUILD_OK}" = "1" ]; then
+ echo ">>> Running PowerMangerTests (gtest)"
+ t0=$(now_ms)
+ if "${BIN_DIR}/PowerMangerTests" --gtest_output=xml:"${REPORT_DIR}/gtest-junit.xml" > /tmp/pmtests.log 2>&1; then
+ PMTESTS_OK=1
+ echo ">>> PowerMangerTests passed"
+ else
+ echo ">>> PowerMangerTests FAILED" >&2
+ tail -40 /tmp/pmtests.log >&2
+ fi
+ PMTESTS_TIME=$(( $(now_ms) - t0 ))
+fi
+
+#-----------------------------------------------------------------------
+# 4. Startup smoke test: MOOSDB + pPowerManger liveness
#-----------------------------------------------------------------------
if [ "${BUILD_OK}" = "1" ]; then
echo ">>> Smoke test: launch MOOSDB + pPowerManger"
@@ -116,6 +132,23 @@ EOF
echo ">>> Smoke test FAILED (pPowerManger exited)" >&2
fi
+ # --- 集成测试:UDP 反馈 → SQLite 落行 ---
+ if [ "${SMOKE_OK}" = "1" ]; then
+ echo ">>> Integration test: feed UDP feedback + check DB"
+ t1=$(now_ms)
+ if "${BIN_DIR}/udpFeeder" 127.0.0.1 5001 6 > /tmp/integ_feed.log 2>&1 \
+ && sleep 2 \
+ && PM_DB="${BIN_DIR}/power_data.db" python3 "${SRC_DIR}/test/integration/check_db.py" > /tmp/integ_check.log 2>&1; then
+ INTEG_OK=1
+ echo ">>> Integration test PASSED (UDP->DB rows)"
+ else
+ echo ">>> Integration test FAILED" >&2
+ cat /tmp/integ_feed.log >&2
+ cat /tmp/integ_check.log >&2
+ fi
+ INTEG_TIME=$(( $(now_ms) - t1 ))
+ fi
+
kill "${PM_PID}" "${MOOSDB_PID}" 2>/dev/null || true
wait "${PM_PID}" "${MOOSDB_PID}" 2>/dev/null || true
SMOKE_TIME=$(( $(now_ms) - t0 ))
@@ -123,11 +156,11 @@ EOF
fi
#-----------------------------------------------------------------------
-# 4. Generate visual test report (markdown + junit.xml)
+# 5. Generate visual test report (markdown + junit.xml)
#-----------------------------------------------------------------------
mark() { [ "$1" = "1" ] && echo "✅ 通过" || echo "❌ 失败"; }
ALL_OK=0
-[ "${BUILD_OK}" = "1" ] && [ "${SQLITE_OK}" = "1" ] && [ "${SMOKE_OK}" = "1" ] && ALL_OK=1
+[ "${BUILD_OK}" = "1" ] && [ "${SQLITE_OK}" = "1" ] && [ "${PMTESTS_OK}" = "1" ] && [ "${SMOKE_OK}" = "1" ] && [ "${INTEG_OK}" = "1" ] && ALL_OK=1
REPORT="${REPORT_DIR}/test-report.md"
: > "$REPORT"
@@ -141,7 +174,9 @@ md "| 步骤 | 结果 | 耗时 |"
md "| :--- | :--- | :--- |"
md "| 构建 (${ARCH}) | $(mark ${BUILD_OK}) | ${BUILD_TIME}s |"
md "| 单元测试 SQLiteTest | $(mark ${SQLITE_OK}) | ${SQLITE_TIME}s |"
+md "| 单元测试 PowerMangerTests (gtest) | $(mark ${PMTESTS_OK}) | ${PMTESTS_TIME}s |"
md "| 冒烟测试 MOOSDB+pPowerManger | $(mark ${SMOKE_OK}) | ${SMOKE_TIME}s |"
+md "| 集成测试 UDP反馈→SQLite | $(mark ${INTEG_OK}) | ${INTEG_TIME}s |"
md ""
if [ "${ALL_OK}" != "1" ]; then
@@ -169,6 +204,17 @@ if [ "${ALL_OK}" != "1" ]; then
md ''
md ""
fi
+ if [ "${PMTESTS_OK}" != "1" ]; then
+ md "### 单元测试 PowerMangerTests — ❌"
+ md ""
+ md 'PowerMangerTests 输出末尾
'
+ md ""
+ md '```text'
+ tail -40 /tmp/pmtests.log >> "$REPORT" 2>/dev/null || true
+ md '```'
+ md ' '
+ md ""
+ fi
if [ "${SMOKE_OK}" != "1" ]; then
md "### 冒烟测试 MOOSDB+pPowerManger — ❌"
md ""
@@ -186,6 +232,23 @@ if [ "${ALL_OK}" != "1" ]; then
md ''
md ""
fi
+ if [ "${INTEG_OK}" != "1" ]; then
+ md "### 集成测试 UDP反馈→SQLite — ❌"
+ md ""
+ md 'feed 日志末尾
'
+ md ""
+ md '```text'
+ tail -20 /tmp/integ_feed.log >> "$REPORT" 2>/dev/null || true
+ md '```'
+ md ''
+ md 'check_db 日志末尾
'
+ md ""
+ md '```text'
+ tail -10 /tmp/integ_check.log >> "$REPORT" 2>/dev/null || true
+ md '```'
+ md ' '
+ md ""
+ fi
fi
md "---"
@@ -197,20 +260,26 @@ if [ -n "${RUN_LINK}" ]; then
md "- 运行: [${RUN_LINK}](${RUN_LINK})"
fi
-# --- JUnit XML report ---
+# --- JUnit XML report(汇总 gtest 单元测试 + 构建 + 冒烟 + 集成) ---
cat > "${REPORT_DIR}/junit.xml" <
-
-
+
+
$([ "${BUILD_OK}" = "1" ] || echo "")
$([ "${SQLITE_OK}" = "1" ] || echo "")
+
+ $([ "${PMTESTS_OK}" = "1" ] || echo "")
+
$([ "${SMOKE_OK}" = "1" ] || echo "")
+
+ $([ "${INTEG_OK}" = "1" ] || echo "")
+
EOF
@@ -219,6 +288,6 @@ echo ">>> Reports generated in ${REPORT_DIR}:"
ls -la "${REPORT_DIR}"
#-----------------------------------------------------------------------
-# 5. Exit status
+# 6. Exit status
#-----------------------------------------------------------------------
[ "${ALL_OK}" = "1" ]
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 1e8a705..7ae9542 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -5,10 +5,12 @@ project(H100PowerMangerTests)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+# 需要 GoogleTest(由 CI Dockerfile 或本地安装,提供 GTestConfig/库)
+find_package(GTest REQUIRED)
-################################################SQLiteTest####################################################################
+################################################SQLiteTest(保留原有)#######
# 添加 SQLite 测试可执行文件(不使用第三方测试库)
-add_executable(SQLiteTest
+add_executable(SQLiteTest
until/testSqlitdb.cpp
${CMAKE_SOURCE_DIR}/src/pPowerManger/sqlit3/SQLite.cpp
${CMAKE_SOURCE_DIR}/src/pPowerManger/sqlit3/sqlite3.c
@@ -16,7 +18,7 @@ add_executable(SQLiteTest
)
# 包含目录
-target_include_directories(SQLiteTest PRIVATE
+target_include_directories(SQLiteTest PRIVATE
${CMAKE_SOURCE_DIR}/src/pPowerManger
${CMAKE_SOURCE_DIR}/src/pPowerManger/sqlit3
)
@@ -27,4 +29,82 @@ target_link_libraries(SQLiteTest
dl
)
-################################################PowerMangerTest####################################################################
+################################################PowerMangerTests(gtest)####
+# 综合单元测试:Driver / SystemData / FaultMap / Upmsg / UdpComm /
+# PmSysvariable / SQLite 读回。仅链接现有 src 源码,不改动生产代码。
+set(PM_SRC_DIR ${CMAKE_SOURCE_DIR}/src/pPowerManger)
+
+set(PM_TEST_SOURCES
+ until/DriverTest.cpp
+ until/SystemDataTest.cpp
+ until/FaultMapTest.cpp
+ until/UpmsgTest.cpp
+ until/UdpCommTest.cpp
+ until/PmSysvariableTest.cpp
+ until/SqliteTest.cpp
+)
+
+add_executable(PowerMangerTests ${PM_TEST_SOURCES}
+ ${PM_SRC_DIR}/driver.cpp
+ ${PM_SRC_DIR}/udpcomm/udpComm.cpp
+ ${PM_SRC_DIR}/upmsg/upmsg_definitions.cpp
+ ${PM_SRC_DIR}/sqlit3/SQLite.cpp
+ ${PM_SRC_DIR}/sqlit3/sqlite3.c
+ ${PM_SRC_DIR}/logc/loguru.cpp
+)
+
+target_include_directories(PowerMangerTests PRIVATE
+ ${PM_SRC_DIR}
+ ${PM_SRC_DIR}/sqlit3
+ ${PM_SRC_DIR}/upmsg
+ ${PM_SRC_DIR}/udpcomm
+ ${PM_SRC_DIR}/logc
+)
+
+# gtest 需要 C++14+
+set_target_properties(PowerMangerTests PROPERTIES
+ CXX_STANDARD 14
+ CXX_STANDARD_REQUIRED ON
+)
+
+# 兼容两种 gtest 安装方式:镜像安装(提供 GTest::gtest_main) 或 apt(仅 GTest_LIBRARIES)
+if(TARGET GTest::gtest_main)
+ set(GTEST_LIBS GTest::gtest_main GTest::gtest)
+else()
+ set(GTEST_LIBS ${GTest_LIBRARIES})
+ target_include_directories(PowerMangerTests PRIVATE ${GTest_INCLUDE_DIRS})
+endif()
+
+target_link_libraries(PowerMangerTests
+ ${GTEST_LIBS}
+ ${MOOS_LIBRARIES}
+ mbutil
+ jsoncpp
+ m
+ pthread
+ dl
+)
+
+################################################udpFeeder(集成测试用)#######
+# 集成测试辅助:向 pPowerManger 发送真实 CCU 状态反馈 UDP 消息(字节级一致)
+add_executable(udpFeeder
+ integration/udp_feeder.cpp
+ ${PM_SRC_DIR}/udpcomm/udpComm.cpp
+ ${PM_SRC_DIR}/logc/loguru.cpp
+)
+target_include_directories(udpFeeder PRIVATE
+ ${PM_SRC_DIR}
+ ${PM_SRC_DIR}/udpcomm
+ ${PM_SRC_DIR}/logc
+)
+set_target_properties(udpFeeder PROPERTIES
+ CXX_STANDARD 11
+ CXX_STANDARD_REQUIRED ON
+)
+target_link_libraries(udpFeeder
+ ${MOOS_LIBRARIES}
+ mbutil
+ m
+ pthread
+ dl
+)
diff --git a/test/integration/check_db.py b/test/integration/check_db.py
new file mode 100644
index 0000000..f646884
--- /dev/null
+++ b/test/integration/check_db.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+#=======================================================================
+# check_db.py: 校验 pPowerManger 写入的 power_data.db 是否有 CCU 反馈记录
+#(验证 UDP 反馈 → SystemData → SQLite 全链路)。
+#=======================================================================
+import sys, os, sqlite3
+
+def main():
+ db = os.environ.get('PM_DB', '/src/bin/power_data.db')
+ if not os.path.exists(db):
+ print(f"DB not found: {db}")
+ return 1
+ conn = sqlite3.connect(db)
+ cur = conn.cursor()
+ try:
+ cur.execute("SELECT COUNT(*) FROM ccuState")
+ n = cur.fetchone()[0]
+ except Exception as e: # noqa: BLE001
+ print(f"query failed: {e}")
+ conn.close()
+ return 1
+ conn.close()
+ print(f"ccuState rows = {n}")
+ return 0 if n > 0 else 1
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/test/integration/udp_feeder.cpp b/test/integration/udp_feeder.cpp
new file mode 100644
index 0000000..ef9c6cc
--- /dev/null
+++ b/test/integration/udp_feeder.cpp
@@ -0,0 +1,54 @@
+//=======================================================================
+// udp_feeder.cpp: 集成测试用 — 向 pPowerManger 发送 CCU 状态反馈 UDP 消息。
+// 直接使用真实 C++ 结构体构造消息(字节级与协议一致,无 Python 打包漂移)。
+// 用法: udpFeeder [host] [port] [count]
+//=======================================================================
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "udpcomm/udpComm.h"
+#include "udpcomm/ccuUdpMsg.h"
+
+int main(int argc, char* argv[])
+{
+ const char* host = (argc > 1) ? argv[1] : "127.0.0.1";
+ int port = (argc > 2) ? atoi(argv[2]) : 5001;
+ int count = (argc > 3) ? atoi(argv[3]) : 6;
+
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) { perror("socket"); return 1; }
+ sockaddr_in addr{};
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(static_cast(port));
+ if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) {
+ perror("inet_pton");
+ close(fd);
+ return 1;
+ }
+
+ udpComm u;
+ for (int i = 0; i < count; i++) {
+ msg_CcuStateFbMsg m;
+ memset(&m, 0, sizeof(m));
+ m.header.start1 = CCU_UDPMSG_START1;
+ m.header.start2 = CCU_UDPMSG_START2;
+ m.header.id = CCU_STATE_FB_ID;
+ m.data.ins_soc = 42;
+ m.data.dyn_soc = 66;
+ m.data.fc_status = 0x04;
+ m.data.heartbeat = i + 1;
+ m.checkCode = u.calculateChecksum(reinterpret_cast(&m), sizeof(m));
+ ssize_t n = sendto(fd, &m, sizeof(m), 0, reinterpret_cast(&addr), sizeof(addr));
+ if (n < 0) { perror("sendto"); close(fd); return 1; }
+ std::this_thread::sleep_for(std::chrono::milliseconds(300));
+ }
+ close(fd);
+ printf("sent %d CCU state msgs to %s:%d\n", count, host, port);
+ return 0;
+}
diff --git a/test/until/DriverTest.cpp b/test/until/DriverTest.cpp
new file mode 100644
index 0000000..cf330d8
--- /dev/null
+++ b/test/until/DriverTest.cpp
@@ -0,0 +1,252 @@
+//=======================================================================
+// DriverTest: Driver 类的设备表生成(覆盖全部 10 个工况)、设备状态读写、
+// JSON 序列化/反序列化。
+// 不改动生产代码;仅链接 src/pPowerManger/driver.cpp。
+//=======================================================================
+#include
+#include
+#include "driver.h"
+#include "pmSysvariable.h"
+
+// ----------------------------------------------------------------------
+// 全字段比较宏(62 个设备字段)
+// ----------------------------------------------------------------------
+#define EXPECT_TABLE_EQ(a, b) do { \
+ EXPECT_EQ((a).depth1,(b).depth1); EXPECT_EQ((a).depth2,(b).depth2); \
+ EXPECT_EQ((a).depth3,(b).depth3); EXPECT_EQ((a).overDeep1,(b).overDeep1); \
+ EXPECT_EQ((a).overDeep2,(b).overDeep2); EXPECT_EQ((a).ctd1,(b).ctd1); \
+ EXPECT_EQ((a).ctd2,(b).ctd2); EXPECT_EQ((a).soundVeloc,(b).soundVeloc); \
+ EXPECT_EQ((a).commMast,(b).commMast); EXPECT_EQ((a).ins,(b).ins); \
+ EXPECT_EQ((a).ntp,(b).ntp); \
+ EXPECT_EQ((a).inputWaterSensor1,(b).inputWaterSensor1); \
+ EXPECT_EQ((a).inputWaterSensor2,(b).inputWaterSensor2); \
+ EXPECT_EQ((a).inputWaterSensor3,(b).inputWaterSensor3); \
+ EXPECT_EQ((a).singleBeamSonar1,(b).singleBeamSonar1); \
+ EXPECT_EQ((a).singleBeamSonar2,(b).singleBeamSonar2); \
+ EXPECT_EQ((a).singleBeamSonar3,(b).singleBeamSonar3); \
+ EXPECT_EQ((a).singleBeamSonar4,(b).singleBeamSonar4); \
+ EXPECT_EQ((a).singleBeamSonar5,(b).singleBeamSonar5); \
+ EXPECT_EQ((a).singleBeamSonar6,(b).singleBeamSonar6); \
+ EXPECT_EQ((a).singleBeamSonar7,(b).singleBeamSonar7); \
+ EXPECT_EQ((a).singleBeamSonar8,(b).singleBeamSonar8); \
+ EXPECT_EQ((a).forwardSonar,(b).forwardSonar); EXPECT_EQ((a).DVL,(b).DVL); \
+ EXPECT_EQ((a).lowFreqSonar,(b).lowFreqSonar); EXPECT_EQ((a).USBL,(b).USBL); \
+ EXPECT_EQ((a).bowWaterInet1,(b).bowWaterInet1); \
+ EXPECT_EQ((a).bowWaterInet2,(b).bowWaterInet2); \
+ EXPECT_EQ((a).bowWaterInet3,(b).bowWaterInet3); \
+ EXPECT_EQ((a).bowWaterInet4,(b).bowWaterInet4); \
+ EXPECT_EQ((a).bowWaterInet5,(b).bowWaterInet5); \
+ EXPECT_EQ((a).bowWaterInet6,(b).bowWaterInet6); \
+ EXPECT_EQ((a).bowWaterInet7,(b).bowWaterInet7); \
+ EXPECT_EQ((a).bowWaterInet8,(b).bowWaterInet8); \
+ EXPECT_EQ((a).bowRudderDeploy,(b).bowRudderDeploy); \
+ EXPECT_EQ((a).bowRudderServo1,(b).bowRudderServo1); \
+ EXPECT_EQ((a).bowRudderServo2,(b).bowRudderServo2); \
+ EXPECT_EQ((a).bowCover1,(b).bowCover1); EXPECT_EQ((a).bowCover2,(b).bowCover2); \
+ EXPECT_EQ((a).bowCover3,(b).bowCover3); EXPECT_EQ((a).bowCover4,(b).bowCover4); \
+ EXPECT_EQ((a).bowCover5,(b).bowCover5); EXPECT_EQ((a).bowCover6,(b).bowCover6); \
+ EXPECT_EQ((a).waterInject,(b).waterInject); \
+ EXPECT_EQ((a).mastLiftingServo,(b).mastLiftingServo); \
+ EXPECT_EQ((a).buoyancyAdjust1,(b).buoyancyAdjust1); \
+ EXPECT_EQ((a).buoyancyAdjust2,(b).buoyancyAdjust2); \
+ EXPECT_EQ((a).buoyancyAdjust3,(b).buoyancyAdjust3); \
+ EXPECT_EQ((a).buoyancyAdjust4,(b).buoyancyAdjust4); \
+ EXPECT_EQ((a).buoyancyAdjust5,(b).buoyancyAdjust5); \
+ EXPECT_EQ((a).sternRudderServo1,(b).sternRudderServo1); \
+ EXPECT_EQ((a).sternRudderServo2,(b).sternRudderServo2); \
+ EXPECT_EQ((a).sternRudderServo3,(b).sternRudderServo3); \
+ EXPECT_EQ((a).sternRudderServo4,(b).sternRudderServo4); \
+ EXPECT_EQ((a).thruster,(b).thruster); EXPECT_EQ((a).bowThrow,(b).bowThrow); \
+ EXPECT_EQ((a).sternThrow,(b).sternThrow); \
+ EXPECT_EQ((a).photoSensor,(b).photoSensor); \
+ EXPECT_EQ((a).electSensor,(b).electSensor); \
+ EXPECT_EQ((a).launchControler,(b).launchControler); \
+ EXPECT_EQ((a).sideSonar,(b).sideSonar); EXPECT_EQ((a).buoyage,(b).buoyage); \
+} while (0)
+
+// 传感器统一全开
+static void setSensors(DriverTable& t) {
+ t.depth1 = t.depth2 = t.depth3 = 1;
+ t.overDeep1 = t.overDeep2 = 1;
+ t.ctd1 = t.ctd2 = 1;
+ t.soundVeloc = 1;
+ t.commMast = 1;
+ t.ins = 1;
+ t.ntp = 1;
+ t.inputWaterSensor1 = t.inputWaterSensor2 = t.inputWaterSensor3 = 1;
+}
+
+// 执行机构(舵/浮调/推进/抛载)全开
+static void setActuatorsOn(DriverTable& t) {
+ t.bowRudderDeploy = t.bowRudderServo1 = t.bowRudderServo2 = 1;
+ t.buoyancyAdjust1 = t.buoyancyAdjust2 = t.buoyancyAdjust3 = 1;
+ t.buoyancyAdjust4 = t.buoyancyAdjust5 = 1;
+ t.sternRudderServo1 = t.sternRudderServo2 = t.sternRudderServo3 = t.sternRudderServo4 = 1;
+ t.thruster = 1;
+ t.bowThrow = t.sternThrow = 1;
+}
+
+static DriverTable makeSTANDBY() {
+ DriverTable t{}; setSensors(t); /* 其余全 0 */ return t;
+}
+static DriverTable makeSHORE_WATER() {
+ DriverTable t{}; setSensors(t); setActuatorsOn(t);
+ t.mastLiftingServo = 1;
+ t.bowCover1 = t.bowCover2 = t.bowCover3 = 1;
+ t.bowCover4 = t.bowCover5 = t.bowCover6 = 1;
+ t.bowWaterInet1 = t.bowWaterInet2 = t.bowWaterInet3 = t.bowWaterInet4 = 1;
+ t.bowWaterInet5 = t.bowWaterInet6 = t.bowWaterInet7 = t.bowWaterInet8 = 1;
+ t.waterInject = 1;
+ return t;
+}
+static DriverTable makeCRUISE() {
+ DriverTable t{}; setSensors(t); setActuatorsOn(t);
+ t.mastLiftingServo = 1;
+ return t; // 水声设备不设置(保持 0)
+}
+static DriverTable makeHIGH_SPEED() {
+ DriverTable t{}; setSensors(t);
+ t.bowRudderDeploy = t.bowRudderServo1 = t.bowRudderServo2 = 1;
+ t.sternRudderServo1 = t.sternRudderServo2 = t.sternRudderServo3 = t.sternRudderServo4 = 1;
+ t.thruster = 1; t.bowThrow = t.sternThrow = 1;
+ /* mastLiftingServo / buoyancyAdjust 全 0 */
+ return t;
+}
+static DriverTable makeASCEND_BUOYANCY() {
+ DriverTable t{}; setSensors(t); setActuatorsOn(t);
+ t.mastLiftingServo = 1;
+ return t;
+}
+static DriverTable makeUNDERWATER() {
+ DriverTable t{}; setSensors(t); setActuatorsOn(t);
+ t.sideSonar = DRIVER_STANDBY; /* mastLiftingServo = 0 */
+ return t;
+}
+static DriverTable makeSURFACE() {
+ DriverTable t{}; setSensors(t); setActuatorsOn(t);
+ t.mastLiftingServo = 1;
+ t.photoSensor = t.electSensor = DRIVER_STANDBY;
+ return t;
+}
+static DriverTable makeDJ() {
+ DriverTable t{}; setSensors(t); setActuatorsOn(t);
+ t.mastLiftingServo = 1;
+ t.launchControler = 1;
+ t.buoyage = DRIVER_STANDBY;
+ t.bowCover1 = t.bowCover2 = t.bowCover3 = 1;
+ t.bowCover4 = t.bowCover5 = t.bowCover6 = 1;
+ t.bowWaterInet1 = t.bowWaterInet2 = t.bowWaterInet3 = t.bowWaterInet4 = 1;
+ t.bowWaterInet5 = t.bowWaterInet6 = t.bowWaterInet7 = t.bowWaterInet8 = 1;
+ return t;
+}
+
+using TableFn = void (Driver::*)(DriverTable*);
+
+TEST(DriverTest, StandbyTable) {
+ DriverTable e = makeSTANDBY(); DriverTable a{}; Driver d; d.getSTANDBYTable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, ShoreBasedReadyTable) {
+ DriverTable e = makeSHORE_WATER(); DriverTable a{}; Driver d; d.getSHORE_BASED_READYable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, WaterBasedReadyTable) {
+ DriverTable e = makeSHORE_WATER(); DriverTable a{}; Driver d; d.getWATER_BASED_READYable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, CruiseModeTable) {
+ DriverTable e = makeCRUISE(); DriverTable a{}; Driver d; d.getCRUISE_MODEable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, HighSpeedModeTable) {
+ DriverTable e = makeHIGH_SPEED(); DriverTable a{}; Driver d; d.getHIGH_SPEED_MODEable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, AscendDescendTable) {
+ DriverTable e = makeASCEND_BUOYANCY(); DriverTable a{}; Driver d; d.getASCEND_DESCENDable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, BuoyancyAdjustTable) {
+ DriverTable e = makeASCEND_BUOYANCY(); DriverTable a{}; Driver d; d.getBUOYANCY_ADJUSTable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, UnderwaterReconTable) {
+ DriverTable e = makeUNDERWATER(); DriverTable a{}; Driver d; d.getUNDERWATER_RECONable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, SurfaceReconTable) {
+ DriverTable e = makeSURFACE(); DriverTable a{}; Driver d; d.getSURFACE_RECONable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+TEST(DriverTest, DjModeTable) {
+ DriverTable e = makeDJ(); DriverTable a{}; Driver d; d.getDJ_MODEable(&a);
+ EXPECT_TABLE_EQ(a, e);
+}
+
+// ----------------------------------------------------------------------
+// setDeviceState / getDeviceState
+// ----------------------------------------------------------------------
+TEST(DriverTest, SetGetDeviceState)
+{
+ Driver d;
+ DriverTable t{};
+ d.setDeviceState(t, DeviceId::thruster, DRIVER_FULL_POWER_ON);
+ EXPECT_EQ(d.getDeviceState(t, DeviceId::thruster), 1u);
+ d.setDeviceState(t, DeviceId::thruster, DRIVER_POWER_OFF);
+ EXPECT_EQ(d.getDeviceState(t, DeviceId::thruster), 0u);
+ d.setDeviceState(t, DeviceId::sideSonar, DRIVER_STANDBY);
+ EXPECT_EQ(d.getDeviceState(t, DeviceId::sideSonar), 4u);
+ d.setDeviceState(t, DeviceId::bowCover1, DRIVER_FULL_POWER_ON);
+ EXPECT_EQ(d.getDeviceState(t, DeviceId::bowCover1), 1u);
+}
+
+// ----------------------------------------------------------------------
+// JSON 序列化/反序列化往返
+// ----------------------------------------------------------------------
+TEST(DriverTest, JsonRoundTrip)
+{
+ Driver d;
+ DriverTable t{};
+ d.setDeviceState(t, DeviceId::thruster, DRIVER_FULL_POWER_ON);
+ d.setDeviceState(t, DeviceId::sideSonar, DRIVER_STANDBY);
+ d.setDeviceState(t, DeviceId::bowCover3, DRIVER_FULL_POWER_ON);
+ std::string json = d.saveDriverTableToJson(&t);
+ ASSERT_FALSE(json.empty());
+
+ DriverTable out{};
+ EXPECT_EQ(d.loadDriverTableFromJson(json, out), 0);
+ EXPECT_EQ(out.thruster, 1u);
+ EXPECT_EQ(out.sideSonar, 4u);
+ EXPECT_EQ(out.bowCover3, 1u);
+ EXPECT_EQ(out.depth1, 0u); // 未设置字段为 0
+}
+
+TEST(DriverTest, LoadDriverIdFromJson)
+{
+ Driver d;
+ std::string json = R"({"thruster_uint8":1,"sideSonar_uint8":4})";
+ auto cmds = d.loadDriverIdFromJson(json);
+ ASSERT_EQ(cmds.size(), 2u);
+ bool hasThruster = false, hasSide = false;
+ for (auto& c : cmds) {
+ if (c.id == DeviceId::thruster) { EXPECT_EQ(c.cmd, 1); hasThruster = true; }
+ if (c.id == DeviceId::sideSonar) { EXPECT_EQ(c.cmd, 4); hasSide = true; }
+ }
+ EXPECT_TRUE(hasThruster);
+ EXPECT_TRUE(hasSide);
+}
+
+TEST(DriverTest, SafeReadUInt)
+{
+ Driver d;
+ Json::Value root;
+ root["a"] = 5u;
+ root["b"] = -3;
+ unsigned char v = 0;
+ EXPECT_EQ(d.SafeReadUInt(root, "a", v), 0);
+ EXPECT_EQ(v, 5u);
+ // 缺字段
+ EXPECT_EQ(d.SafeReadUInt(root, "missing", v), -1);
+ // 类型错误(有符号负数)
+ EXPECT_EQ(d.SafeReadUInt(root, "b", v), -2);
+}
diff --git a/test/until/FaultMapTest.cpp b/test/until/FaultMapTest.cpp
new file mode 100644
index 0000000..fb50ab0
--- /dev/null
+++ b/test/until/FaultMapTest.cpp
@@ -0,0 +1,45 @@
+//=======================================================================
+// FaultMapTest: 锂电池/配电故障码表的完整性(码值唯一、级别正确)
+// 不改动生产代码;仅验证 faultCode.h 中的静态表。
+//=======================================================================
+#include
+#include
+#include "faultCode.h"
+
+TEST(FaultMapTest, BatteryFaultMap)
+{
+ const size_t n = sizeof(batfaultMap) / sizeof(batfaultMap[0]);
+ ASSERT_EQ(n, 54u) << "batfaultMap 应有 54 条";
+ std::set codes;
+ for (size_t i = 0; i < n; ++i) {
+ EXPECT_EQ(batfaultMap[i].id, (int)i + 1) << "id 必须从 1 连续";
+ EXPECT_GE(batfaultMap[i].level, 1);
+ EXPECT_LE(batfaultMap[i].level, 4);
+ EXPECT_TRUE(batfaultMap[i].desc != nullptr && batfaultMap[i].desc[0] != '\0')
+ << "desc 不能为空, id=" << i + 1;
+ EXPECT_TRUE(codes.insert(batfaultMap[i].code).second)
+ << "重复故障码 0x" << std::hex << (int)batfaultMap[i].code;
+ }
+ // 抽样:4级故障码在 0xCx,3级在 0x8x,1级在 0x0x-0x1x
+ EXPECT_EQ(batfaultMap[0].code, 0xC1); // id1 四级 MBMS绝缘故障
+ EXPECT_EQ(batfaultMap[9].code, 0x81); // id10 三级
+ EXPECT_EQ(batfaultMap[40].code, 0x01); // id41 一级
+}
+
+TEST(FaultMapTest, DistributionFaultMap)
+{
+ const size_t n = sizeof(disfaultMap) / sizeof(disfaultMap[0]);
+ ASSERT_EQ(n, 45u) << "disfaultMap 应有 45 条";
+ std::set codes;
+ for (size_t i = 0; i < n; ++i) {
+ EXPECT_EQ(disfaultMap[i].id, (int)i + 1) << "id 必须从 1 连续";
+ EXPECT_GE(disfaultMap[i].level, 1);
+ EXPECT_LE(disfaultMap[i].level, 4);
+ EXPECT_TRUE(disfaultMap[i].desc != nullptr && disfaultMap[i].desc[0] != '\0');
+ EXPECT_TRUE(codes.insert(disfaultMap[i].code).second)
+ << "重复故障码 " << disfaultMap[i].code;
+ }
+ // 断路器脱扣基本是 4 级故障
+ EXPECT_EQ(disfaultMap[0].level, 3); // id1 燃料电池断路器 3级
+ EXPECT_EQ(disfaultMap[1].level, 4); // id2 动力锂电池断路器 4级
+}
diff --git a/test/until/PmSysvariableTest.cpp b/test/until/PmSysvariableTest.cpp
new file mode 100644
index 0000000..dcf66c6
--- /dev/null
+++ b/test/until/PmSysvariableTest.cpp
@@ -0,0 +1,138 @@
+//=======================================================================
+// PmSysvariableTest: 协议结构体 pack(1) 大小/偏移 + 枚举/宏完整性
+// 不改动生产代码;检测二进制协议漂移与枚举值变化。
+//=======================================================================
+#include
+#include
+#include
+#include "pmSysvariable.h"
+#include "driver.h"
+#include "udpcomm/ccuUdpMsg.h"
+
+// ----------------------------------------------------------------------
+// pack(1) 协议结构体大小断言
+// ----------------------------------------------------------------------
+TEST(PmSysvariableTest, HeaderStructSize)
+{
+ EXPECT_EQ(sizeof(m_header), 6u); // start1+start2+id+length = 1+1+2+2
+ EXPECT_EQ(offsetof(m_header, id), 2u);
+ EXPECT_EQ(offsetof(m_header, length), 4u);
+}
+
+TEST(PmSysvariableTest, PackedMessageStructSizes)
+{
+ // header(6) + flag(1) + failureCode(1) + Checksum(4)
+ EXPECT_EQ(sizeof(msg_CcuSetParmFbMsg), 12u);
+ // header(6) + m_date(8) + ccuColCmd + Checksum(4)
+ EXPECT_EQ(sizeof(msg_CcuColCmdMsg), 6u + sizeof(m_date) + sizeof(ccuColCmd) + 4u);
+ // header(6) + disHighVolBusCmd + cChecksum(1)
+ EXPECT_EQ(sizeof(msg_disHVBCmd), 6u + sizeof(disHighVolBusCmd) + 1u);
+ // header(6) + ccuState + Checksum(4)
+ EXPECT_EQ(sizeof(msg_CcuStateFbMsg), 6u + sizeof(ccuState) + 4u);
+}
+
+TEST(PmSysvariableTest, NoImplicitPadding)
+{
+ // pack(1):字段偏移必须连续(抽样检查,防意外 padding)
+ EXPECT_EQ(offsetof(ccuColCmd, heartbeat), offsetof(ccuColCmd, powerBatEfficiency) + sizeof(unsigned short));
+ EXPECT_EQ(offsetof(disHighVolBusCmd, coolingSystem),
+ offsetof(disHighVolBusCmd, dcdc5Module) + 1);
+}
+
+// ----------------------------------------------------------------------
+// 宏值
+// ----------------------------------------------------------------------
+TEST(PmSysvariableTest, MacroConstants)
+{
+ EXPECT_EQ(BREAK_ON, 0x55);
+ EXPECT_EQ(BREAK_OFF, 0xAA);
+ EXPECT_EQ(BREAK_FALSE, 0x5A);
+ EXPECT_EQ(DIS_BREAKER_ON, 0x55);
+ EXPECT_EQ(DIS_BREAKER_OFF, 0xAA);
+ EXPECT_EQ(DIS_FAULTOFF, 0x5A);
+ EXPECT_EQ(DRIVER_POWER_OFF, 0x00);
+ EXPECT_EQ(DRIVER_FULL_POWER_ON, 0x01);
+ EXPECT_EQ(DRIVER_INSTRUMENT_POWER_ON, 0x02);
+ EXPECT_EQ(DRIVER_MOTIVE_POWER_ON, 0x03);
+ EXPECT_EQ(DRIVER_STANDBY, 0x04);
+}
+
+// ----------------------------------------------------------------------
+// 工况枚举
+// ----------------------------------------------------------------------
+TEST(PmSysvariableTest, WorkConditionEnum)
+{
+ EXPECT_EQ((int)WorkCondition::CHANING, 0);
+ EXPECT_EQ((int)WorkCondition::STANDBY, 1);
+ EXPECT_EQ((int)WorkCondition::SHORE_BASED_READY, 2);
+ EXPECT_EQ((int)WorkCondition::WATER_BASED_READY, 3);
+ EXPECT_EQ((int)WorkCondition::REMOTE_CONTROL, 4);
+ EXPECT_EQ((int)WorkCondition::CRUISE_MODE, 5);
+ EXPECT_EQ((int)WorkCondition::HIGH_SPEED_MODE, 6);
+ EXPECT_EQ((int)WorkCondition::ASCEND_DESCEND, 7);
+ EXPECT_EQ((int)WorkCondition::BUOYANCY_ADJUST, 8);
+ EXPECT_EQ((int)WorkCondition::UNDERWATER_RECON, 9);
+ EXPECT_EQ((int)WorkCondition::SURFACE_RECON, 10);
+ EXPECT_EQ((int)WorkCondition::DJ_MODE, 11);
+ EXPECT_EQ((int)WorkCondition::LIFT_MODE, 12);
+}
+
+TEST(PmSysvariableTest, TaskTypeEnum)
+{
+ EXPECT_EQ((int)TaskType::THROW_LOAD, 1);
+ EXPECT_EQ((int)TaskType::FLOAT_UP, 2);
+ EXPECT_EQ((int)TaskType::RECYCLE, 3);
+ EXPECT_EQ((int)TaskType::HOVER, 4);
+ EXPECT_EQ((int)TaskType::SAT_COMM, 5);
+ EXPECT_EQ((int)TaskType::SAT_CALIBRATE, 6);
+ EXPECT_EQ((int)TaskType::TOUR, 7);
+ EXPECT_EQ((int)TaskType::SAIL, 8);
+ EXPECT_EQ((int)TaskType::EMER_SAT_COMM, 9);
+ EXPECT_EQ((int)TaskType::EMER_SAIL, 10);
+ EXPECT_STREQ(TaskType::toString(TaskType::SAIL).c_str(), "sail");
+ EXPECT_STREQ(TaskType::toString(TaskType::EMER_SAIL).c_str(), "emerSail");
+ EXPECT_EQ((int)TaskType::fromString("tour"), (int)TaskType::TOUR);
+}
+
+TEST(PmSysvariableTest, ModeEnum)
+{
+ EXPECT_EQ((int)PMMode::Normal, 0);
+ EXPECT_EQ((int)PMMode::Test, 1);
+ EXPECT_EQ((int)PMMode::Debug, 2);
+ EXPECT_EQ((int)RunMode::Normal, 0);
+ EXPECT_EQ((int)RunMode::Test, 1);
+ EXPECT_EQ((int)RunMode::Debug, 2);
+}
+
+TEST(PmSysvariableTest, EquipmentCommandEnum)
+{
+ EXPECT_EQ((int)EquipmentCommand::OFF, 0);
+ EXPECT_EQ((int)EquipmentCommand::ON, 1);
+ EXPECT_EQ((int)EquipmentCommand::METER_ON, 2);
+ EXPECT_EQ((int)EquipmentCommand::POWER_ON, 3);
+ EXPECT_EQ((int)EquipmentCommand::METER_OFF, 4);
+ EXPECT_EQ((int)EquipmentCommand::POWER_OFF, 5);
+}
+
+TEST(PmSysvariableTest, DeviceIdSpotCheck)
+{
+ EXPECT_EQ((int)DeviceId::thruster, 53);
+ EXPECT_EQ((int)DeviceId::sideSonar, 35);
+ EXPECT_EQ((int)DeviceId::bowRudderDeploy, 15);
+ EXPECT_EQ((int)DeviceId::mastLiftingServo, 41);
+ EXPECT_EQ((int)DeviceId::buoyancyAdjust1, 44);
+ EXPECT_EQ((int)DeviceId::sternRudderServo1, 49);
+ EXPECT_EQ((int)DeviceId::commMast, 40);
+ EXPECT_STREQ(DeviceId::toString(DeviceId::thruster).c_str(), "thruster");
+}
+
+TEST(PmSysvariableTest, DeviceIdSets)
+{
+ // 水声设备集合与无电设备集合互斥
+ EXPECT_TRUE(underWterDevices.count(DeviceId::DVL) == 1);
+ EXPECT_TRUE(underWterDevices.count(DeviceId::singleBeamSonar1) == 1);
+ EXPECT_TRUE(noPowerDevices.count(DeviceId::depth1) == 1);
+ EXPECT_TRUE(noPowerDevices.count(DeviceId::ins) == 1);
+ // 无电设备不应出现在水声设备里
+ EXPECT_TRUE(underWterDevices.count(DeviceId::depth1) == 0);
+}
diff --git a/test/until/SqliteTest.cpp b/test/until/SqliteTest.cpp
new file mode 100644
index 0000000..3832811
--- /dev/null
+++ b/test/until/SqliteTest.cpp
@@ -0,0 +1,149 @@
+//=======================================================================
+// SqliteTest(gtest): 验证 SQLite 封装 10 个 insertData 写入正确。
+// 不改动 SQLite 封装;用裸 sqlite3 打开同一文件读回验证。
+//=======================================================================
+#include
+#include
+#include
+#include
+#include "sqlit3/SQLite.h"
+#include "sqlit3/sqlite3.h"
+
+static std::string tmpPath(const char* name) {
+ return std::string("/tmp/pmsqlite_") + name + "_" + std::to_string(::getpid()) + ".db";
+}
+
+static bool rawQueryInt(const std::string& path, const std::string& sql, long long& out) {
+ sqlite3* db = nullptr;
+ if (sqlite3_open(path.c_str(), &db) != SQLITE_OK) return false;
+ sqlite3_stmt* stmt = nullptr;
+ bool ok = false;
+ if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
+ if (sqlite3_step(stmt) == SQLITE_ROW) { out = sqlite3_column_int64(stmt, 0); ok = true; }
+ }
+ if (stmt) sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ return ok;
+}
+
+static bool rawCount(const std::string& path, const std::string& table, long long& out) {
+ return rawQueryInt(path, "SELECT COUNT(*) FROM " + table, out);
+}
+
+TEST(SqliteTest, InsertCcuStateReadBack)
+{
+ std::string path = tmpPath("ccu");
+ {
+ SQLite db(path);
+ ASSERT_TRUE(db.createTable());
+ ccuState c{}; c.ins_soc = 42; c.dyn_soc = 66; c.fc_status = 4;
+ EXPECT_TRUE(db.insertData(c));
+ }
+ long long v = 0;
+ EXPECT_TRUE(rawQueryInt(path, "SELECT ins_soc FROM ccuState ORDER BY id DESC LIMIT 1", v));
+ EXPECT_EQ(v, 42);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT dyn_soc FROM ccuState ORDER BY id DESC LIMIT 1", v));
+ EXPECT_EQ(v, 66);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT fc_status FROM ccuState ORDER BY id DESC LIMIT 1", v));
+ EXPECT_EQ(v, 4);
+ std::remove(path.c_str());
+}
+
+TEST(SqliteTest, InsertDisHighVolBusStateReadBack)
+{
+ std::string path = tmpPath("dihv");
+ {
+ SQLite db(path);
+ ASSERT_TRUE(db.createTable());
+ disHighVolBusState s{};
+ s.fuelCellCircuitBreaker = 1;
+ s.powerBusVoltage = 5200;
+ EXPECT_TRUE(db.insertData(s));
+ }
+ long long v = 0;
+ EXPECT_TRUE(rawQueryInt(path, "SELECT fuelCellCircuitBreaker FROM disHighVolBusState ORDER BY id DESC LIMIT 1", v));
+ EXPECT_EQ(v, 1);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT powerBusVoltage FROM disHighVolBusState ORDER BY id DESC LIMIT 1", v));
+ EXPECT_EQ(v, 5200);
+ std::remove(path.c_str());
+}
+
+TEST(SqliteTest, InsertDisHighAVolBusStateReadBack)
+{
+ std::string path = tmpPath("dihva");
+ {
+ SQLite db(path); ASSERT_TRUE(db.createTable());
+ disHighAVolBusState s{}; s.bowFTDevice123CircuitBreaker = 1; s.busbarBVoltage = 100;
+ EXPECT_TRUE(db.insertData(s));
+ }
+ long long v = 0;
+ EXPECT_TRUE(rawQueryInt(path, "SELECT bowFTDevice123CircuitBreaker FROM disHighAVolBusState", v));
+ EXPECT_EQ(v, 1);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT busbarBVoltage FROM disHighAVolBusState", v));
+ EXPECT_EQ(v, 100);
+ std::remove(path.c_str());
+}
+
+TEST(SqliteTest, InsertDisLowMainBusStateReadBack)
+{
+ std::string path = tmpPath("dilmb");
+ {
+ SQLite db(path); ASSERT_TRUE(db.createTable());
+ disLowMainBusState s{}; s.unit4InstrumentDC48VCircuitBreaker = 1;
+ EXPECT_TRUE(db.insertData(s));
+ }
+ long long v = 0;
+ EXPECT_TRUE(rawQueryInt(path, "SELECT unit4InstrumentDC48VCircuitBreaker FROM disLowMainBusState", v));
+ EXPECT_EQ(v, 1);
+ std::remove(path.c_str());
+}
+
+TEST(SqliteTest, InsertDisLowBusStateReadBack)
+{
+ std::string path = tmpPath("dilb");
+ {
+ SQLite db(path); ASSERT_TRUE(db.createTable());
+ disLowBusState s{}; s.unit1CircuitBreaker = 1;
+ EXPECT_TRUE(db.insertData(s));
+ }
+ long long v = 0;
+ EXPECT_TRUE(rawQueryInt(path, "SELECT unit1CircuitBreaker FROM disLowBusState", v));
+ EXPECT_EQ(v, 1);
+ std::remove(path.c_str());
+}
+
+TEST(SqliteTest, InsertCommandsReadBack)
+{
+ std::string path = tmpPath("cmd");
+ {
+ SQLite db(path); ASSERT_TRUE(db.createTable());
+ ccuColCmd cc{}; cc.heartbeat = 5; EXPECT_TRUE(db.insertData(cc));
+ disHighVolBusCmd c1{}; c1.dcdc5Module = 1; c1.coolingSystem = 1; EXPECT_TRUE(db.insertData(c1));
+ disHighAVolBusCmd c2{}; c2.xczCircuitBreaker = 1; EXPECT_TRUE(db.insertData(c2));
+ disLowMainBusCmd c3{}; c3.dcC1DCDistributionPanelCircuitBreaker = 1; EXPECT_TRUE(db.insertData(c3));
+ disLowBusCmd c4{}; c4.unit2CircuitBreaker = 1; EXPECT_TRUE(db.insertData(c4));
+ }
+ long long v = 0;
+ EXPECT_TRUE(rawQueryInt(path, "SELECT heartbeat FROM ccuColCmd", v)); EXPECT_EQ(v, 5);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT dcdc5Module FROM disHighVolBusCmd", v)); EXPECT_EQ(v, 1);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT xczCircuitBreaker FROM disHighAVolBusCmd", v)); EXPECT_EQ(v, 1);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT dcC1DCDistributionPanelCircuitBreaker FROM disLowMainBusCmd", v)); EXPECT_EQ(v, 1);
+ EXPECT_TRUE(rawQueryInt(path, "SELECT unit2CircuitBreaker FROM disLowBusCmd", v)); EXPECT_EQ(v, 1);
+ std::remove(path.c_str());
+}
+
+TEST(SqliteTest, MultipleInsertsCount)
+{
+ std::string path = tmpPath("multi");
+ {
+ SQLite db(path); ASSERT_TRUE(db.createTable());
+ ccuState c{}; c.ins_soc = 10;
+ EXPECT_TRUE(db.insertData(c));
+ EXPECT_TRUE(db.insertData(c));
+ EXPECT_TRUE(db.insertData(c));
+ }
+ long long n = 0;
+ EXPECT_TRUE(rawCount(path, "ccuState", n));
+ EXPECT_EQ(n, 3);
+ std::remove(path.c_str());
+}
diff --git a/test/until/SystemDataTest.cpp b/test/until/SystemDataTest.cpp
new file mode 100644
index 0000000..df494ab
--- /dev/null
+++ b/test/until/SystemDataTest.cpp
@@ -0,0 +1,203 @@
+//=======================================================================
+// SystemDataTest: SystemData 的故障检测逻辑(断路器映射、绝缘、电源失电、
+// 漏水、DCDC、继电器位提取、缩放、故障码集合、漏水位)。
+// 不改动生产代码;仅链接现有 systemData.h + SQLite + MOOS。
+//=======================================================================
+#include
+#include "MOOS/libMOOS/Utils/MOOSUtilityFunctions.h" // MOOSTime (updateCommonStatus)
+#include "systemData.h"
+#include "faultCode.h"
+
+TEST(SystemDataTest, BreakerMapping)
+{
+ SystemData sd;
+ unsigned char status = 0;
+ // 0x55 = 闭合(1),无故障码
+ sd.updateBreakerFault(status, 0x55, 99);
+ EXPECT_EQ(status, 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(99), 0u);
+ // 0xAA = 断开(0),无故障码
+ sd.updateBreakerFault(status, 0xAA, 99);
+ EXPECT_EQ(status, 0u);
+ EXPECT_EQ(sd.disSysFaultCode.count(99), 0u);
+ // 0x5A = 故障脱扣(2),插入故障码
+ sd.updateBreakerFault(status, 0x5A, 99);
+ EXPECT_EQ(status, 2u);
+ EXPECT_EQ(sd.disSysFaultCode.count(99), 1u);
+ // 再次正常后擦除
+ sd.updateBreakerFault(status, 0x55, 99);
+ EXPECT_EQ(sd.disSysFaultCode.count(99), 0u);
+}
+
+TEST(SystemDataTest, HighVolBusFaultDetection)
+{
+ SystemData sd;
+ disHighVolBusState s{};
+ // 全 0xAA(断开,正常)
+ s.fuelCellCircuitBreaker = 0xAA;
+ s.powerLithiumBatteryCircuitBreaker = 0xAA;
+ s.propulsionMotorCircuitBreaker = 0xAA;
+ s.lithiumBatteryGroupInstrumentCircuitBreaker = 0xAA;
+ s.dcDc5ModuleCircuitBreaker = 0xAA;
+ s.bowHighVoltageDistributionBoxCircuitBreaker = 0xAA;
+ s.sternFTDevice45CircuitBreaker = 0xAA;
+ s.sternRudderSwitch1CircuitBreaker = 0xAA;
+ s.sternRudderSwitch2CircuitBreaker = 0xAA;
+ s.powerBusInsulationStatus = 0x55;
+ s.aBusInsulationStatus = 0x55;
+ s.meterPowerLossSignal = 0x55;
+ s.emergencyPowerLossSignal = 0x55;
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.power_bus_status.fuelCellCircuitBreaker, 0u);
+ EXPECT_EQ(sd.disSysFaultCode.count(1), 0u);
+
+ // 燃料电断路器故障脱扣
+ s.fuelCellCircuitBreaker = 0x5A;
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.power_bus_status.fuelCellCircuitBreaker, 2u);
+ EXPECT_EQ(sd.disSysFaultCode.count(1), 1u);
+
+ // 动力锂电池闭合 + A汇流排/动力母排绝缘低 → 34/35
+ s.fuelCellCircuitBreaker = 0xAA;
+ s.powerLithiumBatteryCircuitBreaker = 0x55;
+ s.powerBusInsulationStatus = 0xAA;
+ s.aBusInsulationStatus = 0xAA;
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.disSysFaultCode.count(34), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(35), 1u);
+ EXPECT_EQ(sd.power_bus_status.powerBusInsulationStatus, 1u);
+ EXPECT_EQ(sd.power_bus_status.aBusInsulationStatus, 1u);
+
+ // 电源失电 → 36/37
+ s.meterPowerLossSignal = 0xAA;
+ s.emergencyPowerLossSignal = 0xAA;
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.disSysFaultCode.count(36), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(37), 1u);
+
+ // 全部恢复正常后故障码擦除
+ s.powerLithiumBatteryCircuitBreaker = 0xAA;
+ s.powerBusInsulationStatus = 0x55;
+ s.aBusInsulationStatus = 0x55;
+ s.meterPowerLossSignal = 0x55;
+ s.emergencyPowerLossSignal = 0x55;
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.disSysFaultCode.count(34), 0u);
+ EXPECT_EQ(sd.disSysFaultCode.count(35), 0u);
+ EXPECT_EQ(sd.disSysFaultCode.count(36), 0u);
+ EXPECT_EQ(sd.disSysFaultCode.count(37), 0u);
+}
+
+TEST(SystemDataTest, HighAVolBusFaultDetection)
+{
+ SystemData sd;
+ disHighAVolBusState s{};
+ s.actuatorCircuitBreaker = 0x5A; // 启闭机构断路器脱扣 → 14
+ s.xczCircuitBreaker = 0x55;
+ s.instrumentPowerFailureSignal = 0x55;
+ s.emergencyPowerFailureSignal = 0x55;
+ s.waterIngressionAlarm = 0x55;
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.power_a_bus_status.actuatorCircuitBreaker, 2u);
+ EXPECT_EQ(sd.disSysFaultCode.count(14), 1u);
+
+ s.actuatorCircuitBreaker = 0xAA;
+ s.instrumentPowerFailureSignal = 0xAA; // 失电 → 38
+ s.emergencyPowerFailureSignal = 0xAA; // 失电 → 39
+ s.waterIngressionAlarm = 0xAA; // 浸水 → leakStatus[BOW_HIGH_VOLTAGE]
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.disSysFaultCode.count(14), 0u);
+ EXPECT_EQ(sd.disSysFaultCode.count(38), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(39), 1u);
+ EXPECT_TRUE(sd.leakStatus[SystemData::LEAKAGE_BOW_HIGH_VOLTAGE]);
+}
+
+TEST(SystemDataTest, LowMainBusFaultDetection)
+{
+ SystemData sd;
+ disLowMainBusState s{};
+ s.lithiumBatteryGroupInstrumentCircuitBreaker = 0x5A; // 19
+ s.unit4InstrumentDC48VCircuitBreaker = 0x5A; // 21
+ s.instrumentPowerFailureSignal = 0xAA; // 41
+ s.emergencyPowerFailureSignal = 0xAA; // 42
+ s.instrumentBusbarInsulationLow = 0xAA; // 43
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.disSysFaultCode.count(19), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(21), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(41), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(42), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(43), 1u);
+}
+
+TEST(SystemDataTest, LowBusFaultDetection)
+{
+ SystemData sd;
+ disLowBusState s{};
+ s.unit1CircuitBreaker = 0x5A; // 26
+ s.emergencyLithiumBatteryGroup1CircuitBreaker = 0x5A; // 33
+ s.powerFailureSignal = 0xAA; // 44
+ s.emergencyPowerFailureSignal = 0xAA; // 45
+ s.waterIngressionAlarm = 0xAA; // leakage BOW_LOW_VOLTAGE
+ sd.updateSystemData(s);
+ EXPECT_EQ(sd.disSysFaultCode.count(26), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(33), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(44), 1u);
+ EXPECT_EQ(sd.disSysFaultCode.count(45), 1u);
+ EXPECT_TRUE(sd.leakStatus[SystemData::LEAKAGE_BOW_LOW_VOLTAGE]);
+}
+
+TEST(SystemDataTest, CcuStateScalingAndRelayBits)
+{
+ SystemData sd;
+ ccuState c{};
+ c.ins_soc = 60;
+ c.dyn_soc = 80;
+ c.ins_relay_status1 = 0x12; // 高4位=1(充电), 低4位=2(正极)
+ c.ins_relay_status2 = 0x34;
+ c.ins_voltage_link = 4800; // ×0.1 = 480.0
+ c.ins_water_leak_dcdc_status = 0x11; // 高4位=1 漏水, 低4位=1 DCDC故障
+ c.dyn_water_leak_status = 0x21; // 高4位=2, 低4位=1
+ sd.updateSystemData(c);
+ EXPECT_EQ(sd.ins_soc, 60);
+ EXPECT_EQ(sd.dyn_soc, 80);
+ EXPECT_EQ(sd.ins_pos_relay_status, 2u); // 低4位
+ EXPECT_EQ(sd.ins_charge_relay_status, 1u); // 高4位
+ EXPECT_EQ(sd.ins_precharge_relay_status, 4u); // 低4位 of status2
+ EXPECT_EQ(sd.ins_neg_relay_status, 3u); // 高4位 of status2
+ EXPECT_NEAR(sd.ins_voltage_link, 480.0, 0.01);
+ EXPECT_EQ(sd.water_leak_status_from_insbat, 1u);
+ EXPECT_EQ(sd.dcdc_status_from_insbat, 1u);
+ EXPECT_TRUE(sd.leakStatus[SystemData::LEAKAGE_LI_BATTERY]);
+}
+
+TEST(SystemDataTest, FaultCodeSetAndLevel)
+{
+ SystemData sd;
+ EXPECT_EQ(sd.getFaultCodeLevel(), 0u);
+ sd.insertFaultCode(0x1102); // 0x11>>... level = (0x1102>>8)&0xF = 1
+ EXPECT_EQ(sd.getFaultCodeLevel(), 1u);
+ sd.insertFaultCode(0x4402); // level = (0x4402>>8)&0xF = 4
+ EXPECT_EQ(sd.getFaultCodeLevel(), 4u);
+ sd.eraseFaultCode(0x4402);
+ EXPECT_EQ(sd.getFaultCodeLevel(), 1u);
+ auto codes = sd.getFaultCode();
+ EXPECT_EQ(codes.count(0x1102), 1u);
+}
+
+TEST(SystemDataTest, LeakageCodeGeneration)
+{
+ SystemData sd;
+ EXPECT_EQ(sd.generateLeakageCode(), 0u);
+ sd.leakStatus[SystemData::LEAKAGE_BOW_EMERGENCY] = true; // 0x01
+ sd.leakStatus[SystemData::LEAKAGE_FUEL_CELL] = true; // 0x08
+ sd.leakStatus[SystemData::LEAKAGE_LI_BATTERY] = true; // 0x10
+ EXPECT_EQ(sd.generateLeakageCode(), 0x19u);
+}
+
+TEST(SystemDataTest, CommonStatusTimeout)
+{
+ SystemData sd;
+ sd.updateCommonStatus(30.0);
+ // 新建 SystemData 的 lastUpdateTime=0,与当前 MOOSTime 之差远大于 30s
+ EXPECT_TRUE(sd.device_common_status.ccu_status.isTimeout);
+}
diff --git a/test/until/UdpCommTest.cpp b/test/until/UdpCommTest.cpp
new file mode 100644
index 0000000..a651063
--- /dev/null
+++ b/test/until/UdpCommTest.cpp
@@ -0,0 +1,111 @@
+//=======================================================================
+// UdpCommTest: udpComm 纯逻辑函数(校验和、消息头识别、校验解析、日期)
+// 仅链接现有 udpcomm/udpComm.cpp(+ MOOS 提供 MOOSTime 等符号)。
+//=======================================================================
+#include
+#include
+#include "udpcomm/udpComm.h"
+
+TEST(UdpCommTest, CalculateChecksum)
+{
+ udpComm u;
+ unsigned char data[5] = {1, 2, 3, 4, 5};
+ // 2 参:sum 覆盖 size - sizeof(Checksum)=size-4 字节
+ EXPECT_EQ(u.calculateChecksum(data, 4), 0u);
+ EXPECT_EQ(u.calculateChecksum(data, 5), 1u); // 只 sum data[0]=1
+ EXPECT_EQ(u.calculateChecksum(data, 6), 3u); // sum data[0..1]=1+2
+ // 3 参:覆盖 dataSize - checkSize 字节
+ EXPECT_EQ(u.calculateChecksum(data, 5, 2), 6u); // sum data[0..2]=1+2+3
+ EXPECT_EQ(u.calculateChecksum(data, 5, 1), 10u);// sum data[0..3]
+}
+
+TEST(UdpCommTest, GetMsgId)
+{
+ udpComm u;
+ m_header h{};
+ char buf[sizeof(m_header)];
+ unsigned short id = 0;
+
+ // DIS 头
+ h.start1 = DIS_MSG_HEAD1;
+ h.start2 = DIS_MSG_HEAD2;
+ h.id = DIS_HVBUS_FB_ID;
+ memcpy(buf, &h, sizeof(h));
+ EXPECT_TRUE(u.getMsgId(buf, id));
+ EXPECT_EQ(id, DIS_HVBUS_FB_ID);
+
+ // CCU 头
+ h.start1 = CCU_UDPMSG_START1;
+ h.start2 = CCU_UDPMSG_START2;
+ h.id = CCU_STATE_FB_ID;
+ memcpy(buf, &h, sizeof(h));
+ EXPECT_TRUE(u.getMsgId(buf, id));
+ EXPECT_EQ(id, CCU_STATE_FB_ID);
+
+ // 非法头
+ h.start1 = 0x11;
+ h.start2 = 0x22;
+ memcpy(buf, &h, sizeof(h));
+ EXPECT_FALSE(u.getMsgId(buf, id));
+}
+
+TEST(UdpCommTest, GetMsgFromBuffCcu)
+{
+ udpComm u;
+ msg_CcuStateFbMsg m;
+ memset(&m, 0, sizeof(m));
+ m.header.start1 = CCU_UDPMSG_START1;
+ m.header.start2 = CCU_UDPMSG_START2;
+ m.header.id = CCU_STATE_FB_ID;
+ m.data.ins_soc = 42;
+ m.data.dyn_soc = 66;
+ m.data.fc_status = 0x04;
+ m.checkCode = u.calculateChecksum((unsigned char*)&m, sizeof(m));
+
+ msg_CcuStateFbMsg out{};
+ EXPECT_TRUE(u.getMsgFromBuff((char*)&m, out));
+ EXPECT_EQ(out.data.ins_soc, 42u);
+ EXPECT_EQ(out.data.dyn_soc, 66u);
+ EXPECT_EQ(out.data.fc_status, 0x04u);
+
+ // 篡改数据 → 校验失败
+ m.data.dyn_soc = 99;
+ EXPECT_FALSE(u.getMsgFromBuff((char*)&m, out));
+}
+
+TEST(UdpCommTest, GetMsgFromBuffDis)
+{
+ udpComm u;
+ msg_disHighVolBusFbMsg m;
+ memset(&m, 0, sizeof(m));
+ m.header.start1 = DIS_MSG_HEAD1;
+ m.header.start2 = DIS_MSG_HEAD2;
+ m.header.id = DIS_HVBUS_FB_ID;
+ m.data.fuelCellCircuitBreaker = 0x55;
+ m.data.powerBusVoltage = 5200;
+ m.checkCode = (cChecksum)(u.calculateChecksum((unsigned char*)&m, sizeof(m), sizeof(cChecksum)) & 0xFF);
+
+ msg_disHighVolBusFbMsg out{};
+ EXPECT_TRUE(u.getMsgFromBuff((char*)&m, out));
+ EXPECT_EQ(out.data.fuelCellCircuitBreaker, 0x55u);
+ EXPECT_EQ(out.data.powerBusVoltage, 5200u);
+
+ // 篡改 → 校验失败
+ m.data.fuelCellCircuitBreaker = 0xAA;
+ EXPECT_FALSE(u.getMsgFromBuff((char*)&m, out));
+}
+
+TEST(UdpCommTest, GetDate)
+{
+ udpComm u;
+ unsigned short year = 0;
+ unsigned char month = 0, day = 0, hour = 0, minute = 0, second = 0, ms = 0;
+ EXPECT_TRUE(u.getDate(year, month, day, hour, minute, second, ms));
+ EXPECT_GE(year, 2000u);
+ EXPECT_LE(year, 2100u);
+ EXPECT_GE(month, 1u); EXPECT_LE(month, 12u);
+ EXPECT_GE(day, 1u); EXPECT_LE(day, 31u);
+ EXPECT_LE(hour, 23u);
+ EXPECT_LE(minute, 59u);
+ EXPECT_LE(second, 59u);
+}
diff --git a/test/until/UpmsgTest.cpp b/test/until/UpmsgTest.cpp
new file mode 100644
index 0000000..e2ed676
--- /dev/null
+++ b/test/until/UpmsgTest.cpp
@@ -0,0 +1,107 @@
+//=======================================================================
+// UpmsgTest: 上位机/外部通信 JSON 消息解析与序列化往返
+// 仅链接现有 upmsg/*.h + upmsg_definitions.cpp。
+// 注意:部分 upmsg 头文件未包含 json/loguru/pmSysvariable,需先包含。
+//=======================================================================
+#include
+#include "json/json.h"
+#include "logc/loguru.hpp"
+#include "pmSysvariable.h"
+#include "upmsg/uPower_sysState.h"
+#include "upmsg/uExternComm_setOpMode.h"
+#include "upmsg/uExternComm_setOpCondition.h"
+#include "upmsg/uExternComm_setPowerSystem.h"
+#include "upmsg/uExternComm_setDeviceSwitch.h"
+#include "upmsg/uPlan_taskStart.h"
+#include "upmsg/uPlan_planStop.h"
+#include "upmsg/uDetect_xcVoltage_cmd.h"
+#include "upmsg/uDetect_zdpVoltage_cmd.h"
+
+TEST(UpmsgTest, PowerSysStateParse)
+{
+ uPower_sysStateMsg msg;
+ MSControlCmd cmd;
+ EXPECT_TRUE(msg.parseControlCmdFromJson(cmd, R"({"powerCMD":1,"modCMD":2,"workCMD":5})"));
+ EXPECT_EQ(cmd.powerCMD, 1u);
+ EXPECT_EQ(cmd.modCMD, 2u);
+ EXPECT_EQ(cmd.workCMD, 5u);
+ // 缺字段 → false
+ EXPECT_FALSE(msg.parseControlCmdFromJson(cmd, R"({"powerCMD":1})"));
+ // 非法 JSON → false
+ EXPECT_FALSE(msg.parseControlCmdFromJson(cmd, "{bad json"));
+}
+
+TEST(UpmsgTest, SetOpMode)
+{
+ uExternComm_setOpModeMsg msg;
+ EXPECT_EQ(msg.getSetOpMode("uExternComm_setOpMode_cmd", R"({"state_uint8":1})"), 1);
+ EXPECT_EQ(msg.getSetOpMode("uExternComm_setOpMode_cmd", R"({})"), -1);
+ EXPECT_EQ(msg.getSetOpMode("wrong_key", R"({"state_uint8":1})"), 0); // key 不匹配 → 0
+}
+
+TEST(UpmsgTest, SetOpCondition)
+{
+ uExternComm_setOpConditionMsg msg;
+ EXPECT_EQ(msg.getSetOpCondition("uExternComm_setOpCondition_cmd", R"({"state_uint8":7})"), 7);
+ EXPECT_EQ(msg.getSetOpCondition("uExternComm_setOpCondition_cmd", R"({})"), -1);
+}
+
+TEST(UpmsgTest, SetPowerSystem)
+{
+ uExternComm_setPowerSystemMsg msg;
+ EXPECT_EQ(msg.getSetPMSys("uExternComm_setPowerSystem_cmd", R"({"powerCmd_uint8":4})"), 4);
+ EXPECT_EQ(msg.getSetPMSys("uExternComm_setPowerSystem_cmd", R"({})"), -1);
+}
+
+TEST(UpmsgTest, SetDeviceSwitch)
+{
+ uExternComm_setDeviceSwitchMsg msg;
+ DeviceSwitchCmd c = msg.getSetDevSwitch("uExternComm_setDeviceSwitch_cmd",
+ R"({"id_uint8":53,"cmd_uint8":1})");
+ EXPECT_EQ(c.id, 53);
+ EXPECT_EQ(c.cmd, 1);
+ // 空对象 → id=-1
+ DeviceSwitchCmd c2 = msg.getSetDevSwitch("uExternComm_setDeviceSwitch_cmd", R"({})");
+ EXPECT_EQ(c2.id, -1);
+}
+
+TEST(UpmsgTest, TaskStart)
+{
+ uPlan_taskStartMsg msg;
+ MissionState m = msg.getMission("uPlan_taskStart_st",
+ R"({"taskType_str":"sail","balanceEnabled_bool":true,"bowRudderUnfold_bool":false})");
+ EXPECT_EQ(m.type, (int)TaskType::SAIL);
+ EXPECT_TRUE(m.enableBuoyancyAdjustment);
+ EXPECT_FALSE(m.enableBowRudder);
+}
+
+TEST(UpmsgTest, TaskStop)
+{
+ uPlan_planStopMsg msg;
+ EXPECT_TRUE(msg.getTaskStop("uPlan_planStop_st", R"({"op_int32":1})"));
+ EXPECT_FALSE(msg.getTaskStop("uPlan_planStop_st", R"({"op_int32":0})"));
+ EXPECT_FALSE(msg.getTaskStop("uPlan_planStop_st", R"({})"));
+}
+
+TEST(UpmsgTest, XcVoltageRoundTrip)
+{
+ uDetect_xcVoltage_cmd cmd;
+ cmd.xcLowVoltageAdd_int32 = 1;
+ cmd.xcHighVoltageAdd_int32 = 2;
+ std::string json = cmd.toJsonString();
+ uDetect_xcVoltage_cmd cmd2;
+ EXPECT_TRUE(cmd2.fromJsonString(json));
+ EXPECT_EQ(cmd2.xcLowVoltageAdd_int32, 1);
+ EXPECT_EQ(cmd2.xcHighVoltageAdd_int32, 2);
+ EXPECT_FALSE(cmd2.fromJsonString("{bad json"));
+}
+
+TEST(UpmsgTest, ZdpVoltageRoundTrip)
+{
+ uDetect_zdpVoltage_cmd cmd;
+ cmd.zdpVoltageAdd_int32 = 2;
+ std::string json = cmd.toJsonString();
+ uDetect_zdpVoltage_cmd cmd2;
+ EXPECT_TRUE(cmd2.fromJsonString(json));
+ EXPECT_EQ(cmd2.zdpVoltageAdd_int32, 2);
+}