Commit 90227f52 authored by Antoine Lima's avatar Antoine Lima

PiCam replay

parent 35fca230
[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']
...@@ -7,33 +7,32 @@ Created on Wed Apr 18 18:34:40 2018 ...@@ -7,33 +7,32 @@ Created on Wed Apr 18 18:34:40 2018
""" """
import logging import logging
import autopy
import serial import serial
from os.path import dirname, abspath, join, isfile as exists from os.path import isfile as exists
from autopy.key import tap as PressKey, Code as KeyCode from autopy.key import tap as PressKey, Code as KeyCode
from threading import Thread from threading import Thread
from player import Side from player import Side
class InputThread(Thread): class InputThread(Thread):
keyButtonBindings = [KeyCode.ESCAPE, KeyCode.UP_ARROW, KeyCode.LEFT_ARROW, KeyCode.RIGHT_ARROW, KeyCode.DOWN_ARROW, KeyCode.RETURN] keyButtonBindings = [KeyCode.ESCAPE, KeyCode.UP_ARROW, KeyCode.LEFT_ARROW, KeyCode.RIGHT_ARROW, KeyCode.DOWN_ARROW, KeyCode.RETURN]
def __init__(self, dispatcher, side): def __init__(self, dispatcher, side):
Thread.__init__(self) Thread.__init__(self)
self.side = side self.side = side
self.dispatcher = dispatcher self.dispatcher = dispatcher
self.continueRunning = True self.continueRunning = True
self.path = '/dev/ttyUSB0' if self.side==Side.Left else '/dev/ttyUSB1' self.path = '/dev/ttyUSB0' if self.side==Side.Left else '/dev/ttyUSB1'
if exists(self.path): if exists(self.path):
self.arduino = serial.Serial(self.path, 9600, timeout=1) self.arduino = serial.Serial(self.path, 9600, timeout=1)
else: else:
raise RuntimeError('No arduino connected on the {} side'.format(self.side.name.lower())) raise RuntimeError('No arduino connected on the {} side'.format(self.side.name.lower()))
def run(self): def run(self):
while self.arduino.isOpen(): while self.arduino.isOpen():
msg = self.arduino.readline()[:-1] msg = self.arduino.readline()[:-1]
if msg: if msg:
parsedMessage = self.parseMsg(msg) parsedMessage = self.parseMsg(msg)
if 'butn' in parsedMessage: if 'butn' in parsedMessage:
...@@ -46,14 +45,14 @@ class InputThread(Thread): ...@@ -46,14 +45,14 @@ class InputThread(Thread):
def stop(self): def stop(self):
self.continueRunning = False self.continueRunning = False
self.arduino.close() self.arduino.close()
def sendKeyStroke(self, msg): def sendKeyStroke(self, msg):
if 'butn' in msg: if 'butn' in msg:
button = int(msg['butn']) button = int(msg['butn'])
print({button: ['ESCAPE', 'UP_ARROW', 'LEFT_ARROW', 'RIGHT_ARROW', 'DOWN_ARROW', 'RETURN'][button]}) print({button: ['ESCAPE', 'UP_ARROW', 'LEFT_ARROW', 'RIGHT_ARROW', 'DOWN_ARROW', 'RETURN'][button]})
key = InputThread.keyButtonBindings[button] key = InputThread.keyButtonBindings[button]
PressKey(key, []) PressKey(key, [])
def parseMsg(self, msg): def parseMsg(self, msg):
parts = msg.split(':') parts = msg.split(':')
return {parts[0]: parts[1], 'source': self.side} return {parts[0]: parts[1], 'source': self.side}
...@@ -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())
...@@ -55,19 +56,20 @@ class GameModule(Module): ...@@ -55,19 +56,20 @@ class GameModule(Module):
# Button connections # Button connections
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 = Replay(Side.Left)
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
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 +81,18 @@ class GameModule(Module): ...@@ -79,17 +81,18 @@ class GameModule(Module):
logging.debug('Unloading GameModule') logging.debug('Unloading GameModule')
del self.gameStartTime del self.gameStartTime
self.timerUpdateChrono.stop() self.timerUpdateChrono.stop()
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
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 +101,7 @@ class GameModule(Module): ...@@ -98,7 +101,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 +115,54 @@ class GameModule(Module): ...@@ -112,54 +115,54 @@ 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))
replayFile = self.replayer.stop_recording()
if os.path.exists(replayFile):
if True: # Debug Mode if not (Settings['replay.show'] and os.path.exists(replayFile)):
self.updateScores() self.updateScores()
else: else:
self.showingReplay = True self.showingReplay = True
self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.player.stateChanged.connect(self.endOfReplay) self.player.stateChanged.connect(self.endOfReplay)
self.player.setMuted(True) self.player.setMuted(True)
self.player.setVideoOutput(self.ui.videoWidget) self.player.setVideoOutput(self.ui.videoWidget)
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile))) self.player.setMedia(QMediaContent(QUrl.fromLocalFile(replayFile)))
self.player.play() self.player.play()
self.ui.videoWidget.setFullScreen(True) 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()
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)
...@@ -7,6 +7,10 @@ Created on Wed Apr 18 18:34:40 2018 ...@@ -7,6 +7,10 @@ Created on Wed Apr 18 18:34:40 2018
""" """
import os import os
from threading import Event
from multiprocessing import Process, Lock
from main import MainWin
from settings import Settings from settings import Settings
onRasp = os.uname()[1] == 'raspberrypi' onRasp = os.uname()[1] == 'raspberrypi'
...@@ -15,8 +19,17 @@ if onRasp: ...@@ -15,8 +19,17 @@ if onRasp:
import picamera import picamera
class LockableValue():
def __init__(self, value):
self.value = value
self.mutex = Lock()
class Replay(): class Replay():
def __init__(self): def __init__(self, side):
self.recording = LockableValue(False)
self.replayPath = MainWin.getContent('Replay {}.mp4'.format(side.name))
self.stopped = Event()
if onRasp: if onRasp:
self.cam = picamera.PiCamera() self.cam = picamera.PiCamera()
self.cam.resolution = Settings['picam.resolution'] self.cam.resolution = Settings['picam.resolution']
...@@ -24,23 +37,36 @@ class Replay(): ...@@ -24,23 +37,36 @@ class Replay():
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.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 start_recording(self):
if not onRasp:
self.stopped.set()
else:
self.recording.value = True
self.stopped.clear()
self.capture_process = Process(target=self.__capture)
self.capture_process.start()
def stop_recording(self):
self.recording.val = True
self.stopped.wait(timeout=2.0)
return self.replayPath
def capture(self, fileToSave): def capture(self, fileToSave):
if onRasp: if onRasp:
self.cam.start_recording(self.stream, self.format) self.cam.start_recording(self.stream, self.format)
self.continue_recording = True
try: try:
while self.continue_recording: recording = self.recording.val
while recording:
self.cam.wait_recording(1) self.cam.wait_recording(1)
recording = self.recording.val
finally: finally:
self.cam.stop_recording() self.cam.stop_recording()
self.stream.copy_to(fileToSave) self.stopped.set()
self.stream.copy_to(self.replayPath)
self.cam.close() self.cam.close()
self.stream.close() self.stream.close()
def stop():
self.continue_recording = False
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