概述
環境如下:
- 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
- 使用QWebEngineView類,充當瀏覽器的角色,將blockly的html和js添加到qt工程中,並調用函數
webEngineView->load(QUrl("file:///" + strHtml));
即可 - js和qt通信,使用的是QWebChannel,具體js和qt的通信過程不做詳細解釋,網上很多。
- 嵌入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模擬終端的輸入輸出
- 啓動調試,開始單步並觸發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)