1. Introduction

Ce projet a un objectif simple : transformer les pénibles réveils en un pure instant de plaisir.

En effet, les réveils de nos jours sonnent de manière bruyante et cela n’est pas une bonne façon de commencer une journée. Il faut donc repenser cet objet. Son fonctionnement, ses fonctionnalités, etc. Un réveil intelligent : un réveil 2.0

Il devra être capable de décider par lui même l’heure du réveil (en prenant en considération les horaires, les professeurs absents et l’agenda Google pour les jours de congé). Il faut aussi redéfinir la manière de réveiller. En effet le bouton « snooze » devra disparaître et immédiatement réveiller l’utilisateur (c’est en effet une grande source d’arrivée tardives le lundi matin, les fameuses « juste 5 Minutes de plus ») avec de la musique notamment. Ensuite l’utilisateur sera appelé (avec une voix) à résoudre un petit défi qui le maintiendra éveillé quelques minutes. Par exemple l'écran dit à l'utilisateur de bouger le joystick vers la droite, ainsi l'utilisateur exécute ce mouvement.

N’est-ce pas une meilleure façon de se réveiller ?

L’enjeu de ce projet sera la conception de ce réveil 2.0 du point de vue physique et du point de vue logiciel. Ces deux éléments devront fonctionner main dans la main.

Voici une première idée de l'objectif à atteindre

croquis.jpeg Figure 1 - Croquis initial du réveil 2.0

2. Matériel et méthodes

2.1 Matériel

  • Raspberry Pi
  • Haut-Parleur Jack
  • Écran LCD
  • Joystick
  • ~20 cables
  • 4 résistances de 330 Ω
  • LED
  • MCP3008

2.2 Méthode

Comme tout bon projet informatique, il faut procéder par étapes.

2.2.1 Processus de réveil

Le processus de réveil comprends la partie où le réveil sonne jusqu'à son extinction. Ce processus doit donc lancer la musique et afficher le petit jeu expliqué en introduction (avec le Joystick...) Ainsi après avoir terminé ce jeu la musique s'éteindra et l'écran aussi.

2.2.1.1 Spotify

Il faut pouvoir écouter de la musique au réveil. Partie hardware c'est très simple : simplement brancher un petit haut parleur sur la prise jack du Raspberry.

Par contre partie software c'est plus long :

Spotify ne fournis pas directement un moyen simple de faire cela. Il faut donc mettre en place un serveur MPD (Music Player Daemon). Mopidy en est un. Ils servent à écouter du contenu audio de diverses sources (fichiers, urls, etc.) et ils sont contrôlables par des clients MPD.

2.2.1.2 Mise en place de Mopidy

Ajouter la clé d'archivage GPG :

 $ sudo wget https://apt.mopidy.com/mopidy.gpg
 $ sudo apt-key add mopidy.gpg

Installer Mopidy

 $ sudo apt-get install mopidy

Ensuite il faut ajouter une extension à ce serveur : l'extension Spotify pour Mopidy :

 $ sudo apt-get install mopidy-spotify

Il faut avoir un compte premium Spotify et créer les tokens/ids à l'adresse https://developer.spotify.com/dashboard/applications

Désormais il faut connecter ce compte au serveur :

 $ cd /home/pi/.config/modipy
 $ nano modipy.conf

Ajouter à la fin du fichier :

 [spotify]
 username = username ici
 password = mot de passe ici
 client_id = client id ici
 client_secret =  secret ici

Pour lancer le serveur MPD, simplement :

 $ mopidy

2.2.1.3 Contrôle en Python

Il existe plusieurs types de clients MPD certains sont des interfaces graphiques, etc. Il existe mpd2 une bibliothèque pour Python :

 $ pip install python-mpd2

Et ensuite j'ai créé un objet qui va s'occuper de cette tâche:

  from mpd import MPDClient

  class Player(object):
    """C'est notre player"""
    def __init__(self):
        self._client = MPDClient()
        self._client.timeout = 10
        self._client.idletimeout = None
        self._client.connect('localhost', 6600)

    def startMusic(self):
        for uri in self._getSongsURIs():
		self._client.add(str(uri))
        self._client.random(1)
        self._client.play()

    def stopMusic(self):
        self._client.pause()
	self._client.clear()

    def _getSongsURIs(self):
        file = open('songsToWakeUp.txt', 'r')
        URIs = file.read().split('\n')
        return URIs

Le fichier songsToWakeUp.txt a une petite liste de chansons (les URIs de Spotify) qui seront envoyées au serveur Mopidy et jouées aléatoirement.

2.2.1.4 Écran LCD

shemaecran.png Figure 2 - Schéma de connections de l'écran

Après avoir fait toutes ces connections, pour pouvoir commander cet écran LCD 16x2 nous devons installer :

 $ git clone https://github.com/adafruit/Adafruit_Python_CharLCD.git
 $ cd ./Adafruit_Python_CharLCD
 $ sudo python setup.py install

Puis on crée la classe Display dont on peut commander le fonctionnement avec l'interface (les méthodes public) :

# -*- coding: utf-8 -*-
import Adafruit_CharLCD as LCD
import datetime

class Display(object):
    def __init__(self):
        lcd_rs = 25
        lcd_en = 24
        lcd_d4 = 23
        lcd_d5 = 17
        lcd_d6 = 18
        lcd_d7 = 22
        lcd_backlight = 4
        lcd_columns = 16
        lcd_rows = 2

        self._lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7,
                           lcd_columns, lcd_rows, lcd_backlight)
        self.turnOffBacklight()
                           
    def display(self, l1, l2):
        self._lcd.clear()
        self._lcd.message(l1+'\n'+l2)
    
    def displayCurrentTime(self):
        time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
        self.display('   Reveil 2.0', time)
        
    def turnOnBacklight(self):
        self._lcd.set_backlight(1)
        
    def turnOffBacklight(self):
        self._lcd.set_backlight(0)

2.2.1.5 Joystick

Le joystick est en fait une combinaison de deux petits potentiomètres. C'est donc des données analogiques que le Raspberry Pi devra lire. Par défaut il ne peut pas faire cela : il faut un chip qui fait ce passage de analogique au digital. Il s'appelle le MCP3008. Voici donc les connections à faire :

shemapucejoystic.png Figure 3 - Schéma des connections : MCP3008 et Joystick

La classe Joystick ci-dessous s'occupe de lire la position du Joystick et à l'aide de la méthode readPosition() nous pouvons connaître la position du Joystick.

import Adafruit_GPIO.SPI as SPI
import Adafruit_MCP3008

class Joystick(object):

    def __init__(self):
        SPI_PORT = 0
        SPI_DEVICE = 0
        self._mcp = Adafruit_MCP3008.MCP3008(spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE))
    
    def readPosition(self):
        # Read all the ADC channel values in a list.
        adcValues = [0,0,0,0,0,0,0,0]
        for i in range(8):
            adcValues[i] = self._mcp.read_adc(i)
        return self._devine(adcValues)
    
    def _devine(self, adcValues):
        if adcValues[1] > 900:
            return 3 #gauche
        if adcValues[1] < 10:
            return 1 #droite
        if adcValues[2] > 900:
            return 2 #bas
        if adcValues[2] < 10:
            return 0 #haut
        return -1 # initial

2.2.1.6 Led Multi-Colore

import RPi.GPIO as GPIO
import time as time

class Led(object):

    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(12, GPIO.OUT)#red
        GPIO.setup(27, GPIO.OUT)#green
        GPIO.setup(13, GPIO.OUT)#blue
        self._setColor(0, 0, 0)

    def _setColor(r, g, b):
        gpioR = GPIO.HIGH if r == 1 else GPIO.LOW
        gpioG = GPIO.HIGH if g == 1 else GPIO.LOW
        gpioB = GPIO.HIGH if b == 1 else GPIO.LOW
        GPIO.output(12, gpioR)
        GPIO.output(27, gpioG)
        GPIO.output(13, gpioB)
        
    def blinkRed(self):
        self._setColor(1, 0, 0)
        time.sleep(0.6)
        self._setColor(0, 0, 0)
    
    def blinkBlue(self):
        self._setColor(0, 0, 1)
        time.sleep(0.6)
        self._setColor(0, 0, 0)

La led est réglable en RGB avec des valeurs de 1 ou 0. (nous n'avons pas besoin des ~16 millions de couleurs différentes).

2.2.1.7 Ringtone

Nous avons désormais toutes les pièces pour créer le processus de réveil dit auparavant. Ringtone est exactement celui qui permet de faire cela :

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
import time
import random
import os
from WakeUpCalculator import WakeUpCalculator
from Joystick import Joystick
from Display import Display
from Player import Player
from Led import Led

class Ringtone(object):
    '''Object that starts song, and with a game to turn it off'''
    def __init__(self):
        self._player = Player()
        self._display = Display()
        self._joystick = Joystick()
        self._led = Led()
        
        self._desiredMove = 0 # by default first move to do is "Up"
        self._i = 0 # nbr exercice
        
    def startWakingUp(self):
        self._startedGame()
        
        previousValue = -1
        while True:
            current = self._joystick.readPosition()
            if previousValue != current:
                previousValue = current
                
                if current != -1:
                # here we finnally have user's move.
                    if current == self._desiredMove:
                        self._i+=1
                        if self._i == 20:
                            self._finishedGame()
                            break
                        else :
                            self._goodMoveDone()
                    else :
                        self._badMoveDone()
            time.sleep(0.1)
                 
    def _goodMoveDone(self):
        self._led.blinkBlue()
        self._desiredMove = random.randint(0, 3)
        direction = self._getMovesWord(self._desiredMove)
        self._display.display(str(self._i)+'/20 fait', 'Bougez '+direction)
        
    def _badMoveDone(self):
        self._led.blinkRed()
        self._display.display('Erreur bad direction', 'Bougez '+self._getMovesWord(self._desiredMove))
        
    def _getMovesWord(self, moveNbr):
        switcher = {
        0:'Haut',
        1:'Droite',
        2:'Bas',
        3:'Gauche'
        }
        return switcher.get(moveNbr, 'error')
        
    def _startedGame(self):
        os.system("espeak 'Hello, come play to turn off the alarm clock'")
        self._player.startMusic()
        self._display.display('Joue ce jeu :)', 'Bougez Haut')
        self._display.turnOnBacklight()
        
    def _finishedGame(self):
        self._player.stopMusic()
        self._display.display('Bonjour !', 'Bonne journee')
        time.sleep(1.5)
        self._display.turnOffBacklight()

La méthode importante est startWakingUp(). En effet celle-ci appelle une méthode startedGame() et ensuite lit la position du Joystick en continu. S'il y a eu un mouvement de Joystick, Ringtone va comparer le mouvement avec le mouvement attendu pour choisir entre : afficher l'erreur et demander de refaire le mouvement ou tirer au hasard un prochain mouvement attendu et l'afficher. L'utilisateur devra réussir 20 fois pour que le programme "sorte" de startWakingUp.

2.2.2 Déterminer l'heure du réveil

Ce réveil autonomie ne nécessite aucun réglage : il est entièrement autonome. Il faut donc qu'il détermine l'heure du réveil en fonction des horaires habituelles, des professeurs absents (ecran.gyre.ch) et de l'agenda Google dans lequel les jours de congés seront écrits avec un #CONGE.

2.2.2.1 Horaires habituelles

Après maintes réflexions voici la manière finale : Il faut stocker d'une manière ou d'une autre les horaires avec les professeurs du cours. De plus nous devons connaître les heures de début de cour de chaque période.

Alors on commence par créer une classe Periode qui a comme attributs le nom du professeur et l'heure du début de période. Une méthode intéressante dans cet objet est "isSameTeacherAs", il permettra de tester (depuis l'extérieur) si le nom d'un professeur est associé au nom du professeur qui donne le cour à cette période-ci.

from difflib import SequenceMatcher
from datetime import time

class Periode(object):
    '''teacher & hour of a lesson'''

    def __init__(self, teacher, indexInDay):
        self._teacher = teacher
        self._beginTime = self._indexOfDayToBeginTime(indexInDay)

    def _indexOfDayToBeginTime(self, indexInDay):
        switcher = {
        0: time(8, 15, 0),
        1: time(9, 10, 0),
        2: time(10, 15, 0),
        3: time(11, 10, 0),
        }
        return switcher.get(indexInDay, "error")
       
    def isSameTeacherAs(self, textTeacher):
        '''test if they are the same teacher or not'''
        similarity = SequenceMatcher(a=self._teacher,b=textTeacher).ratio()
        return True if similarity > 0.90 else False

    def getBeginTime(self):
        return self._beginTime

Finalement ce que l'on veut faire c'est d'instance un objet Horaire et de pouvoir lui demander de "getTodayPeriodes". La classe Horaire est :

from Periode import Periode
import datetime

class Horaire(object):
    ''' horaires normales '''
    
    def __init__(self):
        lines = open('horaire.txt').readlines()
        monday = self._initPeriodes(lines[0:4])
        tuesday = self._initPeriodes(lines[4:8])
        wednesday = self._initPeriodes(lines[8:12])
        thursday = self._initPeriodes(lines[12:16])
        friday = self._initPeriodes(lines[16:20])
        
        self._horaire = [monday, tuesday, wednesday, thursday, friday]
        
    def _initPeriodes(self, lstProf) :
        '''from a list of names, create a list of object Periode. -> for a day(to 12h00)'''
        periodes = []
        for i, name in enumerate(lstProf):
            name = name.replace('\n', '')# undesired character
            periodes.append(Periode(name, i))
        return periodes
 
    def getTodayPeriodes(self):
        nbrDay = datetime.datetime.today().weekday() # 0 : monday, 6 : sunday
        return self._horaire[nbrDay]

Le fichier horaire.txt est de cette structure :

NomProfesseur 1ère période lundi
NomProfesseur 2ème période lundi
NomProfesseur 3ème période lundi
NomProfesseur 4ème période lundi
etc.
NomProfesseur 4ème période vendredi

avec juste le nom des professeurs ligne après ligne. Dont l'ordre défini la i-ème période de la journée. Notez que j'ai choisi d'écrire uniquement les 4 périodes du matin (en effet le réveil ne va pas sonner l'après-midi :) )

2.2.2.2 Professeurs absents

Le site internet ecran.gyre.ch informe aux élèves des professeurs absents. Le but ici est donc d'extraire cette information et de la modéliser avec des objets pour la rendre manipulable.

import requests
from datetime import datetime, date
from Absence import Absence

class EcranGyre(object):
    ''' ecran.gyre.ch '''
    
    def __init__(self):
        file = open('password.txt','r')
        self._username = file.readline()[:-1]
        self._password = file.readline()[:-1]
  
    def getTodayAbsences(self):
        todayList = []
        todayDate = datetime.today().isoformat()
        
        for a in self._getAbsences():
            if a.getDate() == todayDate :
                todayList.append(a)
        return todayList

    def _getAbsences(self):
        self._gyreMessage = self._refreshFromWebsite()
        
    	lines = self._gyreMessage.split('\n')
    	absenceList = []
    	for line in lines:
            if line.strip():# select only lines that have somethin in it...
                absenceList.append(Absence(line))
                
        return absenceList
     
    def _refreshFromWebsite(self):
        session = requests.Session()
        response = session.get('http://ecran.gyre.ch/login?u='+self._username+'&p='+self._password)
        r = session.get('https://ecran.gyre.ch/sources/absences')
        r.content.decode('ISO-8859-1')
        return r.text.replace('<br>', '').encode('utf-8')

le fichier password.txt contient les identifiants pour accéder aux informations du site internet (la première ligne c'est l'email, et la deuxième c'est le mot de passe) On voit que cet EcranGyre va simplement s'occuper de la partie requête et instance des objets Absence qui s'occupent quant à eux d'extraire l'information. Voici un exemple de contenu reçu :

Mme Nicole Kaech (allemand), absente le mercredi 16 janvier 2019, le matin.
Mme Valérie Roten (sociologie des médias), absente du mercredi 16 au jeudi 17 janvier 2019.
Mme Géraldine Voelke-Viscardi (latin et histoire de l'art), absente le mercredi 16 janvier 2019.
M. Manuel Zenger (biologie), absent le mercredi 16 janvier 2019, l'après-midi. 

J'ai remarqué que les cas simples sont écrits sous cette forme :

 #nomProfesseur (branches), absent-e #date, le matin/apèrs-midi/toute la journée

Voici comme Absence se débrouille avec ça :

import re
import dateparser

class Absence(object):
    '''Objet qui formatte a partir de la ligne du site web un objet avec des variables accessibles'''

    def __init__(self, text):
        self._teacher = self._extractTeacher(text)
        self._dayTxt = self._extractDayTxt(text)
        self._dayMoment = self._extractMoment(text) # matin 0/après-midi 1/toute la journée 2

    def _extractTeacher(self, text):
        result = re.search('(.*) \(', text)
        return result.group(1)

    def _extractDayTxt(self, text):
        result = re.search(' le(.*)', text)
        if result != None :
	    puredatefrench = result.group(1).split(',', 1)[0]
            return puredatefrench
        return None

    def _extractMoment(self, text):
        if text.find('matin') != -1:
            return 0
        elif text.find('midi') != -1:
            return 1
        return 2
    
    def getDate(self):
        if self._dayTxt != None :
            return dateparser.parse(self._dayTxt).date()
        return ''

La seule méthode dite publique est getDate(). Il va convertir dayTxt (de la forme "mercredi 16 janvier 2019") la date en version "humaine" en date ordinateur/"python". La bibliothèque dateparser s'occupe de ça.

 $ pip install dateparser

2.2.2.3 Jours de congé

Personnellement j'inscrit les jours de congé sur mon agenda Google. Il serait génial que ce réveil puisse accéder à cet agenda (ça éviterait que l'on se lève pour aller en cours et se rendre compte qu'il y avait congé :) )

GoogleAgenda s'occupe de ça :

from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from datetime import datetime
from datetime import date 

class GoogleAgenda(object):
    def __init__(self):
        self._service = build('calendar', 'v3', credentials=self._getCredentials())

    def _getCredentials(self):
        '''le code dans cette methode a ete fait par Google. (https://developers.google.com/calendar/quickstart/python)'''
        creds = None
        # The file token.pickle stores the user's access and refresh tokens, and is
        # created automatically when the authorization flow completes for the first
        # time.
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # If there are no (valid) credentials available, let the user log in.
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file('credentials.json', ['https://www.googleapis.com/auth/calendar.readonly'])
                creds = flow.run_local_server()
            # Save the credentials for the next run
            with open('token.pickle', 'wb') as token:
                pickle.dump(creds, token)
        return creds

    def isItFreeDayToday(self):
        now = datetime.today().isoformat() + 'Z'
        api_result = self._service.events().list(calendarId='primary', timeMin=now,maxResults=1, singleEvents=True,orderBy='startTime', q='#CONGE').execute()
        events = api_result.get('items', [])
	event = events[0]
	today = date.today().isoformat()
	nextFreeDay = event['start']['date']
	return True if today == nextFreeDay else False

La partie authentification est une méthode privée (elle est par ailleurs faite par google). La méthode intéressante est isItFreeDayToday(). Elle va simplement répondre par un True/False.

Il faut noter que 2 fichiers (token.pickle, credentials.json) sont utiles pour la libraire google-api-python-client. On les installe de cette manière :

 $ pip install --upgrade google-api-python-client
 $ pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

2.2.2.4 WakeUpCalculator

Nous avons construit toutes les pièces indispensables. Les objets Horaire, EcranGyre et GoogleAgenda ont des bonnes interfaces pour nous simplifier la vie ici. WakeUpCalculator va calculer le moment de réveil en fonction des éléments cités ci-dessus mais aussi en prenant compte d'un temps de marge (par ex : 45 minutes de petit-déjeuner + temps de transport) qui est customisable.

from Absence import Absence
from EcranGyre import EcranGyre
from Horaire import Horaire
from GoogleCalendar import GoogleAgenda
from datetime import datetime

class WakeUpCalculator(object):
    ''' cet objet détermine l'heure du réveil'''

    def __init__(self, marginTime, defaultTime):
        self._marginTime = marginTime
        self._defaultTime = defaultTime # for week-ends, holidays,...

    def getTimeToWakeUp(self):
        agenda = GoogleAgenda()
        if agenda.isItFreeDayToday():
            return self._defaultTime
        else:
            return str(self._substractTwoTimes(self._getBeginningTime(), self._marginTime))
           
    
    def _getBeginningTime(self):
        horaire = Horaire()
        ecran = EcranGyre()
        
        for p in horaire.getTodayPeriodes():
            for absence in ecran.getTodayAbsences():
                if p.isSameTeacherAs(absence.getTeacher()) :
                    return p.getBeginTime()
        
        # no absent teachers, as usual :
        return horaire.getTodayPeriodes()[0].getBeginTime()

    def _substractTwoTimes(self, a, b):
        format = '%H:%M:%S'
        return datetime.strptime(str(a), format) - datetime.strptime(str(b), format)

Dans la méthode privée getBeginningTime j'ai mis en place un "mini algorithme" qui va parcourir toutes les périodes du jour commençant par 8h15 jusqu'à 12h00. Ainsi pouvoir les comparer avec chaque professeur absent ce jour-ci. Il continue à parcourir à la prochaine période jusqu'à ce que la période soit reconnue comme absente. À ce moment là, il va dire de se lever pour être à l'heure à la période suivante. Sinon il retourne l'heure de la première période selon l'horaire habituel.

2.2.3 Le MAIN

Le fichier principal que l'on exécute utilise la bibliothèque schedule pour programmer des temps d'exécutions.

 $ pip install schedule
#!/usr/bin/python

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
from datetime import time
import schedule


from WakeUpCalculator import WakeUpCalculator
from Ringtone import Ringtone


def startRingtone():
    print('ringtone')
    a = Ringtone()
    a.startWakingUp()
    
    return schedule.CancelJob

def calculateWakeUpTime():
    print('calculate')
    wakeupcalculator = WakeUpCalculator(time(0, 40, 0), time(9, 0, 0))
    waketime = wakeupcalculator.getTimeToWakeUp()
    schedule.every().day.at(str(waketime)).do(startRingtone)



schedule.every().day.at("02:00:00").do(calculateWakeUpTime)

while True:
    schedule.run_pending()

Donc tous les jours à deux heures du matin, le réveil va calculer l'heure du réveil avec WakeUpCalculator. Et ensuite quand l'heure de sonnerie est arrivé, avec Ringtone nous allons exécuter le processus de réveil (musique + jeu).

3. Résultats

Voici en vidéo le moment fatidique du réveil (c'est la seule chose que l'on peut voir, le reste étant "invisible") :

Vidéo 1 - La manière d'éteindre ce réveil

4. Discussion

Le résultat final est très satisfaisant. Ce réveil mérite bien son nom de réveil 2.0. Ce lundi j'ai été réveillé au bon moment (c'est déjà une bonne chose) et la manière dont j'ai été réveillé était très douce et agréable.

Il est important de noter que vu l'ampleur du projet il était difficile de s'imaginer tous ces petits éléments connectés bout à bout : leurs interactions. En effet chaque élément a ses problématiques, ses manières de résoudre, etc. Que ce soit du côté software ou du côté hardware. Le paradigme de programmation objet me plaît beaucoup ici. Car grâce à lui j'ai pu structurer et m'occuper d'une seule chose à la fois. Un deuxième point fort et que l'on différencie entre fonctionnement interne (méthodes privées) et interface (méthodes public). Ce qui fait, par exemple, que l'on encapsule la communication avec le protocole SPI du MCP3008 et que "simplement" on demande à l'objet getJoystickPosition().

J'ai aussi appris à programmer l'orienté objet avec la syntaxe de python. C'était une bonne expérience. Même si l'indentation est très pénible à la longue. Ainsi que de ne pas écrire le type des variables. Faillait être d'autant plus concentré sur ce que l'on écrit.

Le résultat final reste quand même ouvert à une multitude d'amélioration :

  1. Faire un boîtier

Un boîtier pour contenir toute cette électronique avec notamment un circuit imprimé (une carte mère). Pour la commercialisation par exemple c'est évidemment un point important.

  1. Avoir un son plus fort

Le problème ici est que ce haut parleur peut faire du son relativement élevé avec un smartphone. Mais ça ne marche pas aussi bien avec le Raspberry. J'imagine qu'il faut un amplificateur (?)

  1. Avoir une meilleure reconnaissance des professeurs absents

Je pense notamment aux absence-intervalles : ... absent du jeudi 8 décembre au vendredi 9 décembre qui sont des cas non traités

  1. Avoir un système automatique d'initialisation

Pour que l'on aie pas besoin de devoir manuellement entrer les mots de passes et lancer le Serveur Mopidy. Même si une fois lancé, il n'y a rien besoin de lui dire : il est entièrement autonome.

  1. Gestion des erreurs.

Sur le schéma vu le premier jour d'informatique (OC) c'était une boîte avec des entrées et des sorties. Mais il y avait aussi les erreurs. Vu l'ampleur de ce projet ça m'aurais pris un temps fou pour gérer toutes les erreurs possibles. C'est pour cela qu'il est tout à fait probable que le programme "crash".

5. Conclusion

Pour conclure, ce projet était un très bon défis pour moi. Je ne connaissais pas la majore partie des éléments. Le hardware tout comme le Software. Il a fallu utiliser plusieurs librairies pour faire marcher ce que je veux. Et il a fallu beaucoup réfléchir sur le programme : qu'il soit le plus "clean" possible. Les interactions entre les différents objets. Sur la modularité du code : que s'il y a le moindre bug, que l'on puisse facilement trouver sa provenance.

Finalement il reste énormément à travailler sur le peaufinage de cet appareil. Il peut être la solution pour résoudre les problèmes de réveil! Tout le monde devrait en avoir un. L'argument marketing serait par exemple : "Ne réglez plus jamais votre réveil, il sait quand vous réveiller". :)

Et s'il y a une chose que je dois retenir de tout ce projet : c'est

RTFM (cherchez sur Google si vous savez pas ce que c'est :))

Oui j'ai du en lire de la doc.

Références

Les principales références :

Serveur Mopidy : https://en.wikipedia.org/wiki/Music_Player_Daemon
https://docs.mopidy.com/
https://pypi.org/project/Mopidy-Spotify/
https://www.mopidy.com/authenticate/#spotify
https://github.com/mopidy/mopidy-spotify

Contrôle de Mopidy
https://github.com/Mic92/python-mpd2
https://python-mpd2.readthedocs.io/en/latest/
Client terminal : mpc https://www.systutorials.com/docs/linux/man/1-mpc/

Puce MCP3008
https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008
https://learn.adafruit.com/mcp3008-spi-adc/python-circuitpython
https://tutorials-raspberrypi.com/raspberry-pi-joystick-with-mcp3008/

Ecran LCD
https://www.sparkfun.com/products/709
https://www.youtube.com/watch?v=TORjcmXFpn8
https://github.com/adafruit/Adafruit_Python_CharLCD

Google Calendar
https://console.developers.google.com/apis/credentials?project=alarmclock-1548020545993
https://developers.google.com/calendar/quickstart/python
https://developers.google.com/resources/api-libraries/documentation/calendar/v3/python/latest/calendar_v3.events.html#list

Parseur language humain en date
https://dateparser.readthedocs.io/

Scheduler
https://schedule.readthedocs.io/en/stable/