"""\
ArduinoConsole.py
PyQt4 window implementing a generic control console for an Arduino sketch,
including command line, plot window, and serial port controls. Functions are
provide to allow extending the basic interface with custom buttons and sliders
without needing to use Qt Designer.
Copyright (c) 2013-2015, Garth Zeglin. All rights reserved. Licensed under the
terms of the BSD 3-clause license as included in LICENSE.
"""
# for documentation on the PyQt4 API, see http://pyqt.sourceforge.net/Docs/PyQt4/index.html
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
# import the underlying class created using Qt Designer and pyuic
from ArduinoConsoleWindow import Ui_ArduinoConsoleWindow
#================================================================
# The Qwt technical widgets system may or may not be available for showing the plot widget.
# For details see:
# https://github.com/PyQwt
# http://qwt.sourceforge.net/
try:
from ArduinoScope import ArduinoScope
except ImportError:
print("Unable to load the ArduinoScope module, perhaps the pyqwt or qwt52 modules are not installed.")
ArduinoScope = None
################################################################
[docs]class ArduinoConsole( QtGui.QMainWindow, Ui_ArduinoConsoleWindow ):
"""A custom window which inherits both from the QMainWindow class and the custom
Ui_ArduinoConsoleWindow defined using Qt Designer."""
def __init__( self, view = None):
QtGui.QMainWindow.__init__( self )
self.setupUi( self )
self.scope = None
self.connect_callback = None
self.command_callback = None
self.show()
return
[docs] def write( self, string ):
"""Write output to the console text area."""
self.consoleOutput.insertPlainText(str(string))
self.consoleOutput.ensureCursorVisible()
return
# --------- callbacks from Ui_ArduinoConsoleWindow ---------------
[docs] def commandEntered( self ):
"""Callback invoked whenever command line text is entered."""
command = self.commandLine.text()
self.commandLine.clear()
if self.command_callback is not None:
self.command_callback(str(command))
return
[docs] def ArduinoConnectToggled( self, flag ):
"""Callback invoked whenever the Arduino Connect checkbox changes state."""
if self.connect_callback is not None:
self.connect_callback( str(self.ArduinoPortName.text()), flag )
return
[docs] def ArduinoPortEntered( self ):
"""Callback invoked whenever text is entered into the Arduino Port field."""
port_name = self.ArduinoPortName.text()
return
[docs] def resetPlotPressed( self ):
"""Callback invoked whenever the Reset Plot button is clicked."""
if self.scope is not None:
self.scope.reset_plot()
return
#----- functions to manage events -------------------------------
[docs] def attachConnectCallback(self, callback):
"""Set the callback function to be invoked when the user requests a connect or disconnect from the Arduino.
:param callback: callback( port_name, flag ) flag is True for connect, False for disconnect
"""
self.connect_callback = callback
return
[docs] def attachCommandCallback(self, callback):
"""Set the callback function to be invoked when the user enters a command.
:param callback: callback( command_string )
"""
self.command_callback = callback
return
[docs] def setArduinoPortName(self, name):
"""Set the initial state of the Arduino Port name field (e.g., from command-line arguments)."""
self.ArduinoPortName.setText(name)
return
[docs] def isShowingRawData(self):
"""Returns true if the Show Raw Data checkbox is selected."""
return self.ArduinoRawSwitch.isChecked()
[docs] def newPeriodicTimer( self, interval, callback ):
"""Convenience function to create a periodic timer which calls a function at the
given interval.
:param interval: interval in milliseconds
:param callback: no-argument function callback() to be called at intervals
:return: the underlying QTimer object
"""
timer = QtCore.QTimer() # create a polling timer
timer.start(int(interval) ) # units are milliseconds
timer.timeout.connect(callback) # attach to callback
return timer
[docs] def newSingleShotTimer( self, interval, callback ):
"""Convenience function to set up a single-shot timer which calls a function
once after the given interval.
:param interval: interval in milliseconds
:param callback: no-argument function callback() to be called once after a delay
"""
QtCore.QTimer.singleShot( int(interval), callback )
return
[docs] def addSlider( self, title, callback ):
"""Add a horizontal slider to the controls area. The range is fixed over [0,1.0).
:param title: string used as a tooltip
:param callback: function receiving floating slider value
"""
newSlider = QtGui.QSlider(self.centralwidget)
newSlider.setOrientation(QtCore.Qt.Horizontal)
newSlider.setObjectName(_fromUtf8( title ))
self.sliderLayout.addWidget( newSlider )
# This sets a fixed maximum larger than any current screen resolution; the user can scale to what they require.
newSlider.setMaximum( 10000 )
# this slider does not itself have text, so this sets a tooltip for it
newSlider.setToolTip( "<html><head/><body><p>%s</p></body></html>" % title )
# create a wrapper function to rescale the slider value to [0,1]
wrapper = lambda ticks: callback(0.0001*ticks)
# connect the slider motion signal to the wrapper
QtCore.QObject.connect( newSlider, QtCore.SIGNAL(_fromUtf8("sliderMoved(int)")), wrapper)
return newSlider
#------ functions to control the data plot area -----------------
[docs] def addScope(self):
"""Add an ArduinoScope widget to the top of the layout."""
if ArduinoScope is None:
print("Warning, the ArduinoScope object is not available.")
else:
# add the plot area to the top of the window
self.scope = ArduinoScope(self.centralwidget)
self.verticalLayout.insertWidget(0, self.scope)
[docs] def addScopeChannel(self, *args, **kwargs ):
"""Add a named channel to the ArduinoScope plotting widget. The arguments are
passed unchanged to add_channel()."""
if self.scope is not None:
self.scope.add_channel( *args, **kwargs )
[docs] def addScopeSamples(self, name, y, t ):
"""Add a set of new samples to a given named channel to the ArduinoScope
plotting widget. The arguments are passed unchanged to
add_samples().
"""
if self.scope is not None:
self.scope.add_samples( name, y, t )
[docs] def replotScope(self):
"""Update the ArduinoScope plot area after all new data has been added."""
if self.scope is not None:
self.scope.replot()
################################################################