工商业储能系列: BMS分散式主动均衡详解
2026/6/13 15:52:54
目标:用5-6小时掌握表达式解析、寄存器替换、数学计算的核心流程,理解原始数据如何转化为最终曲线数值。
理解如何将用户输入的表达式{40001}+{40002*2}转换为可计算的数学表达式。
从场景出发:理解表达式格式
{40001[@1][:f32b]} + {40002[@2][:32b]} * 2ExpressionParser类的_cRegisterFunctionTemplate,理解目标是将{...}替换为r(索引)分析ExpressionParser类结构
expressionparser.h和expressionparser.cppExpressionParser类的"数据成员"部分QStringList _processedExpressions;// 处理后的表达式列表QList<ModbusRegister>_registerList;// 解析出的寄存器列表QRegularExpression _findRegRegex;// 查找寄存器表达式的正则QRegularExpression _regParseRegex;// 解析单个寄存器的正则查看正则表达式定义
expressionregex.h(如果存在)或在代码中搜索cMatchRegister、cParseReg{40001[@1][:f32b]}或{h0[@1][:f32b]}[@N]和数据类型[:type]深入解析算法
ExpressionParser::processExpression函数1.使用_findRegRegex查找所有{...}模式2.对每个匹配,使用_regParseRegex进一步解析3.解析结果:地址、连接ID、数据类型4.创建ModbusRegister临时对象5.检查是否已存在于_registerList,不存在则添加6.获取寄存器在列表中的索引7.用r(索引)替换原表达式中的{...}// 在processExpression函数中添加qDebug()<<"原始表达式:"<<graphExpr;qDebug()<<"匹配到的寄存器:"<<match.captured();qDebug()<<"解析后的寄存器对象:"<<modbusRegister;qDebug()<<"替换为:"<<QString("r(%1)").arg(regIdx);测试不同表达式格式
QStringList testExpressions={"{40001}",// 简单地址"{40001} + {40002}",// 两个寄存器相加"{h0[@1][:f32b]}",// 助记符地址,连接1,浮点数"{30001[@2][:s16b] * 0.1}",// 带乘法的表达式"sin({40001}) + {40002}"// 使用数学函数};ExpressionParser的解析结果理解寄存器索引映射
_registerList中一次{40001} + {40001}中的两个{40001}会被映射到同一个索引ExpressionParser将{...}格式替换为r(索引)的完整流程{40001[@1]} + {40002[@1]} * {40001[@1]}会被解析成几个不同的寄存器?替换后的表达式是什么?掌握表达式如何从字符串变为实际数值的计算过程,理解数据流如何衔接。
理解GraphDataHandler的桥梁作用
graphdatahandler.h和graphdatahandler.cppGraphDataHandler类部分,理解它的三个关键容器:QList<ModbusRegister>_registerList;// 寄存器列表QList<quint16>_registerIndexList;// 寄存器索引列表(可能已弃用或笔记有误)QList<QMuParser>_expressionParserList;// 表达式解析器列表_registerIndexList可能不存在。以实际代码为准。分析数据处理流程
GraphDataHandler::processActiveRegisters函数GraphDataModel获取激活图形的表达式ExpressionParser实例,解析表达式QMuParser对象深入QMuParser计算核心
qmuparser.h和qmuparser.cppQMuParser类部分staticQList<Result<double>>_registerValues;// 所有解析器共享的寄存器值分析计算回调机制
QMuParser构造函数和mu::ParserRegister::setRegisterCallback1.QMuParser构造函数设置回调函数为registerValue2.registerValue通过索引从静态_registerValues获取值3.mu::ParserRegister在计算表达式时调用此回调// 伪代码示意voidregisterValue(intidx,double*val,bool*ok){if(idx>=0&&idx<_registerValues.size()){*val=_registerValues[idx].value();*ok=_registerValues[idx].isValid();}}跟踪实时计算流程
GraphDataHandler::handleRegisterData函数1.接收来自RegisterValueHandler的原始寄存器值2.调用QMuParser::setRegistersData更新静态寄存器值3.遍历_expressionParserList中的每个QMuParser4.调用evaluate()计算表达式结果5.收集所有结果,发出graphDataReady信号理解mu::ParserRegister的扩展功能
muparserregister.h和muparserregister.cppmu::ParserRegister继承自mu::ParserBaseSetExpr设置表达式,Eval计算表达式动手实验:观察表达式计算
{40001} + {40002}sin({40001} * 3.14159 / 180){40001} > 100 ? {40001} : 0QMuParser::evaluate设置断点,查看不同表达式的计算过程设置完整的断点链
// 从接收到数据到计算出结果GraphDataHandler::handleRegisterDataQMuParser::setRegistersData(静态方法)QMuParser::evaluate mu::ParserRegister::Eval(第三方库)GraphDataHandler::graphDataReady(信号发射处)创建测试场景
{40001} + {40002} * 2调试观察
_registerValues静态成员的变化registerValue被调用的次数和参数异常情况测试
{40001} +(不完整表达式),观察错误处理核心概念掌握:
表达式解析双阶段:
ExpressionParser将用户友好语法转换为机器友好语法QMuParser(基于muParser)执行数学计算数据流清晰分离:
GraphDataHandler负责接收和分发QMuParser实例_registerValues确保所有表达式使用相同数据扩展性设计:
典型问题解答:
Q: 如果表达式包含10个{40001}引用,这个寄存器会被读取几次?
A: 只读取1次。ExpressionParser会去重,所有引用指向同一个寄存器索引。
Q: 表达式计算是同步还是异步的?
A: 在handleRegisterData中是同步计算的,但这个过程很快,不会阻塞UI。
Q: 如何添加自定义函数?
A: 可以扩展mu::ParserRegister,添加新的函数定义。
实际应用思考:
明日预告:第6天将进入数据模型层(GraphDataModel),学习数据如何存储、组织,以及模型如何与视图交互。这是连接数据处理和图形显示的关键桥梁。
建议行动:晚上可以尝试修改一个简单表达式,观察软件行为变化,巩固今天所学。