Les concepts matériels étant présentés dans l’article 1/2, il est temps d’aborder le côté logiciel du projet. La lecture préliminaire du premier article est recommandée pour comprendre plus facilement le développement du script Python qui sera détaillé ici pour utiliser les GPIOs du Raspberry Pi.
L’utilitaire fortune au cœur du système
Avant d’entrer dans les détails du code, commençons par aborder la commande fortune sur laquelle s’appuie le script Python. La commande fortune affiche un message choisi au hasard parmi une source de citations. Il peut s’agir de proverbes, citations de célébrités , de blagues ayant attrait à l’informatique ou la programmation. On peut évidemment créer sa propre source. Commençons par installer le programme fortune :
1 |
$ sudo apt-get install fortune |
Vous disposez maintenant de l’outil, allez testons-le :
1 2 |
$ fortune Is that really YOU that is reading this? |
L’outil utilise une source par défaut. Personnellement, les blagues ne me font pas rire systématiquement… de l’humour anglais sans doute… Fortune utilise deux fichiers pour chaque source de citations :
- un fichier texte listant les citations séparées par une ligne avec le caractère % ;
- un fichier .dat généré par le programme strfile pour l’accès aléatoire aux données.
Je vous livre ici le gisement de blagues Chuck Norris sous forme de fichier texte. Je l’ai volontairement épuré de tout caractères accentués pour m’affranchir d’un algorithme de substitution pour l’affichage sur le LCD HD44780 qui ne permet pas nativement leur affichage.
Créer le fichier source :
1 |
$ nano chucknorris |
Ici, le seul moyen que j’ai trouvé pour créer un fichier source valide, c’est par le biais d’un copier-coller de l’ensemble du texte à travers l’utilitaire Putty. Une fois le gisement chargé et enregistré, lancez la commande :
1 2 3 4 5 |
$ strfile chucknorris "chucknorris.dat" created There were 9692 strings Longest string: 500 bytes Shortest string: 1 byte |
La commande strfile permet de créer un fichier de données contenant une structure permettant l’accès aléatoire aux chaînes de caractères enregistrées dans le fichier source. Oui, vous avez bien lu : 9692 strings ! Copiez les deux fichiers dans à l’endroit adéquat :
1 |
$ cp chucknorris chucknorris.dat /usr/games |
On peut maintenant afficher une blague Chuck Norris :
1 2 |
$ fortune chucknorris Chuck Norris arrive a faire sortir un chapeau du lapin... et c'est pas de la magie ! |
Le système est maintenant prêt. Le script Python pourra lancer la commande système.
Configuration du système pour l’utilisation du port série
Les permissions de l’utilisateur
Pour pouvoir utiliser le port série, l’utilisateur pi (ou celui que vous souhaitez utiliser) doit disposer des permissions nécessaires. Vérifier que pi est membre du groupe dialout (ce groupe octroie les droits d’utiliser le port série) :
1 2 |
$ groups pi adm dialout cdrom sudo audio video plugdev games users input netdev gpio i2c spi |
Si l’utilisateur pi n’est pas membre du groupe dialout, y remédier :
1 |
$ sudo usermod -a -G dialout pi |
Le serial port est maintenant ouvert pour l’utilisateur pi mais encore inaccessible car utilisé par le système.
Libérer le port série /dev/ttyAMA0
Le port série du Raspberry se caractérise par deux signaux : un signal de transmission TxD (GPIO 14/pin 8) et un signal de réception RxD (GPIO 15/pin 10). Pour connecter un matériel série, il faut connecter le TxD sur le RxD de l’un, respectivement sur le RxD et le TxD de l’autre. Il est également nécessaire de connecter les masses des deux matériels ensemble.
On parle également de port UART, composant Braodcom UART… UART est un acronyme pour Universal Asynchronous Receiver Transmitter (émetteur-récepteur asynchrone universel). Le problème à résoudre est le suivant : le Raspberry envoie les données en parallèle (autant de fils que de bits de données) alors qu’on souhaite en série. Il faut donc transformer ces données parallèles pour les transmettre via une liaison série qui utilise un seul fil pour faire passer tous les bits de données, c’est le rôle du composant UART.
Le composant UART Broadcom est désigné par /dev/ttyAMA0 sous Raspbian. Par défaut, le système Raspbian utilise ce port série, des modifications sont donc nécessaires pour demander au système de libérer le /dev/ttyAMA0 et pouvoir ainsi y accéder.
Le port série permet donc d’envoyer des données entre le Raspberry et un autre matériel. Il existe deux cas d’usage :
- connecter un PC via un câble USB-to-serial pour accéder à un terminal avec Putty configuré sur Serial Line (utile pour se connecter si la vidéo et le réseau ne sont pas disponibles) ;
- connecter un périphérique possédant une interface série, afin de contrôler un autre matériel depuis le Raspberry.
Par défaut, Raspbian utilise le port série pour envoyer les informations console (premier cas d’usage). Pour le vérifier, tapez la commande :
1 2 |
$ ps aux | grep ttyAMA0 root 2732 0.0 0.1 2080 1568 ? Ss+ avril12 0:00 /sbin/getty -L ttyAMA0 115200 vt100 |
Le fichier /proc/cmdline contient la commande avec les arguments passés au noyau lors du démarrage. Si on affiche cette ligne de commande :
1 2 |
$ cat /proc/cmdline |grep ttyAMA0 dma.dmachans=0x7f35 bcm2708_fb.fbwidth=1776 bcm2708_fb.fbheight=952 bcm2709.boardrev=0xa01041 bcm2709.serial=0x5aa3798a smsc95xx.macaddr=B8:27:EB:A3:79:8A bcm2708_fb.fbswap=1 bcm2709.disk_led_gpio=47 bcm2709.disk_led_active_low=0 sdhci-bcm2708.emmc_clock_freq=250000000 vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p6 rootfstype=ext4 elevator=deadline rootwait |
On constate effectivement qu’elle contient l’argument « console=ttyAMA0,115200 ». Pour ne plus fournir cet argument au démarrage du noyau, éditez le fichier /boot/cmdline.txt :
1 |
$ sudo nano /boot/cmdline.txt |
La ligne de commande comporte la séquence « console=ttyAMA0,115200 » qui indique au noyau d’envoyer l’affichage des messages lors du boot sur le port série. Selon la version de votre environnement, il se peut que la ligne comporte également la séquence « kgdboc=ttyAMA0,115200 » qui active le débugging du noyau.
1 |
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p6 rootfstype=ext4 elevator=deadline rootwait |
Vous devez supprimer toutes les références à ttyAMA0, ce qui doit vous donner la ligne suivante :
1 |
dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait |
Attention, une erreur de syntaxe dans la commande du fichier /boot/cmdline.txt peut empêcher le boot du système.
Sur le Raspberry Pi 3, /dev/ttyAMA0 désigne désormais le port série connecté au Bluetooth. Le port série miniUART qui nous intéresse ici est maintenant disponible dans /dev/ttyS0. Sur les systèmes les plus récents (c’est notamment le cas sur la Raspbian Jessie 8.0), c’est désormais /dev/serial0 qui désigne le port série miniUART sur les GPIOs pins 8 et 10, quel que soit le modèle du Raspberry.
Vous pouvez obtenir les informations de votre distribution à l’aide de la commande :
1 2 3 4 5 6 |
$ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 8.0 (jessie) Release: 8.0 Codename: Jessie |
Ensuite, un prompt de login apparaît sur le port série après le démarrage. Ceci est paramétré dans le fichier /etc/inittab. Editez ce fichier :
1 |
sudo nano /etc/inittab |
et commentez la ligne :
1 |
T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100 |
Il ne reste plus qu’à redémarrer pour appliquer les modifications. Une fois redémarré, le port /dev/ttyAMA0 peut être utilisé comme un port série classique sans trafic parasitant le matériel connecté sur le port série. Après le redémarrage, on peut observer que la commande de démarrage du noyau ne comporte plus le paramètre « console=ttyAMA0,115200 » et que par conséquent, aucun processus n’utilise ttyAMA0 :
1 2 3 4 |
$ cat /proc/cmdline dma.dmachans=0x7f35 bcm2708_fb.fbwidth=656 bcm2708_fb.fbheight=416 bcm2709.boardrev=0xa21041 bcm2709.serial=0x4dbf2fcc smsc95xx.macaddr=B8:27:EB:BF:2F:CC bcm2708_fb.fbswap=1 bcm2709.uart_clock=3000000 bcm2709.disk_led_gpio=47 bcm2709.disk_led_active_low=0 vc_mem.mem_base=0x3dc00000 vc_mem.mem_size=0x3f000000 dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait $ ps -aef | grep ttyAMA0 pi 863 673 0 23:38 pts/0 00:00:00 grep --color=auto ttyAMA0 |
/etc/inittab n’existe plus depuis Raspbian Jessie. Inittab faisait partie de sysvinit qui a été remplacé par systemd. Serial getty est maintenant une service comme un autre.
Il faut donc stopper le service serial-Getty, voire même le désactiver pour empêcher son lancement au prochain redémarrage :
1 2 |
sudo systemctl stop serial-getty@ttyAMA0.service sudo systemctl disable serial-getty@ttyAMA0.service |
Développement du script Python
Les intérêts du langage Python
Il existe de nombreux langages de haut niveau (C, C++, C#, Java,…). Python est un puissant langage de programmation orienté objet. En tant que langage interprété, il est particulièrement adapté sur des systèmes embarqués tels que le Raspberry. D’ailleurs, l’environnement Python est nativement installé sur la distribution Raspbian. De syntaxe claire, Python est un excellent langage de programmation pour les débutants comme pour les experts. Très populaire, Python dispose de nombreuses bibliothèques (en plus des fonctions natives) permettant d’obtenir rapidement le résultat attendu en un minimum de lignes de code.
La bibliothèque Rpi.GPIO
Pour pouvoir configurer, lire et écrire sur les GPIOs du Raspberry depuis un script Python, il est nécessaire d’installer la librairie RPi.GPIO avec la commande :
1 |
$ sudo apt-get install python-rpi.gpio |
La librairie installée, il suffira de l’importer dans le script Python à l’aide de la directive suivante :
1 |
import RPi.GPIO as GPIO # Importation avec un alias plus court |
Avec cet import, l’ensemble des fonctions définies dans la librairie sont accessible depuis le script.
La librairie HD44780.py
Le script Python HD44780.py présenté ci-dessous est issu de deux sources d’informations :
- le tutoriel du site www.raspberrypi-spy.co.uk détaillant l’utilisation de l’écran une bibliothèque en python ;
- un guide d’utilisation de l’écran LCD avec Python, qui m’a notamment permis de comprendre comment générer et charger des « custom symbol » dans la CGRAM.
Le module Python HD44780.py permet de définir les fonctions nécessaires pour utiliser l’écran LCD HD44780, à savoir :
- la fonction send_string(message, line) qui permet d’afficher une message sur la ligne 1 ou 2 ;
- l’ensemble des fonctions de « bas niveau » qui permettent d’initialiser les GPIOs, d’envoyer un octets de données (en deux fois un quartets) sur les 4 lignes DATA de l’écran, de pulser la ligne Enable pour cadencer la transmission des données ou encore pour initialiser l’écran LCD.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
#!/usr/bin/python # -*- coding:Utf-8 -*- #################################################################################### # Affichage sur un écran LCD Hitachi HD44780 16x2 #################################################################################### import RPi.GPIO as GPIO import time #################################################################################### # OUTPUTS : map GPIO to LCD lines : On paramètre selon le cablage fait #################################################################################### LCD_RS = 7 # LCD_line REGISTER SELECT sur GPIO7 (RS indique si on envoie une commande ou des données) LCD_E = 8 # LCD_line ENABLE sur GPIO 8 (E permet l'horloge) LCD_D4 = 25 # LCD_line DATA 4 sur GPIO 25 LCD_D5 = 24 # LCD_line DATA 5 sur GPIO 24 LCD_D6 = 23 # LCD_line DATA 6 sur GPIO 23 LCD_D7 = 18 # LCD_line DATA 7 sur GPIO 18 OUTPUTS = [LCD_RS,LCD_E,LCD_D4,LCD_D5,LCD_D6,LCD_D7] # On place les LCD_lines dans une liste #################################################################################### # CONSTANTES #################################################################################### LCD_WIDTH = 16 # Maximum characters per line LCD_CHR, LCD_CMD = True, False # Pour placer RS en mode envoi de données/commandes LCD_LINE_1, LCD_LINE_2 = 0x80, 0xC0 # LCD RAM address for the 1st line et 2nd line E_PULSE = 0.0005 # Timing constants : pour le clocking des données E_DELAY = 0.0005 # Il faut respecter des laps de temps pour l'affichage sur le LCD #HD44780 Controller Commands CLEARDISPLAY = 0x01 RETURNHOME = 0x02 RIGHTTOLEFT = 0x04 LEFTTORIGHT = 0x06 DISPLAYOFF = 0x08 CURSOROFF = 0x0C CURSORON = 0x0E CURSORBLINK = 0x0F CURSORLEFT = 0x10 CURSORRIGHT = 0x14 LOADSYMBOL = 0x40 SETCURSOR = 0x80 #################################################################################### # Low Level Functions #################################################################################### # Initialise les GPIO en sortie def init_gpio(): GPIO.setwarnings(False) # Désactive les Warnings : à creuser GPIO.setmode(GPIO.BCM) # Utilise BCM GPIO numbers : l'alternative c'est GPIO.BOARD qui utilise les numéros du Raspberry for lcd_lines in OUTPUTS: # Pour chacune des GPIO de la liste OUTPUTS GPIO.setup(lcd_lines, GPIO.OUT) # Configuration des GPIO en sortie (en mode écriture) # Envoi un octet sur les LCD_lines D4 D5 D6 D7 en deux temps puisqu'on utilise l'envoi sur 4 bits (D0 D1 D2 D3 non utilisées) def send_1byte(bits, mode=LCD_CHR): # Le paramètre mode sera placé à LCD_CHR (True) si seul le premier paramètre est envoyé GPIO.output(LCD_RS, mode) # On place le ligne RS à True (data) ou False (cmd) selon ce qu'on envoie send_4bits(bits) # Appel de la procédure qui va envoyer les 4 bits de poids forts de l'octet pulse_enable_line() # Permet de faire le clocking bits = (bits & 0x0F)<< 4 # Shift de 4 bits vers la gauche des bits de poids faibles send_4bits(bits) # Appel de la procédure qui va envoyer les 4 bits de poids forts de l'octet pulse_enable_line() # Permet de faire le clocking # Envoi les bits de poids forts sur les lignes D4 D5 D6 D7 def send_4bits(octet): GPIO.output(LCD_D4, bool(octet & 0x10)) # Broche D4 placée au niveau Haut si octet = XXX1 XXXX GPIO.output(LCD_D5, bool(octet & 0x20)) # Broche D5 placée au niveau Haut si octet = XX1X XXXX GPIO.output(LCD_D6, bool(octet & 0x40)) # Broche D6 placée au niveau Haut si octet = X1XX XXXX GPIO.output(LCD_D7, bool(octet & 0x80)) # Broche D7 placée au niveau Haut si octet = 1XXX XXXX # Pulse the LCD Enable line; used for clocking in data def pulse_enable_line(): time.sleep(E_DELAY) # Délai nécessaire pour envoyer les données vers le contrôleur HD44780 GPIO.output(LCD_E, GPIO.HIGH) # Envoi du signal HAUT sur la ligne E time.sleep(E_PULSE) # Délai nécessaire pour envoyer les données vers le contrôleur HD44780 GPIO.output(LCD_E, GPIO.LOW) # Retour à l'état BAS de la ligne E time.sleep(E_DELAY) # Délai nécessaire pour envoyer les données vers le contrôleur HD44780 # Série de commandes envoyées (sous forme d'octet avec la ligne RS à False) à l'écran pour l'initialisation def init_lcd(): send_1byte(0x33,LCD_CMD) # 110011 Initialise send_1byte(0x32,LCD_CMD) # 110010 Initialise / 4 bits send_1byte(0x28,LCD_CMD) # 101000 Initialise 4 bits, 2 lignes, 5x8 font send_1byte(LEFTTORIGHT,LCD_CMD) # Cursor move direction LEFT TO RIGHT send_1byte(CURSOROFF,LCD_CMD) # 110011 Initialise send_1byte(CLEARDISPLAY,LCD_CMD) # 110011 Initialise time.sleep(E_DELAY) # Cette commande nécessite 1.5 ms pour s'exécuter # Envoi une chaine de caractère, pour affichage sur la ligne 1 ou 2, caractères par caractères def send_string(message, line): # La fonction ljust complète à droite le string de 16 x le caractères espace # Si le message comporte plus de 16 caractères, ljust renvoie la chaine inchangée message=message.ljust(LCD_WIDTH, " ") send_1byte(line, LCD_CMD) # On envoie l'adresse de LCD_LINE_1 ou LCD_LINE_2 en tant que commande for i in range (LCD_WIDTH): send_1byte(ord(message[i])) # Equivalent à send_1byte(ord(message[i]), LCD_CHR) puisque LCD_CHR est le paramètre par défaut |
Les modules Python nécessaires pour utiliser l’imprimante
Pour pouvoir utiliser le port série, installer le module Python python-serial avec la commande :
1 |
sudo apt-get install python-serial |
Si vous souhaitez imprimer des images, le module python-imaging-tk est également nécessaire :
1 |
sudo apt-get install python-imaging-tk |
La librairie printer.py
Comme évoqué plus haut, le langage Python est très populaire est de nombreuses librairies sont disponibles. Bonne nouvelle ! Il existe un module pour exploiter l’imprimante. Vous pouvez la télécharger en clonant le répertoire git avec la commande :
1 |
git clone git://github.com/luopio/py-thermal-printer.git |
Il est nécessaire d’effectuer quelques modifications/contrôles :
1 2 |
cd py-thermal-printer nano printer.py |
Vous pouvez utilisez la recherche dans l’éditeur nano avec « Ctrl+w » pour accéder rapidement aux paramètres SERIALPORT et BAUDRATE pour vérifier que :
1 2 3 |
SERIALPORT = '/dev/ttyAMA0' ... BAUDRATE = 9600 |
- ligne 38 : le port série est correctement configuré : ‘/dev/ttyAMA0’ ;
- ligne 56 : la vitesse de transmission des données correspond à la vitesse de l’imprimante : 9 600 Bauds (ou 19 200 selon l’imprimante).
Il est possible de jouer sur la qualité de l’impression à l’aide de 3 paramètres :
- heatTime par défaut à 150 : temps de chauffe pour marquer le papier, plus le paramètre est élevé, plus l’impression sera dense et lente ;
- heatInterval par défaut à 2 : temps entre chaque impulsion de chauffe (plusieurs pour une même lignes), plus le paramètre est élevé, plus l’impression sera nette et lente ;
- heatingDots par défaut à 7 : quantité de points pour imprimer, plus le paramètre est élevé, plus l’impression sera rapide et la consommation de courant importante.
En explorant les différentes fonctions de cette librairie, vous découvrirez le potentiel de l’imprimante :
- imprimer du texte selon deux tailles de polices (jusqu’à 32 caractères par ligne avec la font A et 40 avec la font B, plus petite), en gras, en inverse vidéo, alignement gauche/droite/centré/justifié, upside down et souligné ;
- imprimer des images (largeur idéale : 384 pixels) ;
- imprimer des codes barres.
Il existe d’autres fonctions comme la mise en veille, le test de présence de papier (pour cette fonction, la broche TX de l’imprimante doit être connectée).
A ce stade, vous pouvez tester l’envoi de données vers l’imprimante avec la commande :
1 |
$ ./printer.py |
Vous devriez obtenir l’impression d’un ticket démontrant les possibilités de l’imprimante.
Le script principal chucknorris.py
Comme l’indiquent les premières lignes, le code principal du script Python chucknorris.py comporte une série d’import parmi lesquels évidemment HD44780 et printer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
#!/usr/bin/python # -*- coding:Utf-8 -*- ########################################################################################################### # VERSIONNING : chucknorris.py v1.4 # Utilisation de textwrapped et split() pour affichage LCD par 16 char # Threading blink LED_RED_BUTTON # Log avec la fonction log() # Impression sur ticket # 2016-03-29 : Ajout du deuxième switch # Mise en forme des logs ########################################################################################################### from HD44780 import * import os import math import textwrap import threading import datetime import printer import sys LED_RED = 26 LED_GREEN = 4 # La fonction main doit être située avant if __name__ == '__main__': def main(): log("*** START SCRIPT PYTHON ***\n\n") init_gpio() # On initialise les GPIO en sortie GPIO.setup(21, GPIO.IN) # On met le GPIO 21 (PIN 40) en entree sur lequel est connecté le switch GPIO.setup(LED_RED, GPIO.OUT, initial=GPIO.LOW) # On met le GPIO 26 (PIN 37) en sortie pour commander la LED du RED SWITCH : etat inital : False = 0 GPIO.setup(12, GPIO.IN) # On met le GPIO 12 (PIN 40) en entree sur lequel est connecté le switch GPIO.setup(LED_GREEN, GPIO.OUT, initial=GPIO.LOW) # On met le GPIO 4 (PIN 7) en sortie pour commander la LED du GREEN SWITCH : etat inital : False = 0 init_lcd() # On initialise l'écran HD44780 load_custom_symbol() # On charge des caractères personnalisées dans l'espace CGRAM balayage() # On effectue un balayage de l'écran avec des caractères while True: send_string("PUSH BUTTON FOR",LCD_LINE_1) send_string("C.NORRIS FACT ->",LCD_LINE_2) while GPIO.input(21) == True: # Tant que état GPIO 21 sur laquelle est connectee le switch time.sleep(0.1) # reste à 1 (cad pas à la masse par appui) : on ne fait rien et on attend 0.1s # pour ne pas surcharger le CPU # Si sortie du while, c'est que le switch a été actionné #os.system("systemctl stop serial-getty@ttyAMA0.service") log("**************************************************************************************************************") log("*** RED SWITCH ACTIVE : BLAGUE DEMANDEE***") cmd = os.popen("/usr/games/fortune /usr/games/chucknorris","r") joke = cmd.read() # Récuperation dans l'objet cmd du résultat de la commande lcd_display(joke) # Appel de la fonction d'affichage LCD de joke ticket_print(joke) # Appel de la fonction d'impression de joke #Thread_Blink_Green_Button.join() log("*** FIN CYCLE CHUCK NORRIS FACT ***") log("**************************************************************************************************************\n\n") ########################################################################################################### # Fonction pour l'impression sur l'imprimante thermique # Timeout à 5 s pour lancer l'impression, Utilisation d'un Thread pour BLINK LED_GRENN_SWITCH ########################################################################################################### def ticket_print(texte): send_string("PRINT TICKET ?",LCD_LINE_1) send_string("YES:GRN - NO:RED",LCD_LINE_2) timeout=6 while GPIO.input(12) == True and GPIO.input(21) == True and timeout >= 0: time.sleep(0.1) timeout -= 0.1 send_string("PRINT TICKET ? " + str(math.floor(timeout)),LCD_LINE_1) if timeout>0 and GPIO.input(12) == False: # Si on sort du while et timeout non écoulé + GREEN SWITCH actionné Thread_Blink_Green_Button = ButtonBlink(LED_GREEN) Thread_Blink_Green_Button.start() log("*** GREEN SWITCH ACTIVE : IMPRESSION DEMANDEE ***") send_string("TICKET PRINTING",LCD_LINE_1) send_string("PLEASE, WAIT...",LCD_LINE_2) p = printer.ThermalPrinter(serialport="/dev/ttyAMA0") p.justify("C") p.inverse(True) p.print_text(" --- CHUCK NORRIS FACT --- ") p.linefeed(1) p.justify("L") p.inverse(False) texte_wrapped = textwrap.fill(texte,40) # Decoupage par lignes de 16 char max sans couper de mot p.font_b(True) p.print_text(texte_wrapped) p.linefeed(1) print_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") p.justify("R") p.print_text(print_time) p.linefeed(1) p.print_text("SB INDUSTRIES") p.linefeed(1) p.print_text("stephane.bennevault@gmail.com") # Impression de l'image from PIL import Image i = Image.open("/home/pi/PYTHON/sticker.png") data = list(i.getdata()) w, h = i.size p.print_bitmap(data, w, h, True) p.linefeed(3) time.sleep(5) send_string("TICKET PRINTED",LCD_LINE_1) send_string("YOU CAN CUT IT",LCD_LINE_2) time.sleep(2) Thread_Blink_Green_Button.stop() log("*** FIN IMPRESSION ***") log("*** FIN BLINK ***") elif GPIO.input(21) == False: # Si RED SWITCH activé log("*** RED SWITCH ACTIVE : IMPRESSION REFUSEE ***") time.sleep(0.5) # Pour éviter que l'appui pour annuler le print ne lance la blague suivante ########################################################################################################### # Fonction pour loguer l'execution du script à l'écran et dans un fichier de log # Permet de faire des statistiques sur l'utilisation (nb blague affichées, nb blagues imprimées) ########################################################################################################### def log(log_msg): file = open("/home/pi/chuckylog", "a") # Ouverture du fichier en mode append, comme w mais à la suite log_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") log_msg = log_time + " : " + log_msg # Concatène le timestamp et le message if "-noprint" in sys.argv: # Si l'option -noprint est fournie en paramètre pass # On passe à la suite du code else: # Sinon, on affiche à l'écran le log_msg print(log_msg) file.write(log_msg + "\n") # Ecriture du message dans le fichier file.flush() # Force l'écriture dans le fichier file.close() # Fermeture du fichier ########################################################################################################### # Classe héritant de la classe Thread pour exécuter un traitement parallèle # Au thread principal (BLINK LED_SWITCH) ########################################################################################################### class ButtonBlink(threading.Thread): def __init__(self, switch): threading.Thread.__init__(self) self.gpio_id = switch # Enrichissement avec l'attribut gpio_id self.Terminated = False # Variable témoin pour fin de Thread def run(self): try: while not self.Terminated: GPIO.output(self.gpio_id, not GPIO.input(self.gpio_id)) # Toggle sur l'état de la GPIO time.sleep(1) GPIO.output(self.gpio_id, not GPIO.input(self.gpio_id)) # Toggle sur l'état de la GPIO time.sleep(1) GPIO.output(self.gpio_id, GPIO.LOW) # Evite de laisser la GPIO à HIGH après le thread except RuntimeError: # Exception ajoutée pour gérer la remontée d'erreur (set GPIO.mode) lorsque Ctrl+C pendant l'exécution d'un thread log("Script interrompu par l'utilisateur pendant l'exécution d'un thread.") #log("*** FIN BLINK ***\n") def stop(self): self.Terminated = True def lcd_display(joke): Thread_Blink_Red_Button = ButtonBlink(LED_RED) # Instanciation de l'objet Thread en passant en paramètre l'id 26 pour la GPIO Thread_Blink_Red_Button.start() # Lancement du Thread avec la méthode start(), recommandé de ne pas utiliser run() joke = joke.upper() # Passage en MAJUSCULES log("*** BLAGUE EN MAJUSCULES : ***") log(joke) joke_wrapped = textwrap.fill(joke,16) # Decoupage par lignes de 16 char max sans couper de mot log("*** BLAGUE EN MAJUSCULES WRAPPEE : ***") log("\n" + joke_wrapped) list_joke_wrapped_16 = joke_wrapped.split("\n") # Remplissage d'une liste (en supprimant le char séparateur) if len(list_joke_wrapped_16)%2 ==1: # Si le nombre d'éléments dans la liste est impair list_joke_wrapped_16.append(" ") # Ajout d'une chaine vide de 16 char pour LCD_LINE_2 log("*** BLAGUE EN MAJUSCULES WRAPPEE ET COMPLETEE AVEC UNE LIGNE D'ESPACES : ***") log(str(list_joke_wrapped_16)) for (i, subjoke) in enumerate(list_joke_wrapped_16): # enumerate retourne l'index et sa valeur de chacun des items if i%2 == 0: send_string(subjoke, LCD_LINE_1) else: send_string(subjoke, LCD_LINE_2) time.sleep(3) Thread_Blink_Red_Button.stop() #del Thread_Blink_Red_Button : inutile puisque l'objet sera désalloué à la fin de la fonction log("*** FIN AFFICHAGE BLAGUE SUR LCD ***") ########################################################################################################### # Balayage de l'écran ########################################################################################################### # Affichage de caractères par balayage def balayage(): send_1byte(0x80, LCD_CMD) #On se place sur la ligne 1 for i in range (16): send_1byte(0x00,LCD_CHR) #On affiche la caractère situé à l'adresse 0x00 dans la CGRAM time.sleep(0.05) send_1byte(0xC0, LCD_CMD) #On se place sur la ligne 2 for i in range (LCD_WIDTH): send_1byte(0x04,LCD_CHR) time.sleep(0.05) ########################################################################################################### # Custom Character Generation ########################################################################################################### # Enregistre des acractères dans les 8 premiers octets de la CGRAM def load_custom_symbol(): symbols = [ # défintion des 5 lignes de pixels pour le caratères [0x4,0x2,0xe,0x1,0xf,0x11,0xf,0x0], # à [0x4,0x8,0xe,0x11,0x1f,0x10,0xe,0x0], # é [0x4,0x2,0xe,0x11,0x1f,0x10,0xe,0x0], # è [0x1b,0x0,0xa,0x0,0x4,0x11,0xe,0x0], # smiley [0x1b,0x0,0xa,0x0,0x4,0x11,0xe,0x0], # TODO [0x1b,0x0,0xa,0x0,0x4,0x11,0xe,0x0], # TODO [0x1b,0x0,0xa,0x0,0x4,0x11,0xe,0x0], # TODO [0x1b,0x0,0xa,0x0,0x4,0x11,0xe,0x0], # smiley ] #smiley = [0x1b,0x0,0xa,0x0,0x4,0x11,0xe,0x0] # Pour écrire un char en CGRAM, il faut envoyé la commande 0x40 + for i in range (len(symbols)): cmd = LOADSYMBOL + (i<<3) # Permet de faire un décalage de 3 bits vers la gauche pour avoir l'adresse du premier emplacement dans la CGRAM send_1byte(cmd, LCD_CMD) for octet in (symbols[i]): send_1byte(octet) ########################################################################################################### # Main ########################################################################################################### if __name__ == '__main__': # Si le script est lancé depuis ce fichier, lancement de la fonction main() try: # sinon, c'est que le code du fichier chucknorris.py a été importé dans un autre script main() except KeyboardInterrupt: # Si une interruption au clavier est reçue pass # On passe, ce qui permet de ne pas remonter l'erreur à l'écran finally: send_1byte(CLEARDISPLAY, LCD_CMD) # Envoi la commande CLEARDISPLAY (0x01) send_string("Goodbye !", LCD_LINE_1) GPIO.cleanup() |
Je pense avoir suffisamment documenté les lignes de code pour qu’un lecteur parvienne à s’y retrouver sans difficultés. Chaque instruction ne sera pas commentée plus en détails. Des articles dédiées au développement Python viendront compléter les notions utilisées dans ce script.
Le scénario nominal
Pour avoir une vision d’ensemble du script, je vous propose la modélisation du workflow d’exécution du scénario nominal. La programmation parallèle est réalisée avec l’instanciation d’objet héritant de la classe Thread.

Et voilà ! Je pense qu’on a fait le tour du sujet. Je suis bien entendu disponible pour répondre aux questions que vous voudrez bien me poser, alors laissez-moi un commentaire si des précisions vous sont nécessaires.
Ah ! J’oubliais… pour que le script soit lancé automatiquement à chaque démarrage de la machine, je vous invite à lire l’article sur le paramétrage pour exécuter un script au boot du système.
Pratiquant quotidiennement la chucknorrisfact du matin, j’adore
« Ça claque », en effet 🙂