Commit 9ab100f3 authored by Antoine Lima's avatar Antoine Lima

New package structure (Babyfut as root)

Many changes but mainly changing things like `from ui import ..`
to `from Babyfut.ui import ..`
parent fd0e6b96
...@@ -13,7 +13,7 @@ The UI is done with PyQt5. ...@@ -13,7 +13,7 @@ The UI is done with PyQt5.
* Make sure to have Python>3.5 installed (`sudo apt-get install python3` otherwise) * Make sure to have Python>3.5 installed (`sudo apt-get install python3` otherwise)
* Make sure to have PyQt5 installed (`sudo pip install pyqt5` otherwise) * Make sure to have PyQt5 installed (`sudo pip install pyqt5` otherwise)
* Compile the .ui and .rc files: `./devtools.sh ui` * Compile the .ui and .rc files: `./devtools.sh ui`
* Execute main.py: `python3 babyfut.py` * Execute the software: `./devtools.sh exec`
## Edit the UI ## Edit the UI
The UI is mostly created using Qt-Designer: The UI is mostly created using Qt-Designer:
......
...@@ -17,10 +17,10 @@ from PyQt5.QtWidgets import QMainWindow, QApplication ...@@ -17,10 +17,10 @@ from PyQt5.QtWidgets import QMainWindow, QApplication
def getContent(path): def getContent(path):
contentFolder = join(dirname(dirname(abspath(__file__))), 'content') contentFolder = join(dirname(dirname(abspath(__file__))), 'content')
return join(contentFolder, path) return join(contentFolder, path)
def getMainWin(): def getMainWin():
from ui.mainwin import MainWin from Babyfut.ui.mainwin import MainWin
# Global function to find the (open) QMainWindow in application # Global function to find the (open) QMainWindow in application
for widget in QApplication.instance().topLevelWidgets(): for widget in QApplication.instance().topLevelWidgets():
if isinstance(widget, QMainWindow): if isinstance(widget, QMainWindow):
...@@ -28,46 +28,46 @@ def getMainWin(): ...@@ -28,46 +28,46 @@ def getMainWin():
return None return None
if __name__=='__main__': if __name__=='__main__':
from ui.mainwin import MainWin print(__package__)
from modules import GameModule from Babyfut.ui.mainwin import MainWin
from player import Side from Babyfut.modules import GameModule
from settings import Settings from Babyfut.core.player import Side
from Babyfut.core.settings import Settings
from input import GPIOThread from Babyfut.core.input import GPIOThread
from database import Database from Babyfut.core.database import Database
from replay import Replay as ReplayThread from Babyfut.core.replay import Replay as ReplayThread
try: try:
#logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG) #logging.basicConfig(filename='babyfoot.log', level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
lang = Settings['ui.language'] lang = Settings['ui.language']
qtTranslator = QtCore.QTranslator() qtTranslator = QtCore.QTranslator()
if lang!='en' and qtTranslator.load("translations/babyfut_{}.qm".format(lang)): if lang!='en' and qtTranslator.load("translations/babyfut_{}.qm".format(lang)):
app.installTranslator(qtTranslator) app.installTranslator(qtTranslator)
myapp = MainWin() myapp = MainWin()
if ReplayThread.isCamAvailable(): if ReplayThread.isCamAvailable():
threadReplay = ReplayThread(Side.Left) threadReplay = ReplayThread(Side.Left)
threadReplay.start() threadReplay.start()
myapp.dispatchMessage({'replayThread': threadReplay}, toType=GameModule) myapp.dispatchMessage({'replayThread': threadReplay}, toType=GameModule)
threadGPIO = GPIOThread(myapp) threadGPIO = GPIOThread(myapp)
threadGPIO.start() threadGPIO.start()
myapp.show() myapp.show()
app.exec_() app.exec_()
threadGPIO.stop() threadGPIO.stop()
if ReplayThread.isCamAvailable(): if ReplayThread.isCamAvailable():
threadReplay.stop() threadReplay.stop()
threadReplay.join() threadReplay.join()
threadGPIO.join() threadGPIO.join()
finally: finally:
GPIOThread.clean() GPIOThread.clean()
Database.instance().close() Database.instance().close()
...@@ -11,42 +11,42 @@ class DatabaseError(Exception): ...@@ -11,42 +11,42 @@ class DatabaseError(Exception):
class Database(): class Database():
__db = None __db = None
def __init__(self): def __init__(self):
if not Database.__db: if not Database.__db:
from babyfut import getContent from Babyfut.babyfut import getContent
db_path = getContent('babyfut.sqlite') db_path = getContent('babyfut.sqlite')
self._connection = sqlite3.connect(db_path) self._connection = sqlite3.connect(db_path)
@staticmethod @staticmethod
def instance(): def instance():
''' '''
Singleton Singleton
''' '''
if not Database.__db: if not Database.__db:
Database.__db = Database() Database.__db = Database()
return Database.__db return Database.__db
@property @property
def _cursor(self): def _cursor(self):
return self._connection.cursor() return self._connection.cursor()
def select_one(self, query, *args): def select_one(self, query, *args):
res = self._cursor.execute(query, args).fetchone() res = self._cursor.execute(query, args).fetchone()
if not res: if not res:
raise DatabaseError('Query \"{}\" returned nothing with args {}'.format(query, args)) raise DatabaseError('Query \"{}\" returned nothing with args {}'.format(query, args))
return res return res
def select_guest_team(self): def select_guest_team(self):
return self.select_one('SELECT id FROM players WHERE fname LIKE "guest"')[0] return self.select_one('SELECT id FROM players WHERE fname LIKE "guest"')[0]
def insert_team(self, players, goals): def insert_team(self, players, goals):
if len(players)<2: if len(players)<2:
players.append(None) players.append(None)
self._cursor.execute('INSERT INTO Teams (nGoals, player1, player2) VALUES (?, ?, ?)', (goals, players[0], players[1],)) self._cursor.execute('INSERT INTO Teams (nGoals, player1, player2) VALUES (?, ?, ?)', (goals, players[0], players[1],))
self._connection.commit() self._connection.commit()
return self._cursor.execute('SELECT seq FROM sqlite_sequence WHERE name="Teams"').fetchone()[0] return self._cursor.execute('SELECT seq FROM sqlite_sequence WHERE name="Teams"').fetchone()[0]
...@@ -56,7 +56,7 @@ class Database(): ...@@ -56,7 +56,7 @@ class Database():
self._connection.commit() self._connection.commit()
def select_all_rfid(self, debug=False): def select_all_rfid(self, debug=False):
from settings import Settings from Babyfut.core.settings import Settings
if Settings['app.mode']=='prod': if Settings['app.mode']=='prod':
return self._cursor.execute('SELECT rfid FROM Players WHERE rfid>0').fetchall() return self._cursor.execute('SELECT rfid FROM Players WHERE rfid>0').fetchall()
else: else:
...@@ -65,6 +65,6 @@ class Database(): ...@@ -65,6 +65,6 @@ class Database():
def delete_player(self, playerID): def delete_player(self, playerID):
self._cursor.execute('DELETE FROM Players WHERE id==?', (playerID,)) self._cursor.execute('DELETE FROM Players WHERE id==?', (playerID,))
self._connection.commit() self._connection.commit()
def close(self): def close(self):
self._connection.close() self._connection.close()
...@@ -8,13 +8,12 @@ import logging ...@@ -8,13 +8,12 @@ import logging
import pyautogui # PyPi library import pyautogui # PyPi library
from threading import Thread from threading import Thread
from babyfut import OnRasp from Babyfut.babyfut import OnRasp
from Babyfut.core.player import Side
if OnRasp: if OnRasp:
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
from player import Side
class GPIOThread(Thread): class GPIOThread(Thread):
_keyButtonBindings = { _keyButtonBindings = {
26: 'up', 26: 'up',
...@@ -29,7 +28,7 @@ class GPIOThread(Thread): ...@@ -29,7 +28,7 @@ class GPIOThread(Thread):
Thread.__init__(self) Thread.__init__(self)
self.dispatcher = dispatcher self.dispatcher = dispatcher
self.continueRunning = True self.continueRunning = True
if OnRasp: if OnRasp:
GPIO.setwarnings(False) GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
...@@ -54,10 +53,10 @@ class GPIOThread(Thread): ...@@ -54,10 +53,10 @@ class GPIOThread(Thread):
key = GPIOThread._keyButtonBindings[button_pin] key = GPIOThread._keyButtonBindings[button_pin]
logging.debug('Sending {} as {}'.format(button_pin, key)) logging.debug('Sending {} as {}'.format(button_pin, key))
pyautogui.press(key) pyautogui.press(key)
def stop(self): def stop(self):
self.continueRunning = False self.continueRunning = False
@staticmethod @staticmethod
def clean(): def clean():
if OnRasp: if OnRasp:
......
...@@ -8,12 +8,13 @@ Created on Wed Apr 18 18:34:40 2018 ...@@ -8,12 +8,13 @@ Created on Wed Apr 18 18:34:40 2018
import logging import logging
from enum import Enum from enum import Enum
from database import Database, DatabaseError
from PyQt5.QtCore import Qt, QCoreApplication from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtWidgets import QDialog from PyQt5.QtWidgets import QDialog
from ui.consent_dialog_ui import Ui_Dialog as ConsentDialogUI from Babyfut.babyfut import getMainWin
from Babyfut.core.database import Database, DatabaseError
from Babyfut.ui.consent_dialog_ui import Ui_Dialog as ConsentDialogUI
class Side(Enum): class Side(Enum):
''' '''
...@@ -22,17 +23,17 @@ class Side(Enum): ...@@ -22,17 +23,17 @@ class Side(Enum):
Undef = -1 Undef = -1
Left = 0 Left = 0
Right = 1 Right = 1
@property @property
def opposite(self): def opposite(self):
return Side.Right if self==Side.Left else Side.Left return Side.Right if self==Side.Left else Side.Left
class ConsentDialog(QDialog): class ConsentDialog(QDialog):
def __init__(self, parent): def __init__(self, parent):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.ui = ConsentDialogUI() self.ui = ConsentDialogUI()
self.ui.setupUi(self) self.ui.setupUi(self)
self.ui.txtConsent.setHtml(QCoreApplication.translate('consent', '''<p> self.ui.txtConsent.setHtml(QCoreApplication.translate('consent', '''<p>
You are about to connect yourself for the first time. We will need to access: You are about to connect yourself for the first time. We will need to access:
<ul> <ul>
...@@ -40,29 +41,29 @@ class ConsentDialog(QDialog): ...@@ -40,29 +41,29 @@ class ConsentDialog(QDialog):
<li>Your Picture (if public)</li> <li>Your Picture (if public)</li>
<li>...</li> <li>...</li>
</ul> </ul>
</p> </p>
<p> <p>
It is possible to play withtout connecting yourslef, but this will allow you to keep track of your score and to provide a better experience for you and the ones you play with! It is possible to play withtout connecting yourslef, but this will allow you to keep track of your score and to provide a better experience for you and the ones you play with!
<br/><br/> <br/><br/>
Do you agree with this? Do you agree with this?
</p>''')) </p>'''))
def keyPressEvent(self, e): def keyPressEvent(self, e):
if e.key()==Qt.Key_Return: if e.key()==Qt.Key_Return:
self.accept() self.accept()
else: else:
self.reject() self.reject()
class Player(): class Player():
__query_infos = 'SELECT id, fname, lname, pic FROM Players WHERE rfid==?' __query_infos = 'SELECT id, fname, lname, pic FROM Players WHERE rfid==?'
__query_time_goals_games = 'SELECT SUM(Matchs.duration) AS timePlayed, SUM(Teams.nGoals) AS goalsScored, COUNT(*) AS gamesPlayed FROM Teams INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam OR Teams.id==Matchs.losingTeam) WHERE (Teams.player1==? OR player2==?)' __query_time_goals_games = 'SELECT SUM(Matchs.duration) AS timePlayed, SUM(Teams.nGoals) AS goalsScored, COUNT(*) AS gamesPlayed FROM Teams INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam OR Teams.id==Matchs.losingTeam) WHERE (Teams.player1==? OR player2==?)'
__query_victories = 'SELECT COUNT(*) AS victories FROM Players INNER JOIN Teams ON (Players.id==Teams.player1 OR Players.id==Teams.player2) INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam) WHERE Players.id==?' __query_victories = 'SELECT COUNT(*) AS victories FROM Players INNER JOIN Teams ON (Players.id==Teams.player1 OR Players.id==Teams.player2) INNER JOIN Matchs ON (Teams.id==Matchs.winningTeam) WHERE Players.id==?'
_placeholder_pic_path = ':ui/img/placeholder_head.jpg' _placeholder_pic_path = ':ui/img/placeholder_head.jpg'
_first_time = True # Debug _first_time = True # Debug
def __init__(self, id, rfid, fname, lname, pic_path, stats): def __init__(self, id, rfid, fname, lname, pic_path, stats):
self.id = id self.id = id
self.rfid = rfid self.rfid = rfid
...@@ -70,52 +71,60 @@ class Player(): ...@@ -70,52 +71,60 @@ class Player():
self.lname = lname self.lname = lname
self.pic_path = pic_path if pic_path else Player._placeholder_pic_path # Default pic if None self.pic_path = pic_path if pic_path else Player._placeholder_pic_path # Default pic if None
self.stats = stats self.stats = stats
@staticmethod @staticmethod
def fromRFID(rfid): def fromRFID(rfid):
db = Database.instance() db = Database.instance()
try: try:
# Retrieve generic informations # Retrieve generic informations
id, fname, lname, pic = db.select_one(Player.__query_infos, rfid) id, fname, lname, pic = db.select_one(Player.__query_infos, rfid)
# Ask for consent # Ask for consent
if rfid==-2 and Player._first_time: if rfid==-2 and Player._first_time:
import babyfut consentDialog = ConsentDialog(getMainWin())
consentDialog = ConsentDialog(babyfut.getMainWin())
consentDialog.exec() consentDialog.exec()
if not consentDialog.result()==QDialog.Accepted: if not consentDialog.result()==QDialog.Accepted:
Player._first_time = False Player._first_time = False
return PlayerGuest return PlayerGuest
# Retrieve stats # Retrieve stats
stats = {} stats = {}
stats['time_played'], stats['goals_scored'], stats['games_played'] = db.select_one(Player.__query_time_goals_games, id, id) stats['time_played'], stats['goals_scored'], stats['games_played'] = db.select_one(Player.__query_time_goals_games, id, id)
stats['victories'], = db.select_one(Player.__query_victories, id) stats['victories'], = db.select_one(Player.__query_victories, id)
for key, val in stats.items(): for key, val in stats.items():
if val==None: if val==None:
stats[key] = 0 stats[key] = 0
return Player(id, rfid, fname, lname, pic, stats) return Player(id, rfid, fname, lname, pic, stats)
except DatabaseError as e: except DatabaseError as e:
logging.warn('DB Error: {}'.format(e)) logging.warn('DB Error: {}'.format(e))
return PlayerGuest return PlayerGuest
def displayImg(self, containerWidget): def displayImg(self, container_widget):
containerWidget.setStyleSheet('border-image: url({});'.format(self.pic_path)) self.pic_container = container_widget
if self.pic_path.startswith('http'):
# Download from the internet
container_widget.setStyleSheet('border-image: url({});'.format(Player._placeholder_pic_path))
else:
# Already downloaded and stored locally
container_widget.setStyleSheet('border-image: url({});'.format(self.pic_path))
def save(self): def save(self):
''' '''
Update or create the player in database Update or create the player in database
''' '''
pass
# DONT SAVE PIC PATH, IT IS CHANGED FOR THE LOCAL PATH
@property @property
def name(self): def name(self):
return '{} {}'.format(self.fname, self.lname.upper()) return '{} {}'.format(self.fname, self.lname.upper())
@property @property
def stats_property(self): def stats_property(self):
''' '''
...@@ -129,9 +138,9 @@ class Player(): ...@@ -129,9 +138,9 @@ class Player():
self.time_played = stats['time_played'] self.time_played = stats['time_played']
self.goals_scored = stats['goals_scored'] self.goals_scored = stats['goals_scored']
self.games_played = stats['games_played'] self.games_played = stats['games_played']
return Stat(self.stats) return Stat(self.stats)
@staticmethod @staticmethod
def allStoredPlayers(): def allStoredPlayers():
return [Player.fromRFID(rfid) for rfid, in Database.instance().select_all_rfid()] return [Player.fromRFID(rfid) for rfid, in Database.instance().select_all_rfid()]
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
from threading import Thread, Event from threading import Thread, Event
from babyfut import getContent, OnRasp from Babyfut.babyfut import getContent, OnRasp
from settings import Settings from Babyfut.core.settings import Settings
if OnRasp: if OnRasp:
import picamera import picamera
...@@ -17,7 +17,7 @@ class Replay(Thread): ...@@ -17,7 +17,7 @@ class Replay(Thread):
Thread.__init__(self) Thread.__init__(self)
self.replayPath = getContent('Replay {}.mp4'.format(side.name)) self.replayPath = getContent('Replay {}.mp4'.format(side.name))
self.shutdown = False self.shutdown = False
self.start_flag = Event() self.start_flag = Event()
self.stop_flag = Event() self.stop_flag = Event()
self.stopped_flag = Event() self.stopped_flag = Event()
...@@ -33,12 +33,12 @@ class Replay(Thread): ...@@ -33,12 +33,12 @@ class Replay(Thread):
def start_recording(self): def start_recording(self):
if OnRasp: if OnRasp:
self.start_flag.set() self.start_flag.set()
def stop_recording(self): def stop_recording(self):
if OnRasp: if OnRasp:
self.stop_flag.set() self.stop_flag.set()
self.stopped_flag.wait() self.stopped_flag.wait()
# Clear all control flags # Clear all control flags
self.stop_flag.clear() self.stop_flag.clear()
self.start_flag.clear() self.start_flag.clear()
...@@ -49,20 +49,20 @@ class Replay(Thread): ...@@ -49,20 +49,20 @@ class Replay(Thread):
def stop(self): def stop(self):
self.start_flag.set() self.start_flag.set()
self.shutdown = True self.shutdown = True
def run(self): def run(self):
while not self.shutdown: while not self.shutdown:
self.start_flag.wait() self.start_flag.wait()
if not self.shutdown: if not self.shutdown:
self.cam.start_recording(self.stream, Settings['picam.format']) self.cam.start_recording(self.stream, Settings['picam.format'])
try: try:
while not self.stop_flag.is_set(): while not self.stop_flag.is_set():
self.cam.wait_recording(1) self.cam.wait_recording(1)
finally : finally :
self.cam.stop_recording() self.cam.stop_recording()
self.stream.copy_to(self.replayPath) self.stream.copy_to(self.replayPath)
self.stream.clear() self.stream.clear()
...@@ -71,11 +71,11 @@ class Replay(Thread): ...@@ -71,11 +71,11 @@ class Replay(Thread):
self.cam.close() self.cam.close()
self.stream.close() self.stream.close()
@classmethod @classmethod
def Dummy(cls): def Dummy(cls):
return getContent('Replay Left.mp4') return getContent('Replay Left.mp4')
@staticmethod @staticmethod
def isCamAvailable(): def isCamAvailable():
return OnRasp # and other checks (ToDo) return OnRasp # and other checks (ToDo)
...@@ -8,7 +8,7 @@ import json ...@@ -8,7 +8,7 @@ import json
class MyEncoder(json.JSONEncoder): class MyEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
if isinstance(obj, Setting): if isinstance(obj, Setting):
return obj.__dict__ return obj.__dict__
elif isinstance(obj, String) and obj=='settingsPath': elif isinstance(obj, String) and obj=='settingsPath':
return None return None
...@@ -17,65 +17,65 @@ class MyEncoder(json.JSONEncoder): ...@@ -17,65 +17,65 @@ class MyEncoder(json.JSONEncoder):
class Setting(object): class Setting(object):
TypeName = '' TypeName = ''
def __init__(self, value): def __init__(self, value):
self.type = type(self).TypeName self.type = type(self).TypeName
self.value = value self.value = value
class SettingBoolean(Setting): class SettingBoolean(Setting):
TypeName = 'boolean' TypeName = 'boolean'