Esempio n. 1
0
	def __init__(self):
		QMainWindow.__init__(self)
		
		self.loadSettings()
		#self.saveSettings()
		
		self._serialCom = SerialCom()
		self._serialCom.readyRead.connect(self.serialEvent)
		#print('Found ports:', self._serialCom.ports())
		
		self.createMenus()
		
		self.resize(800, 500)
		
		self.statusBar().showMessage('Open a robot configuration file...')
		
		#if self.recentFilesActs[0].data():
		#	self.recentFilesActs[0].triggered.emit()
		
		# Open last port if possible
		if self.lastComPort:
			if self.lastComPort in self.commObject().portNames():
				self.changeComPort(self.lastComPort)
Esempio n. 2
0
from flask import Flask, render_template, Response, request
import time

# emulated camera
from camera import Camera

#from data import Data
from serialCom import SerialCom

# Raspberry Pi camera module (requires picamera package)
# from camera_pi import Camera
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
app = Flask(__name__)
comm = SerialCom('/dev/ttyAMA0')

DirectionTab = [[0, 0], [0, 0], [300, 300], [-300, -300], [-200, 200],
                [200, -200]]


@app.route('/')
def index():
    """Video streaming home page."""
    return render_template('index_design.html')


def gen(camera):
    """Video streaming generator function."""
    while True:
        frame = camera.get_frame()
Esempio n. 3
0
def main():
    global expectingData, serialCom
    if len(sys.argv) != 2:
        logError('Please provide serial port as first argument')
        exit(1)

    try:
        serialCom = SerialCom(sys.argv[1])
    except serial.serialutil.SerialException as e:
        logError('Could not open serial port: {}'.format(e.args[1]))
        exit(1)

    gui = GUI()
    gui.draw([], 'Looking for device')
    while serialCom.ping() != 0:
        gui.draw(
            [],
            'Device is not responding to\nPING command\nCheck connection\n\nRetrying...'
        )
        sleep(1)

    gui.draw([], 'Device is connected\n\nSending initial configuration')
    actions = [{
        'job': serialCom.setTriggerLevel,
        'name': 'Setting trigger level',
        'value': 1
    }, {
        'job': serialCom.setMode,
        'name': 'Setting mode',
        'value': 0
    }, {
        'job': serialCom.setNumberOfSamples,
        'name': 'Setting number of samples',
        'value': gui.graph.numberOfSamples
    }, {
        'job': serialCom.setPrecision,
        'name': 'Setting frequency',
        'value': gui.graph.freq
    }]
    for action in actions:
        gui.draw([],
                 'Device is connected\n\nSending initial configuration\n\n' +
                 action['name'])
        while action['job'](action['value']) != 0:
            sleep(3)
            gui.draw(
                [],
                'Device is connected\n\nSending initial configuration\n\nRetrying: '
                + action['name'])

    gui.draw([])
    exData = []
    while True:
        message = None
        while not (expectingData
                   and serialCom.isDataAvail()) and not processUserInput(
                       gui, serialCom):
            sleep(0.3)

        if expectingData and serialCom.isDataAvail():
            (status, exData) = serialCom.downloadData()
            if status:
                expectingData = False
            else:
                message = 'Downloading samples failed\nCommunication error\noccurred\n\nPress any key'

        gui.draw(exData, message)
Esempio n. 4
0
class MainWindow(QtGui.QMainWindow):
	
	_fids = {}	# Functionalities display objects by functionality ID
	_wgtgrp = {}	# Widgets by group
	
	_layoutMatchString = r'(r(?P<row>\d+))?(c(?P<col>\d+))?(rs(?P<rowspan>\d+))?(cs(?P<colspan>\d+))?'
	
	mdiarea = None
	functionalitiesMenu = None
	signalMapper = None
	
	lastComPort = None
	
	def __init__(self):
		QMainWindow.__init__(self)
		
		self.loadSettings()
		#self.saveSettings()
		
		self._serialCom = SerialCom()
		self._serialCom.readyRead.connect(self.serialEvent)
		#print('Found ports:', self._serialCom.ports())
		
		self.createMenus()
		
		self.resize(800, 500)
		
		self.statusBar().showMessage('Open a robot configuration file...')
		
		#if self.recentFilesActs[0].data():
		#	self.recentFilesActs[0].triggered.emit()
		
		# Open last port if possible
		if self.lastComPort:
			if self.lastComPort in self.commObject().portNames():
				self.changeComPort(self.lastComPort)
		
		#self.setDockNestingEnabled(True)
	
	
	def commObject(self):
		return self._serialCom
	
	def createMenus(self):
		''' Create the app default menus '''
		
		fileMenu = self.menuBar().addMenu('&File')
		openAct = fileMenu.addAction('&Open Robot...', None, QKeySequence.Open)
		openAct.triggered.connect(self.openConfigFile)
		
		self.rfSeparatorAct = fileMenu.addSeparator()
		
		self.recentFilesActs = []
		for i in range(0, self.MaxRecentFiles):
			act = QAction(self)
			self.recentFilesActs.append(act)
			act.setVisible(False)
			act.triggered.connect(self.openRecentFile)
			
			fileMenu.addAction(act)
		
		self.updateRecentFilesActions()
		
		# Add com ports
		portSelectGroup = QActionGroup(self)
		self.portsMenu = self.menuBar().addMenu('&Ports')
		# Construct port list menu
		for port in self.commObject().ports():
			act = self.portsMenu.addAction(port.name)
			act.setCheckable(True)
			act.setToolTip(port.description)
			portSelectGroup.addAction(act)
			
		portSelectGroup.triggered.connect(self._changePort)
	
	
	def createSubWindowMenus(self):
		''' Create a menu containing the subWindows list '''
		
		subwActGroup = QActionGroup(self)
		subwActGroup.setExclusive(False)
		
		if not self.functionalitiesMenu:
			self.functionalitiesMenu = self.menuBar().addMenu('&Functionalities')
			
		subwMenu = self.functionalitiesMenu
		
		# Create subwindows list actions
		for subw in self.mdiarea.subWindowList():
			act = subwMenu.addAction(subw.windowTitle())
			act.setCheckable(True)
			act.setChecked(True)
			act.setData(subw)
			subw.hided.connect(act.toggle)
			subwActGroup.addAction(act)
		
		subwActGroup.triggered.connect(self.showSubWindow)
		
		subwMenu.addSeparator()
		
		# Add action to show all subwindows
		self._subWindowsActions = subwActGroup.actions()
		showall = subwMenu.addAction('Show All')
		showall.triggered.connect(self.showAllSubWindows)
	
	
	def openConfigFile(self, fileName = None):
		''' Open a dialog to get robot configuration file name, and load it '''
		
		stream = QApplication.keyboardModifiers() == Qt.ControlModifier
		
		if not fileName:
			fileName = QFileDialog.getOpenFileName(self, 'Open robot config file', '', 'BotVisor Config Files (*.bvc)')
			if fileName: fileName = fileName[0]
		
		if fileName:
			print('Open file:', fileName)
			self.setCurrentFile(fileName)
			
			if not stream:
				self.loadRobotFromConfigFile(fileName)
			else:
				self.streamRobotConfigFile(fileName)
	
	def openRecentFile(self):
		''' Called by recent files actions '''
		self.openConfigFile(self.sender().data())
	
	
	def loadRobotFromConfigFile(self, configFileName):
		''' Load a robot from configuration file '''
		
		# Load robot config file
		bc = BotConfigParser()
		data, botData, groupsData = bc.loadConfigFile(configFileName)
		
		# fullData = OrderedDict()
		# fullData['__CONFIGURATION__'] = OrderedDict()
		# fullData['__CONFIGURATION__']['robot'] = botData
		# fullData['__CONFIGURATION__']['functionalities'] = data
		
		# print('>> Send JSON data')
		# self._serialCom.sendJSON(fullData)
		
		self.loadRobot(botData, groupsData, data)
	
	def loadStreamedConfig(self, jsonData):
		''' Load a config file streamed by a robot through com port '''
			
		if 'robot' not in jsonData or 'functionalities' not in jsonData:
			print('Streamed config file seems invalid ! (no functionalities in)')
			self.statusBar().showMessage('Streamed config file loading failed !', 6000)
			return
		
		groupsData = None
		if 'groups' in jsonData:
			groupsData = jsonData['groups']
		
		self.loadRobot(jsonData['robot'], groupsData, jsonData['functionalities'])
		
		self.statusBar().showMessage('Streamed config file loaded !', 6000)
	
	
	def streamRobotConfigFile(self, configFileName):
		''' Stream a robot configuration file through com port '''
		
		# Load robot config file
		bc = BotConfigParser()
		data, botData, groupsData = bc.loadConfigFile(configFileName)
		
		fullData = OrderedDict()
		fullData['__CONFIGURATION__'] = OrderedDict()
		fullData['__CONFIGURATION__']['robot'] = botData
		fullData['__CONFIGURATION__']['groups'] = groupsData
		fullData['__CONFIGURATION__']['functionalities'] = data
		
		print('>> Stream config file')
		self._serialCom.sendJSON(fullData)
	
	
	def loadRobot(self, botData, groupsData, functionalitiesData):
		''' Load a robot from configuration file '''
		
		self.clearRobot()
		
		self.mdiarea = QMdiArea()
		self.setCentralWidget(self.mdiarea)
		
		self.signalMapper = QSignalMapper()
		self.signalMapper.mapped[QObject].connect(self.functionalityValueChanged)
		
		
		self.setWindowTitle('BotVisor - ' + botData['name'])

		
		#print('All keys:', functionalitiesData.keys())
		#print(json.dumps(functionalitiesData, sort_keys=True, indent=2, separators=(',', ': ')))
		fnum = 0
		fnume = 0
		for fuKeys in functionalitiesData:
			#print(fuKeys)
			#print(functionalitiesData[fuKeys].keys())
			options = functionalitiesData[fuKeys]
			if 'display' in options.keys():
				self.loadFunctionality(fuKeys, options['display'], options, groupsData)
				fnum = fnum+1
			else:
				print('Error: functionality', fuKeys, 'not loaded')
				fnume = fnume+1
		
		self.statusBar().showMessage('{} functionalities sucessfully loaded. {} failed.'.format(fnum, fnume), 6000)
		
		self.createSubWindowMenus()
	
	
	def clearRobot(self):
		''' Clear window, to load a robot '''
		
		if self.mdiarea:
			del self.mdiarea
		
		# Clear functionalities menu
		if self.functionalitiesMenu:
			self.functionalitiesMenu.clear()
		
		self._fids = {}
		self._wgtgrp = {}
		
		# Reset signal mapper
		if self.signalMapper:
			del self.signalMapper
		
	
	def showSubWindow(self, action):
		''' Show or hide a subWindow depending of action's state '''
		if action.isChecked():
			action.data().show()
		else:
			action.data().hide()
	
	def showAllSubWindows(self):
		''' Show all subWindows '''
		for act in self._subWindowsActions:
			act.data().show()
			act.setChecked(True)
	
	
	def _changePort(self, action):
		''' Use new port '''
		#print('Change port:', action.text())
		
		self.changeComPort(action.text())
		
	
	def changeComPort(self, portName):
		''' Try to connect to 'portName' '''
		
		if self.commObject().isConnected():
			print('Disconnect from', self.commObject().portName())
		
		if not self.commObject().connectPort(portName):
			self.statusBar().showMessage('Unable to connect {}.'.format(portName), 6000)
			return
		
		# Update last port
		self.lastComPort = portName
		
		# Check menu action for current port
		for act in self.portsMenu.actions():
			if act.text() == portName:
				act.setChecked(True)
		
		self.statusBar().showMessage('{} connected.'.format(portName), 6000)
	
	
	def serialEvent(self):
		''' JSON objects received '''
		
		jsonObjs = self._serialCom.readAllObjects()
		
		#print('\nJSON data received:')
		for jsonObj in jsonObjs:
			#print(json.dumps(jsonObj, sort_keys=True, indent=2, separators=(',', ': ')))
			
			if '__CONFIGURATION__' in jsonObj.keys():
				print('>> Streamed config file received')
				self.loadStreamedConfig(jsonObj['__CONFIGURATION__'])
			
			# Loop over all keys
			for fid in jsonObj:
				# Check if key is in stored functionalities
				if fid in self._fids:
					# Update value
					value = jsonObj[fid];
					self._fids[fid].setValue(value)
					print('Value: ', value, 'for display ', fid)
					
					self.statusBar().showMessage('Value for {} received ({}).'.format(fid, value), 2000)
	
	
	
	def addSubWindow(self, widget, title):
		''' Add a subwindow with title containing widget '''
		newSubWin = MdiSubWindow()
		newSubWin.setWindowTitle(title)
		self.mdiarea.addSubWindow(newSubWin)
		newSubWin.setWidget(widget)
		return widget
	
	def addDockWidget(self, widget, title):
		''' Add a dock with title containing widget '''
		newDock = QDockWidget(title)
		newDock.setWidget(widget)
		self.addDockWidget(Qt.LeftDockWidgetArea, newDock)
		return widget
	
	
	def loadFunctionality(self, fid, display, options, groups = None):
		''' Load a functionality '''
		
		def availableLayoutPosition(layout, direction = 'r', start = (0,0) ):
			''' Return first available position in layout by incrementing on rows or cols '''
			r, c = start
			while layout.itemAtPosition(r, c):
				if direction == 'r':	# Search on rows
					r = r+1
				if direction == 'c':	# Search on cols
					c = c+1
			return r, c
		
		def loadClassFromModule(module_name, class_name):
			''' Dynamic class loading from a module '''
			# load the module, will raise ImportError if module cannot be loaded
			m = importlib.import_module(module_name)
			# get the class, will raise AttributeError if class cannot be found
			c = getattr(m, class_name)
			return c
		
		
		name = fid
		# Check for friendly name
		if 'name' in options:
			name = options['name']
		
		#print('Add display:', fid, '(' +display+ ')')
		
		# Load class instance
		classInstance = loadClassFromModule('displays', display)
		
		data = None
		if 'data' in options:
			data = options['data']
		
		if display == 'Led':
			c = classInstance(fid, name, True)
		elif display == 'ProgressBar':
			c = classInstance(fid, name, data=data, valueFormatting='{0}')
		elif display == 'Slider':
			c = classInstance(fid, name, data=data, valueFormatting='{0}')
		elif display == 'Dial':
			c = classInstance(fid, name, data=data, valueFormatting='{0}')
		elif display == 'Alphanum':
			c = classInstance(fid, name)
		else:
			raise TypeError('Unknown display type')
		
		
		# Connect signals
		c.valueChanged.connect(self.signalMapper.map)
		self.signalMapper.setMapping(c, c)
		
		# if 'range' in options:
		# 	c.setValueRange(int(options['range'][0]), int(options['range'][1]))
		
		if 'disable' in options:
			c.widget().setDisabled(options['disable'])
		
		# Save display by ID (to set values when JSON data received)
		self._fids[fid] = c
		
		# True to create a new subwindow.
		# False if the functionality is in an existing group.
		newsw = True
		
		# Check if group specified
		if 'group' in options:
			group = options['group']
			#print('Functionality in group', group)
			
			newsw = False
			# Group empty, create new widget with layout
			if group not in self._wgtgrp:
				w = QWidget()
				glyt = QGridLayout(w)
				self._wgtgrp[group] = w
				newsw = True
			else:
				if str(group) in groups:	# Check if group name specified
					self._wgtgrp[group].parent().setWindowTitle(groups[str(group)])
				else:
					self._wgtgrp[group].parent().setWindowTitle('Group '+str(group))
			
			widget = self._wgtgrp[group]
			
			# Layout position
			row, col = None, 0
			rowSpan, colSpan = 1, 1
			
			# Check if layout position specified
			if 'layout' in options:
				m = re.search(self._layoutMatchString, options['layout'])
				# Get values
				if m:
					gd = m.groupdict()
					#print(gd)
					row = int(gd['row']) if gd['row'] != None else None
					col = int(gd['col']) if gd['col'] != None else None
					
					rowSpan = int(gd['rowspan']) if gd['rowspan'] != None else 1
					colSpan = int(gd['colspan']) if gd['colspan'] != None else 1
					
					# Default if row & col to None
					if (row, col) == (None, None):
						col = 0
			
			# Get col number
			if col == None:
				row,col = availableLayoutPosition(widget.layout(), 'c', (row, 0))
			
			# Get row number
			if row == None:
				row,col = availableLayoutPosition(widget.layout(), 'r', (0, col))
			
			#print('Layout: ', row, col, rowSpan, colSpan)
			
			# Add the widget to the layout
			widget.layout().addWidget(c, row, col, rowSpan, colSpan)
			
		else:	# No group specified
			widget = c
		
		# Create new subwindow if needed
		if newsw:
			self.addSubWindow(widget, name)
			#self.addDockWidget(widget, name)
	
	
	def functionalityValueChanged(self, fobj):
		''' Send functionality value over comm port '''
		#print('Value for', fobj.id(), ':', fobj.value())
		jsonData = {fobj.id():fobj.value()}
		#print(json.dumps(jsonData, sort_keys=True, indent=2, separators=(',', ': ')))
		self._serialCom.sendJSON(jsonData)
		
		self.statusBar().showMessage('Value for {} sent ({}).'.format(fobj.id(), fobj.value()), 2000)


	def recentFilesList(self):
		''' Get recent files list from settings file '''
		se = QSettings(fullPath('BotVisor.conf'), QSettings.IniFormat)
		files = se.value('recentfiles', [])
		
		if isinstance(files, str):
			return [files]
		#if isinstance(files, list):
		#	return files
		return files
	
	
	def updateRecentFilesActions(self):
		''' Update recent files menu actions '''
		recentFiles = self.recentFilesList()
		
		numRecentFiles = min(len(recentFiles), self.MaxRecentFiles)
		
		for i in range(0, numRecentFiles):
			self.recentFilesActs[i].setText(QFileInfo(recentFiles[i]).fileName())
			self.recentFilesActs[i].setData(recentFiles[i])
			self.recentFilesActs[i].setVisible(True)
		
		for i in range(numRecentFiles, self.MaxRecentFiles):
			self.recentFilesActs[i].setVisible(False)
		
		self.rfSeparatorAct.setVisible(numRecentFiles > 0)
		
	
	def setCurrentFile(self, fileName):
		''' Update recent files '''
		recentFiles = self.recentFilesList()
		
		# Remove occurences of fileName
		while recentFiles.count(fileName):
			recentFiles.remove(fileName)
		
		# Prepend file
		recentFiles.insert(0, fileName)
		
		# Remove old files
		if len(recentFiles) > self.MaxRecentFiles:
			recentFiles = recentFiles[self.MaxRecentFiles:]
		
		# Save recent files list
		se = QSettings(fullPath('BotVisor.conf'), QSettings.IniFormat)
		se.setValue('recentfiles', recentFiles)
		
		self.updateRecentFilesActions()
		
	
	def loadSettings(self):
		''' Load settings '''
		se = QSettings(fullPath('BotVisor.conf'), QSettings.IniFormat)
		
		#recentFiles = se.value('recentfiles')
		self.MaxRecentFiles = int(se.value('maxrecentfiles', 10))
		
		se.beginGroup('commport')
		self.lastComPort = se.value('lastport', '')
		se.endGroup()
		
		
	def saveSettings(self):
		''' Save settings '''
		se = QSettings(fullPath('BotVisor.conf'), QSettings.IniFormat)
		
		se.beginGroup('commport')
		se.setValue('lastport', self.lastComPort)
		se.endGroup()
	
	def closeEvent(self, event):
		''' App closing request '''
		self.saveSettings()
		
		self._serialCom.disconnectPort()
		event.accept()
Esempio n. 5
0
	def __init__(self):
		QMainWindow.__init__(self)
		
		self.loadSettings()
		
		self._serialCom = SerialCom()
		self._serialCom.readyRead.connect(self.serialEvent)
		#print('Found ports:', self._serialCom.ports())
		
		self.createMenus()
		
		view = QDeclarativeView()
		
		widget = QWidget()
		
		lyt = QGridLayout(widget)
		lyt.setContentsMargins(0, 0, 0, 0)
		lyt.addWidget(view, 0, 0)
		
		self.setCentralWidget(widget)
		
		
		self.setWindowTitle("Wulka Bot Simulator")
		view.setRenderHints(QtGui.QPainter.SmoothPixmapTransform)
		
		# Renders 'PyTerm.qml'
		view.setSource(QUrl.fromLocalFile('Robot.qml'))
		# QML resizes to main window
		view.setResizeMode(QDeclarativeView.SizeRootObjectToView)
		
		
		self.root = view.rootObject()
		
		
		dirWheel = self.root.findChild(QtCore.QObject, 'robotDirWheelRotation')
		#dirWheel.setProperty('angle', 0)
		self._objects['robotDirWheelRotation'] = dirWheel
		
		v = self.root.findChild(QtCore.QObject, 'leftWheel')
		self._objects['leftWheel'] = v
		
		v = self.root.findChild(QtCore.QObject, 'rightWheel')
		self._objects['rightWheel'] = v
		
		
		v = self.root.findChild(QtCore.QObject,"sfm_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfl2_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfl1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfr1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfr2_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"srl1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"srr1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		

		v = self.root.findChild(QtCore.QObject,"sens_fm")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fr2")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fr1")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fl1")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fl2")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_rr1")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_rl1")
		v.checked.connect(self.sensorClicked)
		
		
		self.resize(600, 550)
		self.setFixedHeight(550)
		self.setFixedWidth(600)
		
		# self.setSpeed_LeftWheel(-50)
		# self.setSpeed_LeftWheel(50)
		# self.setSpeed_RightWheel(50)
		
		self.statusBar().showMessage('Choose a port to stream configuration')

		# Open last port if possible
		if self.lastComPort:
			if self.lastComPort in self.commObject().portNames():
				print('>> Loading saved port config:', self.lastComPort)
				self.changeComPort(self.lastComPort)
			else:
				print('Saved com port not available.')
		else:
			print('No com port saved.')
Esempio n. 6
0
class MainWindow(QtGui.QMainWindow):
	
	_objects = {}


	mdiarea = None
	functionalitiesMenu = None
	signalMapper = None
	
	lastComPort = None
	
	configStream = '''{'__CONFIGURATION__':{'robot':{'name':'Wulka Bot'},
	'groups':{'1':'Capteurs avants', '2':'Capteurs arrières', '3':'Roues', '4':'Télémètres avants', '5':'Télémètres arrières'},
	
	'functionalities':{
	'sens_fl2':{'display':'Led', 'group':1, 'layout':'r0', 'disable':true},
	'sens_fl1':{'display':'Led', 'group':1, 'layout':'r0', 'disable':true},
	'sens_fm':{'display':'Led', 'group':1, 'layout':'r0', 'disable':true},
	'sens_fr1':{'display':'Led', 'group':1, 'layout':'r0', 'disable':true},
	'sens_fr2':{'display':'Led', 'group':1, 'layout':'r0', 'disable':true},
	
	'sens_rl1':{'display':'Led', 'group':2, 'layout':'r0', 'disable':true},
	'sens_rr1':{'display':'Led', 'group':2, 'layout':'r0', 'disable':true},
	
	'leftWheel':{'display':'Slider', 'group':3, 'layout':'r0', 'data':{'vertical':true, 'range':[-70, 70]}},
	'rightWheel':{'display':'Slider', 'group':3, 'layout':'r0', 'data':{'vertical':true, 'range':[-70, 70]}},
	
	'sfl2_value':{'display':'ProgressBar', 'group':4, 'layout':'r0', 'data':{'vertical':true}},
	'sfl1_value':{'display':'ProgressBar', 'group':4, 'layout':'r0', 'data':{'vertical':true}},
	'sfm_value':{'display':'ProgressBar', 'group':4, 'layout':'r0', 'data':{'vertical':true}},
	'sfr1_value':{'display':'ProgressBar', 'group':4, 'layout':'r0', 'data':{'vertical':true}},
	'sfr2_value':{'display':'ProgressBar', 'group':4, 'layout':'r0', 'data':{'vertical':true}},
	
	'srl1_value':{'display':'ProgressBar', 'group':5, 'layout':'r0', 'data':{'vertical':true}},
	'srr1_value':{'display':'ProgressBar', 'group':5, 'layout':'r0', 'data':{'vertical':true}},
	
	'robotDirWheelRotation':{'display':'Dial', 'name':'Direction', 'data':{'range':[0, 360], 'vertical':true}}
	}}}'''
	
	def __init__(self):
		QMainWindow.__init__(self)
		
		self.loadSettings()
		
		self._serialCom = SerialCom()
		self._serialCom.readyRead.connect(self.serialEvent)
		#print('Found ports:', self._serialCom.ports())
		
		self.createMenus()
		
		view = QDeclarativeView()
		
		widget = QWidget()
		
		lyt = QGridLayout(widget)
		lyt.setContentsMargins(0, 0, 0, 0)
		lyt.addWidget(view, 0, 0)
		
		self.setCentralWidget(widget)
		
		
		self.setWindowTitle("Wulka Bot Simulator")
		view.setRenderHints(QtGui.QPainter.SmoothPixmapTransform)
		
		# Renders 'PyTerm.qml'
		view.setSource(QUrl.fromLocalFile('Robot.qml'))
		# QML resizes to main window
		view.setResizeMode(QDeclarativeView.SizeRootObjectToView)
		
		
		self.root = view.rootObject()
		
		
		dirWheel = self.root.findChild(QtCore.QObject, 'robotDirWheelRotation')
		#dirWheel.setProperty('angle', 0)
		self._objects['robotDirWheelRotation'] = dirWheel
		
		v = self.root.findChild(QtCore.QObject, 'leftWheel')
		self._objects['leftWheel'] = v
		
		v = self.root.findChild(QtCore.QObject, 'rightWheel')
		self._objects['rightWheel'] = v
		
		
		v = self.root.findChild(QtCore.QObject,"sfm_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfl2_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfl1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfr1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"sfr2_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"srl1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		v = self.root.findChild(QtCore.QObject,"srr1_value")
		v.progressValueChanged.connect(self.sensorValueChanged)
		
		

		v = self.root.findChild(QtCore.QObject,"sens_fm")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fr2")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fr1")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fl1")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_fl2")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_rr1")
		v.checked.connect(self.sensorClicked)
		
		v = self.root.findChild(QtCore.QObject,"sens_rl1")
		v.checked.connect(self.sensorClicked)
		
		
		self.resize(600, 550)
		self.setFixedHeight(550)
		self.setFixedWidth(600)
		
		# self.setSpeed_LeftWheel(-50)
		# self.setSpeed_LeftWheel(50)
		# self.setSpeed_RightWheel(50)
		
		self.statusBar().showMessage('Choose a port to stream configuration')

		# Open last port if possible
		if self.lastComPort:
			if self.lastComPort in self.commObject().portNames():
				print('>> Loading saved port config:', self.lastComPort)
				self.changeComPort(self.lastComPort)
			else:
				print('Saved com port not available.')
		else:
			print('No com port saved.')
	
	
	def moveDirWheel(self, value):
		self._objects['robotDirWheelRotation'].setProperty('angle', value)

	def setSpeed_LeftWheel(self, speed):
		self._objects['leftWheel'].setSpeed(speed)
		
	def setSpeed_RightWheel(self, speed):
		self._objects['rightWheel'].setSpeed(speed)
	
	
	def sensorClicked(self, checked):
		#print('CLICKED', checked)
		#print('Value for', fobj.id(), ':', fobj.value())
		jsonData = {self.sender().objectName() : checked}
		self.commObject().sendJSON(jsonData)

	
	def sensorValueChanged(self, value):
		#print('SENSOR', int(value*100), self.sender().objectName())
		#print('Value for', fobj.id(), ':', fobj.value())
		jsonData = {self.sender().objectName() : int(value*100)}
		self.commObject().sendJSON(jsonData)
	
	
	
	def commObject(self):
		return self._serialCom
	
	def createMenus(self):
		''' Create the app default menus '''
		
		portSelectGroup = QActionGroup(self)
		self.portsMenu = self.menuBar().addMenu('&Ports')
		# Construct port list menu
		for port in self.commObject().ports():
			act = self.portsMenu.addAction(port.name)
			act.setCheckable(True)
			act.setToolTip(port.description)
			portSelectGroup.addAction(act)
			
		portSelectGroup.triggered.connect(self._changePort)
		
		configMenu = self.menuBar().addMenu('&Configuration')
		streamAct = configMenu.addAction('&Stream configuration')
		streamAct.triggered.connect(self.streamConfiguration)
	
	
	def streamConfiguration(self):
		
		if not self.commObject().isConnected():
			print('Port unconnected, unable to stream data.')
			self.statusBar().showMessage('Open a port to stream data!', 6000)
			#return
		
		print('>> Stream config file')
		
		data = json.loads(self.configStream.replace("'", '"'), object_pairs_hook=OrderedDict)
		#print(json.dumps(data, sort_keys=False, indent=2, separators=(',', ': ')))
		
		if self.commObject().sendJSON(data):
			self.statusBar().showMessage('Configuration streamed!', 6000)
		else:
			self.statusBar().showMessage('Error: data not sent', 6000)
	
	
	def _changePort(self, action):
		''' Use new port '''
		#print('Change port:', action.text())
		
		self.changeComPort(action.text())
	
	
	def changeComPort(self, portName):
		''' Try to connect to 'portName' '''
		
		if self.commObject().isConnected():
			print('Disconnect from', self.commObject().portName())
		
		if not self.commObject().connectPort(portName):
			self.statusBar().showMessage('Unable to connect {}.'.format(portName), 6000)
			return
		
		# Stream our robot config file
		self.streamConfiguration()
		
		# Update last port
		self.lastComPort = portName
		
		# Check menu action for current port
		for act in self.portsMenu.actions():
			if act.text() == portName:
				act.setChecked(True)
		
		self.statusBar().showMessage('{} connected.'.format(portName), 6000)
		
	
	def serialEvent(self):
		''' JSON objects received '''
		
		def wheelSpeed(value):
			if value == 0: return 0
			if value > 0: return 71-value
			if value < 0: return -71-value
		
		jsonObjs = self.commObject().readAllObjects()
		
		#print('\nJSON data received:')
		for jsonObj in jsonObjs:
			#print(json.dumps(jsonObj, sort_keys=True, indent=2, separators=(',', ': ')))
			
			# Loop over all keys
			for fid in jsonObj:
				# Check if key is in stored functionalities
				if fid in self._objects:
					# Update value
					value = jsonObj[fid]
					
					if fid == 'robotDirWheelRotation':
						self.moveDirWheel(value)
					
					elif fid == 'leftWheel':
						self.setSpeed_LeftWheel(wheelSpeed(value))
					
					elif fid == 'rightWheel':
						self.setSpeed_RightWheel(wheelSpeed(value))
					
					print('Value: ', value, 'for display ', fid)
					
					self.statusBar().showMessage('Value for {} received ({}).'.format(fid, value), 2000)
	
	
	
	def loadSettings(self):
		''' Load settings '''
		se = QSettings(fullPath('wulkabot.conf'), QSettings.IniFormat)
		
		se.beginGroup('commport')
		self.lastComPort = se.value('lastport', '')
		se.endGroup()
		
		
	def saveSettings(self):
		''' Save settings '''
		se = QSettings(fullPath('wulkabot.conf'), QSettings.IniFormat)
		
		se.beginGroup('commport')
		se.setValue('lastport', self.lastComPort)
		se.endGroup()
	
	
	def closeEvent(self, event):
		''' App closing request '''
		self.saveSettings()
		
		self._serialCom.disconnectPort()
		event.accept()