1

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

  1. Install Python, PyQt and sdcc.
  2. Copy/pase the code below in an emtpy file and run it.
  3. Eventually change the path of the s51 executable.
  4. Click on the "Start" button.
  5. Enter r in the Input below and press the Return key. The simulation should now start.
  6. 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_()
maarten
  • 336
  • 2
  • 10
  • This is a _lot_ of code. Could you please produce a simple [SSCCE](http://sscce.org)? – László Papp Dec 27 '14 at 06:20
  • Have you checked if your stop slot is called for the second return press? – László Papp Dec 27 '14 at 06:32
  • Also, any reason why you do not use python's system command execution (e.g. subprocess)? It would be more intuitive in a python program than a wrapper around a C++ API, doing the same concept in the background. – László Papp Dec 27 '14 at 09:56
  • I'll try to boil it down to the essential. I guess I'll have to switch to using subprocess. My main reason for using QProcess over subprocess is to have the advantage of the signals/slots system of qt. – maarten Dec 27 '14 at 23:59

0 Answers0