Blockly+Ros+Qt以Turtle为例,生成python代码,运行python代码以及实现blockly块的单步运行

概述

环境如下:

  • Ros melodic
  • Ubuntu18.04
  • Qt5.9
  • Blockly-2.20190722.1

最终效果

Blockly+Ros+Qt以Turtle为例,生成python代码,运行python代码以及实现blockly块的单步运行_第1张图片生成的python代码如下:

#!/usr/bin/env python2
from turtle_turtlebot.turtlebot import TurtleBot

if True:
  x = TurtleBot()
  x.move2goal(10,10)

blockly嵌入qt

  1. 使用QWebEngineView类,充当浏览器的角色,将blockly的html和js添加到qt工程中,并调用函数webEngineView->load(QUrl("file:///" + strHtml));即可
  2. js和qt通信,使用的是QWebChannel,具体js和qt的通信过程不做详细解释,网上很多。
  3. 嵌入blockly之后,即可实现blockly的块编程

生成python代码并运行

Blockly+Ros+Qt以Turtle为例,生成python代码,运行python代码以及实现blockly块的单步运行_第2张图片
将blockly块拖拽完成之后,可以生成python代码,也可以将工作区保存下来。点击保存文件按钮,将生成的py和xml都存了下来。
1.qt点击保存按钮,触发js方法

/**
 * @brief pushButton相应槽函数
 * @param
 * @retval void
 */
void RobotTaskFrame::pushButtonClickedSlot()
{
    QPushButton *pBtn = qobject_cast<QPushButton *>(sender());
    if(pBtn == ui->pushButton_save1)
    {
        //qt调用js方法
        jsContext->jsSendPathfile(webEngineView->page(),"");

    }
......

2.js调用生成python代码和xml工作区代码,并调用qt的保存文件方法

// 接收qt发送的消息
function recvMessage(msg)
{
    var pathfile = msg;
    var xml = Blockly.Xml.workspaceToDom(robotWorkspace,false);
    var strXml = Blockly.Xml.domToText(xml);
    var pythonCode = Blockly.Python.workspaceToCode(robotWorkspace);
    blocklyObj.onSaveFile(pathfile,strXml,pythonCode);
}

3.qt中保存代码到磁盘

void RobotTaskFrame::saveFileSlot(const QString &msg, const QString &strXml, const QString &strPy)
{
    QString magic = QString(tr("#!/usr/bin/env python2\n"));
    std::string ss = ros::package::getPath("robot_control_python");
    QString fileName = QFileDialog::getSaveFileName(this, tr("选择保存路径"),QString(ss.c_str()));
    if(fileName == nullptr)
    {
        return;
    }
    QString xmlFileName = fileName + QString(".xml");
    QString pyFileName = fileName + QString(".py");

    QFile xmlfile;
    xmlfile.setFileName(xmlFileName);
    xmlfile.open(QIODevice::WriteOnly | QIODevice::Text);
    xmlfile.write(strXml.toUtf8());
    xmlfile.close();

    QFile pyfile;
    pyfile.setFileName(pyFileName);
    pyfile.open(QIODevice::WriteOnly | QIODevice::Text);
    pyfile.write(magic.toUtf8());
    pyfile.write(strPy.toUtf8());
    pyfile.setPermissions(pyfile.permissions() | QFileDevice::ExeOwner);
    pyfile.close();

}

这里要注意的一点是,生成的python代码加上了#!/usr/bin/env python2头,这样使用rosrun是才能运行
4.rosrun运行即可rosrun robot_control_python my_test.py

打开文件

1.qt读取上一步保存的xml,并触发js

else if(pBtn == ui->pushButton_open)
    {
        QString strXml;
        std::string ss = ros::package::getPath("robot_control_python");
        QString fileName = QFileDialog::getOpenFileName(this, tr("打开blockly文件"),QString(ss.c_str()));
        if(fileName != nullptr)
        {
            QFile file;
            file.setFileName(fileName);
            if(file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                QByteArray t ;
                while(!file.atEnd())
                {
                    t += file.readLine();
                }
                strXml = QString(t);
                file.close();
            }
            jsContext->jsSendXml(webEngineView->page(),strXml);
        }

    }

2.js将xml转换为blockly工作区

function recvXml(xml)
{
    var blocklyXml = xml;
    let workspace = Blockly.getMainWorkspace();
    workspace.clear();
    var domXml = Blockly.Xml.textToDom(blocklyXml);
    Blockly.Xml.domToWorkspace(domXml, workspace);
}

blockly块的单步调试

  • qt使用QProcess,启动了一个后台python进程,用于调试py代码
  • python中有pdb模块可以用来进行调试
  • blockly中有相应的块函数,可以支持块的高亮robotWorkspace.highlightBlock(id);
  • qt进程中,使用QProcess的ReadChannel和WriteChannel模拟终端的输入输出
  1. 启动调试,开始单步并触发js函数
		if(!isStep)
        {
            ui->pushButton_debug->setText(QString(tr("停止调试")));
            jsContext->jsSrcReq(webEngineView->page(),"");
            ui->pushButton_step->setEnabled(true);
            isStep = true;
            isStop = false;
        }

2.js中生成python代码

function recvPython(prefix)
{
    Blockly.Python.STATEMENT_PREFIX = 'sys.stdout.flush();\npdb.set_trace();\nblockid=%1;\n';
    latestCode = Blockly.Python.workspaceToCode(robotWorkspace);
    outputArea.value = '<< Program started: >>';
    blocklyObj.onSaveDebugSrc('',latestCode);
}

注意:

  • blockly中有STATEMENT_PREFIX标识符,用于将块对应的代码前边,加上前缀。
  • sys.stdout.flush()必须加上,否则终端的输出会缓存,不能实时显示
  • pdb.set_trace()是让调试器设置断点
  • blockid=%1会将%1替换为blockly块的id
    点击开始调试效果如下:
    Blockly+Ros+Qt以Turtle为例,生成python代码,运行python代码以及实现blockly块的单步运行_第3张图片3.qt中保存生成的临时调试用的源码文件并启动后台的python进程
void RobotTaskFrame::saveDebugSrcSlot(const QString &msg, const QString &strPy)
{
    //qDebug() << strPy;
    QString magic = QString(tr("#!/usr/bin/env python2\nimport pdb\nimport sys\n"));
    std::string ss = ros::package::getPath("robot_control_python");
    QString fileName = QString("%1.py").arg(QDateTime::currentSecsSinceEpoch());
    QString pathFile = QString("/%1/%2").arg(ss.c_str()).arg(fileName);
    tempFilename = pathFile;

#if 1
    QFile pyfile;
    pyfile.setFileName(pathFile);
    pyfile.open(QIODevice::WriteOnly | QIODevice::Text);
    pyfile.write(magic.toUtf8());
    pyfile.write(strPy.toUtf8());
    pyfile.setPermissions(pyfile.permissions() | QFileDevice::ExeOwner);
    pyfile.close();
#endif

    QString program = "python";
    QStringList arguments;
    arguments << fileName;
    process->start(program, arguments);
    process->waitForStarted();
    process->write("c\r\n");
    process->waitForBytesWritten();
}

生成的py文件如下:

#!/usr/bin/env python2
import pdb
import sys
sys.stdout.flush();
pdb.set_trace();
blockid='-2.(~eJmZY@q|j7,tM$n';
if True:
  sys.stdout.flush();
  pdb.set_trace();
  blockid='Fk?d:jA`P!oDat3s])%h';
  print('a')
  sys.stdout.flush();
  pdb.set_trace();
  blockid='g_sB,-:}H,!NUE2EYuIo';
  print('b')

4.连续点击单步执行,即可以按照块进行执行

	else if(pBtn == ui->pushButton_step)
    {
        process->write("c\r\n");
        process->waitForBytesWritten();
    }

注意:pdb中,输入c表示继续执行
5.执行输出和切换块的高亮

void RobotTaskFrame::readFromClientSlot()
{
    //> 程序当前行数 > /home/test2/bit_ws/install/share/robot_control_python/1583120215.py(6)()
    //-> 当前行代码  -> blockid='~LoZ?n!exE/^j0t]*9or';
    //其他的认为是程序输出
    if( !process)
        return;
    QByteArray output = process->readAllStandardOutput();
    //qDebug() << output;
    //blockid='J+3zS5^y.KF88a3V;~%b';
    QString string = output;
    //1.以回车分割,找到以blockid开始的行,然后拿到单引号之内的内容
    QStringList list = string.split('\n');
    //qDebug() << list;
    QString blockid;
    QString pout;
    //-> blockid=
    for(int i = 0;i < list.size(); ++i)
    {
        QString temp = list.at(i);
        if(temp.startsWith("-> blockid=") == true)
        {
            QStringList list1 = temp.split('\'');
            //qDebug() << list1;
            if(list1.length() == 3)
            {
                blockid = list1.at(1);
            }
        }
        else if(temp.startsWith(">") == true)
        {
            qDebug() << temp;
        }
        else if(temp.startsWith("(Pdb)") == true)
        {
            //qDebug() << temp;
            pout = temp.remove("(Pdb) ");
        }
    }
    if(blockid.length() <= 0)
    {
        return;
    }
    //2.调用js函数highlightBlock(blockid)
    jsContext->jsHighLight(webEngineView->page(),blockid,pout);
}

注意:readFromClientSlot为readyRead信号的槽函数,用于读取标准输出内容
6.程序结束

void RobotTaskFrame::processFinishedSlot(int exitCode, QProcess::ExitStatus exitStatus)
{
    isStop = true;
    ui->pushButton_step->setEnabled(false);
    jsContext->jsHighLightOff(webEngineView->page(),"");
    QFile file(tempFilename);
    file.remove();
}

注意:processFinishedSlot为finished信号的槽函数,用于处理进程结束

单步调试整体效果

Blockly+Ros+Qt以Turtle为例,生成python代码,运行python代码以及实现blockly块的单步运行_第4张图片

参考资料

Writing a Simple Publisher and Subscriber (Python)
blockly开发之使用python驱动浏览器中的turtle(2)

你可能感兴趣的:(Blockly+Ros+Qt以Turtle为例,生成python代码,运行python代码以及实现blockly块的单步运行)