pCCU 新增真实CCU配电桥接(rcu链路原帧透传+网页显示)并适配板卡端口拓扑
- protocol/DisProtocol: 配电协议8条消息(指令0x0001~4/反馈0x0005~8, Sum8),复用pmSysvariable结构体+static_assert核对 - ChecksumPolicy 新增 Sum8;Frame 支持 1 字节校验和 - MessageRegistry 支持 (id+帧总长) 二维查找:配电指令与PM操控指令共用id按帧长区分(帧头length字段不可信) - LinkManager: 按实际帧长分发 + sendRaw原帧透传 + 非本协议帧头落库节流告警;UdpEndpoint sendTo 加锁 - comm/RcuLinkManager: 真实CCU链路;core/DisBridge: 配电桥接器(反馈转pPowerManger+解码显示,指令转真实CCU,0x0004仅显示不转发) - 网页新增"真实CCU"页签全字段展示;SnapshotBuilder realCcu 节 + rcu 链路描述 - h100.moos: 端口腾挪(真实CCU固定上报5001归pCCU,pPowerManger iport=5002),rcu_remote=192.168.0.140:7000 - 测试: pccuTest 新增配电/Sum8/长度分发/RCU注册表用例(176项);集成测试新增桥接字节一致+不误转发用例
This commit is contained in:
+216
-1
@@ -86,6 +86,7 @@ th{color:var(--dim);font-weight:500;}
|
||||
</div>
|
||||
<div class="tab-group"><span class="tg-label">系统</span>
|
||||
<button class="tab" data-tab="links">链路状态</button>
|
||||
<button class="tab" data-tab="realCcu">真实CCU</button>
|
||||
<button class="tab" data-tab="raw">原始报文</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +96,7 @@ th{color:var(--dim);font-weight:500;}
|
||||
const $ = id => document.getElementById(id);
|
||||
const pagesEl = $('pages');
|
||||
const statusEl = $('status');
|
||||
const TABS = ['fcStatus','pmCmd','pmParam','bms','fcCmd','pmStatus','pmFb','links','raw'];
|
||||
const TABS = ['fcStatus','pmCmd','pmParam','bms','fcCmd','pmStatus','pmFb','links','realCcu','raw'];
|
||||
let activeTab = 'fcStatus';
|
||||
|
||||
function dot(ok){ return ok ? 'dot-ok' : (ok === undefined ? 'dot-warn' : 'dot-bad'); }
|
||||
@@ -154,6 +155,7 @@ function bmsOnline(n){
|
||||
}
|
||||
// 最后更新时间文本(a:距最后收到报文的秒数)
|
||||
function ageText(a){
|
||||
if(typeof a !== 'number' || isNaN(a)) return '—';
|
||||
if(a < 90) return a.toFixed(1)+' 秒前';
|
||||
if(a < 3600) return Math.floor(a/60)+' 分 '+Math.floor(a%60)+' 秒前';
|
||||
return Math.floor(a/3600)+' 时 '+Math.floor((a%3600)/60)+' 分前';
|
||||
@@ -323,6 +325,10 @@ function linkCard(s){
|
||||
'收:'+l.fc.rx+' 发:'+l.fc.tx+' 误:'+l.fc.err);
|
||||
h+=row('<span><span class="status-dot '+dot(online(l.pm.lastRx))+'"></span>PM 链路</span>',
|
||||
'收:'+l.pm.rx+' 发:'+l.pm.tx+' 误:'+l.pm.err);
|
||||
if(l.rcu){
|
||||
h+=row('<span><span class="status-dot '+dot(online(l.rcu.lastRx))+'"></span>RCU 链路 (真实CCU)</span>',
|
||||
'收:'+l.rcu.rx+' 发:'+l.rcu.tx+' 误:'+l.rcu.err);
|
||||
}
|
||||
// CAN/BMS 行:任一 BCU 节点在线即视为链路在线
|
||||
const bmsNodes = Array.isArray(s.bms) ? s.bms : [];
|
||||
const bmsAnyOnline = bmsNodes.some(function(n){ return bmsOnline(n); });
|
||||
@@ -490,6 +496,214 @@ function pmStatusCard(d){
|
||||
return card('整合后状态', h);
|
||||
}
|
||||
|
||||
//------------------ 真实 CCU(配电桥接) 卡片 ------------------
|
||||
// 数据来源:RCU 链路。配电反馈(0x0005~0x0008)由 pCCU 原帧转发
|
||||
// pPowerManger 并在此全字段展示;配电指令(0x0001~0x0004)由 pPowerManger
|
||||
// 经 pCCU 原帧转发真实CCU。断路器/开关量:0x55 闭合 0xAA 断开 0x5A 故障
|
||||
function brkText(v){
|
||||
if(v===0x55) return '<span class="fc-ok">闭合</span>';
|
||||
if(v===0xAA) return '断开';
|
||||
if(v===0x5A) return '<span class="fc-bad">故障脱扣</span>';
|
||||
return hex(v);
|
||||
}
|
||||
// 双值信号(0x55 正常 / 0xAA 异常)
|
||||
function sigText(v){
|
||||
if(v===0x55) return '<span class="fc-ok">正常</span>';
|
||||
if(v===0xAA) return '<span class="fc-bad">异常</span>';
|
||||
return hex(v);
|
||||
}
|
||||
function insStatText(v){ return v===0x55 ? '<span class="fc-ok">正常</span>'
|
||||
: (v===0xAA ? '<span class="fc-bad">绝缘低</span>' : hex(v)); }
|
||||
// age:距最后收到该类帧的秒数;valid:是否收到过(未收到显示"从未收到")
|
||||
function ageCell(age, valid){
|
||||
if(!valid || typeof age !== 'number' || isNaN(age))
|
||||
return '<span style="color:var(--dim)">从未收到</span>';
|
||||
const online = age >= 0 && age < 5;
|
||||
return (online ? '<span class="fc-ok">' : '<span class="fc-bad">') + ageText(age)
|
||||
+ (online ? ' (在线)' : ' (超时)') + '</span>';
|
||||
}
|
||||
function fwdLine(okN, errN){
|
||||
return okN + ' 成功' + (errN ? ' <span class="fc-bad">'+errN+' 失败</span>' : '');
|
||||
}
|
||||
|
||||
// 母线卡片通用:breakers 子表 + 信号/电气量行(无数据时只显示概要行)
|
||||
function busCard(title, bus, age, rows){
|
||||
let h='';
|
||||
const b = bus && bus.breakers ? bus.breakers : {};
|
||||
const keys = Object.keys(bNamesDis).filter(function(k){ return b[k] !== undefined; });
|
||||
if(!keys.length){
|
||||
h+=sub('断路器状态');
|
||||
h+=row('状态','暂无数据');
|
||||
h+=row('数据更新', ageCell(age ? age.age : -1, !!(age && age.valid)));
|
||||
return card(title, h);
|
||||
}
|
||||
h+=sub('断路器状态');
|
||||
for(const k of keys) h+=row(bNamesDis[k], brkText(b[k]));
|
||||
for(const r of rows) h+=row(r[0], r[1]);
|
||||
h+=row('数据更新', ageCell(age ? age.age : -1, !!(age && age.valid)));
|
||||
return card(title, h);
|
||||
}
|
||||
// 母线断路器中文名表(busCard 共用)
|
||||
const bNamesDis = {
|
||||
fuelCell:'燃料电池断路器', powerLithiumBattery:'动力锂电池断路器',
|
||||
propulsionMotor:'推进电机断路器', lithiumBatteryGroupInstrument:'锂电池组(仪表)断路器',
|
||||
dcDc5Module:'DC/DC5模块断路器', bowHighVoltageDistributionBox:'艏部高压配电箱断路器',
|
||||
sternFTDevice45:'艉部FT装置4/5断路器', fbReserved:'FB预留断路器',
|
||||
sternRudderSwitch1:'艉舵开关1断路器', sternRudderSwitch2:'艉舵开关2断路器',
|
||||
tyzReserved:'TYZ预留断路器', reserved:'预留断路器',
|
||||
bowFTDevice123:'艏部FT装置1/2/3断路器', actuator:'启闭机构断路器',
|
||||
mastSteeringGearControlBox:'桅杆舵机控制箱断路器', xcz:'XCZ断路器',
|
||||
bowRudderControlBox:'艏舵控制箱断路器', openWaterCoverStartCylinder:'敞水盖启动电缸断路器',
|
||||
bowLowVoltageDistributionBox:'艏部低压配电箱断路器', unit4InstrumentDC48V:'UNIT4仪表DC48V断路器',
|
||||
dcC1DCDistributionPanel:'DC-C1直流配电板断路器', reserved1:'预留断路器1', reserved2:'预留断路器2',
|
||||
emergencyLithiumBatteryGroup2:'应急锂电池组2断路器',
|
||||
unit1:'UNIT1断路器', unit2:'UNIT2断路器', unit3:'UNIT3断路器', unit5:'UNIT5断路器',
|
||||
bowPZDevice:'艏部PZ装置断路器', emergencyLithiumBatteryGroup1:'应急锂电池组1断路器',
|
||||
dcdc5ModuleStart:'DC/DC5模块启动', coolingSystemStart:'冷却系统启动'
|
||||
};
|
||||
|
||||
function realCcuPage(d){
|
||||
if(!d) return '<div class="card"><h2>真实CCU</h2><div class="row"><span class="k">数据状态</span><span class="v">等待数据...</span></div></div>';
|
||||
let h='<div class="page-hdr"><span class="raw-id">RCU</span> <span class="raw-name">真实CCU 配电桥接</span> '+
|
||||
'<span class="badge ok">原帧透传</span></div><div class="grid">';
|
||||
|
||||
// 桥接概览卡片
|
||||
{
|
||||
let c='';
|
||||
const fwd = d.forward || {};
|
||||
c+=row('配电反馈收帧', fwd.fbCount || 0);
|
||||
c+=row('反馈转发PM', fwdLine(fwd.fwdFbOk || 0, fwd.fwdFbErr || 0));
|
||||
c+=row('配电指令收帧', fwd.cmdCount || 0);
|
||||
c+=row('指令转发真实CCU', fwdLine(fwd.fwdCmdOk || 0, fwd.fwdCmdErr || 0));
|
||||
const ccu = d.ccuStatus || {};
|
||||
c+=sub('真实CCU 状态报文(仅显示,不转发)');
|
||||
c+=row('在线', ageCell(ccu.age, ccu.valid));
|
||||
if(ccu.valid){
|
||||
c+=row('运行模式', modeText(ccu.fc_mode));
|
||||
c+=row('运行状态', statusText(ccu.fc_status));
|
||||
c+=row('系统故障等级', faultLvlBadge(ccu.fc_fault_level));
|
||||
c+=row('系统发电功率', (ccu.generation_power*0.01).toFixed(2)+' kW');
|
||||
c+=row('仪表/动力SOC', ccu.ins_soc+'% / '+ccu.dyn_soc+'%');
|
||||
c+=row('通信心跳', ccu.heartbeat);
|
||||
}
|
||||
h+=card('桥接概览', c);
|
||||
}
|
||||
|
||||
// 高压母线(0x0005 反馈 / 0x0001 指令)
|
||||
{
|
||||
const hv = d.hv || {};
|
||||
const rows = [
|
||||
['DC/DC5故障字低', hex(hv.dcDcFaultWord1)],
|
||||
['DC/DC5故障字高', hex(hv.dcDcFaultWord2)],
|
||||
['动力母排绝缘状态', insStatText(hv.powerBusInsulationStatus)],
|
||||
['A汇流排绝缘状态', insStatText(hv.aBusInsulationStatus)],
|
||||
['仪表电源状态', sigText(hv.meterPowerLossSignal)],
|
||||
['应急电源状态', sigText(hv.emergencyPowerLossSignal)],
|
||||
['DC/DC温度', hv.dcDcTemperature],
|
||||
['动力汇流排电压', hv.powerBusVoltage],
|
||||
['DC/DC5模块电流', hv.dcDc5ModuleCurrent],
|
||||
['动力汇流排电流', hv.powerBusCurrent],
|
||||
['A汇流排电压', hv.busbarAVoltage],
|
||||
['推进电机控制箱电流', hv.propulsionMotorControlBoxCurrent],
|
||||
['A汇流排电流', hv.busbarACurrent],
|
||||
['锂电池组(仪表)电流', hv.lithiumBatteryGroupMeterCurrent],
|
||||
['艏部高压配电箱电流', hv.bowHighVoltageDistributionBoxCurrent],
|
||||
['艉部FT装置4/5电流', hv.sternFTDevice45Current],
|
||||
['TYZ预留开关电流', hv.tyzReservedSwitchCurrent],
|
||||
['艉舵开关1电流', hv.sternRudderSwitch1Current],
|
||||
['预留电流1', hv.reservedCurrent1],
|
||||
['艉舵开关2电流', hv.sternRudderSwitch2Current],
|
||||
['预留电流2', hv.reservedCurrent2],
|
||||
['FB预留电流', hv.fbReservedSwitchCurrent],
|
||||
['预留电流3', hv.reservedCurrent3],
|
||||
['冷却水压力', hv.coolWaterPressure],
|
||||
];
|
||||
h+=busCard('高压母线 (0x0005 反馈 / 0x0001 指令)', hv, d.hvAge, rows);
|
||||
}
|
||||
|
||||
// 高压母线A(0x0006 反馈 / 0x0002 指令)
|
||||
{
|
||||
const b = d.hva || {};
|
||||
const rows = [
|
||||
['仪表电源失电信号', sigText(b.instrumentPowerFailureSignal)],
|
||||
['应急电源失电信号', sigText(b.emergencyPowerFailureSignal)],
|
||||
['浸水报警', sigText(b.waterIngressionAlarm)],
|
||||
['汇流排B电压', b.busbarBVoltage],
|
||||
['汇流排B电流', b.busbarBCurrent],
|
||||
['艏部FT装置1/2/3电流', b.bowFTDevice123Current],
|
||||
['启闭机构电流', b.actuatorCurrent],
|
||||
['桅杆舵机控制箱电流', b.mastSteeringGearControlBoxCurrent],
|
||||
['XCZ电流', b.xczCurrent],
|
||||
['艏舵控制箱电流', b.bowRudderControlBoxCurrent],
|
||||
['敞水盖启动电缸电流', b.openWaterCoverStartCylinderCurrent],
|
||||
];
|
||||
h+=busCard('高压母线A (0x0006 反馈 / 0x0002 指令)', b, d.hvaAge, rows);
|
||||
}
|
||||
|
||||
// 低压主母线(0x0007 反馈 / 0x0003 指令)
|
||||
{
|
||||
const b = d.hvb || {};
|
||||
const rows = [
|
||||
['仪表电源失电信号', sigText(b.instrumentPowerFailureSignal)],
|
||||
['应急电源失电信号', sigText(b.emergencyPowerFailureSignal)],
|
||||
['仪表汇流排绝缘', insStatText(b.instrumentBusbarInsulationLow)],
|
||||
['仪表母排电压', b.instrumentBusbarVoltage],
|
||||
['艏部低压配电箱电流', b.bowLowVoltageDistributionBoxCurrent],
|
||||
['DC-C1仪表DC48V电流', b.dcC1InstrumentDC48VCurrent],
|
||||
['预留电流1', b.reservedCurrent1],
|
||||
['应急锂电池组2电流', b.emergencyLithiumBatteryGroup2Current],
|
||||
['锂电池组(仪表)电流', b.lithiumBatteryGroupInstrumentCurrent],
|
||||
['UNIT4仪表DC48V电流', b.unit4InstrumentDC48VCurrent],
|
||||
['预留电流2', b.reservedCurrent2],
|
||||
['应急2汇流排电压', b.emergency2BusbarVoltage],
|
||||
['复合能源应急DC48V电流', b.compositeEnergyEmergencyDC48VCurrent],
|
||||
['燃料电池安全应急DC48V电流', b.fuelCellSecurityEmergencyDC48VCurrent],
|
||||
['PLC控制电源电流', b.plcControlPowerCurrent],
|
||||
];
|
||||
h+=busCard('低压主母线 (0x0007 反馈 / 0x0003 指令)', b, d.hvbAge, rows);
|
||||
}
|
||||
|
||||
// 仪表汇流排(0x0008 反馈 / 0x0004 指令)
|
||||
{
|
||||
const b = d.lv || {};
|
||||
const rows = [
|
||||
['仪表电源失电信号', sigText(b.powerFailureSignal)],
|
||||
['应急电源失电信号', sigText(b.emergencyPowerFailureSignal)],
|
||||
['浸水报警', sigText(b.waterIngressionAlarm)],
|
||||
['仪表母排电压', b.instrumentBusbarVoltage],
|
||||
['仪表母排电流', b.instrumentBusbarCurrent],
|
||||
['UNIT1电流', b.unit1Current],
|
||||
['UNIT2电流', b.unit2Current],
|
||||
['UNIT3电流', b.unit3Current],
|
||||
['UNIT5电流', b.unit5Current],
|
||||
['艏部PZ装置电流', b.bowPZDeviceCurrent],
|
||||
['应急锂电池组1电流', b.emergencyLithiumBatteryGroup1Current],
|
||||
['应急1汇流排电压', b.emergency1BusbarVoltage],
|
||||
];
|
||||
h+=busCard('仪表汇流排 (0x0008 反馈 / 0x0004 指令)', b, d.lvAge, rows);
|
||||
}
|
||||
|
||||
// 最近下发指令卡片
|
||||
{
|
||||
const lc = d.lastCmd || {};
|
||||
let c='';
|
||||
c+=row('最后指令时间', ageCell(lc.age, lc.valid));
|
||||
const cmds = lc.cmds || {};
|
||||
const groups = [['hv','高压母线'],['hva','高压母线A'],['hvb','低压主母线'],['lv','仪表汇流排']];
|
||||
for(const g of groups){
|
||||
const gb = cmds[g[0]] || {};
|
||||
const keys = Object.keys(gb);
|
||||
if(!keys.length) continue;
|
||||
c+=sub(g[1]+' (0x'+({hv:'0001',hva:'0002',hvb:'0003',lv:'0004'})[g[0]]+')');
|
||||
for(const k of keys) c+=row(bNamesDis[k]||k, brkText(gb[k]));
|
||||
}
|
||||
h+=card('最近下发的配电指令 (PM→真实CCU)', c);
|
||||
}
|
||||
|
||||
h+='</div>';
|
||||
return h;
|
||||
}
|
||||
|
||||
//------------------ 标签页头部 ------------------
|
||||
function pageHeader(id, name, dir){
|
||||
const idHex = '0x'+id.toString(16).toUpperCase().padStart(4,'0');
|
||||
@@ -559,6 +773,7 @@ function render(snap){
|
||||
pmStatusCard(snap.pmStatus)+batteryCard(snap.pmStatus)+emergCard(snap.pmStatus)+'</div>',
|
||||
pmFb: pageHeader(0x0003,'PM参数反馈','CCU→PM')+'<div class="grid">'+pmFbCard(snap.pmFb)+'</div>',
|
||||
links: '<div class="grid">'+linkCard(snap)+'</div>',
|
||||
realCcu: realCcuPage(snap.realCcu),
|
||||
raw: rawCard(snap.logs)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user