diff --git a/.gitignore b/.gitignore index 334c5c4..bbb4681 100644 --- a/.gitignore +++ b/.gitignore @@ -260,6 +260,12 @@ reports/ # CI test binaries (built into bin/) bin/PowerMangerTests bin/udpFeeder +bin/fullFeeder + +# SQLite runtime artifacts +*.db-shm +*.db-wal +*.db-journal # Cross-compiled aarch64 artifacts (scripts/cross-build-local.sh) bin/arm64/ diff --git a/scripts/clean-data.sh b/scripts/clean-data.sh new file mode 100755 index 0000000..e83e4d6 --- /dev/null +++ b/scripts/clean-data.sh @@ -0,0 +1,138 @@ +#!/bin/bash +#======================================================================= +# FILE: scripts/clean-data.sh +# DESC: 清理目标板卡 (RK3588 / Ubuntu 22.04) 上的数据库与日志文件。 +# 默认清理板卡运行目录 /root/work/h100/data/ 下的: +# power_data.db (+ -wal/-shm) 主机功率数据库 +# feedback_data.db 反馈落盘数据库 +# pPowerManger.log 主机运行日志 +# 默认先停止 3 个服务再清理(避免 SQLite WAL 仍在写入)。 +# +# 用法: +# ./scripts/clean-data.sh # 停服务 -> 清理 data 目录(不重启) +# ./scripts/clean-data.sh --start # 清理后重新启动 3 个服务 +# ./scripts/clean-data.sh --no-stop # 不停服务直接清理(慎用) +# ./scripts/clean-data.sh --stale # 额外清理 bin/ 与 src/bin/ 下的旧 db/log +# +# 参数: +# --host 目标主机(默认 192.168.0.223) +# --user 登录用户(默认 root) +# --board-dir 板卡部署根目录(默认 /root/work/h100) +# --start 清理后重启服务 +# --no-stop 不停服务直接清理(慎用) +# --stale 额外清理历史遗留的 bin/、src/bin/ 下旧数据库与日志 +# -h, --help 显示帮助 +# +# 前置: 板卡已配免密 SSH(见 scripts/deploy.sh setup-ssh)。 +#======================================================================= + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")" + +HOST="192.168.0.223" +USER="root" +BOARD_DIR="/root/work/h100" +DO_START=0 +DO_STOP=1 +DO_STALE=0 + +SERVICES=(moosdb pPowerManger pPowerMangerHost) + +# 板卡 data 目录下需要清理的文件(含 SQLite WAL/SHM) +DATA_FILES=(power_data.db power_data.db-wal power_data.db-shm feedback_data.db pPowerManger.log) + +# 历史遗留目录(旧部署位置),仅当 --stale 时清理 +STALE_DIRS=("${BOARD_DIR}/bin" "${BOARD_DIR}/src/bin") + +info() { printf '\033[1;36m[clean-data]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[clean-data]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[clean-data]\033[0m %s\n' "$*"; } +err() { printf '\033[1;31m[clean-data]\033[0m %s\n' "$*"; } + +usage() { + sed -n '2,29p' "${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-dir) [ $# -ge 2 ] || { err "--board-dir 需要参数"; exit 1; }; BOARD_DIR="$2"; shift 2 ;; + --start) DO_START=1; shift ;; + --no-stop) DO_STOP=0; shift ;; + --stale) DO_STALE=1; shift ;; + -h|--help) usage ;; + *) err "未知参数: $1(见 --help)"; exit 1 ;; + esac +done + +STALE_DIRS=("${BOARD_DIR}/bin" "${BOARD_DIR}/src/bin") + +SSH_TARGET="${USER}@${HOST}" +SSH="ssh -o BatchMode=yes -o ConnectTimeout=5" + +#------------------------------------------------------------------- +# 1. 前置检查 +#------------------------------------------------------------------- +if ! ${SSH} "${SSH_TARGET}" 'true' 2>/dev/null; then + err "无法免密 SSH 到 ${SSH_TARGET}" + err "请先执行: ./scripts/deploy.sh setup-ssh" + exit 1 +fi +ok "SSH 连接正常: ${SSH_TARGET}" + +#------------------------------------------------------------------- +# 2. 停止服务(默认) +#------------------------------------------------------------------- +if [ "${DO_STOP}" = "1" ]; then + info "停止服务 ${SERVICES[*]} ..." + ${SSH} "${SSH_TARGET}" "systemctl stop pPowerMangerHost pPowerManger moosdb 2>/dev/null; true" + ok "服务已停止" +else + warn "--no-stop:不停服务直接清理,可能产生脏数据(SQLite 仍在写入)" +fi + +#------------------------------------------------------------------- +# 3. 清理 data 目录 +#------------------------------------------------------------------- +BOARD_DATA="${BOARD_DIR}/data" +info "清理 ${BOARD_DATA}/ ..." +for f in "${DATA_FILES[@]}"; do + ${SSH} "${SSH_TARGET}" "rm -f ${BOARD_DATA}/${f}" \ + && ok "删除 ${f}" || warn "删除 ${f} 失败(可能不存在)" +done + +#------------------------------------------------------------------- +# 4. 可选: 清理历史遗留目录 +#------------------------------------------------------------------- +if [ "${DO_STALE}" = "1" ]; then + for d in "${STALE_DIRS[@]}"; do + info "清理历史遗留 ${d}/ 下的 db/log ..." + ${SSH} "${SSH_TARGET}" "rm -f ${d}/power_data.db ${d}/power_data.db-wal ${d}/power_data.db-shm ${d}/feedback_data.db ${d}/pPowerManger.log 2>/dev/null; true" + ok "${d} 已清理" + done +fi + +#------------------------------------------------------------------- +# 5. 可选: 重启服务 +#------------------------------------------------------------------- +if [ "${DO_START}" = "1" ]; then + info "启动服务 (moosdb -> pPowerManger -> pPowerMangerHost) ..." + ${SSH} "${SSH_TARGET}" "systemctl start moosdb && sleep 1 && \ + systemctl start pPowerManger pPowerMangerHost" \ + || { err "启动失败"; exit 1; } + ok "服务已启动" +else + echo "" + info "清理完成,服务未启动。需要重启请加 --start 或手动:" + echo " systemctl start moosdb pPowerManger pPowerMangerHost" +fi + +echo "" +ok "完成。" diff --git a/scripts/fetch-data.sh b/scripts/fetch-data.sh new file mode 100755 index 0000000..e654432 --- /dev/null +++ b/scripts/fetch-data.sh @@ -0,0 +1,125 @@ +#!/bin/bash +#======================================================================= +# FILE: scripts/fetch-data.sh +# DESC: 从目标板卡 (RK3588 / Ubuntu 22.04) 下载数据库与日志到本地。 +# 默认抓取板卡运行目录 /root/work/h100/data/ 下的: +# power_data.db (+ -wal/-shm) 主机功率数据库 +# feedback_data.db 反馈落盘数据库 +# pPowerManger.log 主机运行日志 +# 可选 --journal 一并导出三个 systemd 服务的 journal 日志。 +# +# 用法: +# ./scripts/fetch-data.sh # 下载 db + 日志到本地 data/board-<时间>/ +# ./scripts/fetch-data.sh --journal # 额外导出 systemd journal +# ./scripts/fetch-data.sh --out-dir /tmp/xx # 指定本地保存目录 +# +# 参数: +# --host 目标主机(默认 192.168.0.223) +# --user 登录用户(默认 root) +# --board-dir 板卡部署根目录(默认 /root/work/h100) +# --out-dir 本地保存目录(默认 data/board-<时间>) +# --journal 额外导出 3 个服务的 systemd journal 日志 +# -h, --help 显示帮助 +# +# 前置: 板卡已配免密 SSH(见 scripts/deploy.sh setup-ssh)。 +#======================================================================= + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "${SCRIPT_DIR}")" + +HOST="192.168.0.223" +USER="root" +BOARD_DIR="/root/work/h100" +OUT_DIR="" +DO_JOURNAL=0 + +SERVICES=(moosdb pPowerManger pPowerMangerHost) + +# 板卡 data 目录下需要下载的文件(含 SQLite WAL/SHM 以保证数据完整) +DATA_FILES=(power_data.db power_data.db-wal power_data.db-shm feedback_data.db pPowerManger.log) + +info() { printf '\033[1;36m[fetch-data]\033[0m %s\n' "$*"; } +ok() { printf '\033[1;32m[fetch-data]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[fetch-data]\033[0m %s\n' "$*"; } +err() { printf '\033[1;31m[fetch-data]\033[0m %s\n' "$*"; } + +usage() { + sed -n '2,26p' "${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-dir) [ $# -ge 2 ] || { err "--board-dir 需要参数"; exit 1; }; BOARD_DIR="$2"; shift 2 ;; + --out-dir) [ $# -ge 2 ] || { err "--out-dir 需要参数"; exit 1; }; OUT_DIR="$2"; shift 2 ;; + --journal) DO_JOURNAL=1; shift ;; + -h|--help) usage ;; + *) err "未知参数: $1(见 --help)"; exit 1 ;; + esac +done + +SSH_TARGET="${USER}@${HOST}" +SSH="ssh -o BatchMode=yes -o ConnectTimeout=5" +SCP="scp -o BatchMode=yes -o ConnectTimeout=5" + +#------------------------------------------------------------------- +# 1. 前置检查 +#------------------------------------------------------------------- +if ! ${SSH} "${SSH_TARGET}" 'true' 2>/dev/null; then + err "无法免密 SSH 到 ${SSH_TARGET}" + err "请先执行: ./scripts/deploy.sh setup-ssh" + exit 1 +fi +ok "SSH 连接正常: ${SSH_TARGET}" + +if [ -z "${OUT_DIR}" ]; then + OUT_DIR="${PROJECT_ROOT}/data/board-$(date +%Y%m%d-%H%M%S)" +fi +mkdir -p "${OUT_DIR}" || { err "无法创建本地目录 ${OUT_DIR}"; exit 1; } +info "本地保存目录: ${OUT_DIR}" + +#------------------------------------------------------------------- +# 2. 下载 data 目录下的数据库与日志 +#------------------------------------------------------------------- +BOARD_DATA="${BOARD_DIR}/data" +missing=0 +for f in "${DATA_FILES[@]}"; do + if ${SSH} "${SSH_TARGET}" "[ -f ${BOARD_DATA}/${f} ]" 2>/dev/null; then + info "下载 ${f} ..." + ${SCP} -q "${SSH_TARGET}:${BOARD_DATA}/${f}" "${OUT_DIR}/" \ + || { err "下载 ${f} 失败"; exit 1; } + ok "${f}" + else + warn "板卡上不存在: ${BOARD_DATA}/${f}(跳过)" + missing=$((missing+1)) + fi +done +[ "${missing}" -eq "${#DATA_FILES[@]}" ] && { err "板卡 data 目录下没有任何可下载文件"; exit 1; } + +#------------------------------------------------------------------- +# 3. 可选: 导出 systemd journal +#------------------------------------------------------------------- +if [ "${DO_JOURNAL}" = "1" ]; then + for s in "${SERVICES[@]}"; do + info "导出 journal: ${s} ..." + ${SSH} "${SSH_TARGET}" "journalctl --no-pager -u ${s} 2>/dev/null" > "${OUT_DIR}/${s}.journal.log" \ + || { warn "导出 ${s} journal 失败(可能无记录)"; } + ok "${s}.journal.log" + done +fi + +#------------------------------------------------------------------- +# 4. 完成 +#------------------------------------------------------------------- +echo "" +ok "下载完成。本地文件:" +ls -lah "${OUT_DIR}" | sed 's/^/ /' +echo "" +info "查看数据库可用: sqlite3 ${OUT_DIR}/power_data.db .tables" diff --git a/src/pPowerManger/LowerCommManager.cpp b/src/pPowerManger/LowerCommManager.cpp index c6ed56d..b9e0a33 100644 --- a/src/pPowerManger/LowerCommManager.cpp +++ b/src/pPowerManger/LowerCommManager.cpp @@ -80,17 +80,23 @@ void LowerCommManager::setCommLogger(ICommLogger* logger) if (m_udpComm) m_udpComm->setCommLogger(logger); } void LowerCommManager::processPeriodicTasks() { - sendCcuCommand(m_pm->m_CcuColCmd); - m_pm->m_db->insertData(m_pm->m_CcuColCmd); + ccuColCmd cmd; + { + std::lock_guard lock(m_pm->m_stateMutex); + cmd = m_pm->m_CcuColCmd; + } + sendCcuCommand(cmd); + m_pm->m_db->insertData(cmd); //延时5ms usleep(5000); - if(m_pm->m_systemData->device_common_status.dis_hv_c1.isTimeout ) + auto devStatus = m_pm->m_systemData->getDeviceCommonStatus(); + if(devStatus.dis_hv_c1.isTimeout ) { disHighVolBusCmd clr1{}; m_udpComm->sendDisSysCmd(clr1); MOOSPause(200); } - if(m_pm->m_systemData->device_common_status.dis_lv_c1.isTimeout ) + if(devStatus.dis_lv_c1.isTimeout ) { disHighBVolBusCmd clr3{}; m_udpComm->sendDisSysCmd(clr3); @@ -195,10 +201,14 @@ bool LowerCommManager::updatePoweSystemStates(){ double time; if(m_udpComm->popMsgFormQueue(time, m_CcuCurrentState)) { - m_pm->m_pmCurrentState.ccustate = m_CcuCurrentState.data; - m_pm->m_db->insertData(m_pm->m_pmCurrentState.ccustate); //写入数据库 + ccuState local = m_CcuCurrentState.data; + { + std::lock_guard lock(m_pm->m_stateMutex); + m_pm->m_pmCurrentState.ccustate = local; + } + m_pm->m_db->insertData(local); //写入数据库 msg_CcuStateFbMsg_Update_time = time; - m_pm->m_systemData->updateSystemData(m_pm->m_pmCurrentState.ccustate); + m_pm->m_systemData->updateSystemData(local); { std::lock_guard lock(m_pm->m_systemData->m_dataMutex); m_pm->m_systemData->device_common_status.ccu_status.lastUpdateTime = m_pm->m_lowerCommManager->msg_CcuStateFbMsg_Update_time; @@ -219,29 +229,29 @@ bool LowerCommManager::updatePoweSystemStates(){ double time; if(m_udpComm->popMsgFormQueue(time, m_disHighVolBusCurrentState)) { - // m_udpComm->popMsgFormQueue(time,m_disHighVolBusCurrentState); - m_pm->m_pmCurrentState.bus1State = m_disHighVolBusCurrentState.data; - m_pm->m_db->insertData(m_pm->m_pmCurrentState.bus1State); //写入数据库 + disHighVolBusState local = m_disHighVolBusCurrentState.data; + { + std::lock_guard lock(m_pm->m_stateMutex); + m_pm->m_pmCurrentState.bus1State = local; + disBus1Breaker pdis1{}; + pdis1.fuelCellCircuitBreaker = (local.fuelCellCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.powerLithiumBatteryCircuitBreaker = (local.powerLithiumBatteryCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.propulsionMotorCircuitBreaker = (local.propulsionMotorCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.lithiumBatteryGroupInstrumentCircuitBreaker = (local.lithiumBatteryGroupInstrumentCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.dcDc5ModuleCircuitBreaker = (local.dcDc5ModuleCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.bowHighVoltageDistributionBoxCircuitBreaker = (local.bowHighVoltageDistributionBoxCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.sternFTDevice45CircuitBreaker = (local.sternFTDevice45CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.sternRudderSwitch1CircuitBreaker = (local.sternRudderSwitch1CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.sternRudderSwitch2CircuitBreaker = (local.sternRudderSwitch2CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.fbReservedCircuitBreaker = (local.fbReservedCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.tyzReservedCircuitBreaker = (local.tyzReservedCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.reservedCircuitBreaker = (local.reservedCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis1.paceholder = 0; + m_pm->m_currentDisBreakerState.bus1Breaker = pdis1; + } + m_pm->m_db->insertData(local); //写入数据库 msg_disHighVolBusFbMsg_Update_time = time; - - disBus1Breaker* pdis1=new disBus1Breaker; - (m_pm->m_pmCurrentState.bus1State.fuelCellCircuitBreaker == BREAK_ON) ? pdis1 -> fuelCellCircuitBreaker = 1 : pdis1 -> fuelCellCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.powerLithiumBatteryCircuitBreaker == BREAK_ON) ? pdis1 -> powerLithiumBatteryCircuitBreaker = 1 : pdis1 -> powerLithiumBatteryCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.propulsionMotorCircuitBreaker == BREAK_ON) ? pdis1 -> propulsionMotorCircuitBreaker = 1 : pdis1 -> propulsionMotorCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.lithiumBatteryGroupInstrumentCircuitBreaker == BREAK_ON) ? pdis1 -> lithiumBatteryGroupInstrumentCircuitBreaker = 1 : pdis1 -> lithiumBatteryGroupInstrumentCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.dcDc5ModuleCircuitBreaker == BREAK_ON) ? pdis1 -> dcDc5ModuleCircuitBreaker = 1 : pdis1 -> dcDc5ModuleCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.bowHighVoltageDistributionBoxCircuitBreaker == BREAK_ON) ? pdis1 -> bowHighVoltageDistributionBoxCircuitBreaker = 1 : pdis1 -> bowHighVoltageDistributionBoxCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.sternFTDevice45CircuitBreaker == BREAK_ON) ? pdis1 -> sternFTDevice45CircuitBreaker = 1 : pdis1 -> sternFTDevice45CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.sternRudderSwitch1CircuitBreaker == BREAK_ON) ? pdis1 -> sternRudderSwitch1CircuitBreaker = 1 : pdis1 -> sternRudderSwitch1CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.sternRudderSwitch2CircuitBreaker == BREAK_ON) ? pdis1 -> sternRudderSwitch2CircuitBreaker = 1 : pdis1 -> sternRudderSwitch2CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.fbReservedCircuitBreaker == BREAK_ON) ? pdis1 -> fbReservedCircuitBreaker = 1 : pdis1 -> fbReservedCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.tyzReservedCircuitBreaker == BREAK_ON) ? pdis1 -> tyzReservedCircuitBreaker = 1 : pdis1 -> tyzReservedCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus1State.reservedCircuitBreaker == BREAK_ON) ? pdis1 -> reservedCircuitBreaker = 1 : pdis1 -> reservedCircuitBreaker = 0; - pdis1 -> paceholder = 0; - m_pm->m_currentDisBreakerState.bus1Breaker = *pdis1; - delete pdis1; - - m_pm->m_systemData->updateSystemData(m_pm->m_pmCurrentState.bus1State); + m_pm->m_systemData->updateSystemData(local); { std::lock_guard lock(m_pm->m_systemData->m_dataMutex); m_pm->m_systemData->device_common_status.dis_hv_c1.lastUpdateTime = m_pm->m_lowerCommManager->msg_disHighVolBusFbMsg_Update_time; @@ -253,22 +263,23 @@ bool LowerCommManager::updatePoweSystemStates(){ double time; if(m_udpComm->popMsgFormQueue(time,m_disHighAVolBusCurrentState)) { - m_pm->m_pmCurrentState.bus2State = m_disHighAVolBusCurrentState.data; - m_pm->m_db->insertData(m_pm->m_pmCurrentState.bus2State); //写入数据库 + disHighAVolBusState local = m_disHighAVolBusCurrentState.data; + { + std::lock_guard lock(m_pm->m_stateMutex); + m_pm->m_pmCurrentState.bus2State = local; + disBus2Breaker pdis2{}; + pdis2.bowFTDevice123CircuitBreaker = (local.bowFTDevice123CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis2.actuatorCircuitBreaker = (local.actuatorCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis2.mastSteeringGearControlBoxCircuitBreaker = (local.mastSteeringGearControlBoxCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis2.xczCircuitBreaker = (local.xczCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis2.bowRudderControlBoxCircuitBreaker = (local.bowRudderControlBoxCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis2.openWaterCoverStartCylinderCircuitBreaker = (local.openWaterCoverStartCylinderCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis2.paceholder = 0; + m_pm->m_currentDisBreakerState.bus2Breaker = pdis2; + } + m_pm->m_db->insertData(local); //写入数据库 msg_disHighAVolBusFbMsg_Update_time = time; - - disBus2Breaker* pdis2=new disBus2Breaker; - (m_pm->m_pmCurrentState.bus2State.bowFTDevice123CircuitBreaker == BREAK_ON)? pdis2 -> bowFTDevice123CircuitBreaker = 1 : pdis2 -> bowFTDevice123CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus2State.actuatorCircuitBreaker == BREAK_ON)? pdis2 -> actuatorCircuitBreaker = 1 : pdis2 -> actuatorCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus2State.mastSteeringGearControlBoxCircuitBreaker == BREAK_ON)? pdis2 -> mastSteeringGearControlBoxCircuitBreaker = 1 : pdis2 -> mastSteeringGearControlBoxCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus2State.xczCircuitBreaker == BREAK_ON)? pdis2 -> xczCircuitBreaker = 1 : pdis2 -> xczCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus2State.bowRudderControlBoxCircuitBreaker == BREAK_ON)? pdis2 -> bowRudderControlBoxCircuitBreaker = 1 : pdis2 -> bowRudderControlBoxCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus2State.openWaterCoverStartCylinderCircuitBreaker == BREAK_ON)? pdis2 -> openWaterCoverStartCylinderCircuitBreaker = 1 : pdis2 -> openWaterCoverStartCylinderCircuitBreaker = 0; - pdis2 -> paceholder = 0; - m_pm->m_currentDisBreakerState.bus2Breaker = *pdis2; - delete pdis2; - - m_pm->m_systemData->updateSystemData(m_pm->m_pmCurrentState.bus2State); + m_pm->m_systemData->updateSystemData(local); { std::lock_guard lock(m_pm->m_systemData->m_dataMutex); m_pm->m_systemData->device_common_status.dis_hv_c2.lastUpdateTime = m_pm->m_lowerCommManager->msg_disHighAVolBusFbMsg_Update_time; @@ -281,22 +292,24 @@ bool LowerCommManager::updatePoweSystemStates(){ double time; if(m_udpComm->popMsgFormQueue(time,m_disHighBVolBusCurrentState)) { - m_pm->m_pmCurrentState.bus3State = m_disHighBVolBusCurrentState.data; - m_pm->m_db->insertData(m_pm->m_pmCurrentState.bus3State); //写入数据库 + disLowMainBusState local = m_disHighBVolBusCurrentState.data; + { + std::lock_guard lock(m_pm->m_stateMutex); + m_pm->m_pmCurrentState.bus3State = local; + disBus3Breaker pdis3{}; + pdis3.lithiumBatteryGroupInstrumentCircuitBreaker = (local.lithiumBatteryGroupInstrumentCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis3.bowLowVoltageDistributionBoxCircuitBreaker = (local.bowLowVoltageDistributionBoxCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis3.unit4InstrumentDC48VCircuitBreaker = (local.unit4InstrumentDC48VCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis3.dcC1DCDistributionPanelCircuitBreaker = (local.dcC1DCDistributionPanelCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis3.reservedCircuitBreaker1 = (local.reservedCircuitBreaker1 == BREAK_ON) ? 1 : 0; + pdis3.reservedCircuitBreaker2 = (local.reservedCircuitBreaker2 == BREAK_ON) ? 1 : 0; + pdis3.emergencyLithiumBatteryGroup2CircuitBreaker = (local.emergencyLithiumBatteryGroup2CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis3.paceholder = 0; + m_pm->m_currentDisBreakerState.bus3Breaker = pdis3; + } + m_pm->m_db->insertData(local); //写入数据库 msg_disHighBVolBusFbMsg_Update_time = time; - - disBus3Breaker* pdis3=new disBus3Breaker; - (m_pm->m_pmCurrentState.bus3State.lithiumBatteryGroupInstrumentCircuitBreaker == BREAK_ON)? pdis3 -> lithiumBatteryGroupInstrumentCircuitBreaker = 1 : pdis3 -> lithiumBatteryGroupInstrumentCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus3State.bowLowVoltageDistributionBoxCircuitBreaker == BREAK_ON)? pdis3 -> bowLowVoltageDistributionBoxCircuitBreaker = 1 : pdis3 -> bowLowVoltageDistributionBoxCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus3State.unit4InstrumentDC48VCircuitBreaker == BREAK_ON)? pdis3 -> unit4InstrumentDC48VCircuitBreaker = 1 : pdis3 -> unit4InstrumentDC48VCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus3State.dcC1DCDistributionPanelCircuitBreaker == BREAK_ON)? pdis3 -> dcC1DCDistributionPanelCircuitBreaker = 1 : pdis3 -> dcC1DCDistributionPanelCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus3State.reservedCircuitBreaker1 == BREAK_ON)? pdis3 -> reservedCircuitBreaker1 = 1 : pdis3 -> reservedCircuitBreaker1 = 0; - (m_pm->m_pmCurrentState.bus3State.reservedCircuitBreaker2 == BREAK_ON)? pdis3 -> reservedCircuitBreaker2 = 1 : pdis3 -> reservedCircuitBreaker2 = 0; - (m_pm->m_pmCurrentState.bus3State.emergencyLithiumBatteryGroup2CircuitBreaker == BREAK_ON)? pdis3 -> emergencyLithiumBatteryGroup2CircuitBreaker = 1 : pdis3 -> emergencyLithiumBatteryGroup2CircuitBreaker = 0; - pdis3->paceholder = 0; - m_pm->m_currentDisBreakerState.bus3Breaker = *pdis3; - delete pdis3; - m_pm->m_systemData->updateSystemData(m_pm->m_pmCurrentState.bus3State); + m_pm->m_systemData->updateSystemData(local); { std::lock_guard lock(m_pm->m_systemData->m_dataMutex); m_pm->m_systemData->device_common_status.dis_lv_c1.lastUpdateTime = m_pm->m_lowerCommManager->msg_disHighBVolBusFbMsg_Update_time; @@ -308,23 +321,25 @@ bool LowerCommManager::updatePoweSystemStates(){ double time; if(m_udpComm->popMsgFormQueue(time,m_disLowBusState)) { - m_pm->m_pmCurrentState.bus4State = m_disLowBusState.data; - m_pm->m_db->insertData(m_pm->m_pmCurrentState.bus4State); //写入数据库 + disLowBusState local = m_disLowBusState.data; + { + std::lock_guard lock(m_pm->m_stateMutex); + m_pm->m_pmCurrentState.bus4State = local; + disBus4Breaker pdis4{}; + pdis4.unit1CircuitBreaker = (local.unit1CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis4.unit2CircuitBreaker = (local.unit2CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis4.unit3CircuitBreaker = (local.unit3CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis4.unit5CircuitBreaker = (local.unit5CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis4.bowPZDeviceCircuitBreaker = (local.bowPZDeviceCircuitBreaker == BREAK_ON) ? 1 : 0; + pdis4.reservedCircuitBreaker1 = (local.reservedCircuitBreaker1 == BREAK_ON) ? 1 : 0; + pdis4.reservedCircuitBreaker2 = (local.reservedCircuitBreaker2 == BREAK_ON) ? 1 : 0; + pdis4.emergencyLithiumBatteryGroup1CircuitBreaker = (local.emergencyLithiumBatteryGroup1CircuitBreaker == BREAK_ON) ? 1 : 0; + pdis4.paceholder = 0; + m_pm->m_currentDisBreakerState.bus4Breaker = pdis4; + } + m_pm->m_db->insertData(local); //写入数据库 msg_disLowBusFbMsg_Update_time = time; - - disBus4Breaker* pdis4=new disBus4Breaker; - (m_pm->m_pmCurrentState.bus4State.unit1CircuitBreaker == BREAK_ON)? pdis4 -> unit1CircuitBreaker = 1 : pdis4 -> unit1CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus4State.unit2CircuitBreaker == BREAK_ON)? pdis4 -> unit2CircuitBreaker = 1 : pdis4 -> unit2CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus4State.unit3CircuitBreaker == BREAK_ON)? pdis4 -> unit3CircuitBreaker = 1 : pdis4 -> unit3CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus4State.unit5CircuitBreaker == BREAK_ON)? pdis4 -> unit5CircuitBreaker = 1 : pdis4 -> unit5CircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus4State.bowPZDeviceCircuitBreaker == BREAK_ON)? pdis4 -> bowPZDeviceCircuitBreaker = 1 : pdis4 -> bowPZDeviceCircuitBreaker = 0; - (m_pm->m_pmCurrentState.bus4State.reservedCircuitBreaker1 == BREAK_ON)? pdis4 -> reservedCircuitBreaker1 = 1 : pdis4 -> reservedCircuitBreaker1 = 0; - (m_pm->m_pmCurrentState.bus4State.reservedCircuitBreaker2 == BREAK_ON)? pdis4 -> reservedCircuitBreaker2 = 1 : pdis4 -> reservedCircuitBreaker2 = 0; - (m_pm->m_pmCurrentState.bus4State.emergencyLithiumBatteryGroup1CircuitBreaker == BREAK_ON)? pdis4 -> emergencyLithiumBatteryGroup1CircuitBreaker = 1 : pdis4 -> emergencyLithiumBatteryGroup1CircuitBreaker = 0; - pdis4 -> paceholder = 0; - m_pm->m_currentDisBreakerState.bus4Breaker = *pdis4; - delete pdis4; - m_pm->m_systemData->updateSystemData(m_pm->m_pmCurrentState.bus4State); + m_pm->m_systemData->updateSystemData(local); { std::lock_guard lock(m_pm->m_systemData->m_dataMutex); m_pm->m_systemData->device_common_status.dis_lv_c2.lastUpdateTime = m_pm->m_lowerCommManager->msg_disLowBusFbMsg_Update_time; @@ -476,7 +491,7 @@ void LowerCommManager::coutMsg(const msg_CcuStateFbMsg& p, std::ostream& os){ os << "甲醇浓度1 (ch3oh_concentration1): " << p.data.ch3oh_concentration1 * 0.01 << "% LEL\n"; os << "甲醇浓度2 (ch3oh_concentration2): " << p.data.ch3oh_concentration2 * 0.01 << "% LEL\n"; - for (int i = 0; i < 10; ++i) + for (int i = 0; i < 2; ++i) os << "保留字段 reserved1[" << i << "]: 0x" << std::hex << p.data.reserved1[i] << "\n"; os << "仪表舱 氧气浓度 (cabin_ox_concentration): " << p.data.cabin_ox_concentration * 0.1 << "%\n"; @@ -531,12 +546,18 @@ void LowerCommManager::coutMsg(const msg_CcuStateFbMsg& p, std::ostream& os){ os << "仪表紧急状态 (ins_emergency_status): 0x" << std::hex << static_cast(p.data.ins_emergency_status) << "\n"; os << "动力紧急状态 (dyn_emergency_status): 0x" << static_cast(p.data.dyn_emergency_status) << "\n"; - for (int i = 0; i < 2; ++i) + for (int i = 0; i < 1; ++i) os << "保留字段 reserved2[" << i << "]: 0x" << std::hex << static_cast(p.data.reserved2[i]) << "\n"; - for (int i = 0; i < 6; ++i) - os << "SOC门限值 ins_soc_threshold" << i+1 << ": 0x" << std::hex << static_cast(p.data.ins_soc_threshold1) << "\n"; - for (int i = 0; i < 4; ++i) - os << "功率限制 ins_power_limit" << i+1 << ": 0x" << std::hex << static_cast(p.data.ins_power_limit1) << "\n"; + os << "SOC门限值 ins_soc_threshold1: 0x" << std::hex << static_cast(p.data.ins_soc_threshold1) << "\n"; + os << "SOC门限值 ins_soc_threshold2: 0x" << std::hex << static_cast(p.data.ins_soc_threshold2) << "\n"; + os << "SOC门限值 ins_soc_threshold3: 0x" << std::hex << static_cast(p.data.ins_soc_threshold3) << "\n"; + os << "SOC门限值 dyn_soc_threshold1: 0x" << std::hex << static_cast(p.data.dyn_soc_threshold1) << "\n"; + os << "SOC门限值 dyn_soc_threshold2: 0x" << std::hex << static_cast(p.data.dyn_soc_threshold2) << "\n"; + os << "SOC门限值 dyn_soc_threshold3: 0x" << std::hex << static_cast(p.data.dyn_soc_threshold3) << "\n"; + os << "功率限制 ins_power_limit1: 0x" << std::hex << static_cast(p.data.ins_power_limit1) << "\n"; + os << "功率限制 ins_power_limit2: 0x" << std::hex << static_cast(p.data.ins_power_limit2) << "\n"; + os << "功率限制 dyn_power_limit1: 0x" << std::hex << static_cast(p.data.dyn_power_limit1) << "\n"; + os << "功率限制 dyn_power_limit2: 0x" << std::hex << static_cast(p.data.dyn_power_limit2) << "\n"; os << "设备在线状态1 (device_online_flag1): 0x" << std::hex << p.data.device_online_flag1 << "\n"; os << "设备在线状态2 (device_online_flag2): 0x" << p.data.device_online_flag2 << "\n"; @@ -666,11 +687,11 @@ bool LowerCommManager::operate_HV_dcDc5_Breaker(char opt) m_udpComm->sendDisSysCmd(cmd); // ok = m_pm->m_pmCurrentState.bus1State.dcDcFaultWord1&(0x02); - ok = m_pm->m_systemData->m_dcdcStatus.workingStatus; + ok = m_pm->m_systemData->getDcdcStatus().workingStatus; MOOSPause(100); while ( !ok ) { - if( m_pm->m_systemData->m_dcdcStatus.workingStatus ) + if( m_pm->m_systemData->getDcdcStatus().workingStatus ) { ok = true; break; @@ -718,11 +739,11 @@ bool LowerCommManager::operate_HV_coolingSystem_Breaker(char opt) m_udpComm->sendDisSysCmd(cmd); // ok = m_pm->m_pmCurrentState.bus1State.dcDcFaultWord1 & 0x60; - ok = m_pm->m_systemData->m_dcdcStatus.coolingPump1Running || m_pm->m_systemData->m_dcdcStatus.coolingPump2Running; + ok = m_pm->m_systemData->getDcdcStatus().coolingPump1Running || m_pm->m_systemData->getDcdcStatus().coolingPump2Running; MOOSPause(100); while ( !ok ) { - if( (m_pm->m_systemData->m_dcdcStatus.coolingPump1Running || m_pm->m_systemData->m_dcdcStatus.coolingPump2Running) ) + if( (m_pm->m_systemData->getDcdcStatus().coolingPump1Running || m_pm->m_systemData->getDcdcStatus().coolingPump2Running) ) { ok = true; break; @@ -942,7 +963,12 @@ bool LowerCommManager::operateBreakerGeneric( m_udpComm->sendDisSysCmd(cmd); MOOSPause(100); while ( !ok ) { - if( readState() == getBreakerOption(opt) ) + StateT cur; + { + std::lock_guard lock(m_pm->m_stateMutex); + cur = readState(); + } + if( cur == getBreakerOption(opt) ) { ok = true; break; diff --git a/src/pPowerManger/PowerManger.cpp b/src/pPowerManger/PowerManger.cpp index 00369a1..c1c69cd 100644 --- a/src/pPowerManger/PowerManger.cpp +++ b/src/pPowerManger/PowerManger.cpp @@ -86,11 +86,14 @@ PowerManger::PowerManger() PowerManger::~PowerManger() { m_commThread.Stop(); - delete m_lowerCommManager; // 先停止监听线程,确保其不再调用 DB + // 必须先停 HTTP 线程(stop() 内部 pthread_join 阻塞等待线程退出), + // 否则 HTTP 线程每秒仍会读取 m_systemData / m_lowerCommManager / m_upperCommManager, + // 导致 use-after-free。 + delete m_httpServer; + delete m_lowerCommManager; // 停止监听线程,确保其不再调用 DB delete m_db; delete m_upperCommManager; - delete m_systemData; - delete m_httpServer; + delete m_systemData; // 最后删除,此时无任何线程引用 } //--------------------------------------------------------- @@ -374,10 +377,28 @@ void PowerManger::initControlCmd() } +void PowerManger::addFaultCode(unsigned int code) +{ + std::lock_guard lock(m_stateMutex); + faultCode.push_back(code); +} + +void PowerManger::clearFaultCodes() +{ + std::lock_guard lock(m_stateMutex); + faultCode.clear(); +} + +std::vector PowerManger::getFaultCodes() const +{ + std::lock_guard lock(m_stateMutex); + return faultCode; +} + unsigned char PowerManger::getFaultLevel() { unsigned char maxLevel = 0; - for (auto code : faultCode) { + for (auto code : getFaultCodes()) { // 提取0xabcd中的b位 (右移8位后取低4位) unsigned char level = (code >> 8) & 0xF; if (level > maxLevel) { @@ -391,7 +412,10 @@ bool PowerManger::commLoop() { while(!m_commThread.IsQuitRequested()) { - m_CcuColCmd.heartbeat++; + { + std::lock_guard lock(m_stateMutex); + m_CcuColCmd.heartbeat++; + } m_lowerCommManager->processPeriodicTasks(); m_upperCommManager->processPeriodicTasks(); // 延时1秒 diff --git a/src/pPowerManger/PowerManger.h b/src/pPowerManger/PowerManger.h index 1771bbc..e5cd59f 100644 --- a/src/pPowerManger/PowerManger.h +++ b/src/pPowerManger/PowerManger.h @@ -87,6 +87,12 @@ class PowerManger : public AppCastingMOOSApp //===========================FEEDBACK====================================== pmState m_pmCurrentState; //能源系统的当前状态(由下位机线程修改) disSysBreakerList m_currentDisBreakerState; //配电断路器状态 + // 跨线程共享状态保护:m_pmCurrentState / m_currentDisBreakerState / m_CcuColCmd / faultCode + // 纪律:绝不与 SystemData::m_dataMutex 同时持有(持锁期间不调用 updateSystemData/pushSystemData) + mutable std::mutex m_stateMutex; + void addFaultCode(unsigned int code); + void clearFaultCodes(); + std::vector getFaultCodes() const; DriverTable m_currentDriverState; DriverTable m_subDisSysState; DriverTable m_subDisSysFault; diff --git a/src/pPowerManger/UpperCommManager.cpp b/src/pPowerManger/UpperCommManager.cpp index 9d99ba1..4d108a1 100644 --- a/src/pPowerManger/UpperCommManager.cpp +++ b/src/pPowerManger/UpperCommManager.cpp @@ -214,11 +214,20 @@ void UpperCommManager::processPeriodicTasks() { } void UpperCommManager::buildSystemReport() { + // 跨线程共享状态(listen 线程写、comm 线程读):持锁快照,避免撕裂/竞争 + pmState pmStateSnap{}; + disSysBreakerList disBreakerSnap{}; + { + std::lock_guard lock(m_pm->m_stateMutex); + pmStateSnap = m_pm->m_pmCurrentState; + disBreakerSnap = m_pm->m_currentDisBreakerState; + } + m_pmStateMsg.state.workCondition = m_pm->workCondition; m_pmStateMsg.state.currentMod = m_pm->currentMod; m_pmStateMsg.state.powerState = m_pm->powerState; m_pmStateMsg.state.faultLevel = m_pm->faultLevel; - m_pmStateMsg.state.faultCode = m_pm->faultCode; + m_pmStateMsg.state.faultCode = m_pm->getFaultCodes(); // cout << "m_pmStateMsg.state.faultCode.size():" << m_pmStateMsg.state.faultCode.size() << endl; m_pmStateMsg.state.soc = m_pm->soc; m_pmStateMsg.state.sustainableTime = m_pm->sustainableTime; @@ -229,14 +238,14 @@ void UpperCommManager::buildSystemReport() { m_pm->sendNotification(m_pmStateMsg.key, m_pmStateMsg.buildMsg(*(m_pm->m_systemData))); m_pm->sendNotification(m_fcsStateMsg.key, m_fcsStateMsg.buildMsg(*(m_pm->m_systemData))); m_pm->sendNotification(m_liBatStateMsg.key, m_liBatStateMsg.buildMsg(*(m_pm->m_systemData))); - m_pm->sendNotification(m_disSysStateMsg.key, m_disSysStateMsg.buildMsg(*(m_pm->m_systemData),m_pm->m_currentDisBreakerState)); - m_pm->sendNotification(m_currentStateMsg.key, m_currentStateMsg.buildMsg(m_pm->m_pmCurrentState)); + m_pm->sendNotification(m_disSysStateMsg.key, m_disSysStateMsg.buildMsg(*(m_pm->m_systemData), disBreakerSnap)); + m_pm->sendNotification(m_currentStateMsg.key, m_currentStateMsg.buildMsg(pmStateSnap)); #ifdef _DEBUG m_pmStateMsg_fb_debug = std::make_pair(m_pmStateMsg.key, m_pmStateMsg.buildMsg(*(m_pm->m_systemData))); m_fcsStateMsg_st_debug = std::make_pair(m_fcsStateMsg.key, m_fcsStateMsg.buildMsg(*(m_pm->m_systemData))); m_liBatStateMsg_st_debug = std::make_pair(m_liBatStateMsg.key, m_liBatStateMsg.buildMsg(*(m_pm->m_systemData))); - m_disSysStateMsg_st_debug = std::make_pair(m_disSysStateMsg.key, m_disSysStateMsg.buildMsg(*(m_pm->m_systemData),m_pm->m_currentDisBreakerState)); - m_currentStateMsg_st_debug = std::make_pair(m_currentStateMsg.key, m_currentStateMsg.buildMsg(m_pm->m_pmCurrentState)); + m_disSysStateMsg_st_debug = std::make_pair(m_disSysStateMsg.key, m_disSysStateMsg.buildMsg(*(m_pm->m_systemData), disBreakerSnap)); + m_currentStateMsg_st_debug = std::make_pair(m_currentStateMsg.key, m_currentStateMsg.buildMsg(pmStateSnap)); #endif } diff --git a/src/pPowerManger/fsm/Lifting.cpp b/src/pPowerManger/fsm/Lifting.cpp index 5b7f8b2..4cdbf17 100644 --- a/src/pPowerManger/fsm/Lifting.cpp +++ b/src/pPowerManger/fsm/Lifting.cpp @@ -82,7 +82,7 @@ void Lifting::entry() { MOOSPause(3); { int count=0; - while (!(pm->m_systemData->dyn_pos_relay_status == 0x02 && pm->m_systemData->dyn_neg_relay_status == 0x02)) + while (!(pm->m_systemData->getDynPosRelayStatus() == 0x02 && pm->m_systemData->getDynNegRelayStatus() == 0x02)) { LOG_F(INFO,"动力锂电池下电中..."); MOOSPause(3); diff --git a/src/pPowerManger/fsm/PowerManagerFsm.cpp b/src/pPowerManger/fsm/PowerManagerFsm.cpp index 08a0068..e6e3ce6 100644 --- a/src/pPowerManger/fsm/PowerManagerFsm.cpp +++ b/src/pPowerManger/fsm/PowerManagerFsm.cpp @@ -36,7 +36,7 @@ void PowerManagerFsm::react(CycleTriggeredEvent const &){ { if(pm->m_depth < OVER_DEPTH) { - if(pm->m_systemData->power_a_bus_status.xczCircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().xczCircuitBreaker == 1) pm->m_lowerCommManager->operate_HVA_xczCircuit_Breaker(0); //舷侧阵自动下电 MOOSPause(3); @@ -66,10 +66,13 @@ void PowerManagerFsm::handleCuriseWork() //依次接入执行机构动力电设备 //1. 开启动力电 //1.1 动力锂电池上电 - pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + { + std::lock_guard lock(pm->m_stateMutex); + pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + } // 等待动力锂电池上电完成 int count=0; - while (!(pm->m_systemData->dyn_voltage_link > 500.0)) + while (!(pm->m_systemData->getDynVoltageLink() > 500.0)) { LOG_F(INFO,"动力锂电池上电中..."); MOOSPause(1000); @@ -82,27 +85,27 @@ void PowerManagerFsm::handleCuriseWork() count++; } //1.2 闭合动力电断路器 - if(pm->m_systemData->power_bus_status.lithiumBatteryGroupInstrumentCircuitBreaker != 1) + if(pm->m_systemData->getPowerBusStatus().lithiumBatteryGroupInstrumentCircuitBreaker != 1) { pm->m_lowerCommManager->operate_HV_powerLithiumBattery_Breaker(1); MOOSPause(3); } - if(pm->m_systemData->power_bus_status.dcDc5ModuleCircuitBreaker != 1) + if(pm->m_systemData->getPowerBusStatus().dcDc5ModuleCircuitBreaker != 1) { pm->m_lowerCommManager->operate_HV_dcDc5Module_Breaker(1); MOOSPause(3); } - if(pm->m_systemData->m_dcdcStatus.workingStatus != 1) + if(pm->m_systemData->getDcdcStatus().workingStatus != 1) { pm->m_lowerCommManager->operate_HV_dcDc5_Breaker(1); MOOSPause(3); } - if(!(pm->m_systemData->m_dcdcStatus.coolingPump1Running || pm->m_systemData->m_dcdcStatus.coolingPump2Running)) + if(!(pm->m_systemData->getDcdcStatus().coolingPump1Running || pm->m_systemData->getDcdcStatus().coolingPump2Running)) { pm->m_lowerCommManager->operate_HV_coolingSystem_Breaker(1); MOOSPause(10); } - if(pm->m_systemData->power_bus_status.bowHighVoltageDistributionBoxCircuitBreaker != 1) + if(pm->m_systemData->getPowerBusStatus().bowHighVoltageDistributionBoxCircuitBreaker != 1) { pm->m_lowerCommManager->operate_HV_bowHighVoltageBox_Breaker(1); MOOSPause(3); @@ -1040,7 +1043,7 @@ void PowerManagerFsm::getNoPowerDeviceStatus(DriverTable &s) void PowerManagerFsm::getBowRudderSystemStatus(DriverTable &s) { - if(pm->m_systemData->power_a_bus_status.bowRudderControlBoxCircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().bowRudderControlBoxCircuitBreaker == 1) { //检查艏舵电缸 if(pm->m_subDisSysState.bowRudderDeploy == 1) @@ -1103,7 +1106,7 @@ void PowerManagerFsm::getBowRudderSystemStatus(DriverTable &s) } void PowerManagerFsm::getActuatorStatus(DriverTable &s) { - if(pm->m_systemData->power_a_bus_status.actuatorCircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().actuatorCircuitBreaker == 1) { if(pm->m_subDisSysState.bowCover1 == 1) { @@ -1220,7 +1223,7 @@ void PowerManagerFsm::getActuatorStatus(DriverTable &s) void PowerManagerFsm::getSternRudderStatus(DriverTable &s) { //艉舵1 2 - if(pm->m_systemData->power_bus_status.sternRudderSwitch1CircuitBreaker == 1) + if(pm->m_systemData->getPowerBusStatus().sternRudderSwitch1CircuitBreaker == 1) { if(pm->m_subDisSysState.sternRudderServo1== 1) { @@ -1261,7 +1264,7 @@ void PowerManagerFsm::getSternRudderStatus(DriverTable &s) } } //艉舵3 4 - if(pm->m_systemData->power_bus_status.sternRudderSwitch2CircuitBreaker == 1) + if(pm->m_systemData->getPowerBusStatus().sternRudderSwitch2CircuitBreaker == 1) { if(pm->m_subDisSysState.sternRudderServo3 == 1) { @@ -1305,7 +1308,7 @@ void PowerManagerFsm::getSternRudderStatus(DriverTable &s) void PowerManagerFsm::getPropulsionMotorStatus(DriverTable &s) { - if(pm->m_systemData->power_bus_status.propulsionMotorCircuitBreaker == 1) + if(pm->m_systemData->getPowerBusStatus().propulsionMotorCircuitBreaker == 1) { if(pm->m_subDisSysState.thruster == 1) { @@ -1331,7 +1334,7 @@ void PowerManagerFsm::getPropulsionMotorStatus(DriverTable &s) void PowerManagerFsm::getBuoyancyAdjustmentStatus(DriverTable &s) { // 艉部浮调 - if(pm->m_systemData->power_bus_status.sternFTDevice45CircuitBreaker == 1) + if(pm->m_systemData->getPowerBusStatus().sternFTDevice45CircuitBreaker == 1) { if(pm->m_subDisSysState.buoyancyAdjust4 == 1) { @@ -1373,7 +1376,7 @@ void PowerManagerFsm::getBuoyancyAdjustmentStatus(DriverTable &s) } // 艏部浮调 - if(pm->m_systemData->power_a_bus_status.bowFTDevice123CircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().bowFTDevice123CircuitBreaker == 1) { if(pm->m_subDisSysState.buoyancyAdjust1 == 1) { @@ -1435,7 +1438,7 @@ void PowerManagerFsm::getBuoyancyAdjustmentStatus(DriverTable &s) void PowerManagerFsm::getSideSonarStatus(DriverTable &s) { - if(pm->m_systemData->power_a_bus_status.xczCircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().xczCircuitBreaker == 1) { if(pm->m_subDisSysState.sideSonar == 1) { @@ -1461,7 +1464,7 @@ void PowerManagerFsm::getSideSonarStatus(DriverTable &s) void PowerManagerFsm::getFBSystemCmdStatus(DriverTable &s) { - if(pm->m_systemData->power_bus_status.fbReservedCircuitBreaker == 1) + if(pm->m_systemData->getPowerBusStatus().fbReservedCircuitBreaker == 1) { if(pm->m_subDisSysState.buoyage == 1) { @@ -1487,7 +1490,7 @@ void PowerManagerFsm::getFBSystemCmdStatus(DriverTable &s) void PowerManagerFsm::getMastFoldingSystem(DriverTable &s) { - if(pm->m_systemData->power_a_bus_status.mastSteeringGearControlBoxCircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().mastSteeringGearControlBoxCircuitBreaker == 1) { if(pm->m_subDisSysState.mastLiftingServo == 1) { @@ -1513,7 +1516,7 @@ void PowerManagerFsm::getMastFoldingSystem(DriverTable &s) void PowerManagerFsm::getOpenWaterStatus(DriverTable &s) { - if(pm->m_systemData->power_a_bus_status.openWaterCoverStartCylinderCircuitBreaker == 1) + if(pm->m_systemData->getPowerABusStatus().openWaterCoverStartCylinderCircuitBreaker == 1) { if(pm->m_subDisSysState.bowWaterInet1 == 1) { @@ -1752,12 +1755,12 @@ void PowerManagerFsm::basicNavigationFault() if(pm->m_subDisSysFault.sternRudderServo4 == 1) pm->m_systemData->insertFaultCode(0x1309); } - if (pm->m_systemData->disSysFaultCode.find(7) != pm->m_systemData->disSysFaultCode.end()) + if (pm->m_systemData->hasDisSysFaultCode(7)) { pm->m_systemData->insertFaultCode(0x1306); pm->m_systemData->insertFaultCode(0x1307); } - if (pm->m_systemData->disSysFaultCode.find(8) != pm->m_systemData->disSysFaultCode.end()) + if (pm->m_systemData->hasDisSysFaultCode(8)) { pm->m_systemData->insertFaultCode(0x1308); pm->m_systemData->insertFaultCode(0x1309); @@ -1765,19 +1768,19 @@ void PowerManagerFsm::basicNavigationFault() if(count == 0) { // 清空故障码 - if(pm->m_systemData->disSysFaultCode.find(7) == pm->m_systemData->disSysFaultCode.end()) + if(!pm->m_systemData->hasDisSysFaultCode(7)) { pm->m_systemData->eraseFaultCode(0x1306); pm->m_systemData->eraseFaultCode(0x1307); } - if(pm->m_systemData->disSysFaultCode.find(8) == pm->m_systemData->disSysFaultCode.end()) + if(!pm->m_systemData->hasDisSysFaultCode(8)) { pm->m_systemData->eraseFaultCode(0x1308); pm->m_systemData->eraseFaultCode(0x1309); } } //=====处理动力电池故障====================== - if(pm->m_systemData->dyn_emergency_status != 0 ) + if(pm->m_systemData->getDynEmergencyStatus() != 0 ) { pm->m_systemData->insertFaultCode(0x130B); } @@ -1788,7 +1791,7 @@ void PowerManagerFsm::basicNavigationFault() } else { - if(pm->m_systemData->disSysFaultCode.find(3) == pm->m_systemData->disSysFaultCode.end()) + if(!pm->m_systemData->hasDisSysFaultCode(3)) { pm->m_systemData->eraseFaultCode(0x130A); } @@ -1816,7 +1819,7 @@ void PowerManagerFsm::basicNavigationFault() pm->m_systemData->eraseFaultCode(0x1412); } //====处理复合管控器失效 - if(pm->m_systemData->device_common_status.ccu_status.isTimeout) + if(pm->m_systemData->getDeviceCommonStatus().ccu_status.isTimeout) { pm->m_systemData->insertFaultCode(0x1413); } @@ -1831,63 +1834,63 @@ void PowerManagerFsm::basicNavigationFault(FaultEvent const &e) if((e.injectionFaultCode==1)) { LOG_F(INFO, "部分深度计失效"); - pm->faultCode.push_back(0x1102); - pm->faultCode.push_back(0x1103); - pm->faultCode.push_back(0x1104); + pm->addFaultCode(0x1102); + pm->addFaultCode(0x1103); + pm->addFaultCode(0x1104); } if((e.injectionFaultCode==2)) { LOG_F(INFO, "DVL失效"); - pm->faultCode.push_back(0x1301); + pm->addFaultCode(0x1301); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "单个艉舵失效"); - pm->faultCode.push_back(0x1207); + pm->addFaultCode(0x1207); } if ((e.injectionFaultCode==4)) { LOG_F(INFO, "部分艉舵失效"); - pm->faultCode.push_back(0x1308); - pm->faultCode.push_back(0x1307); + pm->addFaultCode(0x1308); + pm->addFaultCode(0x1307); } if ((e.injectionFaultCode==5)) { LOG_F(INFO, "动力锂电池失效"); - pm->faultCode.push_back(0x130B); + pm->addFaultCode(0x130B); } if ((e.injectionFaultCode==6)) { LOG_F(INFO, "推进电机失效"); - pm->faultCode.push_back(0x130A); + pm->addFaultCode(0x130A); } if ((e.injectionFaultCode==7)) { LOG_F(INFO, "惯导失效"); - pm->faultCode.push_back(0x1305); + pm->addFaultCode(0x1305); } if ((e.injectionFaultCode==8)) { LOG_F(INFO, "深度失效"); - pm->faultCode.push_back(0x1302); - pm->faultCode.push_back(0x1303); - pm->faultCode.push_back(0x1304); + pm->addFaultCode(0x1302); + pm->addFaultCode(0x1303); + pm->addFaultCode(0x1304); } if ((e.injectionFaultCode==9)) { LOG_F(INFO, "仪表锂电池失效"); - pm->faultCode.push_back(0x1412); + pm->addFaultCode(0x1412); } if ((e.injectionFaultCode==10)) { LOG_F(INFO, "复合管控器失效"); - pm->faultCode.push_back(0x1413); + pm->addFaultCode(0x1413); } } void PowerManagerFsm::extendedNavigationFault() { //处理艏舵故障 - if(pm->m_systemData->disSysFaultCode.find(17) != pm->m_systemData->disSysFaultCode.end()) + if(pm->m_systemData->hasDisSysFaultCode(17)) { pm->m_systemData->insertFaultCode(0x2311); pm->m_systemData->insertFaultCode(0x2312); @@ -1939,22 +1942,22 @@ void PowerManagerFsm::extendedNavigationFault(FaultEvent const &e) if ((e.injectionFaultCode==1)) { LOG_F(INFO, "艏舵展开电缸失效"); - pm->faultCode.push_back(0x2311); + pm->addFaultCode(0x2311); } if ((e.injectionFaultCode==2)) { LOG_F(INFO, "艏舵控制控制失效"); - pm->faultCode.push_back(0x2312); + pm->addFaultCode(0x2312); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "动力锂电池功率不满/失效"); - pm->faultCode.push_back(0x2323); + pm->addFaultCode(0x2323); } if ((e.injectionFaultCode==4)) { LOG_F(INFO, "燃料电池失效"); - pm->faultCode.push_back(0x2334); + pm->addFaultCode(0x2334); } } void PowerManagerFsm::obstacleAvoidanceFault() @@ -2061,22 +2064,22 @@ void PowerManagerFsm::obstacleAvoidanceFault(FaultEvent const &e) LOG_F(INFO, "obstacleAvoidanceFault"); if ((e.injectionFaultCode==1)) { - pm->faultCode.push_back(0x3108); - pm->faultCode.push_back(0x3109); + pm->addFaultCode(0x3108); + pm->addFaultCode(0x3109); } if ((e.injectionFaultCode==2)) { - pm->faultCode.push_back(0x3201); - pm->faultCode.push_back(0x3202); - pm->faultCode.push_back(0x3203); - pm->faultCode.push_back(0x3204); - pm->faultCode.push_back(0x3205); - pm->faultCode.push_back(0x3206); - pm->faultCode.push_back(0x3207); + pm->addFaultCode(0x3201); + pm->addFaultCode(0x3202); + pm->addFaultCode(0x3203); + pm->addFaultCode(0x3204); + pm->addFaultCode(0x3205); + pm->addFaultCode(0x3206); + pm->addFaultCode(0x3207); } if ((e.injectionFaultCode==3)) { - pm->faultCode.push_back(0x3301); + pm->addFaultCode(0x3301); } } @@ -2089,8 +2092,8 @@ void PowerManagerFsm::buoyancyControlFault() + (pm->m_subDisSysFault.buoyancyAdjust4 == 1) + (pm->m_subDisSysFault.buoyancyAdjust5 == 1); - bool breaker7_fault = pm->m_systemData->disSysFaultCode.find(7) != pm->m_systemData->disSysFaultCode.end(); - bool breaker13_fault = pm->m_systemData->disSysFaultCode.find(13) != pm->m_systemData->disSysFaultCode.end(); + bool breaker7_fault = pm->m_systemData->hasDisSysFaultCode(7); + bool breaker13_fault = pm->m_systemData->hasDisSysFaultCode(13); //===========艏部浮调==================== if(breaker7_fault) { @@ -2256,24 +2259,24 @@ void PowerManagerFsm::buoyancyControlFault(FaultEvent const &e) if ((e.injectionFaultCode==1)) { LOG_F(INFO, "单一浮调机构失效"); - pm->faultCode.push_back(0x4201); + pm->addFaultCode(0x4201); } if ((e.injectionFaultCode==2)) { LOG_F(INFO, "2-4及以上浮调装置失效"); - pm->faultCode.push_back(0x4301); - pm->faultCode.push_back(0x4302); - pm->faultCode.push_back(0x4303); + pm->addFaultCode(0x4301); + pm->addFaultCode(0x4302); + pm->addFaultCode(0x4303); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "功率不足满足浮调要求"); - pm->faultCode.push_back(0x4436); + pm->addFaultCode(0x4436); } if ((e.injectionFaultCode==4)) { LOG_F(INFO, "所有浮调系统失效"); - pm->faultCode.push_back(0x4401); + pm->addFaultCode(0x4401); } } @@ -2319,7 +2322,7 @@ void PowerManagerFsm::communicationSystemFault() else pm->m_systemData->eraseFaultCode(0x5301); - if(pm->m_subDisSysFault.mastLiftingServo == 1 || (pm->m_systemData->disSysFaultCode.find(15) != pm->m_systemData->disSysFaultCode.end())) + if(pm->m_subDisSysFault.mastLiftingServo == 1 || (pm->m_systemData->hasDisSysFaultCode(15))) pm->m_systemData->insertFaultCode(0x5302); else pm->m_systemData->eraseFaultCode(0x5302); @@ -2331,22 +2334,22 @@ void PowerManagerFsm::communicationSystemFault(FaultEvent const &e) if ((e.injectionFaultCode==1)) { LOG_F(INFO, "部分水下通信失效"); - pm->faultCode.push_back(0x5203); - pm->faultCode.push_back(0x5204); - pm->faultCode.push_back(0x5205); + pm->addFaultCode(0x5203); + pm->addFaultCode(0x5204); + pm->addFaultCode(0x5205); } if ((e.injectionFaultCode==2)) { LOG_F(INFO, "水下通信失效"); - pm->faultCode.push_back(0x5303); - pm->faultCode.push_back(0x5304); - pm->faultCode.push_back(0x5305); + pm->addFaultCode(0x5303); + pm->addFaultCode(0x5304); + pm->addFaultCode(0x5305); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "水面通信失效"); - pm->faultCode.push_back(0x5301); - pm->faultCode.push_back(0x5302); + pm->addFaultCode(0x5301); + pm->addFaultCode(0x5302); } } void PowerManagerFsm::payloadSystemFault() @@ -2366,8 +2369,8 @@ void PowerManagerFsm::payloadSystemFault() + (pm->m_subDisSysFault.bowWaterInet7 == 1) + (pm->m_subDisSysFault.bowWaterInet8 == 1); - if((pm->m_systemData->disSysFaultCode.find(14) == pm->m_systemData->disSysFaultCode.end()) - && (pm->m_systemData->disSysFaultCode.find(18) == pm->m_systemData->disSysFaultCode.end())) + if((!pm->m_systemData->hasDisSysFaultCode(14)) + && (!pm->m_systemData->hasDisSysFaultCode(18))) { if(count > 0 && count < 14) { @@ -2387,8 +2390,8 @@ void PowerManagerFsm::payloadSystemFault() if(pm->m_subDisSysFault.bowWaterInet8 == 1) pm->m_systemData->insertFaultCode(0x621E); else pm->m_systemData->eraseFaultCode(0x621E); } } - if((pm->m_systemData->disSysFaultCode.find(14) != pm->m_systemData->disSysFaultCode.end()) - && (pm->m_systemData->disSysFaultCode.find(18) == pm->m_systemData->disSysFaultCode.end())) + if((pm->m_systemData->hasDisSysFaultCode(14)) + && (!pm->m_systemData->hasDisSysFaultCode(18))) { pm->m_systemData->insertFaultCode(0x633F); } @@ -2400,8 +2403,8 @@ void PowerManagerFsm::payloadSystemFault() { pm->m_systemData->insertFaultCode(0x633F); } - if(count == 0 && ((pm->m_systemData->disSysFaultCode.find(14) == pm->m_systemData->disSysFaultCode.end()) - && (pm->m_systemData->disSysFaultCode.find(18) == pm->m_systemData->disSysFaultCode.end()) + if(count == 0 && ((!pm->m_systemData->hasDisSysFaultCode(14)) + && (!pm->m_systemData->hasDisSysFaultCode(18)) && (pm->m_subDisSysFault.launchControler == 0)) ) { @@ -2413,7 +2416,7 @@ void PowerManagerFsm::payloadSystemFault() else pm->m_systemData->eraseFaultCode(0x6321); - if(pm->m_systemData->disSysFaultCode.find(16) == pm->m_systemData->disSysFaultCode.end()) + if(!pm->m_systemData->hasDisSysFaultCode(16)) pm->m_systemData->eraseFaultCode(0x6323); else pm->m_systemData->insertFaultCode(0x6323); @@ -2437,50 +2440,50 @@ void PowerManagerFsm::payloadSystemFault(FaultEvent const &e) if ((e.injectionFaultCode==1)) { LOG_F(INFO, "存在启闭机构或者敞水电缸失效"); - pm->faultCode.push_back(0x6211); - pm->faultCode.push_back(0x6212); - pm->faultCode.push_back(0x6213); - pm->faultCode.push_back(0x6214); - pm->faultCode.push_back(0x6215); - pm->faultCode.push_back(0x6216); - pm->faultCode.push_back(0x6217); - pm->faultCode.push_back(0x6218); - pm->faultCode.push_back(0x6219); - pm->faultCode.push_back(0x621A); - pm->faultCode.push_back(0x621B); - pm->faultCode.push_back(0x621C); - pm->faultCode.push_back(0x621D); - pm->faultCode.push_back(0x621E); + pm->addFaultCode(0x6211); + pm->addFaultCode(0x6212); + pm->addFaultCode(0x6213); + pm->addFaultCode(0x6214); + pm->addFaultCode(0x6215); + pm->addFaultCode(0x6216); + pm->addFaultCode(0x6217); + pm->addFaultCode(0x6218); + pm->addFaultCode(0x6219); + pm->addFaultCode(0x621A); + pm->addFaultCode(0x621B); + pm->addFaultCode(0x621C); + pm->addFaultCode(0x621D); + pm->addFaultCode(0x621E); } if ((e.injectionFaultCode==2)) { LOG_F(INFO, "舷侧探测失效"); - pm->faultCode.push_back(0x6321); + pm->addFaultCode(0x6321); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "舷侧主动探测失效"); - pm->faultCode.push_back(0x6323); + pm->addFaultCode(0x6323); } if ((e.injectionFaultCode==4)) { LOG_F(INFO, "TYZ失效"); - pm->faultCode.push_back(0x6322); + pm->addFaultCode(0x6322); } if ((e.injectionFaultCode==5)) { LOG_F(INFO, "光电探测失效"); - pm->faultCode.push_back(0x6332); + pm->addFaultCode(0x6332); } if ((e.injectionFaultCode==6)) { LOG_F(INFO, "电磁探测失效"); - pm->faultCode.push_back(0x6331); + pm->addFaultCode(0x6331); } if ((e.injectionFaultCode==7)) { LOG_F(INFO, "载荷失效或者全部载荷启闭机构均失效"); - pm->faultCode.push_back(0x633F); + pm->addFaultCode(0x633F); } } void PowerManagerFsm::emergencySystemFault() @@ -2514,14 +2517,14 @@ void PowerManagerFsm::emergencySystemFault() else pm->m_systemData->eraseFaultCode(0x7301); - if(pm->m_systemData->power_bus_status.emergencyPowerLossSignal || pm->m_systemData->instrument_main_bus_status.emergencyPowerFailureSignal - || (pm->m_systemData->disSysFaultCode.find(25) != pm->m_systemData->disSysFaultCode.end())) + if(pm->m_systemData->getPowerBusStatus().emergencyPowerLossSignal || pm->m_systemData->getInstrumentMainBusStatus().emergencyPowerFailureSignal + || (pm->m_systemData->hasDisSysFaultCode(25))) pm->m_systemData->insertFaultCode(0x7306); else pm->m_systemData->eraseFaultCode(0x7306); - if(pm->m_systemData->power_a_bus_status.emergencyPowerFailureSignal || pm->m_systemData->instrument_bus_status.emergencyPowerFailureSignal - || (pm->m_systemData->disSysFaultCode.find(33) != pm->m_systemData->disSysFaultCode.end())) + if(pm->m_systemData->getPowerABusStatus().emergencyPowerFailureSignal || pm->m_systemData->getInstrumentBusStatus().emergencyPowerFailureSignal + || (pm->m_systemData->hasDisSysFaultCode(33))) pm->m_systemData->insertFaultCode(0x7307); else pm->m_systemData->eraseFaultCode(0x7307); @@ -2534,35 +2537,35 @@ void PowerManagerFsm::emergencySystemFault(FaultEvent const &e) { //备注:抛载具有自己的电池 LOG_F(INFO, "部分抛载失效"); - pm->faultCode.push_back(0x7202); - pm->faultCode.push_back(0x7203); + pm->addFaultCode(0x7202); + pm->addFaultCode(0x7203); } if ((e.injectionFaultCode==2)) { LOG_F(INFO, "一个超深检测装置失效"); - pm->faultCode.push_back(0x7204); - pm->faultCode.push_back(0x7205); + pm->addFaultCode(0x7204); + pm->addFaultCode(0x7205); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "注水机构失效"); - pm->faultCode.push_back(0x7301); + pm->addFaultCode(0x7301); } if ((e.injectionFaultCode==4)) { LOG_F(INFO, "抛载失效"); - pm->faultCode.push_back(0x7302); - pm->faultCode.push_back(0x7303); + pm->addFaultCode(0x7302); + pm->addFaultCode(0x7303); } if ((e.injectionFaultCode==5)) { LOG_F(INFO, "艏部应急电源失效"); - pm->faultCode.push_back(0x7306); + pm->addFaultCode(0x7306); } if ((e.injectionFaultCode==6)) { LOG_F(INFO, "艉部应急电源失效"); - pm->faultCode.push_back(0x7307); + pm->addFaultCode(0x7307); } } @@ -2600,18 +2603,18 @@ void PowerManagerFsm::sensorSystemFault(FaultEvent const &e) if ((e.injectionFaultCode==1)) { LOG_F(INFO, "部分CTD失效"); - pm->faultCode.push_back(0x8202); + pm->addFaultCode(0x8202); } if ((e.injectionFaultCode==2)) { LOG_F(INFO, "CTD失效"); - pm->faultCode.push_back(0x8302); - pm->faultCode.push_back(0x8303); + pm->addFaultCode(0x8302); + pm->addFaultCode(0x8303); } if ((e.injectionFaultCode==3)) { LOG_F(INFO, "声速仪失效"); - pm->faultCode.push_back(0x8301); + pm->addFaultCode(0x8301); } } void PowerManagerFsm::react(FaultEvent const &e) @@ -2637,7 +2640,7 @@ void PowerManagerFsm::handelPowerCmd(){ pm->m_systemData->setSystemInfo("INFO:收到上位机控制指令:关机"); // MOOSPause(3); // int count=0; - // while (!(pm->m_systemData->dyn_pos_relay_status == 0x02 && pm->m_systemData->dyn_neg_relay_status == 0x02)) + // while (!(pm->m_systemData->getDynPosRelayStatus() == 0x02 && pm->m_systemData->getDynNegRelayStatus() == 0x02)) // { // LOG_F(INFO,"动力锂电池下电中..."); // MOOSPause(3); @@ -2729,10 +2732,13 @@ void PowerManagerFsm::handelPowerCmd(){ pm->m_lowerCommManager->operate_HV_powerLithiumBattery_Breaker(0); MOOSPause(1000); //最后 关闭动力电池 - pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Stop; + { + std::lock_guard lock(pm->m_stateMutex); + pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Stop; + } { int count=0; - while (!(pm->m_systemData->dyn_pos_relay_status == 0x02 && pm->m_systemData->dyn_neg_relay_status == 0x02)) + while (!(pm->m_systemData->getDynPosRelayStatus() == 0x02 && pm->m_systemData->getDynNegRelayStatus() == 0x02)) { LOG_F(INFO,"动力锂电池下电中..."); MOOSPause(1000); @@ -2751,7 +2757,10 @@ void PowerManagerFsm::handelPowerCmd(){ pm->m_systemData->setSystemInfo("INFO:次级配电系统恢复待机"); MOOSPause(3000); //4. 执行锂电池关机指令 - pm->m_CcuColCmd.insBatCmd = BatteryCommand::Stop; + { + std::lock_guard lock(pm->m_stateMutex); + pm->m_CcuColCmd.insBatCmd = BatteryCommand::Stop; + } break; } case 1: @@ -2759,10 +2768,13 @@ void PowerManagerFsm::handelPowerCmd(){ LOG_F(INFO, "收到上位机控制指令:动力电上电"); pm->m_systemData->setSystemInfo("INFO:收到上位机控制指令:动力电上电"); //1. 动力锂电池上电 - pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + { + std::lock_guard lock(pm->m_stateMutex); + pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + } // 等待动力锂电池上电完成 int count=0; - while (!(pm->m_systemData->dyn_voltage_link > 500.0)) + while (!(pm->m_systemData->getDynVoltageLink() > 500.0)) { LOG_F(INFO,"动力锂电池上电中..."); MOOSPause(1000); @@ -2795,7 +2807,10 @@ void PowerManagerFsm::handelPowerCmd(){ //1. 闭合动力电断路器 pm->m_lowerCommManager->operate_HV_powerLithiumBattery_Breaker(1); //2. 动力锂电池上电 - pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + { + std::lock_guard lock(pm->m_stateMutex); + pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + } break; } case 3: @@ -2820,7 +2835,7 @@ void PowerManagerFsm::handelPowerCmd(){ pm->m_lowerCommManager->operate_HV_powerLithiumBattery_Breaker(0); MOOSPause(3); int count=0; - while (!(pm->m_systemData->dyn_pos_relay_status == 0x02 && pm->m_systemData->dyn_neg_relay_status == 0x02)) + while (!(pm->m_systemData->getDynPosRelayStatus() == 0x02 && pm->m_systemData->getDynNegRelayStatus() == 0x02)) { LOG_F(INFO,"动力锂电池下电中..."); MOOSPause(3); @@ -2957,7 +2972,7 @@ void PowerManagerFsm::react(TestEvent const &e) LOG_F(INFO, "测试消息"); if(e.type == 0){ LOG_F(INFO, "清空故障代码"); - pm->faultCode.clear(); + pm->clearFaultCodes(); pm->m_leakage = 0x00; pm->m_systemData->insfaultCode = 0x00; pm->m_systemData->powfaultCode = 0x00; @@ -2965,7 +2980,7 @@ void PowerManagerFsm::react(TestEvent const &e) pm->m_systemData->fc_fault_level_3 = 0; pm->m_systemData->fc_fault_level_2 = 0; pm->m_systemData->fc_fault_level_1 = 0; - pm->m_systemData->disSysFaultCode.clear(); + pm->m_systemData->clearDisSysFaultCodes(); cout << "清空故障代码" << endl; }; // if(e.eventId == 0) return ; @@ -3070,7 +3085,7 @@ void PowerManagerFsm::react(TestEvent const &e) { LOG_F(INFO,"配电系统故障测试"); // pm->m_systemData->distributeFaultCode.push_back(disfaultMap[e.eventId].code); - pm->m_systemData->disSysFaultCode.insert(disfaultMap[e.eventId].code); + pm->m_systemData->insertDisSysFaultCode(disfaultMap[e.eventId].code); } default: LOG_F(INFO, "测试消息类型错误"); diff --git a/src/pPowerManger/fsm/ShoreBasedReady.cpp b/src/pPowerManger/fsm/ShoreBasedReady.cpp index d33a818..8e263f5 100644 --- a/src/pPowerManger/fsm/ShoreBasedReady.cpp +++ b/src/pPowerManger/fsm/ShoreBasedReady.cpp @@ -15,10 +15,13 @@ void ShoreBasedReady::entry() { //依次接入执行机构动力电设备 //1. 开启动力电 //1.1 动力锂电池上电 - pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + { + std::lock_guard lock(pm->m_stateMutex); + pm->m_CcuColCmd.powerBatCmd = BatteryCommand::Start; + } // 等待动力锂电池上电完成 int count=0; - while (!(pm->m_systemData->dyn_voltage_link > 500.0)) + while (!(pm->m_systemData->getDynVoltageLink() > 500.0)) { LOG_F(INFO,"动力锂电池上电中..."); MOOSPause(1000); diff --git a/src/pPowerManger/httpserver/httpserver.cpp b/src/pPowerManger/httpserver/httpserver.cpp index 0ee59dd..f5ff72c 100644 --- a/src/pPowerManger/httpserver/httpserver.cpp +++ b/src/pPowerManger/httpserver/httpserver.cpp @@ -35,8 +35,8 @@ void HttpServer::handleWebSocketEvent(struct mg_connection* c, int ev, void* ev_ c->id, (int)(intptr_t)c->fd, errno); // 打印socket错误信息 char buf[256]; - strerror_r(errno, buf, sizeof(buf)); - LOG_F(ERROR, "Socket error: %s", buf); + const char *err = strerror_r(errno, buf, sizeof(buf)); + LOG_F(ERROR, "Socket error: %s", err ? err : buf); } else { LOG_F(ERROR, "WebSocket connection closed abnormally: id=%lu fd=%d", c->id, (int)(intptr_t)c->fd); @@ -133,16 +133,19 @@ void HttpServer::handleWebSocketEvent(struct mg_connection* c, int ev, void* ev_ fuelCellMode, fuelCellCommand, fuelCellPower, instrumentBattery, instrumentBatteryCharge, powerBattery, powerBatteryPower, hostStatus); - m_powerManger->m_CcuColCmd.fcmode = fuelCellMode; - m_powerManger->m_CcuColCmd.fcuCmd = fuelCellCommand; - m_powerManger->m_CcuColCmd.fcuPowerEfficiency = fuelCellPower; - m_powerManger->m_CcuColCmd.insBatCmd = instrumentBattery; + { + std::lock_guard lock(m_powerManger->m_stateMutex); + m_powerManger->m_CcuColCmd.fcmode = fuelCellMode; + m_powerManger->m_CcuColCmd.fcuCmd = fuelCellCommand; + m_powerManger->m_CcuColCmd.fcuPowerEfficiency = fuelCellPower; + m_powerManger->m_CcuColCmd.insBatCmd = instrumentBattery; + m_powerManger->m_CcuColCmd.powerBatCmd = powerBattery; + m_powerManger->m_CcuColCmd.powerBatEfficiency = powerBatteryPower; + m_powerManger->m_CcuColCmd.masterState = hostStatus; + m_powerManger->m_CcuColCmd.insChargeEnable = instrumentBatteryCharge; + } cout << "powerBatteryPower:" << (int)instrumentBattery << endl; - m_powerManger->m_CcuColCmd.powerBatCmd = powerBattery; - m_powerManger->m_CcuColCmd.powerBatEfficiency = powerBatteryPower; - m_powerManger->m_CcuColCmd.masterState = hostStatus; - m_powerManger->m_CcuColCmd.insChargeEnable = instrumentBatteryCharge; - cout << "m_powerManger->m_CcuColCmd.masterState:" << (int)m_powerManger->m_CcuColCmd.masterState << endl; + cout << "m_powerManger->m_CcuColCmd.masterState:" << (int)hostStatus << endl; mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{\"success\": true, \"message\": \"电池配置提交成功\"}"); @@ -353,6 +356,13 @@ void HttpServer::pushSystemData() { return; } + // 先持 m_stateMutex 快照 m_CcuColCmd(绝不与下方 m_dataMutex 同时持有,避免锁序死锁) + ccuColCmd ccuCmd; + if (m_powerManger) { + std::lock_guard stateLock(m_powerManger->m_stateMutex); + ccuCmd = m_powerManger->m_CcuColCmd; + } + std::lock_guard dataLock(m_systemData->m_dataMutex); // 序列化系统数据为JSON @@ -772,15 +782,15 @@ void HttpServer::pushSystemData() { j["debug"]["m_liBatStateMsg_st_debug"]["content"] = Json::Value(m_powerManger->m_upperCommManager->m_liBatStateMsg_st_debug.second); #endif - j["ccuColCmd"]["fcmode"] = Json::Value(m_powerManger->m_CcuColCmd.fcmode); - j["ccuColCmd"]["fcuCmd"] = Json::Value(m_powerManger->m_CcuColCmd.fcuCmd); - j["ccuColCmd"]["fcuPowerEfficiency"] = Json::Value(m_powerManger->m_CcuColCmd.fcuPowerEfficiency); - j["ccuColCmd"]["insChargeEnable"] = Json::Value(m_powerManger->m_CcuColCmd.insChargeEnable); - j["ccuColCmd"]["insBatCmd"] = Json::Value(m_powerManger->m_CcuColCmd.insBatCmd); - j["ccuColCmd"]["powerBatCmd"] = Json::Value(m_powerManger->m_CcuColCmd.powerBatCmd); - j["ccuColCmd"]["powerBatEfficiency"] = Json::Value(m_powerManger->m_CcuColCmd.powerBatEfficiency); - j["ccuColCmd"]["heartbeat"] = Json::Value(m_powerManger->m_CcuColCmd.heartbeat); - j["ccuColCmd"]["masterState"] = Json::Value(m_powerManger->m_CcuColCmd.masterState); + j["ccuColCmd"]["fcmode"] = Json::Value(ccuCmd.fcmode); + j["ccuColCmd"]["fcuCmd"] = Json::Value(ccuCmd.fcuCmd); + j["ccuColCmd"]["fcuPowerEfficiency"] = Json::Value(ccuCmd.fcuPowerEfficiency); + j["ccuColCmd"]["insChargeEnable"] = Json::Value(ccuCmd.insChargeEnable); + j["ccuColCmd"]["insBatCmd"] = Json::Value(ccuCmd.insBatCmd); + j["ccuColCmd"]["powerBatCmd"] = Json::Value(ccuCmd.powerBatCmd); + j["ccuColCmd"]["powerBatEfficiency"] = Json::Value(ccuCmd.powerBatEfficiency); + j["ccuColCmd"]["heartbeat"] = Json::Value(ccuCmd.heartbeat); + j["ccuColCmd"]["masterState"] = Json::Value(ccuCmd.masterState); // 时间戳 j["timestamp"] = Json::Value((Json::UInt64)(std::chrono::duration_cast( diff --git a/src/pPowerManger/systemData.h b/src/pPowerManger/systemData.h index 60802bb..40a3bb1 100644 --- a/src/pPowerManger/systemData.h +++ b/src/pPowerManger/systemData.h @@ -37,6 +37,7 @@ public: * @return uint8_t 故障等级,00H-无故障, 10H-一级, 20H-二级, 30H-三级, 40H-四级 */ uint8_t getFaultCodeLevel() const { + std::lock_guard lock(sysFaultCodesMutex); uint8_t maxLevel = 0; // 遍历 sysFaultCodes 集合中的每个故障码 for (auto code : sysFaultCodes) { @@ -536,6 +537,7 @@ public: bool leakStatus[8] = {false}; unsigned char generateLeakageCode() { + std::lock_guard lock(m_dataMutex); unsigned char code = 0; if(leakStatus[LEAKAGE_BOW_EMERGENCY]) code |= 0x01; if(leakStatus[LEAKAGE_BOW_HIGH_VOLTAGE]) code |= 0x02; @@ -615,6 +617,65 @@ public: }; ~SystemData(){}; + //============== 跨线程读访问器(锁内拷贝,避免与更新线程竞争)============== + float getDynVoltageLink() const { + std::lock_guard lock(m_dataMutex); + return dyn_voltage_link; + } + uint8_t getDynPosRelayStatus() const { + std::lock_guard lock(m_dataMutex); + return dyn_pos_relay_status; + } + uint8_t getDynNegRelayStatus() const { + std::lock_guard lock(m_dataMutex); + return dyn_neg_relay_status; + } + uint8_t getDynEmergencyStatus() const { + std::lock_guard lock(m_dataMutex); + return dyn_emergency_status; + } + DcdcStatus getDcdcStatus() const { + std::lock_guard lock(m_dataMutex); + return m_dcdcStatus; + } + decltype(power_bus_status) getPowerBusStatus() const { + std::lock_guard lock(m_dataMutex); + return power_bus_status; + } + decltype(power_a_bus_status) getPowerABusStatus() const { + std::lock_guard lock(m_dataMutex); + return power_a_bus_status; + } + decltype(instrument_main_bus_status) getInstrumentMainBusStatus() const { + std::lock_guard lock(m_dataMutex); + return instrument_main_bus_status; + } + decltype(instrument_bus_status) getInstrumentBusStatus() const { + std::lock_guard lock(m_dataMutex); + return instrument_bus_status; + } + DeviceCommonStatus getDeviceCommonStatus() const { + std::lock_guard lock(m_dataMutex); + return device_common_status; + } + // 配电故障码集合(std::set 非原子,必须加锁访问) + bool hasDisSysFaultCode(unsigned int code) const { + std::lock_guard lock(faultCodeMutex); + return disSysFaultCode.find(code) != disSysFaultCode.end(); + } + void insertDisSysFaultCode(unsigned int code) { + std::lock_guard lock(faultCodeMutex); + disSysFaultCode.insert(code); + } + void clearDisSysFaultCodes() { + std::lock_guard lock(faultCodeMutex); + disSysFaultCode.clear(); + } + std::set getDisSysFaultCodes() const { + std::lock_guard lock(faultCodeMutex); + return disSysFaultCode; + } + void updateCommonStatus(double time_out=30){ std::lock_guard lock(m_dataMutex); //更新时间 @@ -754,10 +815,9 @@ public: ins_emergency_status = data.ins_emergency_status; dyn_emergency_status = data.dyn_emergency_status; - //保留字段 - for(int i=0; i<2; i++) { - reserved2[i] = data.reserved2[i]; - } + //保留字段:ccuState 中 reserved2 仅声明 1 字节(协议预留 2 字节),只拷贝已有元素,避免越界读 + reserved2[0] = data.reserved2[0]; + reserved2[1] = 0; //SOC门限值 ins_soc_threshold1 = data.ins_soc_threshold1; diff --git a/src/pPowerManger/udpcomm/udpComm.cpp b/src/pPowerManger/udpcomm/udpComm.cpp index 838da93..cfe4b63 100644 --- a/src/pPowerManger/udpcomm/udpComm.cpp +++ b/src/pPowerManger/udpcomm/udpComm.cpp @@ -470,17 +470,13 @@ bool udpComm::sendDisSysCmd(const disHighVolBusCmd cmd) msgBuf = reinterpret_cast(msg); cc = calculateChecksum(msgBuf,sizeof(msg_disHVBCmd),sizeof(cChecksum)); msg->checkCode = cc; - try - { - sendMsg(msgBuf,sizeof(msg_disHVBCmd)); - } - catch(const std::exception& e) - { - std::cerr << e.what() << '\n'; + bool sendResult = sendMsg(msgBuf, sizeof(msg_disHVBCmd)); + if (!sendResult) { + LOG_F(ERROR, "sendDisSysCmd(disHighVolBusCmd) send failed"); } if (m_commLogger) m_commLogger->onFrame(1, DIS_HVBUS_CMD_ID, "disHighVolBusCmd", msgBuf, sizeof(msg_disHVBCmd), true); delete msg; - return true; + return sendResult; } bool udpComm::sendDisSysCmd(const disHighAVolBusCmd cmd) @@ -498,17 +494,13 @@ bool udpComm::sendDisSysCmd(const disHighAVolBusCmd cmd) msgBuf = reinterpret_cast(msg); cc = calculateChecksum(msgBuf,sizeof(msg_disHVBACmd),sizeof(cChecksum)); msg->checkCode = cc; - try - { - sendMsg(msgBuf,sizeof(msg_disHVBACmd)); - } - catch(const std::exception& e) - { - std::cerr << e.what() << '\n'; + bool sendResult = sendMsg(msgBuf, sizeof(msg_disHVBACmd)); + if (!sendResult) { + LOG_F(ERROR, "sendDisSysCmd(disHighAVolBusCmd) send failed"); } if (m_commLogger) m_commLogger->onFrame(1, DIS_HVBUSA_CMD_ID, "disHighAVolBusCmd", msgBuf, sizeof(msg_disHVBACmd), true); delete msg; - return true; + return sendResult; } bool udpComm::sendDisSysCmd(const disHighBVolBusCmd cmd) @@ -526,18 +518,13 @@ bool udpComm::sendDisSysCmd(const disHighBVolBusCmd cmd) msgBuf = reinterpret_cast(msg); cc = calculateChecksum(msgBuf,sizeof(msg_disHVBBCmd),sizeof(cChecksum)); msg->checkCode = cc; - try - { - // cout << "=========" <<(int)sizeof(msg_disHVBBCmd) << endl; - sendMsg(msgBuf,sizeof(msg_disHVBBCmd)); - } - catch(const std::exception& e) - { - std::cerr << e.what() << '\n'; + bool sendResult = sendMsg(msgBuf, sizeof(msg_disHVBBCmd)); + if (!sendResult) { + LOG_F(ERROR, "sendDisSysCmd(disHighBVolBusCmd) send failed"); } if (m_commLogger) m_commLogger->onFrame(1, DIS_HVBUSB_CMD_ID, "disLowMainBusCmd", msgBuf, sizeof(msg_disHVBBCmd), true); delete msg; - return true; + return sendResult; } bool udpComm::sendDisSysCmd(const disLowBusCmd cmd) @@ -556,18 +543,13 @@ bool udpComm::sendDisSysCmd(const disLowBusCmd cmd) cc = calculateChecksum(msgBuf,sizeof(msg_disLVBCmd),sizeof(cChecksum)); // msg->checkCode = static_cast(cc & 0xFF); msg->checkCode = cc; - cout << "cc======disLowBusCmd=============================" << (int) msg->checkCode << endl; - try - { - sendMsg(msgBuf,sizeof(msg_disLVBCmd)); - } - catch(const std::exception& e) - { - std::cerr << e.what() << '\n'; + bool sendResult = sendMsg(msgBuf, sizeof(msg_disLVBCmd)); + if (!sendResult) { + LOG_F(ERROR, "sendDisSysCmd(disLowBusCmd) send failed"); } if (m_commLogger) m_commLogger->onFrame(1, DIS_LVBUS_CMD_ID, "disLowBusCmd", msgBuf, sizeof(msg_disLVBCmd), true); delete msg; - return true; + return sendResult; } bool udpComm::sendCcuColCmd(const ccuColCmd cmd) diff --git a/src/pPowerManger/upmsg/uPower_disSysState.h b/src/pPowerManger/upmsg/uPower_disSysState.h index 6af268a..fc0216f 100644 --- a/src/pPowerManger/upmsg/uPower_disSysState.h +++ b/src/pPowerManger/upmsg/uPower_disSysState.h @@ -137,6 +137,7 @@ public: // std::string buildMsg(SystemData data,disSysBreakerList currentDisBreakerState) std::string buildMsg(const SystemData& data,disSysBreakerList currentDisBreakerState) { + std::lock_guard lock(data.m_dataMutex); //构建配电系统状态报告 Json::Value dis; // disBus1Breaker dis1 = currentDisBreakerState.bus1Breaker; diff --git a/src/pPowerManger/upmsg/uPower_fcsState.h b/src/pPowerManger/upmsg/uPower_fcsState.h index 72c8dff..d9dcbdf 100644 --- a/src/pPowerManger/upmsg/uPower_fcsState.h +++ b/src/pPowerManger/upmsg/uPower_fcsState.h @@ -27,6 +27,7 @@ public: // std::string buildMsg(SystemData data) std::string buildMsg(const SystemData& data) { + std::lock_guard lock(data.m_dataMutex); //TODO:根据最新协议修改 Json::Value fcs; // suffix = getTypeName(typeid(ccustate.fcuState).name()); diff --git a/src/pPowerManger/upmsg/uPower_liBatState.h b/src/pPowerManger/upmsg/uPower_liBatState.h index fe3c1bd..bf692ec 100644 --- a/src/pPowerManger/upmsg/uPower_liBatState.h +++ b/src/pPowerManger/upmsg/uPower_liBatState.h @@ -18,6 +18,7 @@ public: // std::string buildMsg(SystemData data) std::string buildMsg(const SystemData& data) { + std::lock_guard lock(data.m_dataMutex); //TODO:根据最新协议修改 Json::Value lbs; //构建锂电池状态报告 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7ae9542..ad83429 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -108,3 +108,20 @@ target_link_libraries(udpFeeder pthread dl ) + +################################################fullFeeder(集成测试用)####### +# 全类型 UDP 仿真器:CCU 状态 + 4 路配电反馈(自包含,仅依赖协议结构体头文件) +add_executable(fullFeeder + integration/full_feeder.cpp +) +target_include_directories(fullFeeder PRIVATE + ${PM_SRC_DIR} + ${PM_SRC_DIR}/udpcomm +) +set_target_properties(fullFeeder PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON +) +target_link_libraries(fullFeeder + pthread +) diff --git a/test/integration/full_feeder.cpp b/test/integration/full_feeder.cpp new file mode 100644 index 0000000..11d2ba2 --- /dev/null +++ b/test/integration/full_feeder.cpp @@ -0,0 +1,292 @@ +//======================================================================= +// full_feeder.cpp: 集成测试用 — 仿真真实 CCU + 配电系统,向 pPowerManger +// 发送全部 5 类状态反馈 UDP 消息(CCU 状态 + 4 路配电反馈)。 +// 直接使用真实 C++ 结构体构造消息(字节级与协议一致,无打包漂移)。 +// 用法: fullFeeder [host] [port] [rounds] [interval_ms] +// rounds=0 表示持续发送直到被 kill。 +//======================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "udpcomm/ccuUdpMsg.h" + +static int g_fd = -1; +static sockaddr_in g_addr{}; + +static bool sendBuf(const void* buf, size_t len) { + ssize_t n = sendto(g_fd, buf, len, 0, + reinterpret_cast(&g_addr), sizeof(g_addr)); + return n == (ssize_t)len; +} + +// CCU 消息校验和:4 字节全宽(sum 覆盖 sizeof-4 字节) +static unsigned int ccuChecksum(const unsigned char* d, int size) { + unsigned int s = 0; + for (int i = 0; i < size - (int)sizeof(Checksum); ++i) s += d[i]; + return s; +} +// 配电消息校验和:1 字节(sum 覆盖 sizeof-1 字节,&0xFF) +static unsigned char disChecksum(const unsigned char* d, int size) { + unsigned int s = 0; + for (int i = 0; i < size - (int)sizeof(cChecksum); ++i) s += d[i]; + return (unsigned char)(s & 0xFF); +} + +static void fillCcuState(msg_CcuStateFbMsg& m, unsigned int beat) { + 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.header.length = sizeof(ccuState); + m.data.fc_mode = 0x02; // 自动 + m.data.fc_status = 0x04; // 运行 + m.data.fc_fault_level = 0x00; // 无故障 + m.data.output_power_limit = 30000; + m.data.total_generation_time = 1234; + m.data.total_generation_power = 8000; + m.data.hydrogen_capacity = 70; + m.data.liquid_oxygen_capacity = 65; + m.data.palladium_temp = 60; + m.data.main_pipe_pressure = 200; + m.data.aux_pipe_pressure = 180; + m.data.cabin_pressure1 = 101; + m.data.cabin_pressure2 = 101; + m.data.cabin_temp1 = 25; + m.data.cabin_temp2 = 26; + m.data.cabin_humidity1 = 50; + m.data.cabin_humidity2 = 52; + m.data.flame_detector1 = 0x00; + m.data.flame_detector2 = 0x00; + m.data.h2_concentration1 = 5; + m.data.o2_concentration1 = 2000; + m.data.ch3oh_concentration1 = 3; + m.data.emergency_battery1_voltage = 240; + m.data.emergency_battery1_current = 10; + m.data.emergency_battery1_max_temp = 30; + m.data.emergency_battery2_voltage = 240; + m.data.emergency_battery2_current = 10; + m.data.emergency_battery2_max_temp = 31; + m.data.cabin_ox_concentration = 2000; + m.data.cabin_temperature = 25; + m.data.cabin_humidity = 50; + m.data.cabin_pressure = 101; + m.data.power_cabin_ox_concentration = 2000; + m.data.power_cabin_temperature = 28; + m.data.power_cabin_humidity = 48; + m.data.power_cabin_pressure = 101; + m.data.ins_relay_status1 = 0x11; + m.data.ins_relay_status2 = 0x12; + m.data.dyn_relay_status1 = 0x11; + m.data.dyn_relay_status2 = 0x12; + m.data.ins_max_discharge_power = 200; + m.data.dyn_max_discharge_power = 400; + m.data.ins_soc = 85; + m.data.dyn_soc = 80; + m.data.ins_total_energy = 100; + m.data.dyn_total_energy = 500; + m.data.ins_power_input = 10; + m.data.dyn_power_input = 30; + m.data.ins_charge_status = 0x20; // 放电 + m.data.dyn_charge_status = 0x20; + m.data.ins_voltage_link = 2400; // 240.0V + m.data.ins_voltage_pack = 2350; + m.data.ins_current = 400; // 20A + m.data.ins_resistance_pos = 999; + m.data.ins_resistance_neg = 999; + m.data.dyn_voltage_link = 5200; // 520.0V + m.data.dyn_voltage_pack = 5150; + m.data.dyn_current = 600; // 30A + m.data.dyn_resistance_pos = 999; + m.data.dyn_resistance_neg = 999; + m.data.ins_emergency_status = 0x00; + m.data.dyn_emergency_status = 0x00; + m.data.ins_soc_threshold1 = 90; + m.data.ins_soc_threshold2 = 80; + m.data.ins_soc_threshold3 = 70; + m.data.dyn_soc_threshold1 = 90; + m.data.dyn_soc_threshold2 = 80; + m.data.dyn_soc_threshold3 = 70; + m.data.device_online_flag1 = 0x000000FF; + m.data.device_online_flag2 = 0x0000001F; + m.data.dyn_charge_level = 0x01; + m.data.dyn_water_leak_status = 0x00; + m.data.ins_water_leak_dcdc_status = 0x00; + m.data.heartbeat = beat; + m.checkCode = ccuChecksum(reinterpret_cast(&m), sizeof(m)); +} + +static void fillDisHighVolBus(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.header.length = sizeof(disHighVolBusState); + m.data.fuelCellCircuitBreaker = BREAK_ON; + m.data.powerLithiumBatteryCircuitBreaker = BREAK_ON; + m.data.propulsionMotorCircuitBreaker = BREAK_OFF; + m.data.lithiumBatteryGroupInstrumentCircuitBreaker = BREAK_ON; + m.data.dcDc5ModuleCircuitBreaker = BREAK_ON; + m.data.bowHighVoltageDistributionBoxCircuitBreaker = BREAK_ON; + m.data.sternFTDevice45CircuitBreaker = BREAK_OFF; + m.data.sternRudderSwitch1CircuitBreaker = BREAK_OFF; + m.data.sternRudderSwitch2CircuitBreaker = BREAK_OFF; + m.data.fbReservedCircuitBreaker = BREAK_OFF; + m.data.tyzReservedCircuitBreaker = BREAK_OFF; + m.data.reservedCircuitBreaker = BREAK_OFF; + m.data.dcDcFaultWord1 = 0x00; + m.data.dcDcFaultWord2 = 0x62; // 工作状态启动 + 冷却泵运行 + m.data.powerBusInsulationStatus = BREAK_ON; // 正常 + m.data.aBusInsulationStatus = BREAK_ON; + m.data.meterPowerLossSignal = BREAK_ON; + m.data.emergencyPowerLossSignal = BREAK_ON; + m.data.dcDcTemperature = 60; + m.data.powerBusVoltage = 5000; // 500.0V + m.data.dcDc5ModuleCurrent = 200; + m.data.powerBusCurrent = 400; + m.data.busbarAVoltage = 5000; + m.data.propulsionMotorControlBoxCurrent = 10; + m.data.busbarACurrent = 300; + m.data.lithiumBatteryGroupMeterCurrent = 50; + m.data.bowHighVoltageDistributionBoxCurrent = 100; + m.data.sternFTDevice45Current = 0; + m.data.tyzReservedSwitchCurrent = 0; + m.data.sternRudderSwitch1Current = 0; + m.data.sternRudderSwitch2Current = 0; + m.data.coolWaterPressure = 150; + m.checkCode = disChecksum(reinterpret_cast(&m), sizeof(m)); +} + +static void fillDisHighAVolBus(msg_disHighAVolBusFbMsg& m) { + memset(&m, 0, sizeof(m)); + m.header.start1 = DIS_MSG_HEAD1; + m.header.start2 = DIS_MSG_HEAD2; + m.header.id = DIS_HVBUSA_FB_ID; + m.header.length = sizeof(disHighAVolBusState); + m.data.bowFTDevice123CircuitBreaker = BREAK_ON; + m.data.actuatorCircuitBreaker = BREAK_OFF; + m.data.mastSteeringGearControlBoxCircuitBreaker = BREAK_OFF; + m.data.xczCircuitBreaker = BREAK_ON; + m.data.bowRudderControlBoxCircuitBreaker = BREAK_OFF; + m.data.openWaterCoverStartCylinderCircuitBreaker = BREAK_OFF; + m.data.instrumentPowerFailureSignal = BREAK_ON; + m.data.emergencyPowerFailureSignal = BREAK_ON; + m.data.waterIngressionAlarm = BREAK_ON; // 正常 + m.data.busbarBVoltage = 5000; + m.data.busbarBCurrent = 300; + m.data.bowFTDevice123Current = 80; + m.data.actuatorCurrent = 0; + m.data.mastSteeringGearControlBoxCurrent = 0; + m.data.xczCurrent = 40; + m.data.bowRudderControlBoxCurrent = 0; + m.data.openWaterCoverStartCylinderCurrent = 0; + m.checkCode = disChecksum(reinterpret_cast(&m), sizeof(m)); +} + +static void fillDisLowMainBus(msg_disLowMainBusFbMsg& m) { + memset(&m, 0, sizeof(m)); + m.header.start1 = DIS_MSG_HEAD1; + m.header.start2 = DIS_MSG_HEAD2; + m.header.id = DIS_HVBUSB_FB_ID; + m.header.length = sizeof(disLowMainBusState); + m.data.lithiumBatteryGroupInstrumentCircuitBreaker = BREAK_ON; + m.data.bowLowVoltageDistributionBoxCircuitBreaker = BREAK_ON; + m.data.unit4InstrumentDC48VCircuitBreaker = BREAK_ON; + m.data.dcC1DCDistributionPanelCircuitBreaker = BREAK_ON; + m.data.reservedCircuitBreaker1 = BREAK_OFF; + m.data.reservedCircuitBreaker2 = BREAK_OFF; + m.data.emergencyLithiumBatteryGroup2CircuitBreaker = BREAK_ON; + m.data.instrumentPowerFailureSignal = BREAK_ON; + m.data.emergencyPowerFailureSignal = BREAK_ON; + m.data.instrumentBusbarInsulationLow = BREAK_ON; // 正常 + m.data.instrumentBusbarVoltage = 480; // 48.0V + m.data.bowLowVoltageDistributionBoxCurrent = 50; + m.data.dcC1InstrumentDC48VCurrent = 30; + m.data.emergencyLithiumBatteryGroup2Current = 10; + m.data.lithiumBatteryGroupInstrumentCurrent = 20; + m.data.unit4InstrumentDC48VCurrent = 15; + m.data.emergency2BusbarVoltage = 480; + m.data.compositeEnergyManagementSystemEmergencyDC48VCurrent = 5; + m.data.fuelCellSecuritySystemEmergencyDC48VCurrent = 5; + m.data.plcControlPowerCurrent = 8; + m.checkCode = disChecksum(reinterpret_cast(&m), sizeof(m)); +} + +static void fillDisLowBus(msg_disLowBusFbMsg& m) { + memset(&m, 0, sizeof(m)); + m.header.start1 = DIS_MSG_HEAD1; + m.header.start2 = DIS_MSG_HEAD2; + m.header.id = DIS_LVBUS_FB_ID; + m.header.length = sizeof(disLowBusState); + m.data.unit1CircuitBreaker = BREAK_ON; + m.data.unit2CircuitBreaker = BREAK_ON; + m.data.unit3CircuitBreaker = BREAK_ON; + m.data.unit5CircuitBreaker = BREAK_OFF; + m.data.bowPZDeviceCircuitBreaker = BREAK_ON; + m.data.reservedCircuitBreaker1 = BREAK_OFF; + m.data.reservedCircuitBreaker2 = BREAK_OFF; + m.data.emergencyLithiumBatteryGroup1CircuitBreaker = BREAK_ON; + m.data.powerFailureSignal = BREAK_ON; + m.data.emergencyPowerFailureSignal = BREAK_ON; + m.data.waterIngressionAlarm = BREAK_ON; // 正常 + m.data.instrumentBusbarVoltage = 240; // 24.0V + m.data.instrumentBusbarCurrent = 60; + m.data.unit1Current = 15; + m.data.unit2Current = 15; + m.data.unit3Current = 15; + m.data.unit5Current = 0; + m.data.bowPZDeviceCurrent = 10; + m.data.emergencyLithiumBatteryGroup1Current = 8; + m.data.emergency1BusbarVoltage = 240; + m.checkCode = disChecksum(reinterpret_cast(&m), sizeof(m)); +} + +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 rounds = (argc > 3) ? atoi(argv[3]) : 0; // 0 = 持续 + int intervalMs = (argc > 4) ? atoi(argv[4]) : 200; + + g_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (g_fd < 0) { perror("socket"); return 1; } + g_addr.sin_family = AF_INET; + g_addr.sin_port = htons(static_cast(port)); + if (inet_pton(AF_INET, host, &g_addr.sin_addr) != 1) { + perror("inet_pton"); close(g_fd); return 1; + } + + unsigned int beat = 1; + int sent = 0; + while (rounds == 0 || sent < rounds) { + msg_CcuStateFbMsg ccu; + msg_disHighVolBusFbMsg hv; + msg_disHighAVolBusFbMsg hva; + msg_disLowMainBusFbMsg lvmain; + msg_disLowBusFbMsg lv; + + fillCcuState(ccu, beat); + fillDisHighVolBus(hv); + fillDisHighAVolBus(hva); + fillDisLowMainBus(lvmain); + fillDisLowBus(lv); + + if (!sendBuf(&ccu, sizeof(ccu)) || + !sendBuf(&hv, sizeof(hv)) || + !sendBuf(&hva, sizeof(hva)) || + !sendBuf(&lvmain, sizeof(lvmain)) || + !sendBuf(&lv, sizeof(lv))) { + perror("sendto"); break; + } + ++beat; + ++sent; + std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); + } + close(g_fd); + printf("sent %d round(s) of 5 message types to %s:%d\n", sent, host, port); + return 0; +} diff --git a/test/until/UdpCommTest.cpp b/test/until/UdpCommTest.cpp index 6cfb9c6..d89255d 100644 --- a/test/until/UdpCommTest.cpp +++ b/test/until/UdpCommTest.cpp @@ -170,3 +170,16 @@ TEST(UdpCommTest, GetDate) EXPECT_LE(minute, 59u); EXPECT_LE(second, 59u); } + +TEST(UdpCommTest, SendDisSysCmdFailsWithoutSocket) +{ + udpComm u; // 默认构造 udpScoket=nullptr,sendMsg 应返回 false + disHighVolBusCmd c1{}; + disHighAVolBusCmd c2{}; + disHighBVolBusCmd c3{}; + disLowBusCmd c4{}; + EXPECT_FALSE(u.sendDisSysCmd(c1)); + EXPECT_FALSE(u.sendDisSysCmd(c2)); + EXPECT_FALSE(u.sendDisSysCmd(c3)); + EXPECT_FALSE(u.sendDisSysCmd(c4)); +} diff --git a/tools/pmpower_db.py b/tools/pmpower_db.py new file mode 100644 index 0000000..47edc68 --- /dev/null +++ b/tools/pmpower_db.py @@ -0,0 +1,1392 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +pPowerManger SQLite 数据查看工具 (tkinter GUI, 仅使用 Python 标准库) + +功能: + 1. 数据浏览 - 打开 power_data.db, 浏览各表, 支持行数限制/时间筛选/条件过滤/人性化解码 + 2. SQL 查询 - 执行任意只读 SQL, 结果表格展示, 导出 CSV + 3. 帧解码 - 解析 msg_log 中的原始 hex 帧, 按 msg_id 还原结构体字段(中文标注) + 4. 趋势图 - 选中数值列, 按时间绘制折线图(支持时间桶聚合) + +依赖: tkinter(系统包 python3-tk), 其余均为标准库(sqlite3/csv/struct/datetime) + +运行: python3 tools/pmpower_db.py +""" + +import csv +import os +import struct +import sqlite3 +from datetime import datetime, timedelta + +try: + import tkinter as tk + from tkinter import ttk, filedialog, messagebox +except Exception: # tkinter 未安装时仍可导入本模块用于逻辑测试 + tk = None + ttk = None + +APP_TITLE = "pPowerManger 数据查看工具" + +# 由 setup_fonts() 在启动时设置的实际可用中文字体族 +_UI_SANS = "TkDefaultFont" +_UI_MONO = "TkFixedFont" + +# ===================================================================== +# 枚举映射 +# ===================================================================== +BREAKER_MAP = {0x00: "无效", 0x55: "合闸", 0xAA: "分闸", 0x5A: "故障"} +SIGNAL_MAP = {0x00: "无效", 0x55: "正常", 0xAA: "故障"} +INSUL_MAP = {0x00: "无效", 0x55: "正常", 0xAA: "绝缘低"} +ONOFF_MAP = {0x00: "无效", 0x55: "启动", 0xAA: "停止", 0x5A: "故障"} + +ENUMS = { + "breaker": BREAKER_MAP, + "signal": SIGNAL_MAP, + "insulation": INSUL_MAP, + "onoff": ONOFF_MAP, + "work_condition": { + 0: "工况切换中", 1: "待机", 2: "岸基备航", 3: "水中备航", 4: "遥控工况", + 5: "巡航模式", 6: "高速模式", 7: "上浮下潜", 8: "浮调模式", 9: "水下侦查", + 10: "水面侦查", 11: "DJ模式", 12: "调试模式", + }, + "fcu_cmd": { + 0x01: "自检", 0x02: "启动", 0x03: "停机", 0x04: "复位", + 0x05: "急停", 0x06: "补给", 0x07: "预热", 0x08: "维护", + }, + "fcu_state": { + 0x01: "初始化", 0x02: "待机", 0x03: "自检异常", 0x04: "就绪", 0x05: "启动", + 0x06: "运行", 0x07: "停机", 0x08: "故障", 0x09: "补给", 0x0A: "预热", + 0x0B: "预热完成", 0x0C: "维护", 0x0D: "维护完成", + }, + "battery_cmd": {0x00: "保持", 0x10: "启动", 0x20: "停止"}, + "fc_mode": {0x00: "无效", 0x01: "调试", 0x02: "自动", 0x03: "补给"}, + "fc_fault_level": {0x00: "无故障", 0x10: "一级", 0x20: "二级", 0x30: "三级", 0x40: "四级"}, + "charge_status": {0x00: "无效", 0x10: "充电", 0x20: "放电"}, + "emergency_status": {0x00: "无报警", 0x3A: "4级报警", 0x3B: "3级报警", 0x3C: "2级报警", 0x3D: "1级报警"}, + "flame": {0x00: "无效", 0x01: "报警", 0x02: "故障"}, + "direction": {0: "接收", 1: "发送"}, + "checksum_ok": {0: "错误", 1: "正确"}, +} + +# ===================================================================== +# 列元数据: 表 -> {列名: (中文名, 缩放倍数, 单位, 枚举key 或 None)} +# ===================================================================== +COL_META = {} + +COL_META["ccuColCmd"] = { + "workCondition": ("工况", 1, "", "work_condition"), + "fcuCmd": ("燃料电池指令", 1, "", "fcu_cmd"), + "fcuPowerEfficiency": ("燃料电池功率效率", 1, "%", None), + "insChargeEnable": ("仪表电池充电使能", 1, "", None), + "insBatCmd": ("仪表电池指令", 1, "", "battery_cmd"), + "powerBatCmd": ("动力电池指令", 1, "", "battery_cmd"), + "powerBatEfficiency": ("动力电池效率", 1, "%", None), + "heartbeat": ("心跳计数", 1, "", None), + "masterState": ("主机状态", 1, "", "hex"), + "paceholder1": ("占位字段1", 1, "", "hex"), + "paceholder2": ("占位字段2", 1, "", "hex"), +} + +COL_META["ccuSetParmCmd"] = { + "insSocHold1": ("仪表SOC保持1", 1, "%", None), + "insSocHold2": ("仪表SOC保持2", 1, "%", None), + "insSocHold3": ("仪表SOC保持3", 1, "%", None), + "fcuToInsLimit1": ("燃电到仪表限值1", 1, "", None), + "fcuToInsLimit2": ("燃电到仪表限值2", 1, "", None), + "powerSocHold1": ("动力SOC保持1", 1, "%", None), + "powerSocHold2": ("动力SOC保持2", 1, "%", None), + "powerSocHold3": ("动力SOC保持3", 1, "%", None), + "fcuToPowerLimit1": ("燃电到动力限值1", 1, "", None), + "fcuToPowerLimit2": ("燃电到动力限值2", 1, "", None), +} + +COL_META["ccuSetParmFb"] = { + "flag": ("标志", 1, "", "hex"), + "failureCode": ("故障码", 1, "", "hex"), +} + +# --- ccuState 复合管控状态反馈 --- +COL_META["ccuState"] = { + "fc_mode": ("燃料电池运行模式", 1, "", "fc_mode"), + "fc_status": ("燃料电池状态", 1, "", "fcu_state"), + "fc_fault_level": ("燃料电池故障等级", 1, "", "fc_fault_level"), + "output_power_limit": ("输出功率限制", 1, "W", None), + "total_generation_time": ("累计发电时间", 0.1, "h", None), + "total_generation_power": ("累计发电功率", 1, "W", None), + "fault_level_1": ("一级故障位", 1, "", "hex"), + "fault_level_2": ("二级故障位", 1, "", "hex"), + "fault_level_3": ("三级故障位", 1, "", "hex"), + "fault_level_4": ("四级故障位", 1, "", "hex"), + "hydrogen_capacity": ("储氢罐剩余容量", 1, "%", None), + "liquid_oxygen_capacity": ("液氧罐剩余容量", 1, "%", None), + "palladium_temp": ("钯膜最高温度", 1, "℃", None), + "main_pipe_pressure": ("主水路压力", 0.01, "kPa", None), + "aux_pipe_pressure": ("辅水路压力", 0.01, "kPa", None), + "cabin_pressure1": ("舱室1压力", 0.01, "kPa", None), + "cabin_pressure2": ("舱室2压力", 0.01, "kPa", None), + "cabin_temp1": ("舱室1温度", 0.01, "℃", None), + "cabin_temp2": ("舱室2温度", 0.01, "℃", None), + "cabin_humidity1": ("舱室1湿度", 0.01, "%RH", None), + "cabin_humidity2": ("舱室2湿度", 0.01, "%RH", None), + "flame_detector1": ("火焰探测器1", 1, "", "flame"), + "flame_detector2": ("火焰探测器2", 1, "", "flame"), + "h2_concentration1": ("舱室H2浓度1", 0.01, "%LEL", None), + "h2_concentration2": ("舱室H2浓度2", 0.01, "%LEL", None), + "h2_concentration3": ("舱室H2浓度3", 0.01, "%LEL", None), + "o2_concentration1": ("舱室O2浓度1", 0.01, "%LEL", None), + "o2_concentration2": ("舱室O2浓度2", 0.01, "%LEL", None), + "ch3oh_concentration1": ("舱室甲醇浓度1", 0.01, "%LEL", None), + "ch3oh_concentration2": ("舱室甲醇浓度2", 0.01, "%LEL", None), + "reserved1_1": ("保留1_1", 1, "", None), + "reserved1_2": ("保留1_2", 1, "", None), + "emergency_battery1_voltage": ("应急电池1总电压", 1, "V", None), + "emergency_battery1_current": ("应急电池1总电流", 1, "A", None), + "emergency_battery1_max_temp": ("应急电池1最高温度", 1, "℃", None), + "emergency_battery1_fault_word": ("应急电池1故障字", 1, "", "hex"), + "emergency_battery2_voltage": ("应急电池2总电压", 1, "V", None), + "emergency_battery2_current": ("应急电池2总电流", 1, "A", None), + "emergency_battery2_max_temp": ("应急电池2最高温度", 1, "℃", None), + "emergency_battery2_fault_word": ("应急电池2故障字", 1, "", "hex"), + "cabin_ox_concentration": ("仪表舱氧气浓度", 1, "%", None), + "cabin_temperature": ("仪表舱温度", 1, "℃", None), + "cabin_humidity": ("仪表舱湿度", 1, "%RH", None), + "cabin_pressure": ("仪表舱大气压", 1, "kPa", None), + "power_cabin_ox_concentration": ("动力舱氧气浓度", 1, "%", None), + "power_cabin_temperature": ("动力舱温度", 1, "℃", None), + "power_cabin_humidity": ("动力舱湿度", 1, "%RH", None), + "power_cabin_pressure": ("动力舱大气压", 1, "kPa", None), + "reserved2": ("保留2", 1, "", None), + "dyn_alarm_flag1": ("动力电池报警1", 1, "", "hex"), + "dyn_alarm_flag2": ("动力电池报警2", 1, "", "hex"), + "dyn_alarm_flag3": ("动力电池报警3", 1, "", "hex"), + "dyn_alarm_flag4": ("动力电池报警4", 1, "", "hex"), + "dyn_alarm_flag5": ("动力电池报警5", 1, "", "hex"), + "dyn_alarm_flag6": ("动力电池报警6", 1, "", "hex"), + "ins_alarm_flag1": ("仪表电池报警1", 1, "", "hex"), + "ins_alarm_flag2": ("仪表电池报警2", 1, "", "hex"), + "ins_alarm_flag3": ("仪表电池报警3", 1, "", "hex"), + "ins_alarm_flag4": ("仪表电池报警4", 1, "", "hex"), + "ins_alarm_flag5": ("仪表电池报警5", 1, "", "hex"), + "ins_alarm_flag6": ("仪表电池报警6", 1, "", "hex"), + "ins_relay_status1": ("仪表继电器正极/充电", 1, "", "hex"), + "ins_relay_status2": ("仪表继电器预充/负极", 1, "", "hex"), + "dyn_relay_status1": ("动力继电器正极/充电", 1, "", "hex"), + "dyn_relay_status2": ("动力继电器预充/负极", 1, "", "hex"), + "ins_max_discharge_power": ("仪表电池最大放电功率", 0.05, "kW", None), + "dyn_max_discharge_power": ("动力电池最大放电功率", 0.05, "kW", None), + "ins_soc": ("仪表锂电池SOC", 1, "%", None), + "dyn_soc": ("动力锂电池SOC", 1, "%", None), + "ins_total_energy": ("仪表电池总能量", 1, "kWh", None), + "dyn_total_energy": ("动力电池总能量", 1, "kWh", None), + "ins_power_input": ("仪表电池输入功率", 1, "kW", None), + "dyn_power_input": ("动力电池输入功率", 1, "kW", None), + "ins_charge_status": ("仪表电池充放电", 1, "", "charge_status"), + "dyn_charge_status": ("动力电池充放电", 1, "", "charge_status"), + "ins_voltage_link": ("仪表电池LINK电压", 0.01, "V", None), + "ins_voltage_pack": ("仪表电池PACK电压", 0.01, "V", None), + "ins_current": ("仪表电池电流", 0.01, "A", None), + "ins_resistance_pos": ("仪表电池正极绝缘电阻", 1, "kΩ", None), + "ins_resistance_neg": ("仪表电池负极绝缘电阻", 1, "kΩ", None), + "dyn_voltage_link": ("动力电池LINK电压", 0.01, "V", None), + "dyn_voltage_pack": ("动力电池PACK电压", 0.01, "V", None), + "dyn_current": ("动力电池电流", 0.01, "A", None), + "dyn_resistance_pos": ("动力电池正极绝缘电阻", 1, "kΩ", None), + "dyn_resistance_neg": ("动力电池负极绝缘电阻", 1, "kΩ", None), + "ins_emergency_status": ("仪表电池紧急状态", 1, "", "emergency_status"), + "dyn_emergency_status": ("动力电池紧急状态", 1, "", "emergency_status"), + "reserved3_1": ("保留3_1", 1, "", None), + "reserved3_2": ("保留3_2", 1, "", None), + "ins_soc_threshold1": ("仪表电池SOC门限1", 1, "%", None), + "ins_soc_threshold2": ("仪表电池SOC门限2", 1, "%", None), + "ins_soc_threshold3": ("仪表电池SOC门限3", 1, "%", None), + "dyn_soc_threshold1": ("动力电池SOC门限1", 1, "%", None), + "dyn_soc_threshold2": ("动力电池SOC门限2", 1, "%", None), + "dyn_soc_threshold3": ("动力电池SOC门限3", 1, "%", None), + "ins_power_limit1": ("仪表电池功率限制1", 1, "%", None), + "ins_power_limit2": ("仪表电池功率限制2", 1, "%", None), + "dyn_power_limit1": ("动力电池功率限制1", 1, "%", None), + "dyn_power_limit2": ("动力电池功率限制2", 1, "%", None), + "device_online_flag1": ("设备在线标志1", 1, "", "hex"), + "device_online_flag2": ("设备在线标志2", 1, "", "hex"), + "reserved4": ("保留4", 1, "", None), + "heartbeat": ("心跳计数", 1, "", None), +} + +# --- 高压母线配电指令 --- +COL_META["disHighVolBusCmd"] = { + "fuelCellCircuitBreaker": ("燃料电池断路器", 1, "", "breaker"), + "powerLithiumBatteryCircuitBreaker": ("动力锂电池断路器", 1, "", "breaker"), + "propulsionMotorCircuitBreaker": ("推进电机断路器", 1, "", "breaker"), + "lithiumBatteryGroupInstrumentCircuitBreaker": ("锂电池组仪表断路器", 1, "", "breaker"), + "dcDc5ModuleCircuitBreaker": ("DC/DC5模块断路器", 1, "", "breaker"), + "bowHighVoltageDistributionBoxCircuitBreaker": ("艏部高压配电箱断路器", 1, "", "breaker"), + "sternFTDevice45CircuitBreaker": ("艉部FT装置4/5断路器", 1, "", "breaker"), + "sternRudderSwitch1CircuitBreaker": ("艉舵开关1断路器", 1, "", "breaker"), + "sternRudderSwitch2CircuitBreaker": ("艉舵开关2断路器", 1, "", "breaker"), + "fbReservedCircuitBreaker": ("FB预留断路器", 1, "", "breaker"), + "tyzReservedCircuitBreaker": ("TYZ预留断路器", 1, "", "breaker"), + "reservedCircuitBreaker": ("预留断路器", 1, "", "breaker"), + "dcdc5Module": ("DC/DC5模块启动", 1, "", "onoff"), + "coolingSystem": ("冷却系统启动", 1, "", "onoff"), +} + +# --- 高压母线A配电指令 --- +COL_META["disHighAVolBusCmd"] = { + "bowFTDevice123CircuitBreaker": ("艏部FT装置1/2/3断路器", 1, "", "breaker"), + "actuatorCircuitBreaker": ("启闭机构断路器", 1, "", "breaker"), + "mastSteeringGearControlBoxCircuitBreaker": ("桅杆舵机控制箱断路器", 1, "", "breaker"), + "xczCircuitBreaker": ("XCZ断路器", 1, "", "breaker"), + "bowRudderControlBoxCircuitBreaker": ("艏舵控制箱断路器", 1, "", "breaker"), + "openWaterCoverStartCylinderCircuitBreaker": ("敞水盖启动电缸断路器", 1, "", "breaker"), +} + +# --- 低压主母排配电指令 --- +COL_META["disLowMainBusCmd"] = { + "lithiumBatteryGroupInstrumentCircuitBreaker": ("锂电池组(仪表)断路器", 1, "", "breaker"), + "bowLowVoltageDistributionBoxCircuitBreaker": ("艏部低压配电箱断路器", 1, "", "breaker"), + "unit4InstrumentDC48VCircuitBreaker": ("UNIT4仪表DC48V断路器", 1, "", "breaker"), + "dcC1DCDistributionPanelCircuitBreaker": ("DC-C1直流配电板断路器", 1, "", "breaker"), + "reservedCircuitBreaker1": ("预留断路器1", 1, "", "breaker"), + "reservedCircuitBreaker2": ("预留断路器2", 1, "", "breaker"), + "emergencyLithiumBatteryGroup2CircuitBreaker": ("应急锂电池组2断路器", 1, "", "breaker"), +} + +# --- 低压母线配电指令 --- +COL_META["disLowBusCmd"] = { + "unit1CircuitBreaker": ("UNIT1断路器", 1, "", "breaker"), + "unit2CircuitBreaker": ("UNIT2断路器", 1, "", "breaker"), + "unit3CircuitBreaker": ("UNIT3断路器", 1, "", "breaker"), + "unit5CircuitBreaker": ("UNIT5断路器", 1, "", "breaker"), + "bowPZDeviceCircuitBreaker": ("艏部PZ装置断路器", 1, "", "breaker"), + "reservedCircuitBreaker1": ("预留断路器1", 1, "", "breaker"), + "reservedCircuitBreaker2": ("预留断路器2", 1, "", "breaker"), + "emergencyLithiumBatteryGroup1CircuitBreaker": ("应急锂电池组1断路器", 1, "", "breaker"), +} + +# --- 高压母线配电反馈 --- +COL_META["disHighVolBusState"] = { + "fuelCellCircuitBreaker": ("燃料电池断路器", 1, "", "breaker"), + "powerLithiumBatteryCircuitBreaker": ("动力锂电池断路器", 1, "", "breaker"), + "propulsionMotorCircuitBreaker": ("推进电机断路器", 1, "", "breaker"), + "lithiumBatteryGroupInstrumentCircuitBreaker": ("锂电池组仪表断路器", 1, "", "breaker"), + "dcDc5ModuleCircuitBreaker": ("DC/DC5模块断路器", 1, "", "breaker"), + "bowHighVoltageDistributionBoxCircuitBreaker": ("艏部高压配电箱断路器", 1, "", "breaker"), + "sternFTDevice45CircuitBreaker": ("艉部FT装置4/5断路器", 1, "", "breaker"), + "sternRudderSwitch1CircuitBreaker": ("艉舵开关1断路器", 1, "", "breaker"), + "sternRudderSwitch2CircuitBreaker": ("艉舵开关2断路器", 1, "", "breaker"), + "fbReservedCircuitBreaker": ("FB预留断路器", 1, "", "breaker"), + "tyzReservedCircuitBreaker": ("TYZ预留断路器", 1, "", "breaker"), + "reservedCircuitBreaker": ("预留断路器", 1, "", "breaker"), + "dcDcFaultWord1": ("DC/DC5故障字低", 1, "", "hex"), + "dcDcFaultWord2": ("DC/DC5故障字高", 1, "", "hex"), + "powerBusInsulationStatus": ("动力母排绝缘状态", 1, "", "insulation"), + "aBusInsulationStatus": ("A汇流排绝缘状态", 1, "", "insulation"), + "meterPowerLossSignal": ("仪表电源状态", 1, "", "signal"), + "emergencyPowerLossSignal": ("应急电源状态", 1, "", "signal"), + "powerBusVoltage": ("动力汇流排电压", 1, "", None), + "dcDc5ModuleCurrent": ("DC/DC5模块电流", 1, "", None), + "powerBusCurrent": ("动力汇流排电流", 1, "", None), + "busbarAVoltage": ("A汇流排电压", 1, "", None), + "propulsionMotorControlBoxCurrent": ("推进电机控制箱电流", 1, "", None), + "busbarACurrent": ("A汇流排电流", 1, "", None), + "lithiumBatteryGroupMeterCurrent": ("锂电池组(仪表)电流", 1, "", None), + "bowHighVoltageDistributionBoxCurrent": ("艏部高压配电箱电流", 1, "", None), + "sternFTDevice45Current": ("艉部FT装置4/5电流", 1, "", None), + "tyzReservedSwitchCurrent": ("TYZ预留开关电流", 1, "", None), + "sternRudderSwitch1Current": ("艉舵开关1电流", 1, "", None), + "reservedCurrent1": ("预留电流1", 1, "", None), + "sternRudderSwitch2Current": ("艉舵开关2电流", 1, "", None), + "reservedCurrent2": ("预留电流2", 1, "", None), + "fbReservedSwitchCurrent": ("FB预留电流", 1, "", None), + "reservedCurrent3": ("预留电流3", 1, "", None), + "coolWaterPressure": ("冷却水压力", 1, "", None), +} + +# --- 高压母线A配电反馈 --- +COL_META["disHighAVolBusState"] = { + "bowFTDevice123CircuitBreaker": ("艏部FT装置1/2/3断路器", 1, "", "breaker"), + "actuatorCircuitBreaker": ("启闭机构断路器", 1, "", "breaker"), + "mastSteeringGearControlBoxCircuitBreaker": ("桅杆舵机控制箱断路器", 1, "", "breaker"), + "xczCircuitBreaker": ("XCZ断路器", 1, "", "breaker"), + "bowRudderControlBoxCircuitBreaker": ("艏舵控制箱断路器", 1, "", "breaker"), + "openWaterCoverStartCylinderCircuitBreaker": ("敞水盖启动电缸断路器", 1, "", "breaker"), + "instrumentPowerFailureSignal": ("仪表电源失电信号", 1, "", "signal"), + "emergencyPowerFailureSignal": ("应急电源失电信号", 1, "", "signal"), + "waterIngressionAlarm": ("浸水报警", 1, "", "signal"), + "busbarBVoltage": ("汇流排B电压", 1, "", None), + "busbarBCurrent": ("汇流排B电流", 1, "", None), + "bowFTDevice123Current": ("艏部FT装置1/2/3电流", 1, "", None), + "actuatorCurrent": ("启闭机构电流", 1, "", None), + "mastSteeringGearControlBoxCurrent": ("桅杆舵机控制箱电流", 1, "", None), + "xczCurrent": ("XCZ电流", 1, "", None), + "bowRudderControlBoxCurrent": ("艏舵控制箱电流", 1, "", None), + "openWaterCoverStartCylinderCurrent": ("敞水盖启动电缸电流", 1, "", None), +} + +# --- 低压主母排配电反馈 --- +COL_META["disLowMainBusState"] = { + "lithiumBatteryGroupInstrumentCircuitBreaker": ("锂电池组(仪表)断路器", 1, "", "breaker"), + "bowLowVoltageDistributionBoxCircuitBreaker": ("艏部低压配电箱断路器", 1, "", "breaker"), + "unit4InstrumentDC48VCircuitBreaker": ("UNIT4仪表DC48V断路器", 1, "", "breaker"), + "dcC1DCDistributionPanelCircuitBreaker": ("DC-C1直流配电板断路器", 1, "", "breaker"), + "reservedCircuitBreaker1": ("预留断路器1", 1, "", "breaker"), + "reservedCircuitBreaker2": ("预留断路器2", 1, "", "breaker"), + "emergencyLithiumBatteryGroup2CircuitBreaker": ("应急锂电池组2断路器", 1, "", "breaker"), + "instrumentPowerFailureSignal": ("仪表电源失电信号", 1, "", "signal"), + "emergencyPowerFailureSignal": ("应急电源失电信号", 1, "", "signal"), + "instrumentBusbarInsulationLow": ("仪表汇流排绝缘低", 1, "", "insulation"), + "instrumentBusbarVoltage": ("仪表母排电压", 1, "", None), + "bowLowVoltageDistributionBoxCurrent": ("艏部低压配电箱电流", 1, "", None), + "dcC1InstrumentDC48VCurrent": ("DC-C1仪表DC48V电流", 1, "", None), + "reservedCurrent1": ("预留电流1", 1, "", None), + "emergencyLithiumBatteryGroup2Current": ("应急锂电池组2电流", 1, "", None), + "lithiumBatteryGroupInstrumentCurrent": ("锂电池组(仪表)电流", 1, "", None), + "unit4InstrumentDC48VCurrent": ("UNIT4仪表DC48V电流", 1, "", None), + "reservedCurrent2": ("预留电流2", 1, "", None), + "emergency2BusbarVoltage": ("应急2汇流排电压", 1, "", None), + "compositeEnergyManagementSystemEmergencyDC48VCurrent": ("复合能源管控应急DC48V电流", 1, "", None), + "fuelCellSecuritySystemEmergencyDC48VCurrent": ("燃料电池安全系统应急DC48V电流", 1, "", None), + "plcControlPowerCurrent": ("PLC控制电源电流", 1, "", None), +} + +# --- 低压母线配电反馈 --- +COL_META["disLowBusState"] = { + "unit1CircuitBreaker": ("UNIT1断路器", 1, "", "breaker"), + "unit2CircuitBreaker": ("UNIT2断路器", 1, "", "breaker"), + "unit3CircuitBreaker": ("UNIT3断路器", 1, "", "breaker"), + "unit5CircuitBreaker": ("UNIT5断路器", 1, "", "breaker"), + "bowPZDeviceCircuitBreaker": ("艏部PZ装置断路器", 1, "", "breaker"), + "reservedCircuitBreaker1": ("预留断路器1", 1, "", "breaker"), + "reservedCircuitBreaker2": ("预留断路器2", 1, "", "breaker"), + "emergencyLithiumBatteryGroup1CircuitBreaker": ("应急锂电池组1断路器", 1, "", "breaker"), + "powerFailureSignal": ("仪表电源失电信号", 1, "", "signal"), + "emergencyPowerFailureSignal": ("应急电源失电信号", 1, "", "signal"), + "waterIngressionAlarm": ("浸水报警", 1, "", "signal"), + "instrumentBusbarVoltage": ("仪表母排电压", 1, "", None), + "instrumentBusbarCurrent": ("仪表母排电流", 1, "", None), + "unit1Current": ("UNIT1电流", 1, "", None), + "unit2Current": ("UNIT2电流", 1, "", None), + "unit3Current": ("UNIT3电流", 1, "", None), + "unit5Current": ("UNIT5电流", 1, "", None), + "bowPZDeviceCurrent": ("艏部PZ装置电流", 1, "", None), + "emergencyLithiumBatteryGroup1Current": ("应急锂电池组1电流", 1, "", None), + "emergency1BusbarVoltage": ("应急1汇流排电压", 1, "", None), +} + +# --- 原始收发帧日志 --- +COL_META["msg_log"] = { + "ts": ("时间戳(unix秒)", 1, "", None), + "ts_local": ("本地时间", 1, "", None), + "direction": ("方向", 1, "", "direction"), + "msg_id": ("消息ID", 1, "", "hex"), + "msg_name": ("消息名", 1, "", None), + "length": ("帧长度", 1, "", None), + "payload_hex": ("载荷(hex)", 1, "", None), + "checksum_ok": ("校验和", 1, "", "checksum_ok"), + "note": ("备注", 1, "", None), +} + +# 反馈表 feedback_data.db +COL_META["fb_log"] = { + "ts": ("时间戳(unix秒)", 1, "", None), + "msg": ("消息类型", 1, "", None), + "payload": ("载荷", 1, "", None), +} + +# ===================================================================== +# 帧解码定义: msg_id -> (消息名, [(字段名, 格式, 中文名, 枚举key)]) +# 格式: B=u8, H=u16, I=u32 +# ===================================================================== +FRAME_DEFS = {} + +FRAME_DEFS[0x01] = ("disHighVolBusCmd 高压母线配电指令", [ + ("fuelCellCircuitBreaker", "B", "燃料电池断路器", "breaker"), + ("powerLithiumBatteryCircuitBreaker", "B", "动力锂电池断路器", "breaker"), + ("propulsionMotorCircuitBreaker", "B", "推进电机断路器", "breaker"), + ("lithiumBatteryGroupInstrumentCircuitBreaker", "B", "锂电池组仪表断路器", "breaker"), + ("dcDc5ModuleCircuitBreaker", "B", "DC/DC5模块断路器", "breaker"), + ("bowHighVoltageDistributionBoxCircuitBreaker", "B", "艏部高压配电箱断路器", "breaker"), + ("sternFTDevice45CircuitBreaker", "B", "艉部FT装置4/5断路器", "breaker"), + ("fbReservedCircuitBreaker", "B", "FB预留断路器", "breaker"), + ("sternRudderSwitch1CircuitBreaker", "B", "艉舵开关1断路器", "breaker"), + ("sternRudderSwitch2CircuitBreaker", "B", "艉舵开关2断路器", "breaker"), + ("tyzReservedCircuitBreaker", "B", "TYZ预留断路器", "breaker"), + ("reservedCircuitBreaker", "B", "预留断路器", "breaker"), + ("dcdc5Module", "B", "DC/DC5模块启动", "onoff"), + ("coolingSystem", "B", "冷却系统启动", "onoff"), +]) + +FRAME_DEFS[0x02] = ("disHighAVolBusCmd 高压母线A配电指令", [ + ("bowFTDevice123CircuitBreaker", "B", "艏部FT装置1/2/3断路器", "breaker"), + ("actuatorCircuitBreaker", "B", "启闭机构断路器", "breaker"), + ("mastSteeringGearControlBoxCircuitBreaker", "B", "桅杆舵机控制箱断路器", "breaker"), + ("xczCircuitBreaker", "B", "XCZ断路器", "breaker"), + ("bowRudderControlBoxCircuitBreaker", "B", "艏舵控制箱断路器", "breaker"), + ("openWaterCoverStartCylinderCircuitBreaker", "B", "敞水盖启动电缸断路器", "breaker"), +]) + +FRAME_DEFS[0x03] = ("disLowMainBusCmd 低压主母排配电指令", [ + ("lithiumBatteryGroupInstrumentCircuitBreaker", "B", "锂电池组(仪表)断路器", "breaker"), + ("bowLowVoltageDistributionBoxCircuitBreaker", "B", "艏部低压配电箱断路器", "breaker"), + ("unit4InstrumentDC48VCircuitBreaker", "B", "UNIT4仪表DC48V断路器", "breaker"), + ("dcC1DCDistributionPanelCircuitBreaker", "B", "DC-C1直流配电板断路器", "breaker"), + ("reservedCircuitBreaker1", "B", "预留断路器1", "breaker"), + ("reservedCircuitBreaker2", "B", "预留断路器2", "breaker"), + ("emergencyLithiumBatteryGroup2CircuitBreaker", "B", "应急锂电池组2断路器", "breaker"), +]) + +FRAME_DEFS[0x04] = ("disLowBusCmd 低压母线配电指令", [ + ("unit1CircuitBreaker", "B", "UNIT1断路器", "breaker"), + ("unit2CircuitBreaker", "B", "UNIT2断路器", "breaker"), + ("unit3CircuitBreaker", "B", "UNIT3断路器", "breaker"), + ("unit5CircuitBreaker", "B", "UNIT5断路器", "breaker"), + ("bowPZDeviceCircuitBreaker", "B", "艏部PZ装置断路器", "breaker"), + ("reservedCircuitBreaker1", "B", "预留断路器1", "breaker"), + ("reservedCircuitBreaker2", "B", "预留断路器2", "breaker"), + ("emergencyLithiumBatteryGroup1CircuitBreaker", "B", "应急锂电池组1断路器", "breaker"), +]) + +FRAME_DEFS[0x40] = ("ccuColCmd 复合管控操作指令", [ + ("year", "H", "年", None), + ("month", "B", "月", None), + ("day", "B", "日", None), + ("hour", "B", "时", None), + ("minute", "B", "分", None), + ("second", "B", "秒", None), + ("millisecond", "B", "毫秒(×10)", None), + ("fcmode", "B", "工况", "work_condition"), + ("fcuCmd", "B", "燃料电池指令", "fcu_cmd"), + ("fcuPowerEfficiency", "B", "燃料电池功率效率", None), + ("insChargeEnable", "B", "仪表电池充电使能", None), + ("insBatCmd", "B", "仪表电池指令", "battery_cmd"), + ("powerBatCmd", "B", "动力电池指令", "battery_cmd"), + ("powerBatEfficiency", "H", "动力电池效率", None), + ("heartbeat", "B", "心跳计数", None), + ("masterState", "B", "主机状态", "hex"), + ("paceholder1", "I", "占位字段1", "hex"), + ("paceholder2", "I", "占位字段2", "hex"), +]) + +FRAME_DEFS[0x41] = ("ccuSetParmCmd 复合管控参数设定指令", [ + ("insSocHold1", "B", "仪表SOC保持1", None), + ("insSocHold2", "B", "仪表SOC保持2", None), + ("insSocHold3", "B", "仪表SOC保持3", None), + ("fcuToInsLimit1", "B", "燃电到仪表限值1", None), + ("fcuToInsLimit2", "B", "燃电到仪表限值2", None), + ("powerSocHold1", "B", "动力SOC保持1", None), + ("powerSocHold2", "B", "动力SOC保持2", None), + ("powerSocHold3", "B", "动力SOC保持3", None), + ("fcuToPowerLimit1", "B", "燃电到动力限值1", None), + ("fcuToPowerLimit2", "B", "燃电到动力限值2", None), +]) + +# 配电状态反馈 (0x05~0x08), 供未来状态帧解码使用 +FRAME_DEFS[0x05] = ("disHighVolBusState 高压母线配电反馈", [ + ("fuelCellCircuitBreaker", "B", "燃料电池断路器", "breaker"), + ("powerLithiumBatteryCircuitBreaker", "B", "动力锂电池断路器", "breaker"), + ("propulsionMotorCircuitBreaker", "B", "推进电机断路器", "breaker"), + ("lithiumBatteryGroupInstrumentCircuitBreaker", "B", "锂电池组仪表断路器", "breaker"), + ("dcDc5ModuleCircuitBreaker", "B", "DC/DC5模块断路器", "breaker"), + ("bowHighVoltageDistributionBoxCircuitBreaker", "B", "艏部高压配电箱断路器", "breaker"), + ("sternFTDevice45CircuitBreaker", "B", "艉部FT装置4/5断路器", "breaker"), + ("fbReservedCircuitBreaker", "B", "FB预留断路器", "breaker"), + ("sternRudderSwitch1CircuitBreaker", "B", "艉舵开关1断路器", "breaker"), + ("sternRudderSwitch2CircuitBreaker", "B", "艉舵开关2断路器", "breaker"), + ("tyzReservedCircuitBreaker", "B", "TYZ预留断路器", "breaker"), + ("reservedCircuitBreaker", "B", "预留断路器", "breaker"), + ("dcDcFaultWord1", "B", "DC/DC5故障字低", "hex"), + ("dcDcFaultWord2", "B", "DC/DC5故障字高", "hex"), + ("powerBusInsulationStatus", "B", "动力母排绝缘状态", "insulation"), + ("aBusInsulationStatus", "B", "A汇流排绝缘状态", "insulation"), + ("meterPowerLossSignal", "B", "仪表电源状态", "signal"), + ("emergencyPowerLossSignal", "B", "应急电源状态", "signal"), + ("dcDcTemperature", "B", "DC/DC温度", None), + ("powerBusVoltage", "H", "动力汇流排电压", None), + ("dcDc5ModuleCurrent", "H", "DC/DC5模块电流", None), + ("powerBusCurrent", "H", "动力汇流排电流", None), + ("busbarAVoltage", "H", "A汇流排电压", None), + ("propulsionMotorControlBoxCurrent", "H", "推进电机控制箱电流", None), + ("busbarACurrent", "H", "A汇流排电流", None), + ("lithiumBatteryGroupMeterCurrent", "H", "锂电池组(仪表)电流", None), + ("bowHighVoltageDistributionBoxCurrent", "H", "艏部高压配电箱电流", None), + ("sternFTDevice45Current", "H", "艉部FT装置4/5电流", None), + ("tyzReservedSwitchCurrent", "H", "TYZ预留开关电流", None), + ("sternRudderSwitch1Current", "H", "艉舵开关1电流", None), + ("reservedCurrent1", "H", "预留电流1", None), + ("sternRudderSwitch2Current", "H", "艉舵开关2电流", None), + ("reservedCurrent2", "H", "预留电流2", None), + ("fbReservedSwitchCurrent", "H", "FB预留电流", None), + ("reservedCurrent3", "H", "预留电流3", None), + ("coolWaterPressure", "H", "冷却水压力", None), +]) + +FRAME_DEFS[0x06] = ("disHighAVolBusState 高压母线A配电反馈", [ + ("bowFTDevice123CircuitBreaker", "B", "艏部FT装置1/2/3断路器", "breaker"), + ("actuatorCircuitBreaker", "B", "启闭机构断路器", "breaker"), + ("mastSteeringGearControlBoxCircuitBreaker", "B", "桅杆舵机控制箱断路器", "breaker"), + ("xczCircuitBreaker", "B", "XCZ断路器", "breaker"), + ("bowRudderControlBoxCircuitBreaker", "B", "艏舵控制箱断路器", "breaker"), + ("openWaterCoverStartCylinderCircuitBreaker", "B", "敞水盖启动电缸断路器", "breaker"), + ("instrumentPowerFailureSignal", "B", "仪表电源失电信号", "signal"), + ("emergencyPowerFailureSignal", "B", "应急电源失电信号", "signal"), + ("waterIngressionAlarm", "B", "浸水报警", "signal"), + ("busbarBVoltage", "H", "汇流排B电压", None), + ("busbarBCurrent", "H", "汇流排B电流", None), + ("bowFTDevice123Current", "H", "艏部FT装置1/2/3电流", None), + ("actuatorCurrent", "H", "启闭机构电流", None), + ("mastSteeringGearControlBoxCurrent", "H", "桅杆舵机控制箱电流", None), + ("xczCurrent", "H", "XCZ电流", None), + ("bowRudderControlBoxCurrent", "H", "艏舵控制箱电流", None), + ("openWaterCoverStartCylinderCurrent", "H", "敞水盖启动电缸电流", None), +]) + +FRAME_DEFS[0x07] = ("disLowMainBusState 低压主母排配电反馈", [ + ("lithiumBatteryGroupInstrumentCircuitBreaker", "B", "锂电池组(仪表)断路器", "breaker"), + ("bowLowVoltageDistributionBoxCircuitBreaker", "B", "艏部低压配电箱断路器", "breaker"), + ("unit4InstrumentDC48VCircuitBreaker", "B", "UNIT4仪表DC48V断路器", "breaker"), + ("dcC1DCDistributionPanelCircuitBreaker", "B", "DC-C1直流配电板断路器", "breaker"), + ("reservedCircuitBreaker1", "B", "预留断路器1", "breaker"), + ("reservedCircuitBreaker2", "B", "预留断路器2", "breaker"), + ("emergencyLithiumBatteryGroup2CircuitBreaker", "B", "应急锂电池组2断路器", "breaker"), + ("instrumentPowerFailureSignal", "B", "仪表电源失电信号", "signal"), + ("emergencyPowerFailureSignal", "B", "应急电源失电信号", "signal"), + ("instrumentBusbarInsulationLow", "B", "仪表汇流排绝缘低", "insulation"), + ("instrumentBusbarVoltage", "H", "仪表母排电压", None), + ("bowLowVoltageDistributionBoxCurrent", "H", "艏部低压配电箱电流", None), + ("dcC1InstrumentDC48VCurrent", "H", "DC-C1仪表DC48V电流", None), + ("reservedCurrent1", "H", "预留电流1", None), + ("emergencyLithiumBatteryGroup2Current", "H", "应急锂电池组2电流", None), + ("lithiumBatteryGroupInstrumentCurrent", "H", "锂电池组(仪表)电流", None), + ("unit4InstrumentDC48VCurrent", "H", "UNIT4仪表DC48V电流", None), + ("reservedCurrent2", "H", "预留电流2", None), + ("emergency2BusbarVoltage", "H", "应急2汇流排电压", None), + ("compositeEnergyManagementSystemEmergencyDC48VCurrent", "H", "复合能源管控应急DC48V电流", None), + ("fuelCellSecuritySystemEmergencyDC48VCurrent", "H", "燃料电池安全系统应急DC48V电流", None), + ("plcControlPowerCurrent", "H", "PLC控制电源电流", None), +]) + +FRAME_DEFS[0x08] = ("disLowBusState 低压母线配电反馈", [ + ("unit1CircuitBreaker", "B", "UNIT1断路器", "breaker"), + ("unit2CircuitBreaker", "B", "UNIT2断路器", "breaker"), + ("unit3CircuitBreaker", "B", "UNIT3断路器", "breaker"), + ("unit5CircuitBreaker", "B", "UNIT5断路器", "breaker"), + ("bowPZDeviceCircuitBreaker", "B", "艏部PZ装置断路器", "breaker"), + ("reservedCircuitBreaker1", "B", "预留断路器1", "breaker"), + ("reservedCircuitBreaker2", "B", "预留断路器2", "breaker"), + ("emergencyLithiumBatteryGroup1CircuitBreaker", "B", "应急锂电池组1断路器", "breaker"), + ("powerFailureSignal", "B", "仪表电源失电信号", "signal"), + ("emergencyPowerFailureSignal", "B", "应急电源失电信号", "signal"), + ("waterIngressionAlarm", "B", "浸水报警", "signal"), + ("instrumentBusbarVoltage", "H", "仪表母排电压", None), + ("instrumentBusbarCurrent", "H", "仪表母排电流", None), + ("unit1Current", "H", "UNIT1电流", None), + ("unit2Current", "H", "UNIT2电流", None), + ("unit3Current", "H", "UNIT3电流", None), + ("unit5Current", "H", "UNIT5电流", None), + ("bowPZDeviceCurrent", "H", "艏部PZ装置电流", None), + ("emergencyLithiumBatteryGroup1Current", "H", "应急锂电池组1电流", None), + ("emergency1BusbarVoltage", "H", "应急1汇流排电压", None), +]) + +# ===================================================================== +# 数据库访问 +# ===================================================================== + + +def connect_db(path): + """只读连接数据库, 兼容 WAL, 程序运行期间可安全读取。""" + con = sqlite3.connect(path) + con.execute("PRAGMA query_only=ON") + con.row_factory = sqlite3.Row + return con + + +def list_tables(con): + """返回 [(表名, 行数, 时间列名, 最小时间, 最大时间)]""" + out = [] + rows = con.execute( + "SELECT name FROM sqlite_master WHERE type='table' " + "AND name NOT LIKE 'sqlite_%' ORDER BY name" + ).fetchall() + for (name,) in rows: + try: + n = con.execute("SELECT COUNT(*) FROM %s" % name).fetchone()[0] + except Exception: + n = -1 + tcol, tmin, tmax = detect_time_range(con, name) + out.append((name, n, tcol, tmin, tmax)) + return out + + +def table_columns(con, table): + return [r[1] for r in con.execute("PRAGMA table_info(%s)" % table).fetchall()] + + +def detect_time_range(con, table): + cols = table_columns(con, table) + tcol = None + for cand in ("timestamp", "ts_local", "ts"): + if cand in cols: + tcol = cand + break + if tcol is None: + return None, None, None + try: + r = con.execute("SELECT MIN(%s), MAX(%s) FROM %s" % (tcol, tcol, table)).fetchone() + return tcol, r[0], r[1] + except Exception: + return tcol, None, None + + +def format_time_value(tcol, val): + if val is None: + return "" + if tcol == "ts" and isinstance(val, (int, float)): + return datetime.fromtimestamp(val).strftime("%Y-%m-%d %H:%M:%S") + return str(val) + + +def parse_time_input(s): + s = s.strip() + if not s: + return None + for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d"): + try: + return datetime.strptime(s, fmt) + except ValueError: + continue + raise ValueError("时间格式应为 YYYY-MM-DD [HH:MM:SS]: " + s) + + +# ===================================================================== +# 数值格式化 / 人性化解码 +# ===================================================================== +_reverse_meta = None + + +def _build_reverse(): + global _reverse_meta + if _reverse_meta is None: + _reverse_meta = {} + for tbl, cols in COL_META.items(): + for col in cols: + _reverse_meta.setdefault(col, (tbl, cols[col])) + return _reverse_meta + + +def fmt_value(table, col, raw, human): + """把原始值转为展示字符串。human=True 时应用缩放/单位/枚举解码。""" + if raw is None: + return "" + if not human: + return str(raw) + meta = COL_META.get(table, {}).get(col) + if meta is None: + rev = _build_reverse().get(col) + if rev is not None: + meta = rev[1] + if meta is None: + return str(raw) + _desc, scale, unit, enum_key = meta + if enum_key == "hex" and isinstance(raw, int): + return "0x%X (%d)" % (raw, raw) + if enum_key and isinstance(raw, int): + m = ENUMS.get(enum_key, {}) + if raw in m: + if enum_key in ("breaker", "signal", "insulation", "onoff"): + return "%s (0x%02X)" % (m[raw], raw) + return "%s (%d)" % (m[raw], raw) + if isinstance(raw, (int, float)) and scale != 1: + v = raw * scale + if isinstance(v, float) and v == int(v): + v = int(v) + return "%s%s" % (v, unit) + if unit: + return "%s%s" % (raw, unit) + return str(raw) + + +def fmt_frame_value(raw, fmt, enum_key): + size = {"B": 1, "H": 2, "I": 4}[fmt] + hexs = "0x%0*X" % (size * 2, raw) + s = "%d (%s)" % (raw, hexs) + if enum_key == "hex": + return hexs + if enum_key and raw in ENUMS.get(enum_key, {}): + s += " = " + ENUMS[enum_key][raw] + return s + + +def decode_frame(hexstr): + """解析单个 hex 帧, 返回字符串行列表。""" + hs = hexstr.strip().replace(" ", "").replace("\n", "") + if not hs: + return ["(空输入)"] + try: + b = bytes.fromhex(hs) + except ValueError: + return ["非法 hex 字符串: %s" % hexstr.strip()] + if len(b) < 6: + return ["长度不足(至少6字节): %s (%d字节)" % (hs, len(b))] + start1, start2 = b[0], b[1] + msg_id = struct.unpack_from(" len(data): + lines.append(" [字段越界] %-32s (剩余 %d 字节)" % (nm, len(data) - off)) + break + raw = struct.unpack_from("<" + fmt, data, off)[0] + off += sz + lines.append(" %-34s %-22s = %s" % (nm, desc, fmt_frame_value(raw, fmt, ekey))) + lines.append(" 校验和: %s 数据区(hex): %s" % (checksum.hex().upper(), data.hex().upper())) + return lines + + +# ===================================================================== +# 默认数据库路径 +# ===================================================================== + + +def default_db_path(): + base = os.path.dirname(os.path.abspath(__file__)) + for p in ( + os.path.join(base, "..", "bin", "power_data.db"), + os.path.join(base, "..", "src", "pPowerMangerHost", "feedback_data.db"), + os.path.join(base, "..", "bin", "power_data.db"), + ): + p = os.path.normpath(p) + if os.path.exists(p): + return p + return os.path.normpath(os.path.join(base, "..", "bin", "power_data.db")) + + +# ===================================================================== +# GUI +# ===================================================================== +class PowerDbApp: + def __init__(self, root, db_path): + self.root = root + self.conn = None + self.db_path = db_path + self.current_table = None + self.current_cols = [] + self.current_raw_rows = [] + self.time_col = None + self.time_col_type = "text" + + root.title(APP_TITLE) + root.geometry("1280x800") + + self._build_toolbar() + self._build_notebook() + self._build_statusbar() + + if db_path and os.path.exists(db_path): + self.set_status("请点击「打开数据库...」选择数据库文件(已默认填入: %s)" % db_path) + else: + self.set_status("请点击「打开数据库...」选择数据库文件") + + # ---------- 工具栏 ---------- + def _build_toolbar(self): + bar = ttk.Frame(self.root, padding=(6, 4)) + bar.pack(fill="x") + self.db_var = tk.StringVar(value=self.db_path) + ttk.Button(bar, text="打开数据库...", command=self.choose_db).pack(side="left") + ent = ttk.Entry(bar, textvariable=self.db_var) + ent.pack(side="left", fill="x", expand=True, padx=6) + self.human_var = tk.BooleanVar(value=True) + ttk.Checkbutton(bar, text="人性化显示", variable=self.human_var, + command=self._on_human_toggle).pack(side="left", padx=6) + + def _on_human_toggle(self): + if self.current_table: + self.load_table(self.current_table) + + # ---------- 主标签页 ---------- + def _build_notebook(self): + nb = ttk.Notebook(self.root) + nb.pack(fill="both", expand=True) + self.nb = nb + + self.tab_browse = ttk.Frame(nb) + self.tab_query = ttk.Frame(nb) + self.tab_decode = ttk.Frame(nb) + self.tab_plot = ttk.Frame(nb) + nb.add(self.tab_browse, text="数据浏览") + nb.add(self.tab_query, text="SQL 查询") + nb.add(self.tab_decode, text="帧解码") + nb.add(self.tab_plot, text="趋势图") + + self._build_browse_tab() + self._build_query_tab() + self._build_decode_tab() + self._build_plot_tab() + + # ---------- 状态栏 ---------- + def _build_statusbar(self): + self.status_var = tk.StringVar(value="就绪") + ttk.Label(self.root, textvariable=self.status_var, relief="sunken", + anchor="w", padding=(6, 2)).pack(fill="x", side="bottom") + + def set_status(self, msg): + self.status_var.set(msg) + + # ---------- 数据库打开 ---------- + def choose_db(self): + p = filedialog.askopenfilename( + title="选择 SQLite 数据库", + filetypes=[("SQLite 数据库", "*.db *.sqlite *.db3"), ("所有文件", "*.*")], + ) + if p: + self.open_db(p) + + def open_db(self, path): + try: + if self.conn: + self.conn.close() + self.conn = connect_db(path) + self.db_path = path + self.db_var.set(path) + self._refresh_tables() + self._refresh_plot_tables() + self.set_status("已打开: %s" % path) + except Exception as e: + messagebox.showerror("打开失败", str(e)) + + def _refresh_tables(self): + for i in self.tables_tree.get_children(): + self.tables_tree.delete(i) + if not self.conn: + return + for name, n, tcol, tmin, tmax in list_tables(self.conn): + tr = "%s ~ %s" % (format_time_value(tcol, tmin), format_time_value(tcol, tmax)) if tmin is not None else "" + self.tables_tree.insert("", "end", iid=name, values=(name, n, tr)) + + # ---------- 数据浏览 ---------- + def _build_browse_tab(self): + left = ttk.Frame(self.tab_browse, width=340) + left.pack(side="left", fill="y") + left.pack_propagate(False) + ttk.Label(left, text="数据表").pack(anchor="w", padx=4, pady=2) + self.tables_tree = ttk.Treeview(left, columns=("name", "rows", "range"), show="headings", height=30) + self.tables_tree.heading("name", text="表名") + self.tables_tree.heading("rows", text="行数") + self.tables_tree.heading("range", text="时间范围") + self.tables_tree.column("name", width=150) + self.tables_tree.column("rows", width=70, anchor="e") + self.tables_tree.column("range", width=180) + tsb = ttk.Scrollbar(left, orient="vertical", command=self.tables_tree.yview) + self.tables_tree.configure(yscrollcommand=tsb.set) + self.tables_tree.pack(side="left", fill="both", expand=True, padx=(4, 0), pady=2) + tsb.pack(side="left", fill="y") + self.tables_tree.bind("<>", self._on_table_select) + + right = ttk.Frame(self.tab_browse) + right.pack(side="left", fill="both", expand=True) + + filter_bar = ttk.Frame(right) + filter_bar.pack(fill="x", padx=4, pady=4) + ttk.Label(filter_bar, text="条件(WHERE)").pack(side="left") + self.where_var = tk.StringVar() + ttk.Entry(filter_bar, textvariable=self.where_var, width=30).pack(side="left", padx=4) + ttk.Label(filter_bar, text="起始").pack(side="left", padx=(8, 0)) + self.since_var = tk.StringVar() + ttk.Entry(filter_bar, textvariable=self.since_var, width=18).pack(side="left", padx=2) + ttk.Label(filter_bar, text="结束").pack(side="left") + self.until_var = tk.StringVar() + ttk.Entry(filter_bar, textvariable=self.until_var, width=18).pack(side="left", padx=2) + ttk.Label(filter_bar, text="行数").pack(side="left", padx=(8, 0)) + self.limit_var = tk.StringVar(value="500") + ttk.Combobox(filter_bar, textvariable=self.limit_var, width=8, + values=("100", "500", "1000", "5000", "10000", "全部")).pack(side="left", padx=2) + ttk.Button(filter_bar, text="查询", command=self._refresh_browse).pack(side="left", padx=6) + ttk.Button(filter_bar, text="导出CSV", command=self._export_browse).pack(side="left", padx=2) + + tree_area = ttk.Frame(right) + tree_area.pack(fill="both", expand=True) + self.data_tree = ttk.Treeview(tree_area, show="headings") + ysb = ttk.Scrollbar(tree_area, orient="vertical", command=self.data_tree.yview) + xsb = ttk.Scrollbar(tree_area, orient="horizontal", command=self.data_tree.xview) + self.data_tree.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set) + self.data_tree.grid(row=0, column=0, sticky="nsew") + ysb.grid(row=0, column=1, sticky="ns") + xsb.grid(row=1, column=0, sticky="ew") + tree_area.rowconfigure(0, weight=1) + tree_area.columnconfigure(0, weight=1) + + def _on_table_select(self, _event): + sel = self.tables_tree.selection() + if not sel: + return + self.load_table(sel[0]) + + def load_table(self, table): + self.current_table = table + self._refresh_browse() + + def _build_where(self): + parts = [] + where = self.where_var.get().strip() + if where: + parts.append("(" + where + ")") + since = self.since_var.get().strip() + until = self.until_var.get().strip() + if (since or until) and self.time_col: + col = self.time_col + if self.time_col_type == "real": + if since: + parts.append("%s >= %f" % (col, parse_time_input(since).timestamp())) + if until: + parts.append("%s <= %f" % (col, parse_time_input(until).timestamp())) + else: + if since: + parts.append("%s >= '%s'" % (col, parse_time_input(since).strftime("%Y-%m-%d %H:%M:%S"))) + if until: + parts.append("%s <= '%s'" % (col, parse_time_input(until).strftime("%Y-%m-%d %H:%M:%S"))) + return (" WHERE " + " AND ".join(parts)) if parts else "" + + def _refresh_browse(self): + if not self.conn or not self.current_table: + return + table = self.current_table + self.data_tree.delete(*self.data_tree.get_children()) + cols = table_columns(self.conn, table) + self.current_cols = cols + + # 时间列检测 + self.time_col = None + self.time_col_type = "text" + for cand in ("timestamp", "ts_local", "ts"): + if cand in cols: + self.time_col = cand + self.time_col_type = "real" if cand == "ts" else "text" + break + + self.data_tree["columns"] = cols + for c in cols: + meta = COL_META.get(table, {}).get(c) + title = meta[0] if meta else c + self.data_tree.heading(c, text=title) + self.data_tree.column(c, width=120, stretch=True, anchor="w") + + sql = "SELECT * FROM %s" % table + try: + sql += self._build_where() + sql += " ORDER BY 1 DESC" + limit = self.limit_var.get().strip() + if limit != "全部": + sql += " LIMIT %d" % int(limit) + t0 = datetime.now() + rows = self.conn.execute(sql).fetchall() + dt = (datetime.now() - t0).total_seconds() * 1000 + except Exception as e: + messagebox.showerror("查询失败", str(e)) + return + + self.current_raw_rows = rows + human = self.human_var.get() + for r in rows: + vals = [fmt_value(table, c, r[i], human) for i, c in enumerate(cols)] + self.data_tree.insert("", "end", values=vals) + total = self.conn.execute("SELECT COUNT(*) FROM %s" % table).fetchone()[0] + self.set_status("表 %s: 显示 %d / 共 %d 行, 耗时 %.1f ms" % (table, len(rows), total, dt)) + + def _export_browse(self): + if not self.current_raw_rows: + messagebox.showinfo("提示", "没有可导出的数据, 请先查询。") + return + self._export_to_csv(self.current_cols, self.current_raw_rows) + + # ---------- SQL 查询 ---------- + def _build_query_tab(self): + top = ttk.Frame(self.tab_query) + top.pack(fill="x", padx=4, pady=4) + ttk.Label(top, text="SQL 语句 (只读)").pack(anchor="w") + self.sql_text = tk.Text(top, height=5) + self.sql_text.pack(fill="x", pady=2) + btns = ttk.Frame(top) + btns.pack(fill="x") + ttk.Button(btns, text="执行", command=self._run_query).pack(side="left") + ttk.Button(btns, text="导出CSV", command=self._export_query).pack(side="left", padx=6) + self.q_human_var = tk.BooleanVar(value=False) + ttk.Checkbutton(btns, text="人性化显示", variable=self.q_human_var).pack(side="left") + self.q_table_hint = ttk.Label(btns, text="") + self.q_table_hint.pack(side="left", padx=10) + + q_area = ttk.Frame(self.tab_query) + q_area.pack(fill="both", expand=True) + self.q_tree = ttk.Treeview(q_area, show="headings") + ysb = ttk.Scrollbar(q_area, orient="vertical", command=self.q_tree.yview) + xsb = ttk.Scrollbar(q_area, orient="horizontal", command=self.q_tree.xview) + self.q_tree.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set) + self.q_tree.grid(row=0, column=0, sticky="nsew") + ysb.grid(row=0, column=1, sticky="ns") + xsb.grid(row=1, column=0, sticky="ew") + q_area.rowconfigure(0, weight=1) + q_area.columnconfigure(0, weight=1) + self.q_rows = [] + self.q_cols = [] + self.q_table = None + + def _guess_table(self, sql): + import re + m = re.search(r"\bfrom\s+([a-zA-Z_][\w]*)", sql, re.I) + if m: + return m.group(1) + return None + + def _run_query(self): + if not self.conn: + messagebox.showinfo("提示", "请先打开数据库。") + return + self.q_tree.delete(*self.q_tree.get_children()) + sql = self.sql_text.get("1.0", "end").strip() + if not sql: + return + try: + t0 = datetime.now() + cur = self.conn.execute(sql) + cols = [d[0] for d in cur.description] if cur.description else [] + rows = cur.fetchall() + dt = (datetime.now() - t0).total_seconds() * 1000 + except Exception as e: + messagebox.showerror("执行失败", str(e)) + return + self.q_cols = cols + self.q_rows = rows + self.q_table = self._guess_table(sql) + self.q_table_hint.config(text="(匹配表: %s)" % self.q_table if self.q_table else "") + self.q_tree["columns"] = cols + for c in cols: + self.q_tree.heading(c, text=c) + self.q_tree.column(c, width=130, stretch=True, anchor="w") + table = self.q_table or "" + human = self.q_human_var.get() + for r in rows: + vals = [fmt_value(table, c, r[i], human) for i, c in enumerate(cols)] + self.q_tree.insert("", "end", values=vals) + self.set_status("查询返回 %d 行, 耗时 %.1f ms" % (len(rows), dt)) + + def _export_query(self): + if not self.q_rows: + messagebox.showinfo("提示", "没有可导出的数据, 请先执行查询。") + return + self._export_to_csv(self.q_cols, self.q_rows) + + # ---------- CSV 导出 ---------- + def _export_to_csv(self, cols, rows): + p = filedialog.asksaveasfilename( + title="导出 CSV", defaultextension=".csv", + filetypes=[("CSV 文件", "*.csv")], + ) + if not p: + return + try: + with open(p, "w", newline="", encoding="utf-8-sig") as f: + w = csv.writer(f) + w.writerow(cols) + for r in rows: + w.writerow(list(r)) + self.set_status("已导出 %d 行到 %s" % (len(rows), p)) + except Exception as e: + messagebox.showerror("导出失败", str(e)) + + # ---------- 帧解码 ---------- + def _build_decode_tab(self): + top = ttk.Frame(self.tab_decode) + top.pack(fill="x", padx=4, pady=4) + ttk.Label(top, text="粘贴 hex 帧 (可多帧, 空格/换行分隔), 也可从 msg_log 表复制 payload_hex:").pack(anchor="w") + self.decode_input = tk.Text(top, height=6) + self.decode_input.pack(fill="x", pady=2) + btns = ttk.Frame(top) + btns.pack(fill="x") + ttk.Button(btns, text="解码", command=self._run_decode).pack(side="left") + ttk.Button(btns, text="从 msg_log 加载示例", command=self._load_decode_samples).pack(side="left", padx=6) + ttk.Button(btns, text="清空", command=lambda: self.decode_input.delete("1.0", "end")).pack(side="left") + + d_area = ttk.Frame(self.tab_decode) + d_area.pack(fill="both", expand=True) + self.decode_out = tk.Text(d_area, wrap="none", font=(_UI_MONO, 10)) + ysb = ttk.Scrollbar(d_area, orient="vertical", command=self.decode_out.yview) + xsb = ttk.Scrollbar(d_area, orient="horizontal", command=self.decode_out.xview) + self.decode_out.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set) + self.decode_out.grid(row=0, column=0, sticky="nsew") + ysb.grid(row=0, column=1, sticky="ns") + xsb.grid(row=1, column=0, sticky="ew") + d_area.rowconfigure(0, weight=1) + d_area.columnconfigure(0, weight=1) + + def _run_decode(self): + text = self.decode_input.get("1.0", "end") + tokens = text.split() + if not tokens: + self.decode_out.insert("end", "请输入 hex 帧。\n") + return + self.decode_out.delete("1.0", "end") + for t in tokens: + for line in decode_frame(t): + self.decode_out.insert("end", line + "\n") + self.decode_out.insert("end", "-" * 70 + "\n") + + def _load_decode_samples(self): + if not self.conn: + messagebox.showinfo("提示", "请先打开数据库。") + return + rows = self.conn.execute( + "SELECT msg_id, payload_hex FROM msg_log GROUP BY msg_id LIMIT 6" + ).fetchall() + if not rows: + messagebox.showinfo("提示", "msg_log 表为空。") + return + samples = [r[1] for r in rows] + self.decode_input.delete("1.0", "end") + self.decode_input.insert("end", "\n".join(samples)) + self.set_status("已加载 %d 个不同消息类型的示例帧" % len(samples)) + + # ---------- 趋势图 ---------- + def _build_plot_tab(self): + top = ttk.Frame(self.tab_plot) + top.pack(fill="x", padx=4, pady=4) + ttk.Label(top, text="表").pack(side="left") + self.plot_table_var = tk.StringVar() + self.plot_table_combo = ttk.Combobox(top, textvariable=self.plot_table_var, width=22, state="readonly") + self.plot_table_combo.pack(side="left", padx=4) + self.plot_table_combo.bind("<>", self._plot_on_table) + ttk.Label(top, text="数值列").pack(side="left") + self.plot_col_var = tk.StringVar() + self.plot_col_combo = ttk.Combobox(top, textvariable=self.plot_col_var, width=24, state="readonly") + self.plot_col_combo.pack(side="left", padx=4) + ttk.Label(top, text="时间桶").pack(side="left") + self.plot_bucket_var = tk.StringVar(value="原始") + ttk.Combobox(top, textvariable=self.plot_bucket_var, width=10, state="readonly", + values=("原始", "1分钟", "10分钟", "1小时", "1天")).pack(side="left", padx=4) + ttk.Button(top, text="绘图", command=self._run_plot).pack(side="left", padx=6) + + self.plot_canvas = tk.Canvas(self.tab_plot, bg="white") + self.plot_canvas.pack(fill="both", expand=True, padx=4, pady=4) + + def _plot_on_table(self, _event=None): + table = self.plot_table_var.get() + if not self.conn or not table: + return + cols = table_columns(self.conn, table) + # 只挑数值列 + num_cols = [] + for c in cols: + r = self.conn.execute("SELECT %s FROM %s WHERE %s IS NOT NULL LIMIT 1" % (c, table, c)).fetchone() + if r and isinstance(r[0], (int, float)): + num_cols.append(c) + self.plot_col_combo["values"] = num_cols + if num_cols: + self.plot_col_var.set(num_cols[0]) + + def _refresh_plot_tables(self): + if not self.conn: + return + names = [t[0] for t in list_tables(self.conn)] + self.plot_table_combo["values"] = names + if names: + self.plot_table_var.set(names[0]) + self._plot_on_table() + + def _run_plot(self): + if not self.conn: + return + table = self.plot_table_var.get() + col = self.plot_col_var.get() + if not table or not col: + return + tcol = None + cols = table_columns(self.conn, table) + for cand in ("timestamp", "ts_local", "ts"): + if cand in cols: + tcol = cand + break + if tcol is None: + messagebox.showinfo("提示", "该表没有可识别的时间列。") + return + try: + rows = self.conn.execute( + "SELECT %s, %s FROM %s WHERE %s IS NOT NULL" % (tcol, col, table, col) + ).fetchall() + except Exception as e: + messagebox.showerror("查询失败", str(e)) + return + pts = [] + for tv, v in rows: + if not isinstance(v, (int, float)): + continue + try: + if tcol == "ts": + t = datetime.fromtimestamp(tv) + else: + t = datetime.strptime(str(tv), "%Y-%m-%d %H:%M:%S") + except Exception: + continue + pts.append((t, v)) + if not pts: + messagebox.showinfo("提示", "没有可绘制的数据点。") + return + pts.sort(key=lambda x: x[0]) + bucket = self.plot_bucket_var.get() + if bucket != "原始": + pts = self._bucket_points(pts, bucket) + self._draw_plot(pts, col) + + def _bucket_points(self, pts, bucket): + if bucket == "1分钟": + sec = 60 + elif bucket == "10分钟": + sec = 600 + elif bucket == "1小时": + sec = 3600 + elif bucket == "1天": + sec = 86400 + else: + sec = 1 + buckets = {} + for t, v in pts: + key = int(t.timestamp() // sec) * sec + buckets.setdefault(key, []).append(v) + out = [] + for k in sorted(buckets): + vs = buckets[k] + out.append((datetime.fromtimestamp(k), sum(vs) / len(vs))) + return out + + def _draw_plot(self, pts, col): + c = self.plot_canvas + c.delete("all") + W = c.winfo_width() or 800 + H = c.winfo_height() or 500 + pad_l, pad_r, pad_t, pad_b = 60, 20, 20, 40 + plot_w = W - pad_l - pad_r + plot_h = H - pad_t - pad_b + if plot_w <= 0 or plot_h <= 0: + return + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + ymin, ymax = min(ys), max(ys) + if ymin == ymax: + ymax = ymin + 1 + xmin, xmax = xs[0], xs[-1] + span = (xmax - xmin).total_seconds() or 1 + + def sx(t): + return pad_l + (t - xmin).total_seconds() / span * plot_w + + def sy(v): + return pad_t + (ymax - v) / (ymax - ymin) * plot_h + + # 网格与轴 + c.create_line(pad_l, pad_t + plot_h, W - pad_r, pad_t + plot_h, fill="black") + c.create_line(pad_l, pad_t, pad_l, pad_t + plot_h, fill="black") + for i in range(5): + yv = ymin + (ymax - ymin) * i / 4 + yy = sy(yv) + c.create_line(pad_l, yy, W - pad_r, yy, fill="#dddddd") + c.create_text(pad_l - 5, yy, text="%.4g" % yv, anchor="e", font=("Arial", 8)) + # 时间标签(首/中/尾) + for frac in (0.0, 0.5, 1.0): + t = xmin + timedelta(seconds=span * frac) + xx = pad_l + plot_w * frac + c.create_text(xx, pad_t + plot_h + 15, text=t.strftime("%m-%d %H:%M"), font=("Arial", 8)) + # 折线 + coords = [] + for t, v in pts: + coords.append(sx(t)) + coords.append(sy(v)) + if len(coords) >= 4: + c.create_line(*coords, fill="#1f77b4", width=1.5) + c.create_text(pad_l + plot_w / 2, pad_t - 8, text="%s (%s, %d 点)" % (col, self.plot_bucket_var.get(), len(pts)), + font=(_UI_SANS, 10)) + + +def _pick_font(candidates, default): + """从候选字体族中选取第一个实际可用的, 否则返回默认。""" + if tk is None: + return default + import tkinter.font as tkfont + available = set(tkfont.families()) + for fam in candidates: + if fam in available: + return fam + return default + + +def setup_fonts(root): + """设置全局默认字体为支持中文的字体, 避免中文显示为方块。""" + import tkinter.font as tkfont + + cjk = _pick_font(["Noto Sans CJK SC", "WenQuanYi Micro Hei", "WenQuanYi Zen Hei", + "Microsoft YaHei", "SimHei", "Source Han Sans SC"], "TkDefaultFont") + cjk_mono = _pick_font(["Noto Sans Mono CJK SC", "WenQuanYi Micro Hei Mono", + "Microsoft YaHei", "SimHei"], "TkFixedFont") + default_font = tkfont.nametofont("TkDefaultFont") + default_font.configure(family=cjk, size=10) + fixed_font = tkfont.nametofont("TkFixedFont") + fixed_font.configure(family=cjk_mono, size=10) + + style = ttk.Style(root) + try: + style.theme_use("clam") + except Exception: + pass + style.configure(".", font=(cjk, 10)) + style.configure("Treeview", font=(cjk, 10), rowheight=24) + style.configure("Treeview.Heading", font=(cjk, 10, "bold")) + style.configure("TNotebook.Tab", font=(cjk, 10)) + style.configure("TLabel", font=(cjk, 10)) + style.configure("TButton", font=(cjk, 10)) + style.configure("TCheckbutton", font=(cjk, 10)) + style.configure("TCombobox", font=(cjk, 10)) + style.configure("TEntry", font=(cjk, 10)) + + root.option_add("*Font", (cjk, 10)) + root.option_add("*Text.Font", (cjk_mono, 10)) + + global _UI_SANS, _UI_MONO + _UI_SANS = cjk + _UI_MONO = cjk_mono + return cjk, cjk_mono + + +def main(): + if tk is None: + print("未检测到 tkinter。请安装系统包后重试:") + print(" sudo apt install python3-tk") + return 1 + root = tk.Tk() + setup_fonts(root) + app = PowerDbApp(root, default_db_path()) + root.mainloop() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())