CellarKare : Différence entre versions
(→Bouton OFF) |
m (Révocation des modifications de Admin (discussion) vers la dernière version de Quentin) |
||
(24 révisions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 9 : | Ligne 9 : | ||
* Documenter les différentes étapes (tutoriel), et fournir les fichiers sources nécessaires aux différentes étapes. | * Documenter les différentes étapes (tutoriel), et fournir les fichiers sources nécessaires aux différentes étapes. | ||
− | = Mark 1: Le matos et un premier proto Arduino= | + | = Mark (1): Le matos et un premier proto Arduino= |
Le premier prototype sera relativement simple: la station affichera la température et l'humidité ambiante sur un écran LCD, le tout piloté par un Arduino Uno. | Le premier prototype sera relativement simple: la station affichera la température et l'humidité ambiante sur un écran LCD, le tout piloté par un Arduino Uno. | ||
Ligne 162 : | Ligne 162 : | ||
La suite: la même chose avec un Raspberry pi, pour bénéficier de sa faciliter à se connecter au réseau. Ce serait quand même cool de pouvoir lire ces valeurs sur un smartphone ;-) | La suite: la même chose avec un Raspberry pi, pour bénéficier de sa faciliter à se connecter au réseau. Ce serait quand même cool de pouvoir lire ces valeurs sur un smartphone ;-) | ||
− | = Mark 2: la même chose avec un Raspberry Pi = | + | = Mark (2): la même chose avec un Raspberry Pi = |
Le but de cette nouvelle itération: réaliser un montage équivalent avec un [https://www.raspberrypi.org/ Raspberry Pi] (RPi en abrégé). Pourquoi un RPi ? | Le but de cette nouvelle itération: réaliser un montage équivalent avec un [https://www.raspberrypi.org/ Raspberry Pi] (RPi en abrégé). Pourquoi un RPi ? | ||
* Parce que j'en avais un sous la main ;-) | * Parce que j'en avais un sous la main ;-) | ||
Ligne 320 : | Ligne 320 : | ||
* le langage Python, c'est pas plus compliqué que le simili-C de l'Arduino une fois qu'on est rentré dedans. | * le langage Python, c'est pas plus compliqué que le simili-C de l'Arduino une fois qu'on est rentré dedans. | ||
− | = Mark 3: Start, Stop, and say Cheese = | + | = Mark (3): Start, Stop, and say Cheese = |
== Sart/Stop == | == Sart/Stop == | ||
Ligne 340 : | Ligne 340 : | ||
#* Un montage en anglais à base de micro contrôleur (pas simple donc) sur [http://www.instructables.com/id/Raspberry-Pi-Shutdown-Button/ Instructables] | #* Un montage en anglais à base de micro contrôleur (pas simple donc) sur [http://www.instructables.com/id/Raspberry-Pi-Shutdown-Button/ Instructables] | ||
#* Un article très complet en anglais reprenant toutes les options, dont un montage assez simple de bouton unique [http://www.raspberry-pi-geek.com/Archive/2013/01/Adding-an-On-Off-switch-to-your-Raspberry-Pi sur raspberry-pi-geek.com] | #* Un article très complet en anglais reprenant toutes les options, dont un montage assez simple de bouton unique [http://www.raspberry-pi-geek.com/Archive/2013/01/Adding-an-On-Off-switch-to-your-Raspberry-Pi sur raspberry-pi-geek.com] | ||
− | #* Des solutions commerciales toutes faites | + | #* Des solutions commerciales toutes faites comme [https://www.pi-supply.com/product/pi-supply-raspberry-pi-power-switch/ PiSupply] ou [http://www.mausberrycircuits.com/collections/frontpage MausberryCircuits] |
Dans un premier temps, ce sera pour moi le choix suivant: un bouton OFF, qui lors d'un appui long éteint proprement le RPi. En gros la solution 2. | Dans un premier temps, ce sera pour moi le choix suivant: un bouton OFF, qui lors d'un appui long éteint proprement le RPi. En gros la solution 2. | ||
Ligne 369 : | Ligne 369 : | ||
Activer le "hard" reset sur le Rpi suppose un peu de soudure, mais rien de bien effrayant, 2 points suffisent. Un fois les connecteur pinout soudés, quand on raccorde les deux pins (par exemple avec un interrupteur), le RPi redémarre ("hard" reset, donc attention à la carte SD...), ou s'allume (si il était raccordé au secteur). | Activer le "hard" reset sur le Rpi suppose un peu de soudure, mais rien de bien effrayant, 2 points suffisent. Un fois les connecteur pinout soudés, quand on raccorde les deux pins (par exemple avec un interrupteur), le RPi redémarre ("hard" reset, donc attention à la carte SD...), ou s'allume (si il était raccordé au secteur). | ||
− | + | J'ai soudé les 2 pins et testé la fonction, mais comme il s'agit d'un redémarrage "hard", je n'ai finalement pas vraiment vu l'utilité dans ce projet: autant directement débrancher / rebrancher le câble d'alimentation dans ce cas. | |
Mais à toutes fins utiles, les photos de l'opération, avant/après. | Mais à toutes fins utiles, les photos de l'opération, avant/après. | ||
Ligne 382 : | Ligne 382 : | ||
Les parties utiles du code: | Les parties utiles du code: | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | # La fonction qui effectuer l'arrêt propre du RPi | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight lang="python" class="mw-collapsible mw-collapsed"> | ||
+ | def btn_proper_halt(pin): | ||
+ | global off_state, cur_millis, off_millis | ||
+ | # On lit le nombre de millisecondes courant | ||
+ | cur_millis = time.time()*1000 | ||
+ | # Utilise la variable globale off_state | ||
+ | off_state = not off_state | ||
+ | # DEBUG | ||
+ | logging.debug("Bouton OFF_PIN pressé, son état est maintenant %s" % str(off_state)) | ||
+ | if off_state == True: | ||
+ | off_millis = cur_millis | ||
+ | elif off_state == False: | ||
+ | # DEBUG | ||
+ | logging.debug("Bouton OFF_PIN pressé pendant {0:0.0f} ms".format(cur_millis - off_millis)) | ||
+ | if (cur_millis - off_millis) > OFF_INTERVAL: | ||
+ | # On a relâché le bouton avec un écart long => shutdown | ||
+ | logging.debug("Appui long sur le bouton OFF_PIN") | ||
+ | logging.info("Arrêt du Raspberry PI, attendre encore 10 sec avant de débrancher") | ||
+ | subprocess.call('halt', shell=False) | ||
+ | else: | ||
+ | # Appuis court => on ne fait rien | ||
+ | logging.debug("Appui court sur le bouton OFF_PIN") | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | # Fonction de terminaison du programme (appelée lorsque le script est arrêté | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight lang="python" class="mw-collapsible mw-collapsed"> | ||
+ | def terminate(): | ||
+ | logging.debug("Terminate function started") | ||
+ | # efface l'écran LCD | ||
+ | lcd_dim_backlight(0.0, 0.2) | ||
+ | lcd.clear() | ||
+ | logging.debug("Terminate function ended") | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | # Fonction d'initialisation | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight lang="python" class="mw-collapsible mw-collapsed"> | ||
+ | def initialize(): | ||
+ | global lcd | ||
+ | logging.debug("Initialize function started") | ||
+ | # Initialisation du LCD avec les variables ci-dessus | ||
+ | lcd = LCD.Adafruit_CharLCD(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7, | ||
+ | LCD_COLUMNS, LCD_ROWS, | ||
+ | LCD_BACKLIGHT, invert_polarity = False, enable_pwm = True, | ||
+ | initial_backlight = lcd_backlight) | ||
+ | # Initialisation des autres GPIO's | ||
+ | GPIO.setmode(GPIO.BCM) # On utilise la numérotation BCM pour les PIN | ||
+ | # On définit la pin OFF_PIN comme entrée, et on active la résistance pull-down interne du RPi | ||
+ | GPIO.setup(OFF_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) | ||
+ | # Configure une interruption qui va détecter les mouvements up et down sur le bouton | ||
+ | GPIO.add_event_detect(OFF_PIN, GPIO.BOTH, callback=btn_proper_halt, bouncetime=20) | ||
+ | ... | ||
+ | </syntaxhighlight> | ||
== Say "cheese" == | == Say "cheese" == | ||
− | Cette partie du projet s'attache à pouvoir prendre rapidement et facilement un photos des étiquettes des bouteilles entrées ou sorties de la cave. Ben oui, j'ai pas forcément une bonne mémoire, et se donner les moyens | + | Cette partie du projet s'attache à pouvoir prendre rapidement et facilement un photos des étiquettes des bouteilles entrées ou sorties de la cave. Ben oui, j'ai pas forcément une bonne mémoire, et se donner les moyens d'enregistrer facilement et rapidement l'info est le meilleur moyen de suivre son stock ;-) |
+ | |||
+ | Cette partie est super simple à réaliser avec le rpi: on raccorde la caméra, on s'assure qu'elle est activée avec <code>sudo raspi-config</code>, et on peut ensuite l'utiliser en ligne de commande ou en python. Le site web [https://www.raspberrypi.org/documentation/usage/camera/README.md raspberrypi.org (en anglais)] explique cela très bien. | ||
+ | |||
+ | On ajoute un deuxième bouton sur le breadboard, qui déclenche la prise d'une photo, et le prototype est maintenant fonctionnel. Les photos sont sauvegardées dans le répertoire courant du RPi, avec la date et l'heure pour nom de fichier. | ||
+ | |||
+ | Au niveau du code, les parties utiles sont: | ||
+ | <syntaxhighlight lang="python"> | ||
+ | # Fonction de prise d'une photo | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight lang="python" class="mw-collapsible mw-collapsed"> | ||
+ | def btn_take_picture(pin): | ||
+ | # DEBUG | ||
+ | logging.info("Bouton CAM_PIN pressé, on prend un photo") | ||
+ | # Construction du chemin pour la photo | ||
+ | now = datetime.datetime.now() | ||
+ | pic_filename = "/home/qb/pic_" + str(now) + ".jpg" | ||
+ | # Initialisation de la camera. On ne le fait pas dans initialize() pour éviter au maximum de la laisser | ||
+ | # allumée (et consommer) en continu. En plus, une fois activée, les lectures sur le DHT sont beaucoup | ||
+ | # plus souvent ratées. | ||
+ | with PICAM.PiCamera() as cam: | ||
+ | cam.capture(pic_filename) | ||
+ | logging.debug("Photos sauvée " + str(cam.resolution) + " dans " + pic_filename) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Pimp my screen == | ||
+ | |||
+ | Pour ce qui est d'allumer ou éteindre l'écran, la bibliothèque CharLCD permet la modulation PWN du rétroéclairage. De quoi avoir un chouette effet de fondu au noir lorsque l'écran s'allume et s'éteint. | ||
+ | |||
+ | Au niveau du code, la partie utile: | ||
+ | <syntaxhighlight lang="python"> | ||
+ | # Module l'intensité du rétro éclairage de l'écran lcd | ||
+ | </syntaxhighlight> | ||
+ | <syntaxhighlight lang="python" class="mw-collapsible mw-collapsed"> | ||
+ | def lcd_dim_backlight(backlight_value, time_step): | ||
+ | global lcd | ||
+ | global lcd_backlight | ||
+ | # calcule l'incrément à avoir entre lcd_backlight et backlight_value | ||
+ | stp = (lcd_backlight > backlight_value) * -2 +1 | ||
+ | brightnesses = map(lambda x: x/10.0, range(int(lcd_backlight*10), int(backlight_value*10)+1, stp)) | ||
+ | for bright in brightnesses: | ||
+ | lcd_backlight = bright | ||
+ | logging.debug("loop on bright= " + str(bright)) | ||
+ | lcd.set_backlight(bright) | ||
+ | time.sleep(time_step) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Le code et le montage == | ||
+ | |||
+ | Le code source complet pour [https://gitlab.com/qberten/cellarkare/blob/master/mark3_camera/cellarkare_cam.py Mark 3 est disponible sur gitlab]. Pour ce qui est du montage, ben pas de schéma de raccordement disponible, j'ai oublié de le documenter en temps utile avant de passer à Mark 4! Donc il faudra attendre la suite... | ||
+ | |||
+ | = Mark (4): We are (almost) done = | ||
+ | |||
+ | == Pimp my screen, reloaded == | ||
+ | Une dernière petite touche intéressante: l'appareil est destiné à être mis dans une cave à vin, endroit où il va faire noir la plupart du temps. En conséquence, l'écran LCD n'a pas vraiment besoin d'être allumé en permanence, pourquoi ne pas l'allumer automatiquement quand la lumière du local dépasse un certain seuil. Une photorésistance permet de réaliser rapidement la mesure d'une intensité lumineuse, pourquoi s'en priver... Oui mais... le RPi n'a pas d'entrée analogique, comment convertir simplement la mesure sur la photorésistance en une valeur analogique, alors que le RPi n'a que des entrées digitales. | ||
+ | |||
+ | Après un peu de recherche, un montage simple à base d'une capacité dont le temps de charge sera directement proportionnel à la mesure sur la photo-résistance. Tout cela est expliqué ici (en anglais): [http://www.raspberrypi-spy.co.uk/2012/08/reading-analogue-sensors-with-one-gpio-pin/] | ||
+ | |||
+ | (TODO - continuer la documentation) | ||
+ | |||
+ | == Buttons, buttons, we want more == | ||
+ | |||
+ | Dernier petit détail, et non des moindre, Pour avoir un projet réellement fonctionnel, il va falloir travailler un peu l'interface homme-machine, et permettre un peu plus d'interaction avec le Rpi, pour par exemple permettre d'entrer le nombre de bouteilles couvertes par l'entrée ou la sortie photographiée, quitter/valider une action, ... | ||
+ | |||
+ | (TODO - continuer la documentation) | ||
+ | |||
+ | == We want leds == | ||
+ | |||
+ | Toujours dans l'optique d'améliorer l'interaction avec l'appareil, pourquoi ne pas intégrer une led de status sur l'appareil, pour indiquer visuellement différents états, comme par exemple: une température trop basse ou trop haute est enregistrée, il n'y a pas de connection réseau, ... | ||
+ | |||
+ | (TODO - à implémenter tout court ;-) | ||
+ | |||
+ | == All your data are belong to us == | ||
− | + | Enfin, c'est bien beau d'afficher la température et l'humidité, c'est encore mieux de l'enregistrer quelque part pour pouvoir tracer l'historique, et intéragir plus facilement avec ces données. Après quelques investigations, c'est [http://thingspeak.com Thingspeak] qui a retenu mon attention. | |
− | + | (TODO - à implémenter tout court ;-) | |
[[Catégorie:Projets]] | [[Catégorie:Projets]] |
Version actuelle datée du 4 juillet 2016 à 10:39
Un projet lancé par --Quentin (discussion) 14 juin 2015 à 19:58 (CEST)
Sommaire
En bref
Le projet est de créer une "station météo" pour une cave à vin, d'où le nom du projet un peu fumeux de CellarKare = Cellar + Care, le K c'est pour faire joli ;-) Dans un premier temps, le but est d'afficher la température et le taux d'humidité (hygrométrie). Ça, c'est pour le but fonctionnel. Mes autres buts sont:
- Au niveau perso: réaliser un premier projet à forte composante électronique, dans la foulée de notre formation Arduino
- Utiliser un maximum toutes les techniques/machines du Makilab
- Documenter les différentes étapes (tutoriel), et fournir les fichiers sources nécessaires aux différentes étapes.
Mark (1): Le matos et un premier proto Arduino
Le premier prototype sera relativement simple: la station affichera la température et l'humidité ambiante sur un écran LCD, le tout piloté par un Arduino Uno.
T° et humidité: DHT22 / AM2303
Au niveau des capteurs, logiquement il en faut un pour lire la température, et un pour lire l'humidité. Les deux existent certainement séparément, mais une rapide recherche sur le net done des références de capteurs déjà calibrés et fournissant les deux pour pas trop cher. Au final, mon choix se porte sur un DHT22. Ce capteur s'appelle aussi AM2303 dans sa version câblée. Quelques liens et documentation utiles:
- Les specs du DHT22 (anglais);
- Pour acheter: ~10 $ chez AdaFruit ou ~17 € chez MCHobby, c'est un peu plus cher, mais ils sont belges et sympas;
- Un tutoriel pour arduino sur Arduino.cc;
- Un tutoriel pour arduino sur AdaFruit, et sa version française;
L'affichage LCD: LCM1602
Pour l'affichage, le choix a été vite fait... J'ai pris ce que j'avais sous la main, soit le LCD fourni dans le starter kit arduino. Cet écran est très répandu, et fait partie de nombreux "kits de démarrage" en électronique. Au niveau fonctionnel, avec 2 lignes de 16 charactères, on devrait pouvoir s'en sortir pour afficher une température et un taux d'humidité ;-). Son contrôleur (hitachi HD44780) est lui aussi très répandu En vrac:
- Les specs (chinois ou anglais)
- Pour acheter: on en trouve plein sur ebay...
- La librairie LiquidCrystal, qui permet de le contrôler sur Arduino
Le montage
Le montage est assez simple, et n'est finalement qu'une combinaison des différents tutoriel et exemple d'utilisation de l'écran et du capteur.
Sur le schéma du capteur DHT, la résistance de 10K sert de "résistance pull-up". Cette résistance n'est pas nécessaire dans le cas du capteur AM2302 (la version avec câble et boitier en plastique, comme sur la photo). Cette version intègre une résistance de 5.1K dans le boîtier, qui connecte déjà VCC et DATA, merci Patrick pour l'info ;-).
Le code
Au niveau programmation du micro-controlleur Arduino, là aussi c'est assez simple: toutes les 3 secondes, on lit les valeurs de t° et humidité, et on les affiches sur l'écran LCD. Les valeurs mesurées et des messages supplémentaires sont aussi envoyés sur la sortie série.
Le code est aussi disponible (et éventuellement mis à jour) sur gitlab
/*
CellarKare - Mark #1
*/
/*
Un projet de station de mesure de la température et l'humidité dans une cave à vin
Plus d'infos et schéma de montage sur http://wiki.makilab.org/index.php/CellarKare
Quentin Berten, 2015 - Domaine public, licence WTFPL v2, http://www.wtfpl.net/
*/
// Bibliothèque LiquidCrystal, pour l'écran LCD. Installée de base avec l'IDE
#include <LiquidCrystal.h>
// Bibliothèque DHT, pour le capteur de température. Installable directement depuis
// l'IDE, via Sketch -> Include LIbrary -> Manage Libraries -> DHT sensor Library
#include "DHT.h"
// Définition l'écran LCD, en argument les pins d'interface, dans l'ordre
// (RS pin, LCD Enable, D4, D5, D6, D7)
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// Définition des pin et type pour le senseur de t° et humidité
#define DHTPIN 7 // senseur (fil data, en jaune), sur pin 7
#define DHTTYPE DHT22 // type DHT 22 (AM2302)
// Définition du capteur DHT22, pour un Arduino "normal" à 16mhz, comme le Uno
DHT dht(DHTPIN, DHTTYPE);
// les variables associées qui vont lire les valeurs (température, ...)
float hum;
float tempC;
float tempF;
float indiceHum;
// les variables qui permettent de gérer le temps,
// voir http://www.arduino.cc/en/pmwiki.php?n=Tutorial/BlinkWithoutDelay
long prevMillis = 0; // enregistre le temps de la denière lecture DHT22
long dhtInterval = 3000; // L'intervalle de lecture du DHT22: 3 secondes
// la procédure d'initialisation arduino
void setup() {
// on ouvre une connection série (pour afficher un maxium d'info)
Serial.begin(9600);
Serial.println("CellarKare Mark #1 Test");
// initialisation du capteur
dht.begin();
// initialisation de l'écran, 16 colonnes et 2 lignes
lcd.begin(16, 2);
}
// la boucle principale arduino
void loop() {
unsigned long curMillis = millis();
if(curMillis - prevMillis < dhtInterval) {
goto end_loop; // pas de lecture DHT, on va à la fin de la boucle loop()
}
// on enregistre le dernier temps d'exécution de la lecture
prevMillis = curMillis;
// la lecture de la t° et l'humidité prends environ 250 millisecondes
// mais les lectures du capteur peuvent etre vieillies de 2 secondes
// le capteur n'est pas rapide (d'où l'attente de 3 secondes pour etre
// certain d'avoir des lectures correctes.
// lecture de l'humidité
hum = dht.readHumidity();
// lecture de la température en degrés Celsius
tempC = dht.readTemperature();
// lecture de la température en degrés Fahreneit
tempF = dht.readTemperature(true);
// On vérifie que les lectures on bien été réalisées. Si pas, on quitte
// la boucle pour ré-essayer
if (isnan(hum) || isnan(tempC) || isnan(tempF)) {
Serial.println("Lecture sur le capteur DHT ratée");
goto end_loop;
}
// Calcul de l'indice de chaleur (voir https://fr.wikipedia.org/wiki/Indice_de_chaleur )
// La température doit etre fournie en degré Fahreneit
indiceHum = dht.computeHeatIndex(tempF, hum);
// On affiche les valeurs sur la console série (n'est pas indispensable)
Serial.print("Humidite: ");
Serial.print(hum);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.print(tempC);
Serial.print(" *C ");
Serial.print(tempF);
Serial.print(" *F\t");
Serial.print("Indice de chaleur: ");
Serial.print(indiceHum);
Serial.println(" *F");
// On affiche les valeurs sur l'écran LCD, curseur à (0,0)
lcd.setCursor(0, 0);
// print Temp in C°
lcd.print("deg C: ");
lcd.print(tempC);
lcd.setCursor(0,1);
lcd.print("hum %: ");
lcd.print(hum);
// label pour goto
end_loop:
;
}
La conclusion
Un premier prototype fonctionnel, qui a au moins le mérite de vérifier que le capteur DHT renvoie des valeurs réalistes. La suite: la même chose avec un Raspberry pi, pour bénéficier de sa faciliter à se connecter au réseau. Ce serait quand même cool de pouvoir lire ces valeurs sur un smartphone ;-)
Mark (2): la même chose avec un Raspberry Pi
Le but de cette nouvelle itération: réaliser un montage équivalent avec un Raspberry Pi (RPi en abrégé). Pourquoi un RPi ?
- Parce que j'en avais un sous la main ;-)
- Parce qu'il permet de se connecter rapidement et facilement à un réseau, via ethernet (câble) ou wifi (via un dongle usb)
- Parce qu'il permet de brancher facilement un module caméra (on y viendra au Mark 3)
Il est certainement possible d'arriver au même résultat avec d'autres cartes (comme par exemple un Arduino Yun), c'est juste que c'est un RPi que j'avais sous la main... Dans mon cas un Raspberry Pi B+.
En ce qui concerne le RPi, quelques ressources très utiles:
- Les tutoriels réalisés/traduits par MC Hobby. Une mine d'or
- Des exemples de montages et réalisations électroniques avec RPi
- La configuration des différentes PIN du RPi
L'installation de l'OS
La principale différence entre une carte Arduino et le RPi, c'est le cœur de la carte:
- le cœur de l'Arduino est un microcontrôleur, qui va exécuter le programme qu'on lui injecte de manière bête et méchante. Chaque entrée/sortie va devoir être programmée directement, il n'y a pas de système de driver, ...
- le cœur du RPi est un microprocesseur (ARM, la même technologie que dans les smartphones). Il s'agit donc en fait d'un véritable "petit ordinateur", avec un système d'exploitation, des drivers (pour l'usb, l'écran, le wifi, ...).
Donc avant de pouvoir démarrer et utiliser le RPi, il va falloir installer un système d'exploitation (OS: Operating System). C'est exactement la même chose qu'installer Windows sur un PC, c'est juste que dans notre cas, ce ne sera pas Windows mais un système Linux. Il en existe plusieurs, le standard de facto étant Raspbian (une distribution Debian adaptée pour le RPi), c'est celle-là que j'ai installée.
Petit truc pour les distraits: écran/clavier/souris ne sont pas indispensables pour l'installation, il est tout à fait possible de se connecter via SSH au RPi une fois son premier démarrage exécuté, pour autant qu'il soit raccordé à votre réseau (via son câble ethernet). Le RPi se déclare automatiquement sur le réseau avec le nom raspberry.local, et un ssh pi@raspberry.local
à fait l'affaire (raspberry, c'est son "hostname" par défaut). Pour le mot de passe, c'est raspberry
par défaut. Ensuite raspi-config
permet de terminer la procédure d'installation.
En ce qui me concerne, tout le reste de la configuration se fera donc en ligne de commande (shell) via SSH. Le wiki de MCHobby détaille l'accès via SSH au RPi.
Un dernier point important: le RPi, c'est comme un "vrai" ordinateur... il vaut mieux l'éteindre proprement plutôt que le débrancher sauvagement. Donc on s'arrange pour faire un sudo poweroff
en ligne de commande avant de le débrancher. Le gros risque: corrompre le système de fichier sur le carte SD, ce qui empêche le démarrage suivant, et impose un reformatage et une réinstallation complète. Pas drôle en somme, je parle d'expérience :-(
Le wifi
Bien que le RPi ne soit pas équipé par défaut du wifi, il est compatible avec la majorité des clefs usb wifi. Dans mon cas, une vieille clef Hercules HWGUSB2-54. Un petit tour par les log (sudo tail -f /var/log/syslog
) pour vérifier qu'elle est reconnue correctement lors du branchement, et puis une configuration via l'édition de /etc/network/interfaces
pour entrer les références du réseau wifi:
auto wlan0 allow-hotplug wlan0 iface wlan0 inet dhcp wpa-ssid "Le_nom_(SSID)_de_votre_wifi" wpa-psk "Le_mot_de_passe_de_votre_wifi" wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
Ensuite un redémmarrage de la carte avec $ sudo ifdown wlan0
suivi de sudo ifup wlan0
et ça devrait être OK. On peut maintenant se passer de câble ethernet!
Si nécessaire: plus de détails sur la configuration réseau sur le wiki MC Hobby
La température avec le DHT22 / AM2302
Pour la lecture des température avec le capteur, les gars d'Adafruit ont déjà développé un bibliothèque de fonctions utilisables en ligne de commande ou en python. Quelques ressources:
- La page d'Adafruit (en anglais) sur l'utilisation du DHT22 avec le RPi
- Une ressource en français (un peu datée) sur l'utilisation DHT22 + RPi.
- La table de référence des 40 pins GPIO du RPi
Pour ma part, j'ai suivi la méthode Adafruit, ce qui donne en résumé, en ligne de commande:
cd /home/pi git clone https://github.com/adafruit/Adafruit_Python_DHT.git cd Adafruit_Python_DHT sudo apt-get install build-essential python-dev sudo python setup.py install
Au niveau du montage, rien de nouveau: le capteur doit toujours être alimenté en 3.3V ou en 5V, et le câble de lecture à une pin d'entrée/sortie (GPIO: General Purpose Input Output), ce qui donne:
Un test en ligne de commande permet de vérifier qu'on a une lecture sur le capteur (dans mon cas un AM2302 raccordé sur la pin GPIO 4):
cd /home/pi/Adafruit_Python_DHT/examples/ sudo ./AdafruitDHT.py 2302 4
Ce qui affiche Temp=23.0*C Humidity=53.8%
, des valeurs censées pour une après-midi d'été ;-)
L'affichage LCD
L'écran LCD est toujours le même que pour la version "Mark 1" Arduino. Quelques resources:
- Un tutoriel (en anglais) sur Adafruit Attention, le code source n'est plus tellement a jour, voir le dernier lien Github.
- La version française, traduite par MC Hobby
- Le code source de la librairie Adafruit pour le gestion du LCD sur GitHub contient la dernière version de la librairie.
La logique de raccordement est strictement identique au circuit du Mark 1. La seule différence est que la résistance variable de 10K dans le circuit est supprimée, la bibliothèque Adafruit pour le LCD permettant le pilotage en modulation PWM.
Le montage
Au final, le montage a été fait pour gagner un maximum de place sur mon breadboard. La pin data du DHT a été déplacée sur la pin 5, et le reste des pin pour le LCD sont documentées dans le code.
Le code
La logique du code est identique à la version mark 1 - arduino. Ce code est aussi disponible (et éventuellement mis à jour) sur gitlab
#!/usr/bin/python
# coding=utf8
# CellarKare - Mark 2
# Un projet de station de mesure de la température et l'humidité dans une cave à vin
# Plus d'infos et schéma de montage sur http://wiki.makilab.org/index.php/CellarKare
# Quentin Berten, 2015 - Domaine public, licence WTFPL v2, http://www.wtfpl.net/
# quentin_#AT#_berten.me
import time
# Bibliothèque CharLCD d'Adafruit, voir https://github.com/adafruit/Adafruit_Python_CharLCD
# pour l'installation
import Adafruit_CharLCD as LCD
# Bibliothèque DHT d'AdaFruit, voir https://github.com/adafruit/Adafruit_Python_DHT
# pour l'installation
import Adafruit_DHT as DHT
# Configuration de l'affichage LCD
lcd_rs = 25 # RS sur pin 25
lcd_en = 24 # EN sur pin 24
lcd_d4 = 23 # D4 sur pin 23
lcd_d5 = 12 # D5 sur pin 12
lcd_d6 = 20 # D6 sur pin 20
lcd_d7 = 16 # D7 sur pin 16
lcd_backlight = 18 # rétro éclairage sur pin 18
lcd_columns = 16 # Affichage à 16 colonnes ...
# Initialisation du LCD avec les variables ci-dessus
lcd = LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7,
lcd_columns, lcd_rows, lcd_backlight)
# Configuration pour le capteur DHT, soit DHT11, DHT22 ou AM2302.
dht_sensor = DHT.AM2302 # Type AM2302
dht_pin = 5 # DHT data sur pin 5
# les variables qui permettent de gérer le temps,
prevMillis = 0 # enregistre le temps de la denière lecture DHT22
secInterval = 1000 # Un intervalle d'une seconde
# L'intervalle de lecture du DHT22: 3 secondes, doit etre plus grand que secInterval
dhtInterval = 3000
def read_and_print_dht():
# On lit une valeur sur le senseur avec la fonction read. Attention, cette fonction
# ne réessaie pas automatiquement en cas d'erreur, mais fournit les valeurs (None, None).
hum, tempC = DHT.read(dht_sensor, dht_pin)
# On vérifie que l'on a bien eu une lecture (Linux n'est pas un OS temps réel et ne
# peut pas garantir le timing des lectures sur le capteur). Si ça arrive, on réessaie.
# On affiche les valeurs sur la console (n'est pas indispensable)
if hum is not None and tempC is not None:
print 'Temp={0:0.2f}*C Humidity={1:0.2f}%'.format(tempC, hum)
# On affiche les valeurs sur l'écran LCD
lcd.clear()
lcd.message('deg C: {0:0.2f}\nhum %: {1:0.2f}'.format(tempC, hum))
else:
print 'Lecture sur le capteur DHT ratée'
# Boucle principale, l'équivalent de void loop() sur Arduino
while 1:
# On lit le nombre de millisecondes courant
curMillis = time.time()*1000
if curMillis - prevMillis > dhtInterval:
# On essaie de lire et afficher les valeurs du DHT
read_and_print_dht()
# on enregistre le dernier temps d'exécution de la lecture
prevMillis = curMillis
La conclusion
En vrac:
- Pas de gros problème ou obstacles majeurs pour convertir le montage et le code Arduino en son équivalent RPi. Mais heureusement que l'équipe d'Adafruit a déjà programmé les bibliothèques Python qui vont bien!
- Au niveau du breadboard, ça commence à prendre de la place... Une solution: passer en "I2C" pour le capteur et le LCD, ce qui limite drastiquement le nombre de pins nécessaires pour le montage, et facilite la programmation. Merci Patrick pour les infos, ce sera sûrement utile pour le prochain montage ;-) !
- Par exemple, pour la température: un capteur I2C comme le SHT15, et pour l'écran, la version I2C du même 1602.
- Par contre dans ce cas, il faut faire gaffe au fait que le passage en 3.3V pour la logique du RPi, le lcd reste en 5V et il faudra ajouter un convertisseurs de niveau pour le bus si utilisé avec un capteur qui ne supporte pas 5V.
- le langage Python, c'est pas plus compliqué que le simili-C de l'Arduino une fois qu'on est rentré dedans.
Mark (3): Start, Stop, and say Cheese
Sart/Stop
Un "problème" du RPi, c'est qu'il n'a pas de bouton ON/OFF simple, et par conséquent, l'éteindre proprement avec sudo halt
ou sudo poweroff
est impossible sans un accès physique (clavier et écran connectés) ou une connection SSH (connection à distance). Pas pratique pour un boitier qui sera installé dans une cave à vin...
Autrement dit, mes besoins:
- Le programme python d'affichage et monitoring des températures démarre automatiquement lorsque le RPi démarre.
- Un bouton OFF permet d'éteindre proprement le RPi avant de le débrancher de son alimentation.
- Un bouton ON permet de l'allumer une fois éteint sans débrancher/rebrancher son alimentation.
- Nec plus ultra: avoir un fonctionnement similaire à un bouton ON/OFF d'un laptop par exemple: si éteint, le bouton allume, si allumé, le bouton éteint proprement le RPi, et coupe l'alimentation USB.
Bon après quelques recherches sur le net:
- C'est simple, il suffit de configurer le "init script" du RPi pour lancer automatiquement le programme python au démarrage
- Un montage simple permet d'écouter sur une des pins du GPIO, et de lancer la procédure propre d'arrêt (shutdown). Soit un bouton OFF
- Le RPi (à partir du modèle B rev2, soit Brev2, B+ et 2) possède un pinout "reset", qui n'est pas soudé de base sur la carte. Si on raccorde ces deux pins, le RPi redémarre électriquement (hard reboot). Donc sans s'éteindre proprement. L'intérêt, c'est que si le RPi est éteint mais encore sous tension, ce bouton permet de le ré-allumer. Donc un bouton ON/RESET. Voir par exemple:
- Un article assez complet en anglais sur MakeUseOf.com
- Le bouton combiné ON/OFF unique est possible, mais le montage est plus compliqué. Quelques exemples:
- Un montage en français sur le site MadeInFR.org.
- Un montage en anglais à base de micro contrôleur (pas simple donc) sur Instructables
- Un article très complet en anglais reprenant toutes les options, dont un montage assez simple de bouton unique sur raspberry-pi-geek.com
- Des solutions commerciales toutes faites comme PiSupply ou MausberryCircuits
Dans un premier temps, ce sera pour moi le choix suivant: un bouton OFF, qui lors d'un appui long éteint proprement le RPi. En gros la solution 2.
Mais dans un premier temps, il faut configurer le RPi pour lancer le programme au démarrage...
Lancement du programme via initscript
Il y a certainement 36 façons d'arriver au même résultat (après tout, le RPi tourne sous Linux ;-), mais dans mon cas, la solution qui me convient bien: un script d'init lance par défaut le script python localisé en /usr/local/bin/pystartup.py
. L'intérêt de la chose: pystartup.py
peut être un lien symbolique vers n'importe quel autre script.
Pour éviter d'encombrer le wiki, je n'ai pas repris le script ici, mais il est dispo sur GitLab. Pour l'installation, en ligne de commande (connecté sur le RPi):
On télécharge le script, on le déplace au bon endroit et on le rend exécutable
wget https://gitlab.com/qberten/cellarkare/raw/master/mark3_camera/initscript_pystartup sudo mv initscript_pystartup /etc/init.d/pystartup sudo chmod a+x /etc/init.d/pystartup
On crée le lien symbolique qui va bien (remplacer /chemin/vers/... par la localisation du programme à lancer)
ln -s /chemin/vers/votre/script/python.py /usr/local/bin/pystartup.py
On configure le système pour qu'il connaisse le nouveau script "pystartup", et on lui demande de l'exécuter à chaque démarrage
sudo update-rc.d pystartup defaults
Il est ensuite possible de lancer son programme en arrière plan comme n'importe quel autre service avec sudo service pystartup start
, de l'arrêter avec sudo service pystartup stop
, etc
Pinout RESET
Activer le "hard" reset sur le Rpi suppose un peu de soudure, mais rien de bien effrayant, 2 points suffisent. Un fois les connecteur pinout soudés, quand on raccorde les deux pins (par exemple avec un interrupteur), le RPi redémarre ("hard" reset, donc attention à la carte SD...), ou s'allume (si il était raccordé au secteur).
J'ai soudé les 2 pins et testé la fonction, mais comme il s'agit d'un redémarrage "hard", je n'ai finalement pas vraiment vu l'utilité dans ce projet: autant directement débrancher / rebrancher le câble d'alimentation dans ce cas.
Mais à toutes fins utiles, les photos de l'opération, avant/après.
Bouton OFF
Rien de très compliqué: on raccorde le bouton à une entrée, on configure la résistance pull-up/down interne du Rpi, on s'assure de récupérer les évènements dans le code.
Les parties utiles du code:
# La fonction qui effectuer l'arrêt propre du RPi
def btn_proper_halt(pin):
global off_state, cur_millis, off_millis
# On lit le nombre de millisecondes courant
cur_millis = time.time()*1000
# Utilise la variable globale off_state
off_state = not off_state
# DEBUG
logging.debug("Bouton OFF_PIN pressé, son état est maintenant %s" % str(off_state))
if off_state == True:
off_millis = cur_millis
elif off_state == False:
# DEBUG
logging.debug("Bouton OFF_PIN pressé pendant {0:0.0f} ms".format(cur_millis - off_millis))
if (cur_millis - off_millis) > OFF_INTERVAL:
# On a relâché le bouton avec un écart long => shutdown
logging.debug("Appui long sur le bouton OFF_PIN")
logging.info("Arrêt du Raspberry PI, attendre encore 10 sec avant de débrancher")
subprocess.call('halt', shell=False)
else:
# Appuis court => on ne fait rien
logging.debug("Appui court sur le bouton OFF_PIN")
# Fonction de terminaison du programme (appelée lorsque le script est arrêté
def terminate():
logging.debug("Terminate function started")
# efface l'écran LCD
lcd_dim_backlight(0.0, 0.2)
lcd.clear()
logging.debug("Terminate function ended")
# Fonction d'initialisation
def initialize():
global lcd
logging.debug("Initialize function started")
# Initialisation du LCD avec les variables ci-dessus
lcd = LCD.Adafruit_CharLCD(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7,
LCD_COLUMNS, LCD_ROWS,
LCD_BACKLIGHT, invert_polarity = False, enable_pwm = True,
initial_backlight = lcd_backlight)
# Initialisation des autres GPIO's
GPIO.setmode(GPIO.BCM) # On utilise la numérotation BCM pour les PIN
# On définit la pin OFF_PIN comme entrée, et on active la résistance pull-down interne du RPi
GPIO.setup(OFF_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Configure une interruption qui va détecter les mouvements up et down sur le bouton
GPIO.add_event_detect(OFF_PIN, GPIO.BOTH, callback=btn_proper_halt, bouncetime=20)
...
Say "cheese"
Cette partie du projet s'attache à pouvoir prendre rapidement et facilement un photos des étiquettes des bouteilles entrées ou sorties de la cave. Ben oui, j'ai pas forcément une bonne mémoire, et se donner les moyens d'enregistrer facilement et rapidement l'info est le meilleur moyen de suivre son stock ;-)
Cette partie est super simple à réaliser avec le rpi: on raccorde la caméra, on s'assure qu'elle est activée avec sudo raspi-config
, et on peut ensuite l'utiliser en ligne de commande ou en python. Le site web raspberrypi.org (en anglais) explique cela très bien.
On ajoute un deuxième bouton sur le breadboard, qui déclenche la prise d'une photo, et le prototype est maintenant fonctionnel. Les photos sont sauvegardées dans le répertoire courant du RPi, avec la date et l'heure pour nom de fichier.
Au niveau du code, les parties utiles sont:
# Fonction de prise d'une photo
def btn_take_picture(pin):
# DEBUG
logging.info("Bouton CAM_PIN pressé, on prend un photo")
# Construction du chemin pour la photo
now = datetime.datetime.now()
pic_filename = "/home/qb/pic_" + str(now) + ".jpg"
# Initialisation de la camera. On ne le fait pas dans initialize() pour éviter au maximum de la laisser
# allumée (et consommer) en continu. En plus, une fois activée, les lectures sur le DHT sont beaucoup
# plus souvent ratées.
with PICAM.PiCamera() as cam:
cam.capture(pic_filename)
logging.debug("Photos sauvée " + str(cam.resolution) + " dans " + pic_filename)
Pimp my screen
Pour ce qui est d'allumer ou éteindre l'écran, la bibliothèque CharLCD permet la modulation PWN du rétroéclairage. De quoi avoir un chouette effet de fondu au noir lorsque l'écran s'allume et s'éteint.
Au niveau du code, la partie utile:
# Module l'intensité du rétro éclairage de l'écran lcd
def lcd_dim_backlight(backlight_value, time_step):
global lcd
global lcd_backlight
# calcule l'incrément à avoir entre lcd_backlight et backlight_value
stp = (lcd_backlight > backlight_value) * -2 +1
brightnesses = map(lambda x: x/10.0, range(int(lcd_backlight*10), int(backlight_value*10)+1, stp))
for bright in brightnesses:
lcd_backlight = bright
logging.debug("loop on bright= " + str(bright))
lcd.set_backlight(bright)
time.sleep(time_step)
Le code et le montage
Le code source complet pour Mark 3 est disponible sur gitlab. Pour ce qui est du montage, ben pas de schéma de raccordement disponible, j'ai oublié de le documenter en temps utile avant de passer à Mark 4! Donc il faudra attendre la suite...
Mark (4): We are (almost) done
Pimp my screen, reloaded
Une dernière petite touche intéressante: l'appareil est destiné à être mis dans une cave à vin, endroit où il va faire noir la plupart du temps. En conséquence, l'écran LCD n'a pas vraiment besoin d'être allumé en permanence, pourquoi ne pas l'allumer automatiquement quand la lumière du local dépasse un certain seuil. Une photorésistance permet de réaliser rapidement la mesure d'une intensité lumineuse, pourquoi s'en priver... Oui mais... le RPi n'a pas d'entrée analogique, comment convertir simplement la mesure sur la photorésistance en une valeur analogique, alors que le RPi n'a que des entrées digitales.
Après un peu de recherche, un montage simple à base d'une capacité dont le temps de charge sera directement proportionnel à la mesure sur la photo-résistance. Tout cela est expliqué ici (en anglais): [1]
(TODO - continuer la documentation)
Buttons, buttons, we want more
Dernier petit détail, et non des moindre, Pour avoir un projet réellement fonctionnel, il va falloir travailler un peu l'interface homme-machine, et permettre un peu plus d'interaction avec le Rpi, pour par exemple permettre d'entrer le nombre de bouteilles couvertes par l'entrée ou la sortie photographiée, quitter/valider une action, ...
(TODO - continuer la documentation)
We want leds
Toujours dans l'optique d'améliorer l'interaction avec l'appareil, pourquoi ne pas intégrer une led de status sur l'appareil, pour indiquer visuellement différents états, comme par exemple: une température trop basse ou trop haute est enregistrée, il n'y a pas de connection réseau, ...
(TODO - à implémenter tout court ;-)
All your data are belong to us
Enfin, c'est bien beau d'afficher la température et l'humidité, c'est encore mieux de l'enregistrer quelque part pour pouvoir tracer l'historique, et intéragir plus facilement avec ces données. Après quelques investigations, c'est Thingspeak qui a retenu mon attention.
(TODO - à implémenter tout court ;-)