Bootstrap

【opentcs】模拟真实车辆和opentcs通信的简单ui界面,opentcs与实物车辆对接步骤一

在网上搜罗了一大圈,只找到了一个大佬用java写的车辆模拟界面,并且链接已经失效且已多年未更新。原始的loopback仿真又没有真实展现通信协议以及相关的技术,仿真了一圈还是摸不着头脑,然后在琢磨了opentcs官方的源代码,对比了loopback、intergration代码的代码逻辑以及相关联系之后,尝试用qt写了一个代替真实车辆的界面,方便在没有实物车辆的通信调试。由于loopback那个源 车辆的仿真代码就是集成在loopbackcommunicationadapter中的,并未涉及到通信协议,因此无法使用这个代码,源代码使用的是官方的intergration项目。

先附上我这个ui界面的主要代码(tcpserver、tcpsocket代码是别人封装好了的,拿来就用,整个源代码我把下载地址放下面,我使用qt5.14.1做的):

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    Server = new TcpServer(this);
    tcpSocket = new QTcpSocket;
    //有数据
    connect(Server,SIGNAL(sig_hasData(qintptr,QString,int,QByteArray)),
            this,SLOT(slot_updateData(qintptr,QString,int,QByteArray)));
}

MainWindow::~MainWindow()
{
    delete ui;
}
#include<QComboBox>
#include<QLineEdit>
//=============接收数据===============================================================
#include<QMessageBox>
void MainWindow::slot_updateData(qintptr serverSocketID,QString IP,int Port,QByteArray data)
{
    QString DATA;
    for(int i=0;i<data.size();i++)
    {
        DATA+=QString::number((unsigned char)data.at(i),16).toUpper().rightJustified(2,QChar('0'))+" ";
    }
   // QString str=QString("socket: %1,ip: %2,port:%3 收到->:   %4\n").arg(serverSocketID).arg(IP).arg(Port).arg(DATA);
    QString str=QString("\n接收原始数据: %1").arg(DATA);
    ui->plainTextEdit_show->appendPlainText(str);
    uint8_t telegramData[17]={0};
    int dataSize=0;
    if(data.at(2)==0x01)//state
    {
        //发来的 数据解析
        const unsigned char * p= (const unsigned char *)data.constData();
        // Start of each telegram
        uint16_t count =  ((uint16_t)p[3])<<8 | p[4]; //计数
        //数据发送
        dataSize=17;
        telegramData[0] = 0x02;
        telegramData[1] = 0x0d;//0D
        telegramData[2] = 0x01;//01
        // set telegram counter
        telegramData[3] = p[3];//((char*)&count)[0];
        telegramData[4] = p[4];//((char*)&count)[1];
        // set pos id
        uint16_t curPos=ui->lineEditPositionID->text().toInt() ; //当前位置 手动切换
        telegramData[5] = ((char*)&curPos)[1];
        telegramData[6] = ((char*)&curPos)[0];
        // set op mode
        //         case 'A': ACTING;
        //         case 'I': IDLE;
        //         case 'M': MOVING;
        //         case 'E': ERROR;
        //         case 'C': CHARGING;
        //         default:  UNKNOWN;
        char operMode=ui->comboBoxMode->currentText().toUtf8().at(0);// 操作模式?  M=executing
        telegramData[7] =operMode; // op_mode;
        // set load state
        //         case 'E' EMPTY;
        //         case 'F' FULL;
        //         default:  UNKNOWN;
        char load=ui->comboBoxLoad->currentText().toUtf8().at(0);// 负载 手动切换
        telegramData[8] = load; //load_state;
        // set last received order id
        uint16_t last_recv_order_id=ui->lineEditRecvedOrderID->text().toUInt();//lastReceivedOrder
        telegramData[9] = ((char*)&last_recv_order_id)[1];//高低字节位置不同
        telegramData[10] = ((char*)&last_recv_order_id)[0];
        // set current order id
        uint16_t currentID=ui->lineEditOrderID->text().toUInt();//订单id,收到订单ID时自动填充
        telegramData[11] = ((char*)&currentID)[1];//((char*)&current_order_id)[0];
        telegramData[12] = ((char*)&currentID)[0]; //((char*)&current_order_id)[1];
        // set last finished order id
        uint16_t lastfinished_order_id=ui->lineEditFinishedOrderID->text().toUInt();//lastFinishedOrder
        telegramData[13] = ((char*)&lastfinished_order_id)[1];//高低字节位置不同
        telegramData[14] = ((char*)&lastfinished_order_id)[0];
        // set checksum
        telegramData[15] = checkSum(telegramData);
        telegramData[16] = 0x03; //03
        QString ss=QString(" 收到状态请求:counter=%1。\n"
                           " 发出数据解析:counter=%2,当前位置=%3,操作=%4,负载=%5,收到订单=%6,当前订单=%7,完成订单=%8 ")
                            .arg(count).arg(count).arg(curPos).arg(operMode)
                .arg(load).arg(last_recv_order_id).arg(currentID).arg(lastfinished_order_id);
        ui->plainTextEdit_show->appendPlainText(ss);
    }
    else if(data.at(2)==0x02)//order
    {
        //发来的 数据解析
        const unsigned char * p= (const unsigned char *)data.constData();
        ui->comboBoxMode->setCurrentText("M");//设置为executing 。A 这个先不管

        // Start of each telegram
        uint16_t count = (uint16_t)p[3]<<8 | p[4]; //计数   高低字节位置不同
        uint16_t orderID=(uint16_t)p[5]<<8 | p[6]; //order id   高低字节位置不同
        uint16_t destinationID=(uint16_t)p[7]<<8 | p[8];

        static int init=0;
        if(!init)
        {//第一次配置的时候,将最后收到的订单设置为当前订单
            init=1;
            ui->lineEditOrderID->setText(QString::number(orderID));//设置当前订单
            ui->lineEditDestinationID->setText(QString::number(destinationID));//destination id 配置目的地
        }
        //设置最后收到的订单
        ui->lineEditRecvedOrderID->setText(QString::number(orderID));//设置控件,作为数据保存
        ui->lineEditRecvedDestinationID->setText(QString::number(destinationID));//设置,作为数据保存
        /*N none;L load;U unload; C Charge   */
        char destinationAction=p[9];//到了目的地的动作,这个先不管
        //数据发送
        dataSize=9;
        telegramData[0] = 0x02;//02
        telegramData[1] = 0x05;//05
        telegramData[2] = 0x02;//02
        // set telegram counter
        telegramData[3] = p[3];
        telegramData[4] = p[4];
        // set last received order id  最后收到的订单
        telegramData[5] =  ((char*)&orderID)[1];//p[5];
        telegramData[6] =  ((char*)&orderID)[0];//p[6];
        // set checksum
        telegramData[7] = checkSum(telegramData);
        telegramData[8] = 0x03;//03

        QString ss=QString(" 收到订单请求:counter=%1,订单=%2,目的地ID=%3,目的地动作=%4,\n"
                           " 发出数据解析:counter=%5,最后收到的订单=%6 ").arg(count).arg(orderID).arg(destinationID).arg(destinationAction)
                .arg(count).arg(orderID);
        ui->plainTextEdit_show->appendPlainText(ss);
    }
    else
    {
        QMessageBox::critical(this,"错误","收到了错误的数据帧");
        return;
    }

    DATA.clear();
    QByteArray datatosend;
    for(int i=0;i<dataSize;i++)
    {
        DATA+=QString::number(telegramData[i],16).toUpper().rightJustified(2,QChar('0'))+" ";
    }
    str=QString("发送原始数据:%1").arg(DATA);
    ui->plainTextEdit_show->appendPlainText(str);
    datatosend.append((char *)telegramData,dataSize);
   // datatosend=DATA.toUtf8();
    m_data=datatosend;
    BroadCast(datatosend);
}

uint8_t MainWindow::checkSum(uint8_t *buf)
{
    uint8_t cs = 0;
    for (int i = 0; i < buf[1]; i++) {
        cs ^= buf[2 + i];
    }
    return  cs;
}
//============广播信息=========================================================
void MainWindow::BroadCast(QByteArray data)
{
    QMap <int,TcpSocket *>::const_iterator Device;
    for(Device=Server->IDSocketMap.constBegin();Device!=Server->IDSocketMap.constEnd();++Device)
    {
        Device.value()->write(data);
    }
}
//=====================================================================
void MainWindow::on_pushButton_open_clicked()   //启动服务器
{
    ui->pushButton_open->setEnabled(false);
    ui->pushButton_close->setEnabled(true);
    int Port = 2000;
    Server->listen(QHostAddress::Any,Port);//监听客户端连接
    ui->label_IP->setText("192.168.43.163");
    ui->label_Port->setText(QString::number(Port));
    ui->label_status->setText("运行中!");
}

void MainWindow::on_pushButton_close_clicked()  //关闭服务器
{
    Server->close();
    ui->pushButton_close->setEnabled(false);
    ui->pushButton_open->setEnabled(true);
    ui->label_status->setText("关闭!");
    ui->label_IP->setText("");
    ui->label_Port->setText("");
 //   ui->plainTextEdit_show->clear();
}

void MainWindow::on_pushButton_clear_clicked()  //清空信息栏
{
    ui->plainTextEdit_show->clear();
}

//判断 目的地是否到,到位就


void MainWindow::on_lineEditPositionID_textChanged(const QString &arg1)
{
    if(arg1== ui->lineEditDestinationID->text())
    {//判断到目的地了
        //将当前订单设置到已完成订单
        ui->lineEditFinishedOrderID->setText(ui->lineEditOrderID->text());
        ui->lineEditFinishedDestinationID->setText(arg1);
        //当前订单改变为最后收到的信息
        QString recvedPosition=ui->lineEditRecvedDestinationID->text();
        ui->lineEditOrderID->setText(ui->lineEditRecvedOrderID->text());//设置当前订单
        ui->lineEditDestinationID->setText(recvedPosition);//destination id 配置目的地
       //状态改变
        if(arg1==recvedPosition)
        {
            ui->comboBoxMode->setCurrentText("I");
        }
    }
}

上面实现了对接opentcs-integration-example涉及到的 TCP服务端,解析订单进行判断以及回应,以及简单的车辆运行逻辑,人为修改车辆地址实现车行走,其他acting等无关紧要。
使用操作步骤:
一、下载编译opentcs,

	https://github.com/openTCS/opentcs-integration-example

我下载时的版本是5.7.0,我打包的资料里面准备了一份,避免版本不同导致的异常。
编译完成的目录:opentcs-integration-example-master/build/install/openTCS-Example/ (记得给权限)。
二、分别在相应文件夹中运行: startKernel.sh、startKernelControlCenter.sh、startModelEditor.sh、startOperationsDesk.sh
在这里插入图片描述
在ModelEditor中,加载工厂文件useforowncar.xml(你们下载下来还是要修改vehicle的ip地址)
该文件做了两个修改:1、ModelEditor中修改vehicle 1的ip地址:
在这里插入图片描述
ip地址改为运行qt仿真车界面运行的电脑的地址(我ubuntu在虚拟机中运行,opentcs运行在里面,ip192.168.43.44,;电脑用的win10,仿真车界面在win10运行,ip192.168.43.163),这个要根据你实际的ip地址更改,要是都运行在同一系统下,就127.0.0.1。port我没变是2000。
2、修改每个点位名称:
在这里插入图片描述
修改后:在这里插入图片描述
(其实就是Demo-01.xml中每个point点位名称本来是point-001改为1这样,因为intergration项目中并没有主动由(uint16_t )1 转为字符串 point-001,所有的点位都是在 uint16_t 下使用,非字符串)。

这里修改完毕可以做一个保存,
之后图纸上传到内核:
在这里插入图片描述
再在工厂界面中修改vehicle 1 集成属性:
在这里插入图片描述

三、编译运行仿真车界面:
在这里插入图片描述
第一步先修改这个port为需要的,默认vehicle 1 是连接在port 2000的。 ip地址为该界面运行系统下的ip地址,cmd窗口自己查,这里只做显示用,同样的,上一步骤的vehicle 1 的ip地址也要改成和这个一样才行。
在这里插入图片描述

上面是运行之后的界面,点击开启服务。并在刚才打开的 opentcs 的控制中心中使能vehicle 1 。如果没有问题则分别显示如下界面;
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

最后,即可以给vehicle分配订单:
在这里插入图片描述
在这里插入图片描述
上面opentcs已经规划好路线,接下来我们就操作车的位置,如下:

在这里插入图片描述

手动修改 当前位置ID 为当前目的地 ,例如上面截图 当前位置ID 改为3 后的结果:

在这里插入图片描述
在这里插入图片描述
按步骤整就完成整个仿真。

如果中途遇到异常,则全部重启,因为还有一些bug没来得及优化,等到实物架设的时候再说。

资料下载链接:

 https://download.csdn.net/download/qq_20826539/88099494

最后我想提及一点,我看网上很多地方都把opentcs中涉及到的 Route 词语翻译为路由,这个“路由”让人很难理解,应该是机翻的,我认为应该翻译为 “路径”、“路线”之类的要好一点

;