What am I trying to do?
I'm trying to build a Python Qt Gui that will allow me to easily run/debug embedded programs for microcontrollers. As a compiler I'm using sdcc. Sdcc also contains an emulator.
In the code, I start a child QProcess that will run the microcontroller emulator (s51
).
In the emulator, the simulation starts by entering r + RETURN ("r\n"
).
The simulation can be stopped at all times by pressing RETURN ("\n"
).
My problem
My problem is that my code is unable to get to the second RETURN. In other words, the simulation continues indefinetely. One way or the other, the child process never receives the second RETURN.
Steps to reproduce
- Install Python, PyQt and sdcc.
- Copy/pase the code below in an emtpy file and run it.
- Eventually change the path of the s51 executable.
- Click on the "Start" button.
- Enter
r
in the Input below and press the Return key. The simulation should now start. - The simulation should stop when you press the Return key a second time. But it does not.
Notes:
I have written the code on Fedora Linux. If you are running Ubuntu, you might have to change the path of the executable to
/usr/bin/s51
(I'm unsure of the exact path).The code 'works fine' on some other console applications like bash, python, ifconfig, echo, gcc, ...
I believe the problem has something to do with sdcc performing an ioct right after reading the "r\n" command (found using strace):
ioctl(0, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
EDIT1: I'm running Qt 4.8.3
EDIT2: Simplified & fixed the code + added a screenshot
Screenshot
The simulation should have stopped after the second RETURN.
(I entered h run\n
, run\n
, \n
, \n
, \help
)
Screen shot can be found here (not enough reputation)
SimpleConsole.py:
#!/usr/bin/env python3
import sys
from PyQt4 import Qt, QtGui, QtCore
class SimpleConsole(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setup()
self.process = None
def setup(self):
l = QtGui.QHBoxLayout()
lbl = QtGui.QLabel('Command:')
self.processExecutable = QtGui.QLineEdit()
self.processExecutable.setEnabled(True)
self.processExecutable.returnPressed.connect(self.startStopProcess)
self.processExecutable.setText('/usr/libexec/sdcc/s51')
lbl.setBuddy(self.processExecutable)
l.addWidget(lbl)
l.addWidget(self.processExecutable)
self.processOutputWidget = QtGui.QTextEdit()
self.processOutputWidget.setReadOnly(True)
self.processInputWidget = QtGui.QLineEdit()
self.processInputWidget.setEnabled(False)
self.processInputWidget.returnPressed.connect(self.inputForProcess)
main = QtGui.QVBoxLayout()
main.addLayout(l)
main.addWidget(self.processOutputWidget)
main.addWidget(self.processInputWidget)
widget = QtGui.QWidget()
widget.setLayout(main)
self.setCentralWidget(widget)
self.setStatusBar(QtGui.QStatusBar())
self.statusBarLabel = QtGui.QLabel()
self.statusBar().addWidget(self.statusBarLabel)
self.toolbar = QtGui.QToolBar()
self.addToolBar(self.toolbar)
self.startStopAction = self.toolbar.addAction('Start')
self.startStopAction.triggered[bool].connect(self.startStopProcess)
def closeEvent(self, event):
self.stopProcess()
@QtCore.pyqtSlot(QtCore.QProcess.ProcessState)
def processStateChanged(self, newState):
self.processInputWidget.setEnabled(newState == QtCore.QProcess.Running)
pid = self.process.pid()
if newState == QtCore.QProcess.Running:
self.startStopAction.setText('Stop')
self.startStopAction.setEnabled(True)
self.processExecutable.setEnabled(False)
elif newState == QtCore.QProcess.NotRunning:
self.processInputWidget.setEnabled(False)
self.processInputWidget.clear()
self.startStopAction.setText('Start')
self.startStopAction.setEnabled(True)
self.processExecutable.setEnabled(True)
self.process = None
status = {QtCore.QProcess.NotRunning:'not running',
QtCore.QProcess.Starting:'starting',
QtCore.QProcess.Running:'running'}
self.statusBarLabel.setText('Process state change: {newState}. (pid={pid})'.format(newState=status[newState], pid=pid))
@QtCore.pyqtSlot()
def startStopProcess(self):
if self.process:
self.stopProcess()
else:
self.processOutputWidget.clear()
self.process = QtCore.QProcess(self)
self.process.stateChanged.connect(self.processStateChanged)
self.process.readyReadStandardOutput.connect(self.processOutputRead)
self.process.readyReadStandardError.connect(self.processErrorRead)
exe = self.processExecutable.text()
self.process.start(exe, QtCore.QIODevice.ReadWrite)
def moveCursorToEnd(self):
self.processOutputWidget.moveCursor(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor)
@QtCore.pyqtSlot()
def inputForProcess(self):
cmd = self.processInputWidget.text() + '\n'
self.moveCursorToEnd()
self.processOutputWidget.insertPlainText(cmd)
self.process.writeData(bytearray(cmd, encoding='utf-8'))
self.process.waitForBytesWritten()
self.processInputWidget.clear()
@QtCore.pyqtSlot()
def processOutputRead(self):
bytesOut = bytes(self.process.readAllStandardOutput()).decode('utf-8')
self.moveCursorToEnd()
self.processOutputWidget.insertPlainText(bytesOut)
@QtCore.pyqtSlot()
def processErrorRead(self):
bytesError = bytes(self.process.readAllStandardError()).decode('utf-8')
self.moveCursorToEnd()
self.processOutputWidget.insertPlainText(bytesError)
def stopProcess(self):
if self.process:
self.process.closeWriteChannel()
if not self.process.waitForFinished(500):
self.process.terminate()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = SimpleConsole()
w.show()
app.exec_()