소스 검색

Ajout de l'affichage des graphiques (fonctionnel) et MAJ du readme. TODO: ajouter titres/légendes et vérifier que le logiciel fonctionne pour un nombre de courbes différent de 2.

tags/PoDoCor-v0.1
lilian 3 년 전
부모
커밋
60bdff5499
5개의 변경된 파일170개의 추가작업 그리고 21개의 파일을 삭제
  1. +36
    -5
      README.md
  2. +1
    -1
      bin/data.py
  3. +92
    -0
      bin/figures.py
  4. +27
    -10
      bin/functions.py
  5. +14
    -5
      main.py

+ 36
- 5
README.md 파일 보기

@@ -4,20 +4,51 @@ Ce logiciel est distribué sous license MIT.

Poeles-Dragon-Calculator (PoDoCor) est un logiciel d'expérimentation développé pour les besoins du Low-Tech Bordeaux.

Pour l'instant il n'existe pas d'interface graphique, de plus il n'existe qu'en français. Cela sera développé plus tard.
Pour l'instant il n'existe pas d'interface graphique et s'utilise donc avec un terminal, de plus il n'existe qu'en français. Le GUI et les autres langues seront développées plus tard.

Ce logiciel est développé pour Linux avec python3 et Arduino. Les autres plateformes viendront plus tard.

## Utilisation et fonctionnement

Au démarrage, le logiciel crée un dossier "data" à la racine du dossier de PoDoCor, où se trouve le script principal; dans ce dossier se trouveront toutes les données au format CSV.
Pour lancer le programme, ouvrez un terminal dans le dossier "PoDoCor", dans le répertoire où vous l'avez téléchargé (et dézippé si besoin), ou utilisez la commande "cd /votre/dossier/dextraction/PoDoCor".
Avant de pouvoir utiliser le programme, lancez-le en tapant "python3 main.py" dans le terminal.
Si aucun Arduino n'est relié au PC, le programme s'arrêtera immédiatement.

Si aucun fichier n'est déjà créé, ou s'il ne contient aucune déclaration de capteur, le logiciel en demandera la création au démarrage.
### Premier démarrage

Au premier démarrage, le logiciel crée plusieurs dossiers et fichiers dans votre dossier PoDoCor:
- un dossier "data", où vous pourrez retrouver tous les fichiers .csv horodatés créés par le logiciel lors de l'enregistrement des données;
- un fichier "parameters.txt", où se trouve pour l'instant la seule ligne du chemin Serial où se trouve l'Arduino. Par défaut, après démarrage, ce chemin est "/dev/ttyUSB0", mais il peut être changé (notamment grâce au logiciel Arduino, le chemin est indiqué tout en bas à droite) en remplaçant cette valeur par une autre;
- un fichier "captors.txt", où sont déclarés les capteurs (nom, unité de mesure, etc.).

### Menus

Si aucun fichier "captors.txt" n'est déjà créé, ou s'il ne contient aucune déclaration de capteur, le logiciel en demandera la création au démarrage.

Le menu principal propose de commencer l'enregistrement des données ou de gérer les capteurs; le sous-menu permet d'afficher les capteurs enregistrés, d'en ajouter ou supprimer, et de changer l'ordre des capteurs pour correspondre à l'ordre des données envoyées par l'Arduino

Pour que le logiciel fonctionne, il est nécessaire de déclarer les capteurs utilisés: leur nom, l'unité dans laquelle il mesure, et une variable (0 ou 1) pour savoir si l'Arduino envoie des données brutes de ce capteur (i.e. une simple conversion analogique-numérique, sans mapping des données acquises), ou si ce sont les données normalisées que renvoie l'Arduino (une température par exemple). Lors de cette création de capteurs, ne JAMAIS utiliser de virgule (","), sans quoi le logiciel s'arrêterait pour ses futures initialisations.
### Arduino et synchronisation

Ce programme ne permet ni de programmer un Arduino, ni d'écrire le code à votre place. Vous devrez écrire le code Arduino vous-même (ou vous faire aider/vous aider de tutos, nombreux sur Internet). Un exemple (complet et détaillé) sera posté prochainement avec le code de PoDoCor.

Le principe est le suivant: l'ordinateur effectue seul la synchronisation de l'envoi des données. Pour celà, il envoie 2 signaux: le signal "1" pour signifier à Arduino que ce dernierpeut récolter les données des capteurs, et les empaqueter en une ligne de données à envoyer. Après quelques millisecondes, PoDoCor envoie le signal "2" pour signifier qu'il souhaite récolter les données. Arduino envoie alors les données à PoDoCor, qui les traite. Ainsi, PoDoCor n'envoie que des données de synchronisation à Arduino, et Arduino n'envoie que des données issues des capteurs.

Ce processus se répète toutes les secondes, selon l'horloge de l'ordinateur utilisé. Il est néanmoins possible d'avoir un très léger retard d'envoi du bit de synchronisation "1" (quelques millisecondes) lors de l'affichage des données; mais ce retard n'est jamais cumulé. L'ordre de l'interrogation des capteurs étant toujours le même, et la synchronisation "1" variant normalement (selon un gaussienne de 3*sigma = 1ms environ), les données, prises selon un échantillon suffisamment grand (plus de 1000 points de mesure), sont donc théoriquement prises à un intervalle régulier de précisément 1 seconde.

### Capteurs

Concernant les données simplement converties en numérique (si l'Arduino extraie la valeur 342 à la suite d'une conversion analogique-numérique par exemple), ce sera cette valeur qui sera transmise. Le CSV contient donc à la fois des données brutes et normalisées; les données brutes nécessiteront donc un traitement, en particulier si les capteurs utilisés nécessitent l'utilisation d'une table de conversion ou d'un étalonnage, ces opérations devront être effectuées avant de lancer PoDoCor. Il est prévu néanmoins, dans une future version, d'inclure la phase d'initialisation dans le processus.
Pour que le logiciel fonctionne, il est nécessaire de déclarer les capteurs utilisés: leur nom, l'unité dans laquelle il mesure, et une variable (0 ou 1) pour savoir si l'Arduino envoie des données brutes de ce capteur (i.e. une simple conversion analogique-numérique, sans mapping des données acquises), ou si ce sont les données normalisées ou converties dans l'unité de destination que renvoie l'Arduino (une température par exemple). Lors de cette création de capteurs, ne JAMAIS utiliser de virgule (","), sans quoi le logiciel s'arrêterait pour ses futures initialisations.

Concernant les données simplement converties en numérique (si l'Arduino extraie la valeur 342 à la suite d'une conversion analogique-numérique par exemple), ce sera cette valeur qui sera transmise. Le CSV contient donc à la fois des données brutes et normalisées; les données brutes nécessiteront donc un traitement, en particulier si les capteurs utilisés nécessitent l'utilisation d'une table de conversion ou d'un étalonnage, ces opérations concernant uniquement l'Arduino devront donc être effectuées avant de lancer PoDoCor. Il est prévu néanmoins, dans une future version, d'inclure la phase d'initialisation dans le processus.

Il est PRIMORDIAL de respecter l'ordre des données envoyées par l'Arduino dans PoDoCor: pour cela, une fois vos capteurs déclarés, vous pouvez changer leur ordre pour qu'ils correspondent exactement à l'ordre des données envoyées par Arduino.

### Enregistrement

Une fois le logiciel démarré, le démarrage du processus d'enregistrement des données démarre en utilisant Shift+P; de même, ce processus s'arrête en utilisant Shift+P.

Une fenêtre s'afiche pour afficher les données transmises par l'Arduino en format graphique. Cette fenêtre ne permet pas, pour l'instant, de visualiser la valeur réelle des données (notamment pour les données raw), elle affiche simplement les données (raw ou réelles) reçues par PoDoCor.

### TODO

Il est prévu, dans une future version, d'ajouter des calculs simples sur certains capteurs, pour convertir les données raw en données réelles.

+ 1
- 1
bin/data.py 파일 보기

@@ -125,7 +125,7 @@ class Arduino_data():
coma_places.append(len(raw_data_txt))
for i in range(min(len(coma_places)-1, self.nb_captor_signals)):
self.raw_data_temp[0,i] = int(raw_data_txt[coma_places[i]+1:coma_places[i+1]])
np.append(self.data_array, self.raw_data_temp, axis=1)
self.data_array = np.append(self.data_array, self.raw_data_temp, axis=0)

def create_data_file(self):
self.data_file_path = self.data_path_dir + "/" + time.asctime()+".csv"


+ 92
- 0
bin/figures.py 파일 보기

@@ -0,0 +1,92 @@
import matplotlib.pyplot as plt
import numpy as np
from multiprocessing import Process, Queue

class Figures():

def __init__(self, nb_signals):
self.nb_curves = nb_signals #nombre de courbes
self.plots_grid = (1,1) # (y,x)
self.total_subplots = self.plots_grid[0]*self.plots_grid[1] # nombre de fenetres de plot
self.repartition = [] # repartition de chaque courbe dans les fenetres
self.plots_grids(nb_signals) # determination de la grille de fenetres à l'init
self.fig = None # objet Figure()
self.axes = None # liste d'objets subplots(), suivant la grille plots_grid definie
self.time = None # axe x des figures (pas dans data_array car reextractible dans un tableur)

def plots_grids(self, nb_plots):
""" Determine la grille (x,y) de fenêtres de plots dans la figure affichée.
Dans tous les cas, pour voir quelque chose des mesures, le nombre de fenêtres
de plots est < 6; on pourra afficher 2 graphiques dns une même fenêtre
lorsqu'il y a plus de courbes à afficher que de fenêtres dans la grille,
dans l'ordre de parcours " de gauche à droite, puis de haut en bas" ."""
if(nb_plots == 1):
self.plots_grid = (1,1)
elif(nb_plots == 2):
self.plots_grid = (1,2)
elif(nb_plots == 3):
self.plots_grid = (1,3)
elif(nb_plots == 4 or nb_plots == 5):
self.plots_grid = (2,2)
elif(nb_plots == 6):
self.plots_grid = (2,3)
else:
if(type(nb_plots/2) == type(int())):
self.plots_grids(nb_plots/2)
elif(nb_plots >= 9):
self.plots_grids(6)
else:
self.plots_grids(nb_plots-1)
self.total_subplots = self.plots_grid[0]*self.plots_grid[1]

# definition de la repartition sous forme d'un nombre à 3 chiffres yXnb

if(self.plots_grid == (1,1)):
self.repartition = [111]
elif(self.plots_grid == (1,2)):
self.repartition = [111, 122]
elif(self.plots_grid == (1,3)):
self.repartition = [111, 122, 133]
elif(self.plots_grid == (2,2)):
self.repartition = [111, 122, 213, 224]
elif(self.plots_grid == (2,3)):
self.repartition = [111, 122, 133, 214, 225, 236]
if(nb_plots > 6):
for i in range(6,nb_plots):
# parcours des cases de la figure de droite à gauche puis de haut en bas, suivant la grille
self.repartition.append(self.repartition[i%self.nb_curves])

def create_figure(self):
"""Creation de la fenêtre contenant les plots.
Mode interactif enclenché pour réactualiser à chaque fois que plot_data est
appelée. Utilisation de plt.pause pour laisser la fenêtre s'afficher et
actualiser les données."""
plt.ion()
self.fig, self.axes = plt.subplots(self.plots_grid[0], self.plots_grid[1])
if(self.plots_grid[0] < 2): # si 1 seule ligne, empaqueter dans une liste pour
# y acceder de la meme facon dans tous les cas
self.axes = [self.axes]
plt.show()
plt.pause(0.05)

def plot_data(self, data_array):
"""Appel pour réactualistion des données. Utilisation de plt.pause pour laisser
afficher les données."""
data_array_t = np.transpose(data_array)
self.time = np.linspace(0,data_array_t.shape[1]-1,data_array_t.shape[1])
for i in self.repartition:
self.axes[i//100-1][(i//10)%10-1].clear()
for i in range(self.nb_curves):
self.axes[self.repartition[i]//100-1][(self.repartition[i]//10)%10-1].scatter(self.time, data_array_t[i])
plt.show()
plt.pause(0.05)

def reinit(self):
"""Remise à zéro (défaut) de l'objet Figures(), permet aussi de fermer la figure
et d'en réouvrir une nouvelle lors de la réutilisation de l'objet."""
plt.close(self.fig)
self.plots_grid = (1,1)
self.total_subplots = self.plots_grid[0]*self.plots_grid[1]
self.repartition = []
self.fig = None
self.axes = None

+ 27
- 10
bin/functions.py 파일 보기

@@ -1,23 +1,22 @@
import os
from multiprocessing import Process, Queue
if(os.name == "posix"):
import getch as gt #clavier
import getch as gt #clavier linux
else:
import msvcrt as gt
import msvcrt as gt #clavier linux; à tester!

def press_key_timeout(caracters, numbers, actual, timeout = 0.1):
"""Utilisation de press_key en insérant un timeout grâce à un thread exécuté
en parallèle de l'exécution principale."""
queue = Queue()
time_thread = Process(target = press_key, args=(caracters, numbers, actual, queue))
time_thread.start()
time_thread.start() # demarre le thread
time_thread.join(timeout=timeout)
if(time_thread.is_alive()):
time_thread.terminate()
time_thread.terminate() #termine le thread pour pouvoir le relancer (thread exclusif)
return actual
else:
return queue.get()
#main_thread.join(timeout=timeout)
#actual = queue.get()
#main_thread.terminate()
return queue.get() # pas besoin de terminer le thread, il s'est termine en quittant press_key

def press_key(caracters, numbers, actual, queue = None):
""" Cette fonction sert à détecter si un caractère est pressé, pour passer d'un
@@ -26,13 +25,31 @@ def press_key(caracters, numbers, actual, queue = None):

pressed_key = gt.getch() #detection de touche pressee (blocant)
place = caracters.find(pressed_key)
if(queue != None):
if(queue != None): #si dans un thread (press_key_timeout)
if(place == -1):
queue.put(actual)
else:
queue.put(numbers[place])
else:
else: #si pas dans un thread
if(place == -1):
return actual
else:
return numbers[place]

def read_line_file(path):

""" Lecture des paramètres du logiciel et de l'Arduino, mis à part les capteurs
définis dans un autre fichier. Pour l'instant 1 seul paramètre, donc une fonction
très simple, mais peut se complexifier si besoin en 'serial_path = dev/ttyUSB0'
par exemple."""
try:
file = open(path, "r") #lecture du fichier si existant
except:
file = open(path,"w") #creation du fichier si pas existant
file.write("/dev/ttyUSB0") #valeur par defaut
file.close()
file = open(path, "r") #reouverture fichier pour lecture
finally: #execute dans tous les cas
line = file.readline() #lis la 1e ligne
file.close() #fermer avant de renvoyer la valeur
return line

+ 14
- 5
main.py 파일 보기

@@ -1,16 +1,17 @@
import serial
import numpy as np
import matplotlib.pyplot as plt
import os
import time
import bin.functions as fc
import bin.data as data
import bin.figures as fig

#Variables principales

serial_path = '/dev/ttyUSB0'
parameters_path = os.path.abspath(os.path.dirname(__file__)) + "/parameters.txt"
data_nb = 10
captors_path = os.path.abspath(os.path.dirname(__file__)) + "/captors.txt"
serial_path = fc.read_line_file(parameters_path)

#Variables cachees

@@ -29,8 +30,9 @@ flag_state = 1 # etat neutre
try:
arduino_ser = serial.Serial(serial_path, 9600) #communication avec Arduino
except serial.serialutil.SerialException:
print("SerialException : la carte Arduino n'est pas connectee ou est connectee sur au autre port.")
print("SerialException : la carte Arduino n'est pas connectee ou est connectee sur au autre port (actuel : "+ serial_path+").")
flag_state = -1
exit()
else:
arduino_data = data.Arduino_data()
arduino_data.add_captors_from_file(captors_path)
@@ -40,6 +42,7 @@ while(flag_state > 0):
if(flag_state == 1): #afficher le menu
print("_________________________________\n\nMAJ + P : Enregistrement des donnees\nMAJ + N : Gerer les capteurs\nMAJ + X : Arrêt du programme (ne fonctionne pas pendant l'enregistrement)\n_________________________________\n")
flag_state = 4
time.sleep(2) # attente de la bonne communication avec Arduino
elif(flag_state == 4): # appui clavier pour les choix du menu principal
flag_state = fc.press_key('XPN', [0, 2, 3], flag_state) #detection de touche pressee (demarrage procedure acquisition)

@@ -62,6 +65,8 @@ while(flag_state > 0):
flag_state = 3

elif(flag_state == 2): # enregistrement des données
figures = fig.Figures(arduino_data.nb_captor_signals)
figures.create_figure()
data_buff_nb = 0
arduino_data.create_data_file()
time_0 = time.time()-1
@@ -73,7 +78,7 @@ while(flag_state > 0):
arduino_ser.write(bytes('2','utf-8')) #envoi de '2' (réception donnees)
time.sleep(0.05)
raw_data_txt = arduino_ser.readline() #format des donnees: data1,data2,data3\n
print(str(raw_data_txt)[2:-5])
#print(str(raw_data_txt)[2:-5])
if(raw_data_txt == ''): # si aucune donnée reçue
print("Arduino n'envoie aucune donnee, veuillez verifier le code televerse.")
flag_state = -1
@@ -84,11 +89,14 @@ while(flag_state > 0):
#procedure extraction donnees pour affichage
arduino_data.extract_data_to_array(str(raw_data_txt)[2:-5])

# procedure d'ecriture fichier
if(data_buff_nb == data_buff_max):
# procedure d'ecriture fichier
arduino_data.append_data_to_file(data_buff)
data_buff = "" #remise a zero
data_buff_nb = 0
# plot
figures.plot_data(arduino_data.data_array)

flag_state = fc.press_key_timeout('P', [1], flag_state)
time.sleep(max(time_0+1-time.time(), 0))
@@ -98,6 +106,7 @@ while(flag_state > 0):
arduino_data.append_data_to_file(data_buff)
data_buff = ""
data_buff_nb = 0
figures.reinit()





불러오는 중...
취소
저장