def run(self): """ Méthode principale qui sera exécuter sur un nouveau thread. Se charge d'établir une connexion à l'automate et de récupérer la vitesse courante toutes les `SLEEP_TIME_MS` millisecondes. En cas d'erreur, un pause de `SLEEP_ON_ERROR_MS` millisecondes est prise avant de recommencer (depuis le début, création de la connexion incluse). """ self.db = Database(self.db_location) try: self._connect() while True: # Récupération de la vitesse ts_before = time.time() mondon_speed = self._get_speed() # Sauvegarde la nouvelle vitesse ts = int(round(time.time() * 1000)) self._save_speed(ts, mondon_speed) # Calcule combien de temps on a mis à récupérer et sauvegarder la vitesse ts_after = time.time() delay = ts_after - ts_before # Pause pendant SpeedThread.SLEEP_TIME_MS (moins le temps qu'il a fallu pour # récupérer la vitesse et la sauvegarder dans la base de données). self.msleep(max(0, SpeedThread.SLEEP_TIME_MS - delay)) except Exception as e: self.ERROR_SIGNAL.emit(str(e)) logger.log("SPEED_THREAD", "Erreur: {}".format(e)) self.msleep(SpeedThread.SLEEP_ON_ERROR_MS) self.run()
def _receive_from_automate(self): """ Attends un message de l'automate :return: Retourne le message reçu de l'automate. Retourne un string vide si rien n'a été reçu. """ response = self.socket.recv(1024) logger.log("SPEED_THREAD", "Reçu de l'automate: {}".format(response)) return response
def _send_to_automate(self, command): """ Envoi une commande à l'automate :param command: Commande a envoyer à l'automate """ logger.log( "SPEED_THREAD", 'Envoi de la commande {} ({})'.format(command.description, command.hex)) self.socket.sendall(command.binary)
def _get_speed(self): """ Simule la récupération d'une nouvelle vitesse en générant une valeur aléatoire. Simule une erreur de manière aléatoire 1% du temps. :return: """ if randint(0, 100) == 1: raise Exception("Simulation d'une erreur!!") random_speed = randint(0, 185) logger.log( "SPEED_THREAD", "Génération d'une vitesse aléatoire ({})".format(random_speed)) return random_speed
def _get_speed(self): """ Récupère la vitesse courante de l'automate. :return: la vitesse de l'automate. """ self._send_to_automate(GET_SPEED) response = self._receive_from_automate() if not response: raise Exception( "Réponse invalide après tentative de récupération de " "la vitesse de l'automate: {}".format(response)) mondon_speed = int.from_bytes(response[-4:], byteorder="big") logger.log("SPEED_THREAD", "Nouvelle vitesse reçue: {}".format(mondon_speed)) return mondon_speed
def insert_speed(self, value, time): """ Insert une nouvelle vitesse dans la base de données :param value: Valeur de la vitesse à insérer :param time: Temps à lequel la vitesse a été reçu """ try: self._run_query("INSERT INTO mondon_speed VALUES (?, ?)", (time, value)) except sqlite3.IntegrityError as e: # IntegrityError veut dire que l'on essaye d'insérer une vitesse avec un timestamp # qui existe déjà dans la base de données. # Dans ce cas, on considère que cette valeur n'a pas besoin d'être insérée et on # ignore l'exception. logger.log("DATABASE", "(Ignorée) IntegrityError: {}".format(e)) pass
def _run_query(self, query, args): """ Exécute une requête sur la base de données :param query: Requête SQL à exécuter :param args: Paramètre de la requête à exécuter :return: Un array avec le résultat de la requête. Retourne un tableau vide pour les CREATE et INSERT """ logger.log("DATABASE", "Requête: {} - Paramêtres: {}".format(query, args)) data = None attempt = 0 while attempt < Database.MAX_ATTEMPT_ON_ERROR: if attempt > 0: sleep(Database.SLEEP_ON_ERROR_MS / 1000) # Pause entre 2 tentatives logger.log( "DATABASE", "(Tentative #{}) Requête: {} - Paramêtres: {}".format( attempt + 1, query, args)) try: cursor = self.conn.cursor() cursor.execute(query, args) self.conn.commit() data = cursor.fetchall() break except sqlite3.OperationalError as e: # OperationalError veut généralement dire que la base de données est locked ou # de manière générale qu'une erreur s'est produite lors de la lecture du fichier # où la base de données est stockée. logger.log("DATABASE", "OperationalError: {}".format(e)) attempt += 1 except sqlite3.DatabaseError as e: if e.__class__.__name__ == "DatabaseError": # DatabaseError veut généralement dire qu'une erreur grave s'est produite. # En générale, cela veut dire que la base de données est corrompue et l'on ne # peut pas faire grand chose. On essaye quand même de s'en sortir en recréant # la connexion à la base de données. logger.log("DATABASE", "DatabaseError: {}".format(e)) attempt += 1 self._init_db_connection() # Si l'exception n'est pas directement une DatabaseError (ex: une sous class de # DatabaseError comme IntegrityError), on abandonne directement. else: raise e # Dans le cas où on a consommé tous les essais possible, on génère une erreur if attempt >= Database.MAX_ATTEMPT_ON_ERROR: raise Exception( "Abandon de la requête {} avec les paramètres {}. Une erreur s'est" "produite à chacun des {} essais".format( query, args, Database.MAX_ATTEMPT_ON_ERROR)) return data
def _save_speed(self, ts, speed): """ Gère l'insertion de la nouvelle vitesse dans la base de données et signal le résultat. :param speed_value: Nouvelle vitesse :param ts: Millitimestamp de quand on a reçu la vitesse """ logger.log( "SPEED_THREAD", "Insert vitesse {} au temps {}".format( speed, SpeedThread.timestamp_to_date(ts))) try: self.db.insert_speed(speed, ts) self.NEW_SPEED_SIGNAL.emit(speed, ts) except Exception as e: self.ERROR_SIGNAL.emit(str(e)) logger.log( "SPEED_THREAD", "Erreur lors de l'insertion dans la base de données: {}". format(e))
def _init_socket(self): """ Crée une nouvelle socket (et ferme la socket courante si il y en a une) et connecte la à l'automate. """ logger.log("SPEED_THREAD", "Initialization de la socket") if self.socket: logger.log("SPEED_THREAD", "Socket déjà initialisée, fermeture de la socket") self.socket.close() logger.log("SPEED_THREAD", "Création d'une socket") self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) logger.log( "SPEED_THREAD", "Connexion à {}:{}".format(self.automate_ip, self.automate_port)) self.socket.connect((self.automate_ip, self.automate_port))
def _init_db_connection(self): logger.log( "DATABASE", "Connection à la base de données {}".format( self.database_location)) self.conn = sqlite3.connect(self.database_location)
def _connect(self): """ Simule la connexion à l'automate. Ne fait rien à part un log. """ logger.log("SPEED_THREAD", "Simulation d'une connexion à l'automate")
import sys from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication from objct.main_window import MainWindow from objct.speed_thread import SpeedThread, SpeedThreadSimulator SIMULATOR_ON = True # Définit si l'on simule la connexion à l'automate DB_LOCATION = '../mondon.db' AUTOMATE_IP = '192.168.0.50' AUTOMATE_PORT = 9600 logger.log( "INITIALISATION", "Création de la QApplication avec les paramètres: {}".format(sys.argv)) app = QApplication(sys.argv) logger.log("INITIALISATION", "Définition de l'icone de l'application") app.setWindowIcon(QIcon('icon/logo_get_speed.ico')) logger.log("INITIALISATION", "Création de MainWindow") window = MainWindow() logger.log("INITIALISATION", "Configuration de MainWindow") window.setFixedSize(400, 50) window.setWindowTitle("Get speed") logger.log("INITIALISATION", "Affichage de MainWindow") window.show()
first_summary = tr.create_summary() while True: latest_summary = tr.create_summary() tr.print_diff(summary1=first_summary, summary2=latest_summary) self_use = resource.getrusage( resource.RUSAGE_SELF).ru_maxrss / (1000 * 1000) print('Memory usage: {} Mb'.format(self_use)) sleep(10) Thread(target=memory_usage_thread_func).start() logger.log_app_start() logger.log( "INITIALISATION", "Création de la QApplication avec les paramètres: {}".format(sys.argv)) app = QApplication(sys.argv) logger.log("INITIALISATION", "Définition de l'icone de l'application") app.setWindowIcon(QIcon('icon/logo_get_speed.ico')) logger.log("INITIALISATION", "Initialisation de Database") db = Database('../../mondon.db') logger.log("INITIALISATION", "Création de MainWindow") window = MainWindow(database=db) logger.log("INITIALISATION", "Configuration de MainWindow") window.setFixedSize(250, 50) window.setWindowTitle("Get speed")
def log_fn(log_text): logger.log('DB_STRESS_TEST', '{} | {}'.format(thread_name.ljust(10), log_text))