...
 
Commits (3)
......@@ -4,3 +4,4 @@
*/*.log
*/*_ui.py
*/*_rc.py
translations/*.qm
# source-rasp
Source code for the foosball embedded Raspberry PI. It runs the UI and manages games.
Source code for the foosball embedded Raspberry PI.
It runs the UI and manages games.
The UI is done with PyQt5.
## Dependencies
* PyQt5
* pyserial
* autopy
* pyautogui
* qtmultimedia5-examples
## Launch
* 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)
* Compile the .ui and .rc files: `./compile_resources.sh`
* Execute main.py: `python3 main.py`
* Compile the .ui and .rc files: `./devtools.sh ui`
* Execute main.py: `python3 babyfut.py`
## Edit the UI
The UI is mostly created using Qt-Designer:
* Download qt4-designer (`sudo apt-get install qt4-designer`)
* Edit .ui files with qt4-designer
* Compile the .ui in a python file: `./compile_resources.sh`
* Compile the .ui in a python file: `./devtool.sh ui`
* Done!
## Sofware Organisation
### UI
The UI is only made of one window with static widgets and changing "modules".
There are currently 3 modules:
There are currently 7 modules:
* Main menu, showed on startup and giving access to other panels
* Game, containing the scores, timers, etc of the current game
* Options, to allow the user tp configure some options
* Authentification for two players
* ...
* Leaderboard, for comparing players scores, times, etc + delete itself from the database
* Options, to allow the user to configure some options(language, max score, etc)
* Authentification for 2 to 4 players in party mode
* Authentification for n players in league mode
* Endgame, giving the winner(s) and storing results in the db
Those inherit from the `Module` class and are composed of 5 things:
* Access to the main window, through the `parent_win` member
......@@ -44,6 +46,11 @@ The widget contained by each module are stacked in a QStackedWidget and only one
To get from one to the other, the current module is unloaded current, then the new one is shown and loaded.
Each module can overload the load/unload/other method in order to have a specific behavior.
### Threads
In addition to the main UI thread, there are 2 other threads running:
* Input, managing buttons control, rfid reading and goal detection
* Replay, capturing video during a match and storing it when there is a goal
### Settings
Settings are stored in a JSON file located in the parent's `content` directory, that looks like:
```
......@@ -95,12 +102,23 @@ Settings['ui.fullscreen'] = False
duration = Settings['replay.duration']
```
### Replays
Replays are 5-10 seconds clips played when a goal is scored. Those are stored in the parent's `content` directory under the name `replay0.mp4` and `replay1.mp4`.
For the time being, those are provided by the PI camera module.
Note that for potential security reasons, this file is not synchronized with git, but is downloadable on the PR's GDrive
### DB
### Replays
Replays are 5-10 seconds clips played when a goal is scored. Those are stored in the parent's `content` directory under the name `Replay Left.mp4` and `Replay Right.mp4`.
Those are provided by the PI camera module, thanks to another thread that records the video while a game takes place and stores it once a goal is marked.
## DB
The database is currently in SQLite but should be transposable to MySQL later on. There are 3 tables:
* Players: Storing player information (name, rfid, etc)
* Matches: Storing matches start/stop times and winners/losers
* Teams: Linking players and matches
## Todo
* [ ] Add the possibility to stop the replay mid-video by pushing any button
* [ ] Check validity of replays on a real rasp
* [ ] Check validity of rfid on a real rasp
* [ ] League mode
* [ ] Translation on the fly (instead of having to manually restart)
* [ ] Installation guide
* [ ] Connection to another PI to exchange RFID and videos
* [ ] Integration of the real UTC DB
......@@ -11,7 +11,7 @@ import sys
import logging
from os.path import dirname, abspath, join
from PyQt5 import QtWidgets
from PyQt5 import QtWidgets, QtCore
def getContent(path):
contentFolder = join(dirname(dirname(abspath(__file__))), 'content')
......@@ -21,6 +21,7 @@ if __name__=='__main__':
from ui.mainwin import MainWin
from modules import GameModule
from player import Side
from settings import Settings
from input import GPIOThread
from database import Database
......@@ -31,6 +32,11 @@ if __name__=='__main__':
logging.basicConfig(level=logging.DEBUG)
app = QtWidgets.QApplication(sys.argv)
lang = Settings['ui.language']
qtTranslator = QtCore.QTranslator()
if lang!='en' and qtTranslator.load("translations/babyfut_{}.qm".format(lang)):
app.installTranslator(qtTranslator)
myapp = MainWin()
if ReplayThread.isCamAvailable():
......
#! /bin/sh
rm ui/*_ui.py
rm ui/*_rc.py
rm -r __pycache__
rm -r ui/__pycache__
rm -r modules/__pycache__
#! /bin/sh
# Modules
pyuic5 --import-from=ui ui/main.ui -o ui/main_ui.py
pyuic5 --import-from=ui ui/menu.ui -o ui/menu_ui.py
pyuic5 --import-from=ui ui/game.ui -o ui/game_ui.py
pyuic5 --import-from=ui ui/endgame.ui -o ui/endgame_ui.py
pyuic5 --import-from=ui ui/options.ui -o ui/options_ui.py
pyuic5 --import-from=ui ui/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=ui ui/authleague.ui -o ui/authleague_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
# Custom widgets
pyuic5 --import-from=ui ui/playerlist.ui -o ui/playerlist_ui.py
pyuic5 --import-from=ui ui/delete_dialog.ui -o ui/delete_dialog_ui.py
pyrcc5 -root /ui ui/assets.qrc -o ui/assets_rc.py
......@@ -14,7 +14,7 @@ class Database():
def __init__(self):
if not Database.__db:
from main import getContent
from babyfut import getContent
db_path = getContent('babyfut.sqlite')
self._connection = sqlite3.connect(db_path)
......
#! /bin/bash
WD=$(pwd)
cd "$( dirname "${BASH_SOURCE[0]}" )"
case "$1" in
"ui"|"update_ui"|"build_ui")
echo "Building UI..."
echo " Modules"
pyuic5 --import-from=ui ui/main.ui -o ui/main_ui.py
pyuic5 --import-from=ui ui/menu.ui -o ui/menu_ui.py
pyuic5 --import-from=ui ui/game.ui -o ui/game_ui.py
pyuic5 --import-from=ui ui/endgame.ui -o ui/endgame_ui.py
pyuic5 --import-from=ui ui/options.ui -o ui/options_ui.py
pyuic5 --import-from=ui ui/authquick.ui -o ui/authquick_ui.py
pyuic5 --import-from=ui ui/authleague.ui -o ui/authleague_ui.py
pyuic5 --import-from=ui ui/leaderboard.ui -o ui/leaderboard_ui.py
echo " Custom Widgets"
pyuic5 --import-from=ui ui/playerlist.ui -o ui/playerlist_ui.py
pyuic5 --import-from=ui ui/delete_dialog.ui -o ui/delete_dialog_ui.py
echo " Resources"
pyrcc5 -root /ui ui/assets.qrc -o ui/assets_rc.py
echo "Done."
;;
"clean"|"clear")
echo "Clearing the project..."
rm -f ui/*_ui.py
rm -f ui/*_rc.py
rm -f translations/*.qm
rm -rf __pycache__
rm -rf ui/__pycache__
rm -rf modules/__pycache__
echo "Done."
;;
"tru"|"trupdate")
echo "Updating translation files..."
pylupdate5 -verbose *.py modules/*.py ui/*.py -ts translations/babyfut_fr.ts
echo "Done. You can modify generated .ts files with Qt Linguist"
;;
"trr"|"trrelease")
echo "Building translation files..."
lrelease translations/*
echo "Done."
;;
"all"|"build_all")
bash ./devtools.sh "ui"
bash ./devtools.sh "tru"
bash ./devtools.sh "trr"
;;
"allc"|"build_all_clean")
bash ./devtools.sh "clean"
bash ./devtools.sh "all"
;;
*)
echo "Unknown command \"$1\". See content of script for available commands."
;;
esac
cd $WD
......@@ -8,7 +8,7 @@ import logging
import pyautogui # PyPi library
from threading import Thread
from main import OnRasp
from babyfut import OnRasp
if OnRasp:
import RPi.GPIO as GPIO
......
......@@ -37,6 +37,8 @@ class AuthLeagueModule(AuthModuleBase):
self.players = {Side.Left: l, Side.Right: l}
def addPlayer(self, side, player):
_translate = QtCore.QCoreApplication.translate
# Add the player if not already in the list
if all([p.id!=player.id for p in self.players[side]]):
if player!=PlayerEmpty:
......@@ -45,9 +47,9 @@ class AuthLeagueModule(AuthModuleBase):
# Update the left side description
player.displayImg(self.ui.img)
self.ui.lblName.setText(player.name)
self.ui.lblStat1.setText('{} Victories'.format(player.stats['victories']))
self.ui.lblStat2.setText('{} Games Played'.format(player.stats['games_played']))
self.ui.lblStat3.setText('{} Goals Scored'.format(player.stats['goals_scored']))
self.ui.lblStat1.setText(_translate('{} Victories').format(player.stats['victories']))
self.ui.lblStat2.setText(_translate('{} Games Played').format(player.stats['games_played']))
self.ui.lblStat3.setText(_translate('{} Goals Scored').format(player.stats['goals_scored']))
if player!=PlayerEmpty:
# Update the right side list, making sure that the added player is showed
......
......@@ -38,7 +38,6 @@ class LeaderboardItemWidget(QWidget):
class DeleteDialog(QDialog):
def __init__(self, parent, player):
print('DeleteDialog {}'.format(player.name))
QDialog.__init__(self, parent)
self.ui = PlayerDeleteDialog()
self.ui.setupUi(self)
......@@ -105,7 +104,7 @@ class LeaderboardModule(Module):
else:
self.players = Player.allPlayers()
self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=True)
self.players.sort(key=attrgetter(self.sortMethodAttr[self.selectedSort]), reverse=(self.sortMethodAttr[self.selectedSort]!='lname'))
for player in self.players:
item = QListWidgetItem()
......
......@@ -6,7 +6,7 @@
from threading import Thread, Event
from main import getContent, OnRasp
from babyfut import getContent, OnRasp
from settings import Settings
if OnRasp:
......
......@@ -117,5 +117,5 @@ class SettingsHolder(object):
self.settingsPath = settingsPath
from main import getContent
from babyfut import getContent
Settings = SettingsHolder(getContent('settings.json'))
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0" language="fr_FR" sourcelanguage="en">
<context>
<name>Dialog</name>
<message>
<location filename="../ui/delete_dialog_ui.py" line="47"/>
<source>Dialog</source>
<translation>Supression</translation>
</message>
<message>
<location filename="../ui/delete_dialog_ui.py" line="48"/>
<source>Deleting {}&apos;s profile</source>
<translation>Supression du profil de {}</translation>
</message>
<message>
<location filename="../ui/delete_dialog_ui.py" line="49"/>
<source>Pass your badge to validate this...</source>
<translation>Passez votre badge pour valider la supression...</translation>
</message>
</context>
<context>
<name>Form</name>
<message>
<location filename="../ui/playerlist_ui.py" line="94"/>
<source>Form</source>
<translation>Form</translation>
</message>
<message>
<location filename="../ui/authquick_ui.py" line="231"/>
<source>Identify yourselves! </source>
<translation>Identifiez vous ! </translation>
</message>
<message>
<location filename="../ui/authquick_ui.py" line="232"/>
<source>Use the table&apos;s RFID reader with your card.</source>
<translation>Utilisez les lecteurs RFID de la table avec votre carte étu.</translation>
</message>
<message>
<location filename="../ui/playerlist_ui.py" line="95"/>
<source>Name</source>
<translation>Nom</translation>
</message>
<message>
<location filename="../ui/authleague_ui.py" line="147"/>
<source>Stat</source>
<translation>Stat</translation>
</message>
<message>
<location filename="../ui/endgame_ui.py" line="104"/>
<source>Player 1</source>
<translation>Joueur 1</translation>
</message>
<message>
<location filename="../ui/endgame_ui.py" line="105"/>
<source>Player 2</source>
<translation>Joueur 2</translation>
</message>
<message>
<location filename="../ui/authquick_ui.py" line="235"/>
<source>vs.</source>
<translation>vs .</translation>
</message>
<message>
<location filename="../ui/authquick_ui.py" line="236"/>
<source>Player 3</source>
<translation>Joueur 3</translation>
</message>
<message>
<location filename="../ui/authquick_ui.py" line="237"/>
<source>Player 4</source>
<translation>Joueur 4</translation>
</message>
<message>
<location filename="../ui/endgame_ui.py" line="103"/>
<source>Congratulations!</source>
<translation>Félicitations !</translation>
</message>
<message>
<location filename="../ui/game_ui.py" line="79"/>
<source>0</source>
<translation>0</translation>
</message>
<message>
<location filename="../ui/menu_ui.py" line="122"/>
<source>Leaderboard</source>
<translation>Classements</translation>
</message>
<message>
<location filename="../ui/leaderboard_ui.py" line="109"/>
<source>Sort By</source>
<translation>Trier Par</translation>
</message>
<message>
<location filename="../ui/leaderboard_ui.py" line="111"/>
<source>Victories</source>
<translation>Victoires</translation>
</message>
<message>
<location filename="../ui/leaderboard_ui.py" line="112"/>
<source>Score</source>
<translation>Score</translation>
</message>
<message>
<location filename="../ui/leaderboard_ui.py" line="113"/>
<source>Games Played</source>
<translation>Parties Jouées</translation>
</message>
<message>
<location filename="../ui/leaderboard_ui.py" line="114"/>
<source>Time Played</source>
<translation>Temps Joué</translation>
</message>
<message>
<location filename="../ui/leaderboard_ui.py" line="115"/>
<source>Jump to</source>
<translation>Sauter à</translation>
</message>
<message>
<location filename="../ui/menu_ui.py" line="119"/>
<source>Babyf&apos; UT</source>
<translation>Babyf&apos;UT</translation>
</message>
<message>
<location filename="../ui/menu_ui.py" line="120"/>
<source>Start Quick Game</source>
<translation>Partie Rapide</translation>
</message>
<message>
<location filename="../ui/menu_ui.py" line="121"/>
<source>Start League Mode</source>
<translation>Championnat</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="219"/>
<source>Options</source>
<translation>Options</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="220"/>
<source>Game Over Condition</source>
<translation>Condition de Fin de Jeu</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="221"/>
<source>By Score</source>
<translation>Par Score</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="222"/>
<source>By Time</source>
<translation>Par Temps</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="223"/>
<source>{} minutes</source>
<translation>{} minutes</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="224"/>
<source>League - Number of players per side</source>
<translation>Championnat - Nombre de joueurs par coté</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="225"/>
<source>1</source>
<translation>1</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="226"/>
<source>2</source>
<translation>2</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="227"/>
<source>Language</source>
<translation>Langage</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="228"/>
<source>English</source>
<translation>English</translation>
</message>
<message>
<location filename="../ui/playerlist_ui.py" line="96"/>
<source>Surname</source>
<translation>Prénom</translation>
</message>
<message>
<location filename="../ui/options_ui.py" line="229"/>
<source>Fran&#xe7;ais</source>
<translation type="obsolete">Français</translation>
</message>
<message>
<location filename="../ui/playerlist_ui.py" line="97"/>
<source>####&#xa0;Victories</source>
<translation type="obsolete">#### Victoires</translation>
</message>
<message>
<location filename="../ui/playerlist_ui.py" line="98"/>
<source>####&#xa0;Goals Scored</source>
<translation type="obsolete">#### Buts Marqués</translation>
</message>
<message>
<location filename="../ui/playerlist_ui.py" line="99"/>
<source>####&#xa0;Games Played</source>
<translation type="obsolete">#### Parties Jouées</translation>
</message>
<message>
<location filename="../ui/playerlist_ui.py" line="100"/>
<source>#### Minutes Played</source>
<translation>#### Minutes Jouées</translation>
</message>
<message encoding="UTF-8">
<location filename="../ui/options_ui.py" line="229"/>
<source>Français</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
<location filename="../ui/playerlist_ui.py" line="97"/>
<source>#### Victories</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
<location filename="../ui/playerlist_ui.py" line="98"/>
<source>#### Goals Scored</source>
<translation type="unfinished"></translation>
</message>
<message encoding="UTF-8">
<location filename="../ui/playerlist_ui.py" line="99"/>
<source>#### Games Played</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../ui/main_ui.py" line="83"/>
<source>Babyfoot</source>
<translation>Babyf&apos;UT</translation>
</message>
</context>
</TS>