基于QT的电动汽车充电协议解析工具

目录

电动汽车充电协议概括
工具设计及与实现
工具运行示例
 生成.exe文件

电动汽车充电协议概括

        电动汽车电动车非车载传导式充电机(以下简称充电机),电池管理系统(Battery Management System,以下简称BMS)之间基于控制局域网(CAN)之间的通信协议。

        在充电过程中,充电机与BMS监测电压、电流和温度等参数,同时BMS根据充电控制算法管理整个充电过程;充电机与BMS之间的CAN通信网络应由充电机和BMS两个节点组成;国标中规定数据传输采用地位先发送的格式。正的电流值代表放电,负的电流值代表充电;物理层应符合ISO 11898-1:2003、SAE J1939-11:2006中之规定;充电机与BMS的通信应使用独立于动力总成控制系统之外的CAN接口。充电机与BMS之间的通信速率可选用50kbit/s、125kbit/s、250kbit/s,推荐采用250kbit/s。

整个充电过程包括四个阶段:充电握手阶段、充电参数配置阶段、充电阶段和充电结束阶段。在各个阶段,充电机和BMS如果在规定的时间内没有收到对方的报文或没有收到正确的报文,即判定为超时,超时时间除特殊规定外,均为5s,当出现超时后,BMS或充电机发送错误报文,并进入错误处理状态。

具体电动汽车充电协议概括请跳转:https://zhuanlan.zhihu.com/p/374943265


工具设计及与实现

UI界面设计

部分代码实现
串口相关代码
//*************************************************串口************************************************
//扫描串口按钮回调函数
void USBCAN::scanport()
{
    QStringList newPortStringList;
    newPortStringList.clear();
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
        newPortStringList += info.portName();
    if(newPortStringList.size() != portStringLine.size())
    {
        portStringLine = newPortStringList;
        ui->PortBox->clear();
        ui->PortBox->addItems(portStringLine);
    }
}

//打开串口并验证
bool USBCAN::openCom(QSerialPort *myCom, QString &myportName, qint32 baudRate)
{
    myCom->setPortName(myportName);
    //验证串口是否打开
    if(! myCom->open(QIODevice::ReadWrite))
    {
        myCom->close();
        return false;
    }

    //设置波特率
    myCom->setBaudRate(baudRate);
    //设置数据流控制
    myCom->setFlowControl(QSerialPort::NoFlowControl);

    return true;
}

//打开端口控件
void USBCAN::on_OpenPort_Button_clicked()
{
    //串口按钮显示 “打开串口”
    if(ui->OpenPort_Button->text()==tr("打开串口"))
    {
        //先打开串口
        QString portName = ui->PortBox->currentText();
        QString baudRate = ui->BaudRateBox->currentText();
        //串口打开失败
        if(!openCom(Port_Box, portName, ui->BaudRateBox->currentText().toInt()))
        {
            ui->remindBrowser->append("串口打开失败");
            if(Port_Box->isOpen())
            {
                Port_Box->close();
            }
            return;
        }
        //串口打开成功
        ui->BaudRateBox->setCurrentText(baudRate);
        ui->OpenPort_Button->setText(tr("关闭串口"));
        ui->PortBox->setEnabled(false);
        ui->BaudRateBox->setEnabled(false);
    }
    //串口按钮显示 “关闭串口”
    else
    {
        ui->PortBox->setEnabled(true);
        ui->BaudRateBox->setEnabled(true);
        ui->OpenPort_Button->setText(tr("打开串口"));
        //关闭串口
        if(Port_Box->isOpen())
        {
            Port_Box->close();
        }
    }
}

//串口数据接收函数 -- 接收到就会触发函数
void USBCAN::PortBox_rece()
{

    //添加读取的数据到COMrecebuf中
    COMrecebuf.append(Port_Box->readAll());

    // 如果定时器没有在运行,则启动定时器
    if (!myTimer->isActive()) {
        myTimer->start(50); // 可自行设置定时器间隔
    }
}

//串口读写超时定时处理
void USBCAN::TimerUpdate_COM()
{
    //定时器结束
    myTimer->stop();

    if(COMrecebuf.length()!=0)
    {
        //有数据则显示
        show_receive_Message(COMrecebuf);
        //qDebug() << "show_Msg";
        int COMrecebuf_size = COMrecebuf.size();
        //处理接收的信息
        deal_receive_Message(COMrecebuf, COMrecebuf_size);
    }
    //清空COMrecebuf
    COMrecebuf.clear();
}

//显示信息 -- 调用时会显示时间
void USBCAN::show_receive_Message(QByteArray str)
{
    //在字符中间加空格
    int i = 0;
    QString str2;
    QByteArray display_array = str.toHex().toUpper();
    for(i = 0; i < display_array.size(); i++)
    {
        str2 += display_array[i];
        if(i % 2 == 1)
        {
            str2 += ' ';
        }
    }

    //获取系统现在的时间
    QDateTime time = QDateTime::currentDateTime();
    //设置显示格式
    QString timeType = time.toString("hh:mm:ss: ");
    if(str  == COMrecebuf)
    {
        ui->remindBrowser->append(timeType + str2);
        ui->remindBrowser->append("");//换行

    }
    else
    {
    }

    display_array.clear();
}

//处理接收的信息
void USBCAN::deal_receive_Message(QByteArray str, int COMrecebuf_size)
{
    int i = 0;
    int idx = 0;


    //如果协议头不正确 那么退出
    while(1)//$$
    {
        if(++idx > COMrecebuf_size)return;
        //查询到数据头
        if((unsigned char)str[i] == 0xAA)
        {
            i += 1;
            break;
        }
        i++;
    }
    unsigned char data =(unsigned char)str[i] ;
    QString binaryString = hex2Bin(&data, 1);
    //数据传输方向
    DateTransfer="接收";
    //数据长度
    int Len=  binaryString.right(4).toInt(nullptr, 2);//数据长度
    USBCAN_FrameLen=QString::number(Len);
    //判断帧类型,0:为标准帧   1:为扩展帧
    if (binaryString.at(2) == QLatin1Char('0'))
    {
        USBCAN_Frametype="标准帧";
        //帧ID
        USBCAN_FrameID=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 2), 2);
        USBCAN_FrameID=reversed(USBCAN_FrameID);
        USBCAN_FrameID.remove(QRegExp("\\s"));//删除空格
        //提取USBCAN_FrameID数据
        QString PacketStr = USBCAN_FrameID.mid(2, 2);
        //16进制数据
        USBCAN_U16date=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 4), Len);
        PacketCode="帧类型为 标准帧   无法解析";
        AnalyseDate=QString("帧类型为 标准帧   无法解析");//显示
    }
    else
    {
        USBCAN_Frametype="扩展帧";
        //帧ID
        USBCAN_FrameID=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 2), 4);
        USBCAN_FrameID=reversed(USBCAN_FrameID);
        USBCAN_FrameID.remove(QRegExp("\\s"));//删除空格
        //提取USBCAN_FrameID数据
        QString PacketStr = USBCAN_FrameID.mid(2, 2);
        //判断设备传输方向
        if(USBCAN_FrameID.mid(4, 4).toInt(nullptr, 16)==0x56f4)
            DeviceTransfer="BMS----充电机";
        else if(USBCAN_FrameID.mid(4, 4).toInt(nullptr, 16)==0xf456)
            DeviceTransfer="充电机----BMS";
        else
            DeviceTransfer="无法判断设备传输方向";
        //16进制数据
        USBCAN_U16date=hex2Str(reinterpret_cast<unsigned char*>(str.data() + 6), Len);
        DeterminePacketCode(PacketStr,USBCAN_U16date);//解析接收到的数据
    }
    //判断帧格式 0:数据帧  1:
    if (binaryString.at(3) == QLatin1Char('0'))
    {
        USBCAN_Frameformat="数据帧";
    }
    else
    {
        USBCAN_Frameformat="远程帧";
    }
    i++;

    if((unsigned char)str[COMrecebuf_size-1] == 0x55)
    {
        QModelIndex index = ui->tableView->model()->index(RecvCnt, 0); // 获取指定行的索引
        ui->tableView->scrollTo(index); // 滚动到指定行
        ShowAndWrite_Date_to_Excle();
    }
    RecvCnt+=1;
}

//显示信息到表格
void USBCAN::ShowAndWrite_Date_to_Excle()
{
    QList<QStandardItem *> Rowtext;
    QString index=QString::number(RecvCnt+1);
    Show_Cell(index,RecvCnt,0);//显示序号
    QString direct=DateTransfer; //接收
    Show_Cell(direct,RecvCnt,1);//显示传输方向
    QString systime=QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); //系统时间
    Show_Cell(systime,RecvCnt,2);//显示系统时间
    QString format=USBCAN_Frameformat; //帧格式
    Show_Cell(format,RecvCnt,3);//显示帧格式
    QString type=USBCAN_Frametype;  //帧类型
    Show_Cell(type,RecvCnt,4);//显示帧类型
    QString FrameID=USBCAN_FrameID; //帧ID
    Show_Cell(("0x"+FrameID),RecvCnt,5);//显示帧ID
    QString datalen=USBCAN_FrameLen; //数据长度
    Show_Cell(datalen,RecvCnt,6);//数据长度
    QString DisplayData=USBCAN_U16date;
    Show_Cell(DisplayData,RecvCnt,7);//16进制数据
    QString directData=DeviceTransfer;
    Show_Cell(directData,RecvCnt,8);//16进制数据
    QString Packetdata=PacketCode; //报文代码
    Show_Cell(Packetdata,RecvCnt,9);//报文代码
    QString ANaldata=AnalyseDate;
    Show_Cell(ANaldata,RecvCnt,10);//解析数据
}

//********************************************************串口END***********************

表格相关代码
//*************************************表格相关*************************************
//创建文件
void USBCAN::New_file()
{
    QString folderPath = QCoreApplication::applicationDirPath() + "/log/";
    QDir logDir(folderPath);

    if (!logDir.exists() && !logDir.mkpath("."))
    {
        QMessageBox::warning(this, "警告", "创建文件夹" + folderPath + "失败");
        return;
    }

    filename = folderPath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".txt";
    file = new QFile(filename);

    if (!file->open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QMessageBox::warning(this, "警告", "创建文件" + filename + "失败");
        return;
    }
}
//判断是否勾选自动保存
void USBCAN::on_radioButton_clicked(bool checked)
{
    if(checked==true)
    {
        New_file();
        //扫描串口及设置其定时器
        File_timer = new QTimer();
        // 设置定时器1秒钟超时
        connect(File_timer, SIGNAL(timeout()), this, SLOT(SaveData2File()));
        File_timer->start(30);
    }
    else
    {
    }
}
//清除数据
void USBCAN::on_ClearTable_Button_clicked()
{
    // 清除整个模型数据
    model->clear();
    //表头都可视
    ui->tableView->horizontalHeader()->setVisible(1);
    ui->tableView->verticalHeader()->setVisible(false);
    model = new QStandardItemModel(USBCAN_line,USBCAN_COL);//定义表格行列数

    labels = QObject::trUtf8("序号,传输方向,时间标识,帧格式,帧类型,帧ID,数据长度,数据(16进制),设备传输方向,报文代码,数据解析").simplified().split(",");
    model->setHorizontalHeaderLabels(labels);//给model设置表头

    //QTableView设置QStandardItemModel
    ui->tableView->setModel(model);

    //设置表格字体格式
    ui->tableView->horizontalHeader()->setStyleSheet("QHeaderView::section {"
                               "color: black;padding-left: 4px;border: 1px solid #6c6c6c;}");

    //表头宽度
    ui->tableView->setColumnWidth(0,40);//序号
    ui->tableView->setColumnWidth(1,80);//传输方向
    ui->tableView->setColumnWidth(2,160);//时间标识
    ui->tableView->setColumnWidth(3,80);//帧格式
    ui->tableView->setColumnWidth(4,80);//帧类型
    ui->tableView->setColumnWidth(5,120);//帧ID
    ui->tableView->setColumnWidth(6,80);//数据长度
    ui->tableView->setColumnWidth(7,200);//数据(16进制)
    ui->tableView->setColumnWidth(8,150);//设备传输方向
    ui->tableView->setColumnWidth(9,200);//报文代码
    ui->tableView->setColumnWidth(10,500);//数据解析

    RecvCnt = 0; // 接收计数清零
}

//将表格数据保存至文件夹
void USBCAN::SaveData2File(QFile *file)
{
    if (!file->exists())
    {
        QMessageBox::warning(this, "警告", "文件不存在");
        return;
    }
    else
    {
        QTextStream stream(file);

        QStandardItemModel *model = qobject_cast<QStandardItemModel *>(ui->tableView->model());

        if (!model)
        {
            qDebug() << "Error: The model of the table view is not a QStandardItemModel.";
            return;
        }

        // 轮询所有的行和列
        for (int row = 0; row < model->rowCount(); row++)
        {
            for (int col = 0; col < model->columnCount(); col++)
            {
                // 获取当前行和列总量
                QStandardItem *item = model->item(row, col);

                // 检查项目是否有效
                if (item)
                {
                    // 从项目中获取文本并将其写入文件
                    stream << item->text() << "\t"; // 可以选择任何隔离符号
                }
                else
                {
                    stream << "\t"; // 如果数据无效,则写入空字段
                }
            }

            // 移动到文件中每行之后的下一行
            stream << "\n";
        }
    }
}

//显示数据格 数据+行+列
void USBCAN::Show_Cell(QString str, int row, int col)
{
    model->setItem(row, col, new QStandardItem(str));
}

//保存文件
void USBCAN::on_SaveTable_Button_clicked()
{
    New_file();
    // 检查文件是否已经创建
    if (file && file->isOpen())
    {
       SaveData2File(file);
    }
    else
    {
       QMessageBox::warning(this, "警告", "文件未打开或无效");
    }
}

//************************************表格相关**************************************

CAN设置相关代码
//************************************CAN相关**************************************
//配置can参数
void USBCAN::on_pushButton_clicked()
{
    if(ui->OpenPort_Button->text()==tr("打开串口")){
        QMessageBox::warning(this, "警告", "请先打开串口");
    }
    else{
    //设置CAN协议类型
    setCANProtocolType();
    //设置CAN波特率
    setCANBaudRate();
    //s设置帧类型
    setFrameType();
    //设置CAN模式
    setCANMode();
    //计算校验值
    calculateChecksum();
    Port_Box->write(reinterpret_cast<const char*>(command), sizeof (command));}
}

//设置CAN协议类型
void USBCAN:: setCANProtocolType()
{
  //索引为1-6
  int intc = ui->ProtocolTypeBox->currentIndex();
  //设置协议类型   0:20字节   1:可变字节
  if(intc==0)
    command[2] = 0x02;
  else if(intc==1)
    command[2] =0x12;
  else
    command[2] =0x02;
}

//设置CAN波特率
void USBCAN:: setCANBaudRate()
{
  //索引为1-6
  int intc = ui->BaudRate_ComB->currentIndex();;
  QString baudRate = QString("%1").arg(intc+1, 2, 16, QLatin1Char('0'));//字符串前面加0,转为8位16进制
  command[3] = baudRate.mid(0, 2).toInt(nullptr, 16);//将波特率放入数组里
}
//设置帧类型
void USBCAN:: setFrameType()
{
    int intc = ui->FrameType_ComB->currentIndex();
    if(intc==0)
      command[4] = 0x01;
    else if(intc==1)
      command[4] =0x02;
    else
      command[4] =0x01;
}

//设置CAN模式
void USBCAN:: setCANMode()
{
    int intc = ui->WorkMode_ComB->currentIndex();
    if(intc==0)
      command[13] = 0x00;//正常模式
    else if(intc==1)
      command[13] =0x01;//静默模式
    else if(intc==2)
      command[13] =0x01;//环回模式
    else if(intc==3)
      command[13] =0x01;//静默环回模式
    else
      command[13] =0x01;
}

//计算校验码
void USBCAN:: calculateChecksum()
{
    uint16_t checksum = 0;
    int leng=sizeof (command);
    for (int i = 2;i < (leng - 1);i++)
    {
        checksum += command[i];
    }
    command[leng-1]= checksum & 0xFF;
}

//************************************CAN相关**************************************

工具运行示例图


生成.exe文件

先Windeployqt生成的.exe文件,最后利用Enigma Virtual Box解压打包,方便他人直接使用。

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用QT串口助手实现协议解析的步骤: 1.打开QT串口助手,连接串口设备。 2.在串口助手中设置协议解析规则,例如:以“#”作为帧头,“\r\n”作为帧尾,第1-2个字节表示数据长度,第3个字节表示命令类型,第4-5个字节表示数据内容。 3.在串口助手中接收到数据后,根据协议解析规则进行解析,提取出数据内容并进行处理。 4.将处理后的数据显示在串口助手的界面上或者发送给其他设备。 下面是一个简单的QT串口助手协议解析的示例代码: ```python # 导入必要的模块 from PyQt5.QtCore import QIODevice, QByteArray from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo # 定义协议解析规则 FRAME_HEAD = b'#' FRAME_TAIL = b'\r\n' DATA_LEN_INDEX = 0 CMD_TYPE_INDEX = 2 DATA_CONTENT_INDEX = 3 # 定义串口助手类 class SerialHelper: def __init__(self): self.serial = QSerialPort() self.serial.readyRead.connect(self.receive_data) # 打开串口 def open_serial(self, port_name, baud_rate): self.serial.setPortName(port_name) self.serial.setBaudRate(baud_rate) self.serial.setDataBits(QSerialPort.Data8) self.serial.setParity(QSerialPort.NoParity) self.serial.setStopBits(QSerialPort.OneStop) self.serial.setFlowControl(QSerialPort.NoFlowControl) if self.serial.open(QIODevice.ReadWrite): return True else: return False # 关闭串口 def close_serial(self): self.serial.close() # 发送数据 def send_data(self, data): self.serial.write(data) # 接收数据 def receive_data(self): data = self.serial.readAll() if data: self.parse_data(data) # 解析数据 def parse_data(self, data): frame_head_pos = data.indexOf(FRAME_HEAD) frame_tail_pos = data.indexOf(FRAME_TAIL) if frame_head_pos != -1 and frame_tail_pos != -1 and frame_tail_pos > frame_head_pos: data_len = data[DATA_LEN_INDEX:CMD_TYPE_INDEX] cmd_type = data[CMD_TYPE_INDEX] data_content = data[DATA_CONTENT_INDEX:frame_tail_pos] # 处理数据内容 print(data_content) ``` 相关问题:
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值