Blockly+Ros+Qt以Turtle爲例,生成python代碼,運行python代碼以及實現blockly塊的單步運行

概述

環境如下:

  • Ros melodic
  • Ubuntu18.04
  • Qt5.9
  • Blockly-2.20190722.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塊拖拽完成之後,可以生成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
    點擊開始調試效果如下:
    在這裏插入圖片描述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)<module>()
    //-> 當前行代碼  -> 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信號的槽函數,用於處理進程結束

單步調試整體效果

在這裏插入圖片描述

參考資料

Writing a Simple Publisher and Subscriber (Python)
blockly開發之使用python驅動瀏覽器中的turtle(2)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章