preface

Lycée Alphonse Benoit 84800 Isle-sur-la-Sorgue

1. Introduction

1.1. Cahier des charges.

Dans le but de venir en aide aux familles vivant au voisinage du site de Fukushima, nous voulons développer une balise permettant la mesure en continu de la radioactivité bêta et gamma. Les données devront être affichées en local mais également transmises à un serveur. La communication se fera par un réseau de type Ethernet. Une communication sans fil peut être envisagée (Wifi, ZigBee). Le serveur devra recevoir ces données et les stocker dans une base de données. Nous devons pouvoir accéder à ces données par un site internet. Ce site devra fournir les informations suivantes :

  • Récapituler dans un tableau :

    • la valeur moyenne des mesures sur la journée, le mois ou l’année.

    • Indiquer le nombre de points de mesure.

    • La valeur maximale.

  • Un affichage dans un graphe, en fonction du temps, des données journalières.

  • Un affichage sous forme d’histogramme des valeurs moyennes par jour du mois.

  • Un affichage sous forme d’histogramme des valeurs moyennes par mois de l’année.

Le prix de la maquette doit avoisiner celui d’un compteur Geiger Radex, soit environ 260€

1.2. Première chaîne fonctionnelle.

1.2.1. Le tube geiger.

chaineFonctionnelle
Electronique Caractéristiques

geigerElectronique

Fabriquant : North Optic

Référence : J305ß
Détection des radiations β et γ
Longueur du tube Geiger : 111mm
Diamètre du tube Geiger : 11mm
Max cpm : 30000
cpm/µSv/h : 123.147092360319
Factor : 0.00812037037037

1.2.2. La carte d’acquisition.

La carte est constituée d’une partie haute tension pour l’alimentation du tube et d’une partie conditionnement des signaux de comptage avant transfert vers une entrée d’interruption de la carte arduino.

1.2.3. Traitement et envoi par liaison série de la donnée.

La carte d’acquisition a été développée afin d’être compatible avec les shields d’arduino. Le traitement consiste en un comptage du nombre d’impulsions pendant 10 secondes, puis de ramener le résultat en nombre de coups par minute (cpm). Cette information sera ensuite envoyée par liaison série avec le protocole suivant : rx.xxx La conversion en Sievert (unité légale pour ce type de mesure) se fera dans la deuxième chaîne fonctionnelle.

1.2.4. La communication.

La carte communique les mesures par plusieurs moyens :
- Un afficheur LCD.
- Un Buzzer.
- Des leds.
- Liaison série.

afficheurLCD

1.3. Deuxième chaîne fonctionnelle.

chaineFctlle2

1.3.1. Acquisition.

Un programme en langage python vient lire toutes les cinq minutes les données envoyées par la première chaîne d’information. Le service crontab de la raspberry est utilisé pour lancer automatiquement toutes les cinq minutes cette acquisition. La finalité de ce programme est détaillée dans la première activité. Il remplit les tâches suivantes :

  • Acquisition de la trame provenant de la liaison série.

  • Extraction de la donnée de comptage de la radioactivité encapsulée dans la trame.

  • Conversion en sievert.

  • Stockage dans un fichier csv avec la date courante.

  • Envoi vers Cayenne , un service Cloud pour les objets connectés

1.3.2. Contexte scientifique.

Afin de mieux comprendre l’écosystème de ce projet , présenter une partie scientifique et réglementaire nous semble importante. Pour appréhender les effets du rayonnement sur le corps humain, on préfère utiliser le microSievert par heure (µSv/h). Le nombre de désintégrations n’indique pas le type de rayonnement, ni l’énergie de celui-ci et donc son impact sur le corps humain. De plus, les organes ne réagissent pas tous de la même façon aux rayonnements. Le Sievert rend compte de l’effet biologique de la dose absorbée. Ce n’est pas une quantité physique mesurable mais le résultat d’un calcul fondé sur des facteurs d’évaluation expérimentaux. Le débit Sv/h correspond en langage courant au « niveau de radioactivité ». Le facteur de conversion fourni par le constructeur du tube geiger est de 0.008120. Ce facteur permettra de convertir la valeur de comptage (coup par minute cpm) en sievert Donc 1 cpm = 0,008120 µS/h

Dangereux ou pas ? la Commission internationale de protection radiologique (CIPR) recommande de ne pas dépasser une limite annuelle de radiation artificielle de 1 mSv pour la population, (20 mSv pour les travailleurs du nucléaire). C’est la norme en France, soit 0,11µS/h. Cette valeur correspond à la dose maximale de la radioactivité artificielle, à ajouter à la radioactivité naturelle. Donc le seuil à détecter correspondrait au seuil de la réglementation additionné à la valeur de la radioactivité naturelle du lieu de la mesure. En Vaucluse, une valeur autour de 0,10µS/h, est courante pour la radioactivité naturelle. En conclusion, en présence de mesures répétées au-delà de 0,21µS/h, une alarme pourrait être programmée.

2. Codes sources produits.

2.1. Présentation générale des programmes produits.

diagramSequence
Figure 1. Diagramme de Séquences

Le diagramme de séquence proposé conditionne la présentation qui suit. Les codes sources sont présentés dans l’ordre du cheminement des données et répondent au cahier des charges proposé.

2.2. Code Arduino (bloc3: robotique)

L’un des premiers travaux d’informatique est de rapatrier les informations issues du capteur d’impulsions radiactives et de les envoyer à l’unité de traitement (PC ou raspberry) afin de les stocker dans un fichier de données au format CSV. L’affichage temps réel se fait sur un afficheur LCD et un paragraphe présenté dans l’introduction.

Code Source Arduino
// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(3,4,5,6,7,8);


// Threshold values for the led bar
#define TH1 45
#define TH2 95
#define TH3 200
#define TH4 400
#define TH5 600

// Conversion factor - CPM to uSV/h
#define CONV_FACTOR 0.00812

// Variables
int ledArray [] = {10,11,12,13,9};
int geiger_input = 2;
long count = 0;
long countPerMinute = 0;
long timePrevious = 0;
long timePreviousMeassure = 0;
long time = 0;
long countPrevious = 0;
float radiationValue = 0.0;

void setup(){
  pinMode(geiger_input, INPUT); (1)
  digitalWrite(geiger_input,HIGH); (2)
  for (int i=0;i<5;i++){
    pinMode(ledArray[i],OUTPUT);
  }

  Serial.begin(9600); (3)

  //set up the LCD's number of columns and rows:
  lcd.begin(16, 2); (4)
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Radiation Sensor");
  lcd.setCursor(0,1);
  lcd.print("Board - Arduino");
  delay(1000);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(" Cooking Hacks");
  delay(1000);

  lcd.clear();
  lcd.setCursor(0,1);
  lcd.print("www.cooking-hacks.com");
  delay(500);
  for (int i=0;i<5;i++){
    delay(200);
    lcd.scrollDisplayLeft();
  }
  delay(500);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("  - Libelium -");
  lcd.setCursor(0,1);
  lcd.print("www.libelium.com");
  delay(1000);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("CPM=");
  lcd.setCursor(4,0);
  lcd.print(6*count);
  lcd.setCursor(0,1);
  lcd.print(radiationValue);

  attachInterrupt(0,countPulse,FALLING); (5)

}

void loop(){
  if (millis()-timePreviousMeassure > 10000){ (6)
    countPerMinute = 6*count; (7)
    radiationValue = countPerMinute * CONV_FACTOR; (8)
    timePreviousMeassure = millis();
    //Serial.print("count = ");
    //Serial.print(count,DEC);
    //Serial.print("cpm = ");
    //Serial.print(countPerMinute,DEC);
    //Serial.print(" - ");
    //Serial.print("uSv/h = ");
    //Serial.println(radiationValue);
    Serial.print("r"); (9)
    Serial.println(count,DEC);  (9)
    //Serial.println(count,4);   //Erreur count est un entier envoyer
    lcd.clear();
    lcd.setCursor(0, 0); (10)
    lcd.print("CPM=");
    lcd.setCursor(4,0);
    lcd.print(countPerMinute);
    lcd.setCursor(0,1);
    lcd.print(radiationValue,4);
    lcd.setCursor(6,1);
    lcd.print(" uSv/h");

    //led var setting   (11)
    if(countPerMinute <= TH1) ledVar(0);
    if((countPerMinute <= TH2)&&(countPerMinute>TH1)) ledVar(1);
    if((countPerMinute <= TH3)&&(countPerMinute>TH2)) ledVar(2);
    if((countPerMinute <= TH4)&&(countPerMinute>TH3)) ledVar(3);
    if((countPerMinute <= TH5)&&(countPerMinute>TH4)) ledVar(4);
    if(countPerMinute>TH5) ledVar(5);

    count = 0;

  }

}

void countPulse(){ (12)
  detachInterrupt(0);
  count++;
  while(digitalRead(2)==0){
  }
  attachInterrupt(0,countPulse,FALLING);
}

void ledVar(int value){
  if (value > 0){
    for(int i=0;i<=value;i++){
      digitalWrite(ledArray[i],HIGH);
    }
    for(int i=5;i>value;i--){
      digitalWrite(ledArray[i],LOW);
    }
  }
  else {
    for(int i=5;i>=0;i--){
      digitalWrite(ledArray[i],LOW);
    }
  }
}
1 Le compteur de radioactivité est branché sur l’entrée 2 de l’arduino qui a la particularité de permettre de travailler en interruption.
2 Configuration en sortie des 4 broches pour permettre la commande du barregraphe.
3 Configuration de la vitesse de communication avec le PC ou la raspberry.
4 Attachement de l’impulsion d’un front montant sur la broche 2 de l’Arduino au sous-programme d’interruption count.
5 Configuration de l’afficheur LCD pour afficher en temps réel la radioactivité mesurée.
6 L’affichage et l’envoi de la donnée sur la liaison série s’effectuent toutes les 10 secondes.
7 Calcul du nombre d’impulsions par minute.
8 Calcul d’irradiation en uS/h.
9 Envoi de la donnée sur la liaison série au format rxxxxx. (xxxx représente le nombre de d’impulsions mesurées en 10 secondes.
10 Affichage de la donnée sur l’afficheur LCD au forma cpm et uS/h
11 Mise à jour du barregraphe
12 Programme d’interruption qui permet de compter le nombre d’impulsions envoyé par le capteur de radioactivité pendant 10 secondes.
Accès au programme Arduino, cliquez sur le lien suivant:

codes/Arduino_328_geiger

2.3. Codes Python/HTML/CSS (bloc1)

2.3.1. Lecture des données.

Première proposition.

Le premier programme python permet la lecture des données envoyées par l’Arduino sur la liaison série. Un premier traitement de la donnée est effectuée en ajoutant l’heure d’acquisition. Puis la sauvegarde dans un fichier CSV est effectué ainsi que l’envoi dans une base de données. L’envoi à Cayenne, une plateforme gratuite de gestion IoT, est effectué à l’aide de MQTT.

Code Source Soulution 1
#!/usr/bin/ python
# -*- coding: utf-8 -*-
#
#
# modules a importer
import time
import datetime
import os,sys
import serial # communication serie
import MySQLdb # Base de données
import cayenne.client
import csv

tubeConvFacteur = 0.00812



def connectIoT():

	# Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
	MQTT_USERNAME  = "5aa76170-9808-11e9-9636-f9904f7b864b"
	MQTT_PASSWORD  = "8a296be8974a243e6ba05f468600f87ce8257795"
	MQTT_CLIENT_ID = "d284bb40-9815-11e9-94e9-493d67fd755e"
	global client
	client = cayenne.client.CayenneMQTTClient()
	client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID)

def envoiIoT(data):
	client.loop()
	client.virtualWrite(1, data,dataUnit='µS/h')




def receptionData(): (1)
	#Configuration de la liaison série.
	ser2 = serial.Serial(port='/dev/ttyACM0', baudrate=19200, timeout=1, parity=serial.PARITY_NONE,
	stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS)

	#on attend que la liaison serie se stabilise.
	time.sleep(1)
	ser2.flushInput()
	# preparation du buffer de reception
	chaine=""
	char=""
	d=0
	while(char !="\r" and len(chaine) < 5):
		char=ser2.read(1)
		if (char == "r" and len(chaine)==0):#on verifie que r est en début de chaine
			d=1	#mise en place d'un drapeau pour certifier le bon format
			print ("drapeau ok r en début de chaine")
			char=""
		if (char !="\r" and d):
			chaine=chaine+char
	ser2.flushInput()
	ser2.close()
	return chaine

def miseEnForme(chaine): (2)

	print chaine
	comptage= float(chaine) #conversion en entier
	comptageParMinute=6*comptage
	sievert= comptageParMinute*tubeConvFacteur
	print sievert
	return sievert

def envoiBaseDonnee(data):
	connection2= MySQLdb.connect(host='localhost', user='root', passwd='appleDay84', db='geiger',charset = "utf8" , use_unicode=True)
	cursor2=connection2.cursor()
	cursor2.execute("INSERT INTO compteur values(null,now(),'"+str(data)+"');") #met bien un float dans la base de données
	connection2.commit()

def envoiCSV(data):
	objetCSV = open('baliseGeiger.csv', 'ab')
	valeur =csv.writer(objetCSV)
	valeur.writerow([datetime.datetime.now(),data])
	objetCSV.close()


"""  ///////////// Programme principal ////////////// """
connectIoT()
compteur=receptionData()
print compteur
sievert=miseEnForme(compteur)
print sievert
envoiBaseDonnee(sievert)
envoiIoT(sievert)
envoiCSV(sievert)
1 Gestion de la lecture de la donnée sur la liaison série. Configuration de la liaison série. Lecture de chaque caractère et mise en forme dans une chaîne de caractères jusqu’au caractère retour chariot qui indique la fin de la donnée.
2 Calcul pour obtenir l’affichage et la sauvegarde en uS/h
Accès au programme Python, cliquez sur le lien suivant:

codes/serie_sol1.py

2.3.2. Deuxième proposition

La tâche a consisté dans une premier temps à produire une page HTML/CSS statique qui affiche la radioactivité; de l’intégrer dans un programme python qui lit les données sur la liaison serie, les sauvegarde et crée une page html qui affiche la radioactivité en temps réel.

Code Source HTML/CSS de la page web Statique
<!DOCTYPE html>
<html lang="fr">


<head>
	<title>Radioactivité</title>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="icon" href="https://image.flaticon.com/icons/svg/1446/1446151.svg">
<style>
body {
    font-family: Arial, Helvetica, sans-serif;
	color: rgb(51, 168, 36);
	background-color: rgba(229, 229, 229,0.5);
}

h1,
p,
hr {
    margin: 0px;
    padding: px;
}

h1 {
    font-size: 25px;
    font-style: italic;
}

table {
    margin: 0px auto;
}

td {
    padding: 5px;
}

.page {
    padding: 5px;
    border: solid 1px;
    width: 500px;
    margin: 0px auto;
    /* Assure le centrage de la page */
    background-color: rgb(229, 229, 229);
}

a {
    color: red;
}

a:visited {
    color: blue;
}


/* ------------------- En-tête de page --------------------*/


/* --------------------------------------------------------*/

.header {}

.logo {
    display: inline-block;
    padding: 1px;
}

.banniere {
    max-width: 447px;
    display: inline-block;
    vertical-align: top;
    padding-top: 22px;
    font-size: 30px;
}


/* ---------------- Contenu de la page --------------------*/


/* --------------------------------------------------------*/

.content {
    padding: 20px;
    font-size: 20px;
}


/* ---------------------Pied de page ----------------------*/


/* --------------------------------------------------------*/

.footer {
    padding: 10px;
    text-align: center;
    font-size: 10px;
}
</style>
</head>

<body>
	<div class="page">
		<div class="header">
			<img class="logo" src="https://image.flaticon.com/icons/svg/1446/1446151.svg" width="53" height="59" title="Ma maison connectée" />
			<div class="banniere">
				<center> <h1> Surveillance de la radioactivité </h1> </center>
			</div>

		</div>
		<hr/>
		<div class="content">
			<table border="1">
				<tr>
					<td>Date heure: </td>
					<td>14/08/2019</td>
					<td>20:30:58</td>
					<td rowspan="2"><img src="https://www.montrealsciencecentre.com/sites/default/files/styles/teaser_view/public/2017-04/de-quoi-est-compose-l-atome.png?itok=pKL1byWQ" width="70" height="80" /></td>
				</tr>

				<tr>
					<td>Mesure instantanée </td>
					<center> <td>0.458</td> </center>
					<center> <td>uS/h</td> </center>
				</tr>
			</table>
		</div>
		<hr/>
		<div class="footer">
			<p> DUI | NSI | Lycée Alphonse Benoit | Lien: <a href="https://www.irsn.fr/FR/Pages/Home.aspx" target="_blank">radiactité.com</a></p>
		</div>
	</div>
</body>
</html>
Accès à la page statique, cliquez sur le lien suivant:

codes/page-HTML-CSS-statique.html

Code Source python lecture des données et construction d’un fichier html.
import serial
import time
import datetime
import csv

file_name = "serial.html" # Once created, open this file in a browser.

# Adapt serial port nr. & baud rate to your system.
serial_port = 'COM4'
baudrate = 9600

page_title = "Radioactivity";

def write_page(data_list):
    fo = open(file_name,"w+", encoding='utf-8')
    # Start of HTML page.
    fo.write("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>")
    fo.write("<html lang="fr">")
    fo.write("<html><head><title>" + page_title + "</title>") # Page & Head begin.
    fo.write("<meta http-equiv='refresh' content='1'>")
    fo.write("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>")
    fo.write("<link href='css/style.css' rel='stylesheet'>")
    fo.write("<link rel='icon' href='https://image.flaticon.com/icons/svg/1446/1446151.svg'>")
    fo.write("</head><body><center>") # Head end, body begin.

    # Table begin.
    fo.write("<div class='page'> <div class='header'>")
    fo.write("<img class='logo' src='https://image.flaticon.com/icons/svg/1446/1446151.svg' width='53' height='59' title='Ma maison connectée' />")
    fo.write("<div class='banniere'><center> <h1> Surveillance de la radioactivité </h1> </center> </div>")
    fo.write("</div><hr/><div class='content'><table border='1'><tr><td>Date heure: </td>")
    hour=data_list[1]
    fo.write("<td>" + data_list[0] + "</td><td>" + hour[0:8] + "</td><td rowspan='2'>")
    fo.write("<img src='https://www.montrealsciencecentre.com/sites/default/files/styles/teaser_view/public/2017-04/de-quoi-est-compose-l-atome.png?itok=pKL1byWQ' width='70' height='80' /></td></tr>")
    fo.write("<tr><td>Mesure instantanée </td><center> <td>"+ data_list[2] + "</td> </center>	<center> <td>uS/h</td> </center>")
    fo.write("</tr></table></div><hr/><div class='footer'>")
    fo.write("<p> DUI | NSI | Lycée Alphonse Benoit | Lien:")
    fo.write("<a href='https://www.irsn.fr/FR/Pages/Home.aspx' target='_blank'>radiactité.com</a></p></div></div>")

    fo.write("</body>") # Body end.
    fo.write("</html>") # Page end.

    # Done, close file.
    fo.close()

s = serial.Serial(serial_port,baudrate) # Open serial port.
s.dtr = 0 # Reset Arduino.
s.dtr = 1
print("Waiting for data...");
time.sleep(2) # Wait for Arduino to finish booting.
s.reset_input_buffer() # Delete any stale data.

while 1:
    data_str = s.readline().decode() # Read data & convert bytes to string type.
    print(data_str)
    # Clean up input data.
    # Expected format: "$,id,value,CRLF"
    data_str = data_str.replace('r','') # Remove r,
    data_str = data_str.replace(' ','') # Remove whitespace.
    #data_str = data_str.replace(',','') # Remove ,.
    data_str = data_str.replace('\r','') # Remove return.
    data_str = data_str.replace('\n','') # Remove new line.
    #data_str = str(datetime.datetime.now()) + ' ' + data_str
    #data_str = data_str.replace(' ',',') # Remove whitespace.
    #print(data_str)
    #convert to uS/h
    data_sievers_float = float(data_str)*6*0.00812
    data_sievers_float = round(data_sievers_float, 4)
    print(data_sievers_float)
    #formatage csv pas
    data_str = str(datetime.datetime.now())+' '+ str(data_sievers_float)
    data_str_csv = str(datetime.datetime.now())+','+ str(data_sievers_float)
    print(data_str)
    # Split data in fields separated by ','.
    data_list = data_str.split(" ")
    #del data_list[0] # Remove '$'
    print(data_list)
    # Write HTML page.
    write_page(data_list)
    # Ecriture dans le fichier csv.
    with open("baliseGeiger.csv", 'a', newline='') as writeFile:
        theWriter = csv.writer(writeFile)
        theWriter.writerow([data_str_csv])
    writeFile.close()
Accès au programme Python, cliquez sur le lien suivant:

codes/Geiger-serial-to-Html.py

Cette solution effectue la lecture de la donnée sur la liaison série, création page web, sauvegarde dans un fichier CSV.

Résultat obtenu

3. Traitement des données post acquisition.

3.1. Traitement des données.

3.1.1. Reformatage du fichier balise.csv

Le fichier balise.csv comporte des erreurs de format. Un premier traitement permet d’éliminer les lignes ne correspondant pas au format:
Date heure,data

Avant:
17;2015-05-11 16:32:16;0,1462
Après:
"2015-05-11 16:32:16,0.1462"

Code Source filtrage des données.
import csv
import string


with open("balise.csv") as readFile:
    reader = csv.reader(readFile)
    #line = []
    lineSave = ''
#Mise en forme de la donnée
    for line in reader:
        line = str(line)
        #print(line[5:len(line)])
        #print(len(line))
        #Soustraction des 5 premierscaractères
        line = line[5:len(line)]

        #line = Substr(line, 0, 2)
        #line =line.replace("['","")
        line =line.replace("']","")
        line =line.replace("', '",".")
        line =line.replace(";",",")
        #line =
        #print(line)
        #line = str(line)
        with open("baliseOutput.csv", 'a', newline='') as writeFile:
            if  len(line) > 25 and line != lineSave:
                #lineString = str(line)
                #lineList = list(line)
                #print(lineList)
                #writeFile.write(line+ '\n')
                #lineSave = line
                theWriter = csv.writer(writeFile)
                theWriter.writerow([line])
        writeFile.close()
    readFile.close()
Accès au programme Python, cliquez sur le lien suivant:

codes/formatageDesDonnees/baliseFileProcessing.py

3.1.2. Découpage du fichier CSV en plusieurs fichiers par an, mois et jour.

Les données issues du capteur sont stockées dans un seul fichier CSV depuis quelques années. Il a été fait le choix de découper ce fichier en plusieurs en les organisant par année, mois et jour. De plus une moyenne des données a été faite par jour et par mois.

Code Source découpage du fichier origine.
import csv

with open("baliseOutput.csv") as readFile:
    #reader = csv.reader(open(f))
    reader = csv.reader(readFile)
    #next(reader) # skip header
    line = []
    for row in reader:
        line.append(row)
        rowSplit = row[0].split()
        #print(type(row[0]))
        #print(row[0])   #date time
        #print(row[1])   #data
        #print(rowSplit)
        #Split du fichier csv jour par jour
        dateSplit = rowSplit[0].split("-")
        csvFileName = rowSplit[0] + ".csv"
        #print(fileName)
        with open(csvFileName, 'a', newline='') as writeFile:
            theWriter = csv.writer(writeFile)
            theWriter.writerow(row)
        writeFile.close()
        #print(rowSplit[0])  #date
        #print(rowSplit[1])  #time
        #print(dateSplit)
        #print(dateSplit[0]) #year
        #print(dateSplit[1]) #month
        #print(dateSplit[2]) #day
        yearCsvFileName = dateSplit[0] + ".csv"

        month = dateSplit[0] + "-" + dateSplit[1] + ".csv"
        #print(month)
        with open(month, 'a', newline='') as writeFile:
            theWriter = csv.writer(writeFile)
            theWriter.writerow(row)
        writeFile.close()
        with open(yearCsvFileName , 'a', newline='') as writeFile:
            theWriter = csv.writer(writeFile)
            theWriter.writerow(row)
        writeFile.close()
    readFile.close()
Accès au programme Python, cliquez sur le lien suivant:

codes/decoupageDuFichier/cuttingFile.py

3.1.3. Faire des moyennes pour l’affichage.

Le fichier est maintenant découpé en plusieurs fichiers jours, mois, années (exemple: 2017-02-03.csv, 2017-05.csv, 2017.csv). Le nombre de données trop important ne permet pas un affichage sur un mois ou une année (rappel: la mesure est effectuée toutes les 10 secondes). Le programme suivant fait une moyenne des données sur la journée ou le mois et la sauvegarde dans un fichier csv.
Pour l’année 2017.

Code Source Calcul de la moyenne par jour et par mois
import csv
dayDateSave="0000-00-00"
monthDateSave="0000-00-00"
count = 0
countMonth = 0
add = 0
average = 0
addMonth = 0
averageMonth = 0

with open("2017-days.csv") as readFile:

    reader = csv.reader(readFile)

    line = []
    data =[]
    for row in reader:
        line.append(row)
        #rowSplit = row[0].split()
        #print(type(row[0]))
        #print(row[0])   #date time
        #print(row[1])   #data
        date = str(row[0])
        dayDate = date[0:10]
        monthDate = date[0:7]
        #print(dayDate)
        #print(dayDateSave)
        #print(monthDate)
#Calcul de la moyenne par jour
        if dayDate == dayDateSave or count == 0:
            #row[1] = row[1].replace(".",",")
            #data = float(row[1])
            #print(count)
            add= add + float(row[1])
            count = count + 1
            dayDateSave = dayDate
            #print(add)
        else :
            average = add / count
            #print(dayDate)
            #print(dayDateSave)
            #print(average)
            #print(count)
            add=0
            count = 0

# Ecriture dans le fichier csv.
            with open("2017-One-Data-by-day.csv", 'a', newline='') as writeFile:
                writeFile.write(str(dayDateSave) + ',' + str(average) + '\n' )
            writeFile.close()
            dayDateSave = dayDate #Sauvegarde de la date après écriture

#Calcul de la moyenne par mois
        if monthDate == monthDateSave or countMonth == 0:
            #row[1] = row[1].replace(".",",")
            #data = float(row[1])
            #print(countMonth)
            addMonth= addMonth + float(row[1])
            countMonth = countMonth + 1
            monthDateSave = monthDate
            #print(add)
        else :
            averageMonth = addMonth / countMonth
            #print(countMonth)
            #print(monthDate)
            #print(monthDateSave)
            #print(averageMonth)
            addMonth=0
            countMonth = 0

# Ecriture dans le fichier csv.
            with open("2017-One-Data-by-month.csv", 'a', newline='') as writeFile:
                writeFile.write(str(monthDateSave) + ',' + str(averageMonth) + '\n' )
            writeFile.close()
            monthDateSave = monthDate #Sauvegarde de la date après écriture
    readFile.close()
Accès au programme Python, cliquez sur le lien suivant:

codes/2017-moyennes/averageCalcul.py

4. Affichage des courbes dans une page web (bloc1: HTML/CSS/javascript)

4.1. Convertion des fichiers csv en un fichier javascript.

Dans un premier temps, il est nécessaire de convertir les fichiers csv en fichier javascipt. On récupère chacune des données du fichier csv pour en faire une chaîne de caractères.

excel 2017
Figure 2. Avant
2017 js notepade
Figure 3. Après
Code Source python csv To js
# -*- coding: utf-8 -*-

import csv

def csv2js(array_js, js_file, csv_file):
	data = []
	titre = True;
	for line in csv.reader(open(csv_file)):
		if titre:
			titre = False
		else:
			print([line[0], float(line[1])])
			data.append([line[0], float(line[1])])

	open(js_file, "a").write( array_js +' = '+ str(data)+";\n")

csv2js("_30jours", "tableau.js", "2017-01-One-Data-by-day.csv")
csv2js("_12mois", "tableau.js", "2017-One-Data-by-month.csv")

4.2. Elaboration de la page Web.

Code Source affichage des courbes
<!DOCTYPE html>
<html class="has-background-grey-dark" lang="fr">
<head>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">
    <title>Radiactivité</title>
    <link href="https://fonts.googleapis.com/css?family=Roboto+Slab&display=swap" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" rel="stylesheet">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>

    <style>
        h1{
            text-align: center;
            font-family: 'Roboto Slab', serif;
        }


    </style>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script src="tableau.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>


    <script>

        function get_date(_liste) {
            console.log(_liste);
            var line;
            var data = [];
            _liste.forEach(function (item) {
                console.log(item)
                data.push(item[0]);
            });
            return data;
        }

        function get_data(_liste) {
            var line;
            var data = [];
            _liste.forEach(function (item) {
                data.push(item[1]);
                //console.log(line[1]);
            });
            return data;
        }

        function get_tableau(tableId, _date, _data) {
            var table = $("<table>");
            table.attr('id', 'ma_table');
            table.attr('class', 'table is-fullwidth')

            head = $("<thead>").html("<tr><th>Date</th><th>Radioactivité (µS/h)</th></tr>");
            table.append(head);

            body = $("<tbody>");
            table.append(body);

            for (var i = 1; i < _date.length; i += 1) {
                line = $("<tr>");
                line.append($("<td>").text(_date[i]));
                line.append($("<td>").text(_data[i]));

                body.append(line);
            }
            $(tableId).html(table);
        }

        function get_chart(_id, _type, _date, _data, _label, _titre) {
            new Chart(document.getElementById(_id), {
            type: _type,
            data: {
                        labels: _date,
                        datasets: [{
                        data: _data,
                        label: _label,
                        borderColor: "#3e95cd",
                        fill: true
                    }]
                },
                options: {
                    scales: {
                        xAxes: [{
                            ticks:{
                                autoSkip: true,
                                autoSkipPadding: 10,
                            }
                        }]
                    },
                    title: {
                        display: true,
                        text: _titre
                    }
                }
            });
        }

        sante = "Les effets sur un organisme vivant d'une exposition aux rayonnements ionisants (irradiation) dépendent du niveau et de la durée de l'exposition (aiguë ou chronique), de la nature du rayonnement ainsi que de la localisation de la radioactivité (exposition externe, interne, en surface,etc...).";

        environnement = "Comme pour l'homme, la radioactivité est dangereuse pour l'environnement. Mais comme pour l'homme seule la radioactivité artificielle « abusive » (comme par exemple des centrales) et ses accidents sont dangereuses. En effet la radioactivité naturelle n'est en aucun cas dangereuse pour l'environnement.";

        $(function () {
            la_periode = $("#periode option:selected").text();
            tableau_js = $("#periode option:selected").val();

            console.log(tableau_js);

            if(tableau_js=="_30jours") tab = _30jours;
            if(tableau_js=="_12mois") tab = _12mois;


            get_tableau("#Table", get_date(tab), get_data(tab));
            get_chart("line-chart", "line", get_date(tab), get_data(tab),
            "Radioactivité (µS/h) sur "+la_periode,
            "Radioactivité à Saint Didier");

            $("#periode").change(function(){
                la_periode = $("#periode option:selected").text();
                tableau_js = $("#periode option:selected").val();

                if(tableau_js=="_30jours") tab = _30jours;
                if(tableau_js=="_12mois") tab = _12mois;

                console.log(la_periode);

                $("#titre_periode").html(la_periode);

                get_tableau("#Table", get_date(tab), get_data(tab));
                get_chart("line-chart", "line", get_date(tab), get_data(tab),
                "Radioactivité (µS/h) sur "+la_periode,
                "Radioactivité à Saint Didier");
            });

            $("#effets").html(sante);

            $("#select_effets").change(function(){
                les_effets_sur = $("#select_effets option:selected").text();
                console.log(les_effets_sur);
                if(les_effets_sur == "la santé") $("#effets").html(sante);
                if(les_effets_sur == "l'environnement") $("#effets").html(environnement);
            });
        });
    </script>


</head>
<body>

    <div class="container is-widescreen">
    <section class="hero has-background-black">
        <div class="hero-body">
            <div class="container">
                <h1 class="title has-text-light">
                    Mesures de la radioactivité à Saint Didier
                </h1>
                <!--
                <h2 class="subtitle has-text-grey-lighter">
                    <a href="https://www.atmosud.org/donnees/acces-par-station/03080">Données</a>
                </h2>
                -->
            </div>
        </div>
    </section>
    </div>

    <div class="container is-widescreen">
    <div class="tile is-ancestor">
        <div class="tile is-8 is-vertical is-parent">
            <div class="tile is-child box">
              <p class="title">Radioactivité et rayonnements</p>
              <p>La <b>radioactivité</b> est le <a href="/wiki/Ph%C3%A9nom%C3%A8ne_physique" title="Phénomène physique">phénomène physique</a>
			  par lequel des <a href="/wiki/Noyau_atomique" title="Noyau atomique">noyaux atomiques</a> instables (dits radionucléides ou
			  <a href="/wiki/Radioisotope" title="Radioisotope">radioisotopes</a>) se transforment spontanément en d'autres atomes
			  <a href="/wiki/D%C3%A9sint%C3%A9gration_radioactive" class="mw-redirect" title="Désintégration radioactive">(désintégration)</a>
			  en émettant simultanément des particules de matière (<a href="/wiki/%C3%89lectron" title="Électron">électrons</a>,
			  <a href="/wiki/Noyau_atomique" title="Noyau atomique">noyaux</a> d'<a href="/wiki/H%C3%A9lium_4" title="Hélium 4">hélium</a>,
			  <a href="/wiki/Neutron" title="Neutron">neutrons</a>,&#160;<abbr class="abbr" title="et cetera">etc.</abbr>) et de l'
			  <a href="/wiki/%C3%89nergie_(physique)" title="Énergie (physique)">énergie</a> (<a href="/wiki/Photon" title="Photon">photons</a>
			  et <a href="/wiki/%C3%89nergie_cin%C3%A9tique" title="Énergie cinétique">énergie cinétique</a>). La radioactivité a été découverte en
			  <a href="/wiki/1896" title="1896">1896</a> par <a href="/wiki/Henri_Becquerel" title="Henri Becquerel">Henri Becquerel</a>
			  dans le cas de l'<a href="/wiki/Uranium" title="Uranium">uranium</a>, et très vite confirmée par <a href="/wiki/Marie_Curie" title="Marie Curie">
			  Marie Curie</a> pour le <a href="/wiki/Radium" title="Radium">radium</a>.
</p><p>L'émission de particules matérielles et immatérielles est appelée rayonnement, et l'énergie des particules est suffisante pour entraîner l'ionisation de
la matière traversée, d'où le nom de <a href="/wiki/Rayonnements_ionisants" class="mw-redirect" title="Rayonnements ionisants">rayonnements ionisants</a>
. On distingue classiquement les rayons α constitués de noyaux d'hélium (également appelés <a href="/wiki/Particule_%CE%B1" title="Particule α"> particules α</a>)
, les rayons β constitués d'électrons (<a href="/wiki/Particule_%CE%B2" title="Particule β">particules β</a>) et les
<a href="/wiki/Rayon_%CE%B3" class="mw-redirect" title="Rayon γ">rayons γ</a> constitués de <a href="/wiki/Photon" title="Photon">photons</a>
, auxquels il faut ajouter les neutrons qui dérivent des <a href="/wiki/Fission_spontan%C3%A9e" title="Fission spontanée">fissions spontanées</a>.
</p>
              <p>Consulter les données sur :
                <select id="periode">
                  <option value="_12mois">les 12 derniers mois</option>
                  <option value="_30jours">les 30 derniers jours</option>
                </select>
              </p>
            </div>
            <div class="tile is-child box">
              <p class="title">Graphique</p>
              <div id="graph"><canvas id="line-chart"></canvas></div>
            </div>

        </div>

        <div class="tile is-4 is-vertical is-parent">
            <div class="tile is-child box">
                <p class="title">Effets sur
                    <select id="select_effets">
                        <option>la santé</option>
                        <option>l'environnement</option>
                    </select></p>
                <p id="effets"></p>
            </div>
            <div class="tile is-child box">
                <div class="container is-widescreen">
                    <p class="title" id="titre_periode">Période :</p>
                    <div id="Table"></div>
                </div>
            </div>

        </div>
    </div>
    </div>

</body>
</html>
Accès à la page, cliquez sur le lien suivant:

codes/PageWebCourbes/GeigerStDidier.html

Accès au code HTML, javascript, cliquez sur le lien suivant:

codes/PageWebCourbes

4.3. Résultat.

Résultat obtenu

5. Construction d’une interface Graphique Utilisateur (GUI)

L’interface graphique Utilisateur a été conçue avec les outils QtLogo QtDesigner et Python and Qt.svg PyQt5.

L’interface est composée de deux fichiers python. Le premier éditer avec QtDesigner et la commande:

pyuic5 maPremiereInterface.ui -o maPremiereInterfaceGUI.py
cmdUItoPY

QtDesigner permet d’éditer l’interface graphique et certains événements. Tandis que le deuxième programme identifie les actions exécutées par l’ordinateur en fonction des actions de l’utilisateur.

Code Source du premier programme.
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'serialGUIqtdesignerV2.ui'
#
# Created by: PyQt5 UI code generator 5.13.0
#
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(343, 332)
        self.groupBox_config = QtWidgets.QGroupBox(Dialog)
        self.groupBox_config.setGeometry(QtCore.QRect(10, 10, 321, 61))
        self.groupBox_config.setObjectName("groupBox_config")
        self.label = QtWidgets.QLabel(self.groupBox_config)
        self.label.setGeometry(QtCore.QRect(10, 20, 61, 16))
        self.label.setObjectName("label")
        self.comboBox_serialPort = QtWidgets.QComboBox(self.groupBox_config)
        self.comboBox_serialPort.setGeometry(QtCore.QRect(70, 20, 161, 22))
        self.comboBox_serialPort.setObjectName("comboBox_serialPort")
        self.pushButton_connect_disconnect = QtWidgets.QPushButton(self.groupBox_config)
        self.pushButton_connect_disconnect.setGeometry(QtCore.QRect(240, 20, 75, 23))
        self.pushButton_connect_disconnect.setObjectName("pushButton_connect_disconnect")
        self.groupBox_communication = QtWidgets.QGroupBox(Dialog)
        self.groupBox_communication.setEnabled(False)
        self.groupBox_communication.setGeometry(QtCore.QRect(10, 80, 321, 241))
        self.groupBox_communication.setObjectName("groupBox_communication")
        self.label_2 = QtWidgets.QLabel(self.groupBox_communication)
        self.label_2.setGeometry(QtCore.QRect(60, 20, 111, 16))
        self.label_2.setObjectName("label_2")
        self.lineEdit_message = QtWidgets.QLineEdit(self.groupBox_communication)
        self.lineEdit_message.setEnabled(False)
        self.lineEdit_message.setGeometry(QtCore.QRect(10, 40, 211, 20))
        self.lineEdit_message.setObjectName("lineEdit_message")
        self.pushButton_sendMessage = QtWidgets.QPushButton(self.groupBox_communication)
        self.pushButton_sendMessage.setEnabled(False)
        self.pushButton_sendMessage.setGeometry(QtCore.QRect(230, 40, 81, 23))
        self.pushButton_sendMessage.setObjectName("pushButton_sendMessage")
        self.label_3 = QtWidgets.QLabel(self.groupBox_communication)
        self.label_3.setGeometry(QtCore.QRect(20, 70, 101, 16))
        self.label_3.setObjectName("label_3")
        self.textEdit_messageRecieved = QtWidgets.QTextEdit(self.groupBox_communication)
        self.textEdit_messageRecieved.setEnabled(False)
        self.textEdit_messageRecieved.setGeometry(QtCore.QRect(10, 90, 301, 141))
        self.textEdit_messageRecieved.setObjectName("textEdit_messageRecieved")

        self.retranslateUi(Dialog)
        self.pushButton_connect_disconnect.clicked.connect(Dialog.connect_disconnect)
        self.pushButton_sendMessage.clicked.connect(Dialog.sendMessage)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.groupBox_config.setTitle(_translate("Dialog", "Config"))
        self.label.setText(_translate("Dialog", "Select Port"))
        self.pushButton_connect_disconnect.setText(_translate("Dialog", "Connect"))
        self.groupBox_communication.setTitle(_translate("Dialog", "Communication"))
        self.label_2.setText(_translate("Dialog", "Write your message"))
        self.pushButton_sendMessage.setText(_translate("Dialog", "Send Message"))
        self.label_3.setText(_translate("Dialog", "Message recieved"))
Accès au Python, cliquez sur le lien suivant:

codes/applicationGUI/serialGUI.py

Code Source du deuxième programme.
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtCore import QTimer, QTime
from PyQt5.QtWidgets import QMessageBox, QApplication

from serialGUI import Ui_Dialog
import os, sys
import serial
from typing import Iterator, Tuple
from serial.tools.list_ports import comports
#https://pyserial.readthedocs.io/en/latest/pyserial_api.html
from _datetime import datetime


class ApplicationIHM(QtWidgets.QMainWindow):
    def __init__(self):
        super(ApplicationIHM, self).__init__()

        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.setWindowTitle('Serial Communication')
        #Ajouter une icône à la fenêtre principale
        self.setWindowIcon(QtGui.QIcon('serial.png'))

        self.logTimer = None

        # Identification des ports disponibles
        self.update_com_ports()

    def connect_disconnect(self):
        sender = self.sender()
        if sender.text() == 'Connect':
            #print(self.ui.pushButton_connect_disconnect.text())
            self.ui.pushButton_connect_disconnect.setText('Disconnect')
        # Choix du port dans la liste box
            #print(self.ui.comboBox_serialPort.currentText())
            startDetect = self.ui.comboBox_serialPort.currentText().find("COM")
            endDetect = self.ui.comboBox_serialPort.currentText().find(")")
            myPort = self.ui.comboBox_serialPort.currentText()[startDetect:endDetect]
            print(myPort)
        # Ouverture du port sélectionné
            self.ui.port = serial.Serial(myPort, baudrate=9600, timeout=1)
        # Activation Désactivation des controles
            self.ui.groupBox_communication.setEnabled(True)
            self.ui.lineEdit_message.setEnabled(True)
            self.ui.pushButton_sendMessage.setEnabled(True)
            self.ui.textEdit_messageRecieved.setEnabled(True)
            self.ui.comboBox_serialPort.setEnabled(False)

            self.timer = QTimer(self)
            self.timer.timeout.connect(self.readBufferReception)
            self.timer.start(100)

        else:
            print(self.ui.pushButton_connect_disconnect.text())
            self.ui.pushButton_connect_disconnect.setText('Connect')
            self.ui.comboBox_serialPort.setEnabled(True)
            self.timer.stop()
            self.ui.groupBox_communication.setEnabled(False)

            self.ui.comboBox_serialPort.clear()
        #Fermeture du port
            self.ui.port.close()


     # Identification des ports disponibles
            self.update_com_ports()

    def sendMessage(self):
        print("sendMessage")
        self.ui.port.write(self.ui.lineEdit_message.text().encode('ascii') + b'\r\n')

    def readBufferReception(self):
        #print("readBufferReception")
        myline = self.ui.port.read(self.ui.port.inWaiting())
        if myline:
            myline = myline.decode("ascii")
            print(myline)
            self.ui.textEdit_messageRecieved.append(myline)

    def update_com_ports(self) -> None:
        "Update COM Port list in GUI."
        for name, device in gen_serial_ports():
            self.ui.comboBox_serialPort.addItem(name, device)

def gen_serial_ports() -> Iterator[Tuple[str, str]]:
    "Return all available serial ports."
    ports = comports()
    return ((p.description, p.device) for p in ports)

def main():
    app = QtWidgets.QApplication(sys.argv)
    application = ApplicationIHM()
    application.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
Accès au Python, cliquez sur le lien suivant:

codes/applicationGUI/mainSerial.py

5.1. Résultat obtenu

application
Figure 4. Interface Utilisateur minimum

6. Conclusion

6.1. Contexte général.

Ce Projet a permis de répondre au besoin d’information de familles vivant en site contaminé par la radioactivité. Aujourd’hui les systèmes embarqués sont de plus en plus présents dans notre environnement. Ces systèmes d’acquisition de données sont indispensables aux scientifiques. Ils doivent appréhender des problèmes et des environnements de plus en plus complexes. Les données sont le point de départ de toutes les analyses et de toutes les tentatives de modélisation d’un phénomène. Elles permettent également d’obtenir les réactions d’un phénomène aux essais des scientifiques. Une rencontre avec M.Bourgeois directeur, business unit du nucléaire, chez Assytem m’indique qu’un tiers de ses ingénieurs travaillent sur la conception de systèmes embarqués, environ une centaine d’ingénieurs.

Par conséquent, nous pouvons l’utiliser pour tout système multiphysique dont nous devons acquérir les grandeurs afin de les stocker et les analyser.

6.2. Contexte de formation.

Ce projet nous a permis de réinvestir une grande partie des compétences et connaissances acquises lors de la formation des 3 blocs. Nous avons essayé d’aborder la majorité des items de la formation.

6.3. Remerciements

Nous remercions tous les formateurs qui nous ont permis d’approfondir nos connaissances et compétences dans les domaines de l’informatique.