Commit d1f058e6 authored by Antoine Lima's avatar Antoine Lima

Merge branch 'dev' of gitlab.utc.fr:PR-Baby-A18/source-rasp into dev

parents a09cf6ff f82fda56
[codestyle]
indentation = True
[main]
version = 0.1.0
[encoding]
text_encoding = utf-8
[main]
version = 0.1.0
[vcs]
use_version_control = False
version_control_system =
[main]
version = 0.1.0
[workspace]
restore_data_on_startup = True
save_data_on_exit = True
save_history = True
save_non_project_files = False
[main]
version = 0.1.0
recent_files = ['/home/antoine/prog/PR/source-rasp/main.py', '/home/antoine/prog/PR/source-rasp/modules/game.py', '/home/antoine/prog/PR/content/settings.json', '/home/antoine/prog/PR/source-rasp/replay.py', '/home/antoine/prog/PR/source-rasp/com.py']
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import logging
import autopy
import serial
from os.path import dirname, abspath, join, isfile as exists
from autopy.key import tap as PressKey, Code as KeyCode
from threading import Thread
from player import Side
class InputThread(Thread):
keyButtonBindings = [KeyCode.ESCAPE, KeyCode.UP_ARROW, KeyCode.LEFT_ARROW, KeyCode.RIGHT_ARROW, KeyCode.DOWN_ARROW, KeyCode.RETURN]
def __init__(self, dispatcher, side):
Thread.__init__(self)
self.side = side
self.dispatcher = dispatcher
self.continueRunning = True
self.path = '/dev/ttyUSB0' if self.side==Side.Left else '/dev/ttyUSB1'
if exists(self.path):
self.arduino = serial.Serial(self.path, 9600, timeout=1)
else:
raise RuntimeError('No arduino connected on the {} side'.format(self.side.name.lower()))
def run(self):
while self.arduino.isOpen():
msg = self.arduino.readline()[:-1]
if msg:
parsedMessage = self.parseMsg(msg)
if 'butn' in parsedMessage:
self.sendKeyStroke(parsedMessage)
else:
self.dispatcher.dispatchMessage(parsedMessage)
else:
logging.warn('No message read on Arduino {}'.format(self.side.name))
def stop(self):
self.continueRunning = False
self.arduino.close()
def sendKeyStroke(self, msg):
if 'butn' in msg:
button = int(msg['butn'])
print({button: ['ESCAPE', 'UP_ARROW', 'LEFT_ARROW', 'RIGHT_ARROW', 'DOWN_ARROW', 'RETURN'][button]})
key = InputThread.keyButtonBindings[button]
PressKey(key, [])
def parseMsg(self, msg):
parts = msg.split(':')
return {parts[0]: parts[1], 'source': self.side}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 18 18:34:40 2018
@author: Antoine Lima, Leo Reynaert, Domitille Jehenne
"""
import logging
import pyautogui # PyPi library
from threading import Thread
import RPi.GPIO as GPIO
from player import Side
class GPIOThread(Thread):
_keyButtonBindings = {
26: 'up',
22: 'left',
27: 'right',
23: 'down',
17: 'return',
18: 'escape'
}
def __init__(self, dispatcher):
Thread.__init__(self)
self.dispatcher = dispatcher
self.continueRunning = True
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
for pin in GPIOThread._keyButtonBindings.keys():
print(pin)
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(pin, GPIO.RISING, callback=self.handleButtonPress)
def run(self):
try:
while self.continueRunning:
pass
finally:
GPIOThread.clean()
def handleButtonPress(self, button_pin):
if button_pin not in GPIOThread._keyButtonBindings.keys():
logging.warn('Unknown button pin: {}'.format(button_pin))
else:
key = GPIOThread._keyButtonBindings[button_pin]
logging.debug('Sending {} as {}'.format(button_pin, key))
pyautogui.press(key)
def stop(self):
self.continueRunning = False
@staticmethod
def clean():
GPIO.cleanup()
...@@ -18,7 +18,7 @@ from PyQt5.QtCore import QTime, Qt ...@@ -18,7 +18,7 @@ from PyQt5.QtCore import QTime, Qt
from ui.main_ui import Ui_MainWindow from ui.main_ui import Ui_MainWindow
from modules import * from modules import *
from player import Side from player import Side
from com import InputThread from input import GPIOThread
class MainWin(QtWidgets.QMainWindow): class MainWin(QtWidgets.QMainWindow):
def __init__(self, parent=None): def __init__(self, parent=None):
...@@ -66,8 +66,12 @@ class MainWin(QtWidgets.QMainWindow): ...@@ -66,8 +66,12 @@ class MainWin(QtWidgets.QMainWindow):
mod_idx = [i for i, x in enumerate(self.modules) if isinstance(x, type)] mod_idx = [i for i, x in enumerate(self.modules) if isinstance(x, type)]
return -1 if len(mod_idx)==0 else mod_idx[0] return -1 if len(mod_idx)==0 else mod_idx[0]
def dispatchMessage(self, msg, toAll=False): def dispatchMessage(self, msg, toType=None, toAll=False):
modulesIdx = self.modules if toAll else [self.findMod(type(self.ui.panels.currentWidget()))] if toType!=None:
modulesIdx = [self.findMod(toType)]
else:
modulesIdx = self.modules if toAll else [self.findMod(type(self.ui.panels.currentWidget()))]
for modIdx in modulesIdx: for modIdx in modulesIdx:
self.modules[modIdx].other(**msg) self.modules[modIdx].other(**msg)
...@@ -88,24 +92,33 @@ class MainWin(QtWidgets.QMainWindow): ...@@ -88,24 +92,33 @@ class MainWin(QtWidgets.QMainWindow):
if __name__=='__main__': if __name__=='__main__':
from settings import Settings from settings import Settings
from replay import Replay as ReplayThread
#logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG) try:
logging.basicConfig(level=logging.DEBUG) #logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv)
myapp = MainWin() app = QtWidgets.QApplication(sys.argv)
myapp = MainWin()
if Settings['app.mode']!='dev':
threadArduinoLeft = InputThread(myapp, Side.Left) if ReplayThread.isCamAvailable():
#threadArduinoRight = InputThread(myapp, Side.Right) threadReplay = ReplayThread(Side.Left)
threadArduinoLeft.start() threadReplay.start()
#threadArduinoRight.start() myapp.dispatchMessage({'replayThread': threadReplay}, toType=GameModule)
myapp.show() threadGPIO = GPIOThread(myapp)
app.exec_() threadGPIO.start()
if Settings['app.mode']!='dev': myapp.show()
threadArduinoLeft.stop() app.exec_()
#threadArduinoRight.stop()
threadArduinoLeft.join() threadGPIO.stop()
#threadArduinoRight.join()
if ReplayThread.isCamAvailable():
threadReplay.stop()
threadReplay.join()
threadGPIO.join()
finally:
GPIOThread.clean()
...@@ -18,6 +18,7 @@ from PyQt5.QtMultimediaWidgets import QVideoWidget ...@@ -18,6 +18,7 @@ from PyQt5.QtMultimediaWidgets import QVideoWidget
from player import Side, PlayerGuest from player import Side, PlayerGuest
from replay import Replay from replay import Replay
from module import Module from module import Module
from settings import Settings
import modules import modules
from ui.game_ui import Ui_Form as GameWidget from ui.game_ui import Ui_Form as GameWidget
...@@ -25,25 +26,25 @@ class GameOverChecker(): ...@@ -25,25 +26,25 @@ class GameOverChecker():
def __init__(self, conditionType, limit): def __init__(self, conditionType, limit):
self.conditionType = conditionType self.conditionType = conditionType
self.limit = limit self.limit = limit
def check(self, time, scores): def check(self, time, scores):
''' '''
Checks if a game is over and return the winner if that's the case Checks if a game is over and return the winner if that's the case
Returns the winning side or Side.Undef otherwise Returns the winning side or Side.Undef otherwise
Takes the game time is seconds and a list containing the two scores Takes the game time is seconds and a list containing the two scores
''' '''
# Gets the index of the highest scoring player # Gets the index of the highest scoring player
bestPlayer = max(scores, key=scores.get) bestPlayer = max(scores, key=scores.get)
if self.conditionType=='score' and scores[bestPlayer]>=self.limit: if self.conditionType=='score' and scores[bestPlayer]>=self.limit:
return bestPlayer return bestPlayer
elif self.conditionType=='time' and time>self.limit: elif self.conditionType=='time' and time>self.limit:
return bestPlayer return bestPlayer
else: else:
return Side.Undef return Side.Undef
class GameModule(Module): class GameModule(Module):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent, GameWidget()) super().__init__(parent, GameWidget())
...@@ -56,18 +57,20 @@ class GameModule(Module): ...@@ -56,18 +57,20 @@ class GameModule(Module):
self.ui.btnScore1.clicked.connect(lambda: self.goal(Side.Left)) self.ui.btnScore1.clicked.connect(lambda: self.goal(Side.Left))
self.ui.btnScore2.clicked.connect(lambda: self.goal(Side.Right)) self.ui.btnScore2.clicked.connect(lambda: self.goal(Side.Right))
self.replayer = Replay() self.replayer = None
def load(self): def load(self):
logging.debug('Loading GameModule') logging.debug('Loading GameModule')
self.gameStartTime = QTime.currentTime() self.gameStartTime = QTime.currentTime()
self.timerUpdateChrono.start(1000) self.timerUpdateChrono.start(1000)
self.ui.lcdChrono.display(QTime(0,0).toString("hh:mm:ss")) self.ui.lcdChrono.display(QTime(0,0).toString("hh:mm:ss"))
self.showingReplay = False self.showingReplay = False
if self.replayer:
self.replayer.start_recording()
self.gameoverChecker = GameOverChecker('score', 10) self.gameoverChecker = GameOverChecker('score', 10)
if all([len(val)==0 for val in self.players.values()]): if all([len(val)==0 for val in self.players.values()]):
self.players[Side.Left ].append(PlayerGuest) self.players[Side.Left ].append(PlayerGuest)
self.players[Side.Right].append(PlayerGuest) self.players[Side.Right].append(PlayerGuest)
...@@ -79,17 +82,23 @@ class GameModule(Module): ...@@ -79,17 +82,23 @@ class GameModule(Module):
logging.debug('Unloading GameModule') logging.debug('Unloading GameModule')
del self.gameStartTime del self.gameStartTime
self.timerUpdateChrono.stop() self.timerUpdateChrono.stop()
if self.replayer:
self.replayer.stop_recording()
def other(self, **kwargs): def other(self, **kwargs):
logging.debug('Other GameModule') logging.debug('Other GameModule')
for key, val in kwargs.items(): for key, val in kwargs.items():
if key=='goal' and 'source' in kwargs: if key=='goal' and 'source' in kwargs:
self.goal(kwargs['source']) self.goal(kwargs['source'])
elif key=='players': elif key=='players':
self.players = val self.players = val
elif key=='replayThread':
self.replayer = val
def resizeEvent(self, event): def resizeEvent(self, event):
# 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin) # 40% of the window width to have (5% margin)-(40% circle)-(10% middle)-(40% circle)-(5% margin)
btnDiameter = self.mainwin.width()*0.4 btnDiameter = self.mainwin.width()*0.4
...@@ -98,7 +107,7 @@ class GameModule(Module): ...@@ -98,7 +107,7 @@ class GameModule(Module):
self.ui.btnScore2.setMinimumSize(btnDiameter, btnDiameter) self.ui.btnScore2.setMinimumSize(btnDiameter, btnDiameter)
self.ui.btnScore1.setMask(region) self.ui.btnScore1.setMask(region)
self.ui.btnScore2.setMask(region) self.ui.btnScore2.setMask(region)
QtWidgets.QWidget.resizeEvent(self, event) QtWidgets.QWidget.resizeEvent(self, event)
def keyPressEvent(self, e): def keyPressEvent(self, e):
...@@ -112,54 +121,58 @@ class GameModule(Module): ...@@ -112,54 +121,58 @@ class GameModule(Module):
def updateChrono(self): def updateChrono(self):
# Updated each second # Updated each second
self.ui.lcdChrono.display(QTime(0,0).addSecs(self.getGameTime()).toString("hh:mm:ss")) self.ui.lcdChrono.display(QTime(0,0).addSecs(self.getGameTime()).toString("hh:mm:ss"))
# Don't check scores while showing a replay to avoid closing the engame screen too soon # Don't check scores while showing a replay to avoid closing the engame screen too soon
if not self.showingReplay: if not self.showingReplay:
self.checkEndGame() self.checkEndGame()
def getGameTime(self): def getGameTime(self):
return self.gameStartTime.secsTo(QTime.currentTime()) return self.gameStartTime.secsTo(QTime.currentTime())
def updateScores(self): def updateScores(self):
self.ui.btnScore1.setText(str(self.scores[Side.Left])) self.ui.btnScore1.setText(str(self.scores[Side.Left]))
self.ui.btnScore2.setText(str(self.scores[Side.Right])) self.ui.btnScore2.setText(str(self.scores[Side.Right]))
self.checkEndGame() self.checkEndGame()
def goal(self, side): def goal(self, side):
if side not in Side: if side not in Side:
logging.error('Wrong goal side: {}'.format(side)) logging.error('Wrong goal side: {}'.format(side))
else: else:
self.scores[side] += 1 self.scores[side] += 1
# Show replay # Show replay
# May require `sudo apt-get install qtmultimedia5-examples` in order to install the right libraries # May require `sudo apt-get install qtmultimedia5-examples` in order to install the right libraries
replayFile = self.mainwin.getContent('Replay {}.mp4'.format(side.name))
if os.path.exists(replayFile): if self.replayer:
if True: # Debug Mode replayFile = self.replayer.stop_recording()
self.updateScores()
else: if not (self.replayer and Settings['replay.show'] and os.path.exists(replayFile)):
self.showingReplay = True self.updateScores()
self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) else:
self.player.stateChanged.connect(self.endOfReplay) self.showingReplay = True
self.player.setMuted(True) self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.player.setVideoOutput(self.ui.videoWidget) self.player.stateChanged.connect(self.endOfReplay)
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile))) self.player.setMuted(True)
self.player.play() self.player.setVideoOutput(self.ui.videoWidget)
self.ui.videoWidget.setFullScreen(True) self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile)))
self.player.play()
self.ui.videoWidget.setFullScreen(True)
def endOfReplay(self, status): def endOfReplay(self, status):
if status!=QMediaPlayer.PlayingState: if status!=QMediaPlayer.PlayingState:
self.ui.videoWidget.setFullScreen(False); self.ui.videoWidget.setFullScreen(False);
self.showingReplay = False self.showingReplay = False
self.updateScores() self.updateScores()
if self.replayer:
self.replayer.start_recording()
def handleCancel(self): def handleCancel(self):
self.switchModule(modules.MenuModule) self.switchModule(modules.MenuModule)
def checkEndGame(self): def checkEndGame(self):
winSide = self.gameoverChecker.check(self.getGameTime(), self.scores) winSide = self.gameoverChecker.check(self.getGameTime(), self.scores)
if winSide!=Side.Undef: if winSide!=Side.Undef:
self.send(modules.EndGameModule, players=self.players, winSide=winSide, scores=self.scores, time=self.getGameTime()) self.send(modules.EndGameModule, players=self.players, winSide=winSide, scores=self.scores, time=self.getGameTime())
self.switchModule(modules.EndGameModule) self.switchModule(modules.EndGameModule)
...@@ -12,8 +12,9 @@ from PyQt5.QtWidgets import QApplication ...@@ -12,8 +12,9 @@ from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from module import Module
import modules import modules
from module import Module
from settings import Settings
from ui.menu_ui import Ui_Form as MenuWidget from ui.menu_ui import Ui_Form as MenuWidget
from player import Side from player import Side
...@@ -42,7 +43,7 @@ class MenuModule(Module): ...@@ -42,7 +43,7 @@ class MenuModule(Module):
self.ui.btnStartQuick.animateClick() self.ui.btnStartQuick.animateClick()
def keyPressEvent(self, e): def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape: if e.key() == Qt.Key_Escape and Settings['app.mode']=='dev':
self.handleExit() self.handleExit()
elif e.key() == Qt.Key_Up: elif e.key() == Qt.Key_Up:
......
...@@ -7,6 +7,9 @@ Created on Wed Apr 18 18:34:40 2018 ...@@ -7,6 +7,9 @@ Created on Wed Apr 18 18:34:40 2018
""" """
import os import os
from threading import Thread, Event
from main import MainWin
from settings import Settings from settings import Settings
onRasp = os.uname()[1] == 'raspberrypi' onRasp = os.uname()[1] == 'raspberrypi'
...@@ -14,33 +17,63 @@ onRasp = os.uname()[1] == 'raspberrypi' ...@@ -14,33 +17,63 @@ onRasp = os.uname()[1] == 'raspberrypi'
if onRasp: if onRasp:
import picamera import picamera
class Replay(Thread):
def __init__(self, side):
Thread.__init__(self)
self.replayPath = MainWin.getContent('Replay {}.mp4'.format(side.name))
self.shutdown = False
self.start_flag = Event()
self.stop_flag = Event()
self.stopped_flag = Event()
class Replay():
def __init__(self):
if onRasp: if onRasp:
self.cam = picamera.PiCamera() self.cam = picamera.PiCamera()
self.cam.resolution = Settings['picam.resolution'] self.cam.resolution = Settings['picam.resolution']
self.cam.framerate = Settings['picam.fps'] self.cam.framerate = Settings['picam.fps']
self.cam.hflip = Settings['picam.hflip'] self.cam.hflip = Settings['picam.hflip']
self.cam.vflip = Settings['picam.vflip'] self.cam.vflip = Settings['picam.vflip']
self.format = Settings['picam.format']
self.continue_recording = False
self.stream = picamera.PiCameraCircularIO(self.cam, seconds=Settings['replay.duration']) self.stream = picamera.PiCameraCircularIO(self.cam, seconds=Settings['replay.duration'])
def capture(self, fileToSave): def start_recording(self):
if onRasp:
self.start_flag.set()
def stop_recording(self):
if onRasp: if onRasp:
self.cam.start_recording(self.stream, self.format) self.stop_flag.set()
self.continue_recording = True self.stopped_flag.wait()
try: self.stop_flag.clear()
while self.continue_recording: self.start_flag.clear()
self.cam.wait_recording(1) self.stopped_flag.clear()
finally:
self.cam.stop_recording() return self.replayPath
def stop(self):
self.start_flag.set()
self.shutdown = True
def run(self):
while not self.shutdown:
self.start_flag.wait()
if not self.shutdown:
self.cam.start_recording(self.stream, Settings['picam.format'])
try:
while not self.stop_flag.is_set():
self.cam.wait_recording(1)
self.stream.copy_to(fileToSave) finally :
self.cam.close() self.cam.stop_recording()
self.stream.close()
self.stream.copy_to(self.replayPath)
self.stream.clear()
self.stopped_flag.set()
def stop(): self.cam.close()
self.continue_recording = False self.stream.close()
@staticmethod
def isCamAvailable():
return onRasp # and other checks (ToDo)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment