pCCU 锂电池页新增 BCU 断路器控制(0x10XX81FF 经 pCanBridge 下行 CAN 总线)
- CanBms: bcuCtrlCanId(0x10XX81FF) + buildBcuRelayCtrlData(02 00 总正 总负 FF*4) - SystemData: BcuCtrlCmd 下行队列(Web线程入队->Iterate/MOOS线程出队Notify) + 下发统计 - CCU: /api/bcu_ctrl(addr/action 或 pos/neg, 校验地址01h~36h) + Iterate 经 MOOS CAN_TX_0x* 下发 - pCanBridge 变双向透传: 订阅 CAN_TX_0x*,CanEndpoint::sendFrame(13字节CANET帧,TCP写,互斥保护) - 网页: 每BCU节点断路器闭合/断开按钮(confirm确认) + 下发统计卡片 - 测试: pccuTest 控制帧编码用例(183项);集成测试新增假CANET服务器验证完整下行链路(闭合/断开/非法地址)
This commit is contained in:
@@ -520,6 +520,25 @@ static void testRcuRegistry() {
|
||||
CHECK(gotName == "pm_status");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 13. BCU 断路器控制帧(0x10XX81FF 下行)
|
||||
static void testBcuCtrlFrame() {
|
||||
// CAN ID:0x10 XX 81 FF,XX=节点地址
|
||||
CHECK(bcuCtrlCanId(1) == 0x100181FFu);
|
||||
CHECK(bcuCtrlCanId(54) == 0x103681FFu);
|
||||
|
||||
// 数据域:02 00 总正 总负 FF*4
|
||||
uint8_t d[8];
|
||||
buildBcuRelayCtrlData(1, 1, d);
|
||||
CHECK(d[0] == 0x02 && d[1] == 0x00);
|
||||
CHECK(d[2] == 0x01 && d[3] == 0x01);
|
||||
CHECK(d[4] == 0xFF && d[5] == 0xFF && d[6] == 0xFF && d[7] == 0xFF);
|
||||
buildBcuRelayCtrlData(0, 0, d);
|
||||
CHECK(d[2] == 0x00 && d[3] == 0x00);
|
||||
buildBcuRelayCtrlData(1, 0, d);
|
||||
CHECK(d[2] == 0x01 && d[3] == 0x00);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
int main() {
|
||||
testFrameLengths();
|
||||
@@ -535,6 +554,7 @@ int main() {
|
||||
testDisRoundTrip();
|
||||
testRegistryLengthDispatch();
|
||||
testRcuRegistry();
|
||||
testBcuCtrlFrame();
|
||||
|
||||
std::printf("\n==== pccuTest: %d passed, %d failed ====\n", g_pass, g_fail);
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
|
||||
@@ -33,6 +33,7 @@ import subprocess
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
# ---- 配置 ----
|
||||
@@ -42,10 +43,12 @@ PM_CTRL_PORT = 16002
|
||||
PM_STATUS_RECV_PORT = 16003
|
||||
RCU_FB_PORT = 16004 # pCCU rcu 链路本地端口(收配电反馈)
|
||||
RCU_CMD_RECV_PORT = 16005 # 模拟真实CCU 接收转发的配电指令
|
||||
CANET_PORT = 16006 # 模拟 USBCAN-8E-U TCP Server(捕获下行 CAN 帧)
|
||||
WEB_PORT = 18081
|
||||
DB_PATH = "/tmp/pccu_it_test.db"
|
||||
MOOSDB_EXE = "/usr/local/bin/MOOSDB"
|
||||
PCCU_EXE = "/home/zjk/project/H100PowerManger/bin/pCCU"
|
||||
CANBRIDGE_EXE = "/home/zjk/project/H100PowerManger/bin/pCanBridge"
|
||||
MISSION = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pccu_it.moos")
|
||||
|
||||
# 帧头
|
||||
@@ -184,6 +187,72 @@ def dis_lv_cmd_frame():
|
||||
return dis_frame(0x0004, bytes(p))
|
||||
|
||||
|
||||
#------------------ BCU 断路器控制下行测试 ------------------
|
||||
|
||||
class CanetServer(threading.Thread):
|
||||
"""最小 CANET TCP Server 模拟:接受 pCanBridge 连接,捕获下行 13 字节帧"""
|
||||
def __init__(self, port):
|
||||
super().__init__(daemon=True)
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.sock.bind(("127.0.0.1", port))
|
||||
self.sock.listen(2)
|
||||
self.frames = []
|
||||
self.lock = threading.Lock()
|
||||
self.stop_flag = False
|
||||
|
||||
def run(self):
|
||||
self.sock.settimeout(1.0)
|
||||
conn = None
|
||||
while not self.stop_flag:
|
||||
try:
|
||||
if conn is None:
|
||||
conn, _ = self.sock.accept()
|
||||
conn.settimeout(0.5)
|
||||
buf = b""
|
||||
while not self.stop_flag:
|
||||
try:
|
||||
d = conn.recv(4096)
|
||||
except socket.timeout:
|
||||
break
|
||||
if not d:
|
||||
conn.close()
|
||||
conn = None
|
||||
break
|
||||
buf += d
|
||||
while len(buf) >= 13:
|
||||
with self.lock:
|
||||
self.frames.append(buf[:13])
|
||||
buf = buf[13:]
|
||||
except socket.timeout:
|
||||
continue
|
||||
except OSError:
|
||||
break
|
||||
|
||||
def snapshot(self):
|
||||
with self.lock:
|
||||
return list(self.frames)
|
||||
|
||||
|
||||
def bcu_ctrl_expect(addr, pos, neg):
|
||||
"""期望的下行 13 字节 CANET 帧:扩展帧 DLC8 + 0x10XX81FF + 02 00 pos neg FF*4"""
|
||||
return bytes([0x80 | 8,
|
||||
(0x10000000 | (addr << 16) | 0x81FF) >> 24 & 0xFF,
|
||||
(0x10000000 | (addr << 16) | 0x81FF) >> 16 & 0xFF,
|
||||
(0x10000000 | (addr << 16) | 0x81FF) >> 8 & 0xFF,
|
||||
(0x10000000 | (addr << 16) | 0x81FF) & 0xFF,
|
||||
0x02, 0x00, 0x01 if pos else 0x00, 0x01 if neg else 0x00,
|
||||
0xFF, 0xFF, 0xFF, 0xFF])
|
||||
|
||||
|
||||
def api_get(path):
|
||||
try:
|
||||
resp = urllib.request.urlopen(f"http://127.0.0.1:{WEB_PORT}{path}", timeout=3)
|
||||
return json.loads(resp.read().decode())
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
|
||||
def recv_loop(sock, label, results, msg_ids, timeout):
|
||||
"""持续接收指定 msg_id 的帧,记录到 results"""
|
||||
sock.settimeout(timeout)
|
||||
@@ -204,11 +273,13 @@ def recv_loop(sock, label, results, msg_ids, timeout):
|
||||
|
||||
def main():
|
||||
# 清理
|
||||
for f in (DB_PATH, DB_PATH + "-wal", DB_PATH + "-shm", "/tmp/pccu_it_test.log"):
|
||||
for f in (DB_PATH, DB_PATH + "-wal", DB_PATH + "-shm", "/tmp/pccu_it_test.log",
|
||||
"/tmp/pccu_it_can.db", "/tmp/pccu_it_can.db-wal", "/tmp/pccu_it_can.db-shm"):
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
|
||||
procs = []
|
||||
canet = CanetServer(CANET_PORT)
|
||||
try:
|
||||
# 1. 启动 MOOSDB
|
||||
moosdb = subprocess.Popen([MOOSDB_EXE, "--moos", "--port", "9000"],
|
||||
@@ -216,11 +287,15 @@ def main():
|
||||
procs.append(moosdb)
|
||||
time.sleep(1.0)
|
||||
|
||||
# 2. 启动 pCCU
|
||||
# 2. 启动 pCCU + pCanBridge(pCanBridge 连接模拟 USBCAN,用于下行测试)
|
||||
pccu = subprocess.Popen([PCCU_EXE, MISSION],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
procs.append(pccu)
|
||||
time.sleep(2.0)
|
||||
canet.start()
|
||||
pcan = subprocess.Popen([CANBRIDGE_EXE, MISSION],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
procs.append(pcan)
|
||||
time.sleep(2.5)
|
||||
if pccu.poll() is not None:
|
||||
print("FAIL: pCCU exited early, code", pccu.returncode)
|
||||
return 1
|
||||
@@ -349,6 +424,39 @@ def main():
|
||||
ok = ok and (db_count >= 6) # 3条FC状态 + 1条PM操控 + 1条PM参数 + 周期PM状态(至少1)
|
||||
ok = ok and (db_rcu >= 8) # 4条配电反馈(收) + 4条配电指令(发)
|
||||
|
||||
# 校验 8:BCU 断路器控制下行(网页API -> pCCU -> MOOS -> pCanBridge -> 模拟USBCAN)
|
||||
# 闭合(addr=5, 总正+总负=1)
|
||||
j = api_get("/api/bcu_ctrl?addr=5&pos=1&neg=1")
|
||||
print(f"[断路器闭合 API] {j}")
|
||||
ok = ok and j.get("ok") is True
|
||||
# action 语法(addr=6, action=1 等价 pos=neg=1)
|
||||
j = api_get("/api/bcu_ctrl?addr=6&action=1")
|
||||
print(f"[断路器闭合 API(action)] {j}")
|
||||
ok = ok and j.get("ok") is True
|
||||
# 非法地址
|
||||
j = api_get("/api/bcu_ctrl?addr=99&action=1")
|
||||
print(f"[非法地址 API] {j}")
|
||||
ok = ok and j.get("ok") is False
|
||||
# 断开(addr=5, 总正+总负=0)
|
||||
j = api_get("/api/bcu_ctrl?addr=5&pos=0&neg=0")
|
||||
print(f"[断路器断开 API] {j}")
|
||||
ok = ok and j.get("ok") is True
|
||||
time.sleep(3.0)
|
||||
frames = canet.snapshot()
|
||||
print(f"[下行帧捕获] {len(frames)} 帧")
|
||||
exp_close5 = bcu_ctrl_expect(5, 1, 1)
|
||||
exp_close6 = bcu_ctrl_expect(6, 1, 1)
|
||||
exp_open5 = bcu_ctrl_expect(5, 0, 0)
|
||||
hit5 = any(f == exp_close5 for f in frames)
|
||||
hit6 = any(f == exp_close6 for f in frames)
|
||||
hit5o = any(f == exp_open5 for f in frames)
|
||||
leak99 = any(f[1] == 0x63 and f[2] == 0x00 for f in frames) # addr=99 未下发
|
||||
print(f"[断路器闭合下行(addr=5)] {'OK' if hit5 else 'MISSING'}")
|
||||
print(f"[断路器闭合下行(addr=6)] {'OK' if hit6 else 'MISSING'}")
|
||||
print(f"[断路器断开下行(addr=5)] {'OK' if hit5o else 'MISSING'}")
|
||||
print(f"[非法地址未下发(addr=99)] {'OK' if not leak99 else 'LEAK'}")
|
||||
ok = ok and hit5 and hit6 and hit5o and not leak99
|
||||
|
||||
web_ok = False
|
||||
try:
|
||||
resp = urllib.request.urlopen(f"http://127.0.0.1:{WEB_PORT}/", timeout=3)
|
||||
@@ -363,6 +471,7 @@ def main():
|
||||
return 0 if ok else 1
|
||||
|
||||
finally:
|
||||
canet.stop_flag = True
|
||||
for p in reversed(procs):
|
||||
try:
|
||||
p.terminate()
|
||||
|
||||
@@ -30,4 +30,16 @@ ProcessConfig = pCCU
|
||||
|
||||
web_port = 18081 // 避开 pPowerMangerHost(18080)
|
||||
web_enable = true
|
||||
}
|
||||
|
||||
ProcessConfig = pCanBridge
|
||||
{
|
||||
AppTick = 4
|
||||
CommsTick = 4
|
||||
|
||||
// 测试用 CANET TCP Server(本脚本模拟,捕获 pCanBridge 下行帧)
|
||||
can_host = 127.0.0.1
|
||||
can_port = 16006
|
||||
can_channel_name = CAN0
|
||||
dbpath = /tmp/pccu_it_can.db
|
||||
}
|
||||
Reference in New Issue
Block a user