# Activité Arduino (Serveur de données) vers Python¶

## Contexte¶

On souhaite dans le cadre d'une expérimentation effectuer des mesures de températures de la surface des océans.

## Pré-requis:¶

- Utilisation basique d'arduino

## Arduino:¶

L'aide en ligne (API : interface de programmation applicative) :

- L'officielle : https://www.arduino.cc/reference/en/
- En français : http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.Reference

## Lire des valeurs sur une entrée analogique et transfert sur la liaison série¶

    //Code Arduino
    void setup(){
        Serial.begin(9600);      //Initialisation de la liaison série à la vitesse de 9600 bauds
    }

    void loop(){
        mesure = analogRead(0); //Lecture de l'information sur l'entrée analogique A0
        Serial.print(millis()); //Envoie de la valeur temps vers la liaison série (ou USB)
        Serial.print("\t");     //Envoie d'une tabulation : \t comme séparateur
        Serial.println(mesure); //Envoie de la mesure puis retour à la ligne
        delay(500);
    }

## La connexion Arduino - Python¶

### Les modules Python¶

In [ ]:

    import os                         # pour les commandes systeme
    import serial                     # pour la connexion série
    import numpy as np                # pour les tableaux et calcul
    import pandas as pd               # pour les fichiers CSV et traitement des données
    import matplotlib.pyplot as plt   # afficher des courbes

Établir la communication entre Arduino et Python nécessite l'utilisation du module `serial`. Toutes les fonctionnalités liées à ce module seront donc préfixées par le nom du module. Il est en sera de même pour tout autre module.

Attention : La vitesse de transfert des informations sur le port série ou USB doit être la même que celle d'Arduino, dans notre cas : 9600 bauds

In [ ]:

    serial_port = serial.Serial(port = "/dev/ttyUSB1", baudrate = "9600")

#### On peut maintenant transférer des informations de la carte Arduino vers Python et les afficher dans le notebook¶

In [ ]:

    print(serial_port.readline())

TRÈS IMPORTANT : Il ne faut pas oublier de fermer le port de communication quand les mesures sont terminées. La connexion avec la carte Arduino peut être vue comme une conversation téléphonique. Si vous téléphonez à un copain, et qu'une autre personne vous appelle votre numéro sonne occupé. En général, il faut raccrocher pour engager une nouvelle conversation téléphonique. Avec la connexion Arduino il en est de même. Si le moniteur série est ouvert Python ne pourra communiquer avec la carte Arduino et inversement s'il existe une connexion Arduino-Python vous ne pourrez plus ouvrir un moniteur série ni même téléverser un programme vers la carte Arduino

In [ ]:

    serial_port.close()

Pour répéter plusieurs fois une instruction ou un ensemble d'instructions, on utilise une structure répétitive

In [ ]:

    for i in range(5):
        print("coucou")

Pour conserver l'information de manière temporaire on utilise la mémoire de l'ordinateur, dans notre cas nous allons utiliser des listes Python pour conserver les mesures. Dans l'exemple qui suit nous allons stocker les différentes valeurs de la variable `i`

In [ ]:

    liste = []          # une liste vide qui s'appelle liste
    for i in range(5):
        liste.append(i)
    print(liste)        # Affichage du contenu de la liste quand la boucle est terminée

Question : Lire cinq mesures numériques envoyées par la carte Arduino et les stocker dans une liste `mesures`

In [ ]:

    #Code python à trouver par les élèves
    serial_port = serial.Serial(port = "/dev/ttyUSB1", baudrate = "9600")
    mesures = []
    for i in range(5):
        m = serial_port.readline()
        mesures.append(m)
    print(mesures)
    serial_port.close()

Il ne reste plus qu'à mettre en mémoire les informations reçues, mais avant cela une petite transformation s'impose. Nous avons vu que les informations reçues de la carte Arduino se présentent sous forme d'une ligne contenant : le temps, une tabulation et la mesure provenant du capteur analogique. Mais l'ennui est que ces informations arrivent sous la forme de chaîne de caractères (en fait ce n'est pas tout à fait vrai puisque c'est des `bytes` mais on ferra comme si...)

Nous allons commencer par séparer le temps et la mesure grâce à la fonction `split` qui va découper la chaine grâce à la tabulation et ranger les 2 valeurs dans une liste

In [ ]:

    mesure  = b'23\t651\r\n'  # exemple d'une mesure temps, tabulation, mesure
    decoupe = mesure.split()
    print(decoupe)

Pour accéder à chacune des valeurs il suffit maintenant d'indiquer entre crochets le numéro d'emplacement de la valeur désirée en n'oubliant pas que Python commence la numérotation des emplacements à zéro.

In [ ]:

    print(decoupe[0])
    print(decoupe[1])

Le travail est pratiquement terminé il ne reste plus qu'à convertir les chaînes de caractères en valeurs numériques

In [ ]:

    t = float(decoupe[0])
    m = float(decoupe[1])
    print(t)
    print(m)

Activité : Compléter la suite d'instructions ci-dessous en respectant les consignes suivantes:

1. Indiquer le nom de votre port série
2. Initialiser deux listes vides que l'on appelera `mesures` et `temps`
3. Indiquer le nombre de mesures
4. Affecter à `valeurs` une ligne d'information provenant d'Arduino découpée
5. Affecter à `m` la valeur flottante (numérique) de la mesure
6. ajouter cette valeur à la liste des mesures
7. Fermer le port série

In [ ]:

    # connexion au port série
    serial_port = serial.Serial(port = "", baudrate = 9600 )
    # les listes temps et mesures (vides au départ)

    serial_port.flushInput()
    nb_mesures =
    for i in range(nb_mesures):
        valeurs =
        try:
            t = float(valeurs[0])
            m =
            temps.append(t)
        except:
            pass
    # fermeture du port série

Question : Est-on sûr d'avoir le bon nombre de mesures ? Comment corriger le code dans le cas contraire ?

## Le module `matplotlib.pyplot`¶

Rappelez-vous on avait dit que pour utiliser une fonction d'un module, il fallait la préfixer avec le nom de module. Dans le cas de matplotlib.pyplot le nom de module est pénible à écrire à chaque utilisation donc on le remplace par un petit nom `plt` que l'on appelle un alias

## Exemple avec matplotlib.pyplot¶

On commence par la liste des abscisses et des ordonnées

In [ ]:

    x = [1, 2, 3, 4, 5]
    y = [10, 10, 5, 5, 9]

Puis on utilise la fonction plot() pour tracer les points et la fonction `show()` pour les afficher

In [ ]:

    plt.plot(x,y)
    plt.show()

Question : Nous venons de voir que pour obtenir le graphe d'un ensemble de points il suffit d'avoir la liste des abscisses et la liste des ordonnées. Afficher les valeurs discrétisées de la série de mesures en tension en fonction du temps

## Discrétisation de l'information¶

Valeurs numériques obtenues pour des tensions dans l'intervalle $[0\,\mathrm{mV}; 50\,\mathrm{mV}]$ avec le CAN de la carte Arduino sur 10 bits. Toutes les valeurs analogiques ne sont pas exactement représentables par des valeurs numériques. Pour une valeur numérique nous avons une plage de valeurs analogiques qui va dépendre du pas de résolution du CAN, c'est à dire de la précision en bits du CAN.

## Sauvegarder les données de l'acquisition dans un fichier CSV¶

Pour manipuler et analyser les données, le module panda (https://pandas.pydata.org/), développé pour Python, s'avère des plus pratique. En particulier, il permet la lecture et l'écriture de fichiers csv.

Il est maintenant important de pouvoir stocker l'information à long terme. L'installation du Jupyter Notebook sur le réseau pédagogique ne permet pas forcément à l'élève d'enregistrer l'information dans son répertoire personnel.

In [ ]:

    print(os.getcwd())

Attention le chemin indiqué par `"U:"` est propre au lycée Camille Jullian. Il correspond à la racine du répertoire perso des élèves. Il convient donc d'adapter ce chemin en fonction de votre établissement.

In [ ]:

    os.chdir("U:")

Une fois le chemin pour sauvegarder les données fixé. Dans un premier temps, il convient de créer un dataframe qui contient toutes les donneés tabulaires que l'on souhaite enregistrer. Dans notre cas, il s'agit des listes (tableaux) `temps` et `mesures`. La méthode DataFrame (http://pandas.pydata.org/pandas-docs/stable/dsintro.html#dataframe) assure cette opération. Elle reçoit en arguments les différentes listes précédées d'une intitulé de colonne. Dans notre exemple, nous intitulons 'Temps' et 'Mesures' les deux colonnes.

Pour manipuler et analyser les données, le module panda (https://pandas.pydata.org/), développé pour Python, s'avère des plus pratique. En particulier, il permet la lecture et l'écriture de fichiers csv.

Exemple de fichier csv :

- Séparateur de colonnes : le point virgule.
- Le # indique un commentaire contenant du texte non convertible en valeur numérique.
- La virgule est utilisée comme séparateur de la partie entière et décimale.

In [ ]:

    data = pd.DataFrame({
      "Temps"   : temps,
      "Mesures" : mesures
    })
    filename = "mon_fichier.csv"
    data.to_csv(filename, sep=";", index = False, encoding="utf-8")

Remarque :

Pour ouvrir un fichier en lecture et charger les données dans la mémoire de l'ordinateur, nous utilisons la fonction `read_csv`du module `pandas`. Celle-ci reçoit plusieurs arguments parmi lesquels :

- le nom du fichier;
- une indication sur le séparateur des données : virgule `,`, point-virgule `;`, espace ``, tabulation`\t`;
- une option précisant si la première ligne du fichier est une ligne d'entêtes ou une ligne de données.

In [ ]:

    filename = "mon_fichier.csv"
    data     = pd.read_csv(filename, sep = ';')
    data.head()

# Traitement statistique des données¶

### Moyenne et écart type¶

Nous avons dans le paragraphe précédent sauvegarder les données à l'aide du module `pandas` en utilisant une structure de données appelée `dataFrame` associée à la variable `data`. Il est maintenant tout à fait possible d'obtenir une étude statistique automatique avec la fonction `describe` (moyenne, écart type normalisé à N-1 et quantile).

In [ ]:

    data.describe()

Si une seule de ces valeurs nous intéresse par exemple l'écart type (https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.std.html), nous pouvons écrire :

In [ ]:

    data.mean()