在网上搜罗了一大圈,只找到了一个大佬用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*)¤tID)[1];//((char*)¤t_order_id)[0];
telegramData[12] = ((char*)¤tID)[0]; //((char*)¤t_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 词语翻译为路由,这个“路由”让人很难理解,应该是机翻的,我认为应该翻译为 “路径”、“路线”之类的要好一点