Suivi de valeurs avec application C/C++ en mode console Préambule Dans le cadre des projets, vous serez sûrement amenés à afficher des valeurs provenant de capteurs. De façon à disposer d’une interface de visualisation à la fois agréable et simple à réaliser, on peut mettre à profit l’émulation de terminal VT100 pris en charge par défaut par le terminal Linux (mais aussi par les versions récentes du terminal Windows). Cette émulation VT100 fait en sorte que certaines séquences de caractères — dîtes séquences de contrôle — ne sont pas affichées mais plutôt interprétées par le terminal pour : déplacer le curseur sur l’écran modifier la taille des lignes (80 ou 132 colonnes) fixer les attributs du texte (couleur, clignotant, gras, vidéo inverse, souligné) utiliser un jeu de caractères semi-graphiques (pour le dessin de formulaires par exemple) Ces séquences de contrôle permettent alors de construire des IHMs simples mais néanmoins utiles et suffisantes en phase de développement. Une séquence de contrôle est une suite de caractères qui débute par le caractère ASCII “ESCAPE” (27 en décimal, 0x1b en hexadécimal, 033 en octal). De la documentation sur ces séquences de contrôle se trouve facilement sur le web. Ex. : Console Virtual Terminal Sequences VT100 commands and control sequences … L’envoi de ces séquences de contrôle en C/C++ sur le terminal se fait à l’aide d’un simple printf() ou std::cout(). vt100.c #include <iostream> using namespace std; int main(void) { // Efface l'écran cout << "\x1b[2J"; // Positionne le curseur sur la 1ère colonne de la 2ème ligne cout << "\x1b[2;1H"; // Affiche "Hello" en rouge sur fond noir // à la position actuelle du curseur (ligne 2, colonne 1) ... cout << "\x1b[31;1m" << "Hello"; (1) // ... suivi par "world !" en jaune sur fond bleu et souligné cout << "\x1b[33;44;4m" << " world !"; // Réinitialise sur les couleurs par défaut // et désactive le soulignement cout << "\x1b[39;49;24m" << endl; return 0; } 1 On peut aussi écrire cout << "\x1b[31;1mHello"; mais c’est moins lisible L’exécution de ce programme affichera : Mise en situation On désire faire une application qui affiche toutes les secondes la mémoire libre et la charge moyenne d’une machine Linux (voir Load average ). Travail à faire Cette activité est réalisable sur tout environnement Linux (PC ou Raspberry Pi) mais également à l’aide d’un compilateur C/C++ en ligne comme GDB online Debugger 🖮 Travail n° 1 : Amélioration de l’IHM On vous donne ci-dessous le code partiel de cette application : Code source vt100-dashboard.cpp /** * Affiche un tableau de bord qui affiche en permanence la mémoire libre * disponible et la charge moyenne du système. * * Compilation : g++ -o vt100-dashboard vt100-dashboard.cpp * Exécution : ./vt100-dashboard * */ #include <unistd.h> #include <fcntl.h> #include <termios.h> #include <sys/ioctl.h> #include <string> #include <iostream> #include <thread> using namespace std; /** * Désactive le mode canonique du terminal * (i.e. interprétation par caractère plutôt que par ligne) */ void disable_canonical_mode() { termios term; tcgetattr(0, &term); term.c_lflag &= ~(ICANON | ECHO); tcsetattr(0, TCSANOW, &term); } /** * Active le mode canonique du termianl * (i.e. interprétation par ligne plutôt que par caractère) */ void enable_canonical_mode() { termios term; tcgetattr(0, &term); term.c_lflag |= ICANON | ECHO; tcsetattr(0, TCSANOW, &term); } /** * Indique si une touche clavier a été enfoncée */ bool kbhit() { int byteswaiting; ioctl(0, FIONREAD, &byteswaiting); return byteswaiting > 0; } /** * Récupère la sortie d'une commande */ string getStdoutFromCommand(string cmd) { string data; FILE * stream; const int max_buffer = 256; char buffer[max_buffer]; cmd.append(" 2>&1"); stream = popen(cmd.c_str(), "r"); if (stream) { while (!feof(stream)) if (fgets(buffer, max_buffer, stream) != NULL) data.append(buffer); pclose(stream); } return data; } /** * Retourne la mémoire libre en octets */ unsigned long getFreeMem() { string freeMemStr = getStdoutFromCommand("cat /proc/meminfo | sed -n '2p' | grep -P '\\d+' -o"); return stoi(freeMemStr); } /** * Retourne l'heure avec le format hh:mm:ss */ string getHour() { return getStdoutFromCommand("date '+\%T'"); } /** * Retourne la charge moyenne du processeur * Voir https://fr.wikipedia.org/wiki/Load_average */ float getLoadAvg() { string loadAvgStr = getStdoutFromCommand("uptime | grep -P '[\\d.]+$' -o"); return stof(loadAvgStr); } /** * Point d'entrée du programme */ int main() { disable_canonical_mode(); cout << "\x1b[2J"; cout << "\x1b[1;1H" << "Heure :" << endl; cout << "\x1b[2;1H" << "Mémoire libre :" << endl; cout << "\x1b[4;1H\x1b[36m" << "<Appuyer sur une touche pour quitter>" << "\x1b[39m" << endl; // Mettre à jour les informations toutes les secondes tant qu'une // touche n'est pas appuyée while( !kbhit() ) { cout << "\x1b[1;18H" << getHour(); cout << "\x1b[2;18H" << getFreeMem() << " kB"; this_thread::sleep_for(1000ms); } enable_canonical_mode(); tcflush(STDIN_FILENO, TCIFLUSH); cout << "\x1b[4;1H\x1b[2K" << "Bye !" << endl; return 0; } Analyser le code source fourni et le compléter au niveau de la fonction main() pour réaliser l’affichage de la charge moyenne du système en dessous de celui de la mémoire libre en utilisant la séquence de contrôle VT100 qui permet de contrôler la position du curseur. La charge moyenne du système — déterminée durant les 15 dernières minutes — est retournée par la fonction getLoadAvg() présente dans le code source fourni. À partir des documents fournis en référence (Console Virtual Terminal Sequences , VT100 commands and control sequences ), déterminer les séquences de contrôle permettant d’activer/désactiver l’affichage du curseur puis compléter le code pour faire disparaître le curseur pendant toute la durée d’exécution du programme. Ne pas oublier de réactiver l’affichage curseur en fin de programme Modifier l’affichage pour que chaque information (heure, mémoire, charge) se fasse désormais en gras (→ bold/bright) Faire en sorte que l’heure soit aussi soulignée Faire évoluer le code pour afficher en rouge la charge système lorsqu’elle dépasse un certain seuil d’alerte et en vert lorsqu’elle est en deçà. Le seuil d’alerte sera programmé “en dur” dans un 1er temps Vous pouvez provoquer une augmentation de charge du système en invoquant, depuis un terminal, plusieurs fois la commande yes > /dev/null &. Taper ensuite sudo killall yes pour revenir à la normale. Faire évoluer le code pour quitter l’application lorsque l’utilisateur appuie sur la touche q ou Q Pour lire la valeur de la touche appuyée qui a fait quitter la boucle controlée par kbhit() utiliser : int c = getchar(); Faire évoluer le code pour afficher la mémoire libre cycliquement en kilo-octets, méga-octets, giga-octets lorsque l’utilisateur appuie sur la touche u (pour “unité”) 🖮 Travail n° 2 : Arguments de la ligne de commande On désire à présent pouvoir fournir le seuil d’avertissement de dépassement de charge de système à partir d’un argument depuis la ligne de commande. Ex. : ./vt100-dashboard -m 0.25 (1) 1 On précise un seuil de 0.25 grâce à l’option -m Se renseigner sur internet sur les fonctions getopt() et/ou getopt_long() de façon à ajouter cette nouvelle amélioration à votre programme. Proposition de corrigé Une proposition de corrigé pour le programme final est fournie ci-après dans une archive compressée et protégée par un mot de passe. Le demander à l’enseignant pour avoir accès à son contenu. vt100-dashboard-corrige.zip Illustration de l’exécution sur GDB online Debugger : 🞄 🞄 🞄 Les DELs Opérations bit à bit