« SE4 2024/2025 EC3 » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
 
(18 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 269 : Ligne 269 :




Après un retour de l'encadrant M. REDON, qui m'a fait remarqué que mon code de simulation de communication avec un équipement cisco ne prenait pas en compte le passage du mode utilisateur au mode administrateur, j'ai entamé une phase de correction de ce problème car en effet comme me l'a bien fait comprendre M. REDON "Les matériels Cisco démarrent en mode utilisateur et pour remettre à 0
 
 
 
====Intégration du passage '''utilisateur (>)''' → '''administrateur (#)''' dans ma simulation Cisco ====
 
'''Après un retour de l'encadrant M. REDON''', qui m'a fait remarqué que mon code de simulation de communication avec un équipement cisco ne prenait pas en compte le passage du mode utilisateur au mode administrateur, j'ai entamé une phase de correction de ce problème car en effet comme me l'a bien fait comprendre M. REDON "Les matériels Cisco démarrent en mode utilisateur et pour remettre à 0
il faut passer en mode administrateur". Sur ses conseils j'ai donc effectué les modifications suivantes:
il faut passer en mode administrateur". Sur ses conseils j'ai donc effectué les modifications suivantes:


\== Intégration du passage '''utilisateur (>)''' '''administrateur (#)''' dans ma simulation Cisco ==
Ce chapitre résume et illustre les changements apportés à '''mon simulateur Cisco''' et à '''mon contrôleur hôte''' pour respecter la procédure réelle des équipements Cisco : démarrage en '''mode utilisateur (>)''', passage en '''mode administrateur (#)''' via la commande '''enable''', puis exécution des commandes sensibles (''write erase'', ''reload'', etc.).


Ce chapitre résume et illustre les changements apportés à **mon simulateur Cisco** et à **mon contrôleur hôte** pour respecter la procédure réelle des équipements Cisco : démarrage en '''mode utilisateur (>)''', passage en '''mode administrateur (#)''' via la commande '''enable''', puis exécution des commandes sensibles (''write erase'', ''reload'', etc.).
===== Objectifs =====
 
\=== Objectifs ===


* Reproduire la séparation des modes : '''utilisateur (>)''' / '''admin (#)''' / '''config (config)#''' / '''config-if (config-if)#'''.
* Reproduire la séparation des modes : '''utilisateur (>)''' / '''admin (#)''' / '''config (config)#''' / '''config-if (config-if)#'''.
* Interdire les commandes sensibles en mode utilisateur (message '''% Privileged mode required''').
* Interdire les commandes sensibles en mode utilisateur (message '''% Privileged mode required''').
* Côté hôte : **envoyer `enable` d’abord**, puis **attendre explicitement le prompt `#`** avant de lancer la séquence de remise à zéro.
* Côté hôte : '''envoyer `enable` d’abord''', puis '''attendre explicitement le prompt `#`''' avant de lancer la séquence de remise à zéro.
* Rendre le basculement **visible dans les logs** (affichage des commandes envoyées et du prompt admin détecté).
* Rendre le basculement '''visible dans les logs''' (affichage des commandes envoyées et du prompt admin détecté).


\== Changements côté simulateur ('''sim\_ios.c''') ==
===== Changements côté simulateur ('''sim_ios.c''') =====


; '''Avant'''
; '''Avant'''
Ligne 293 : Ligne 296 :
; '''État du CLI''' <syntaxhighlight lang=c>
; '''État du CLI''' <syntaxhighlight lang=c>
static int enabled  = 0; // 0: utilisateur '>', 1: admin '#'
static int enabled  = 0; // 0: utilisateur '>', 1: admin '#'
static int in\_config = 0;
static int in_config = 0;
static int in\_if     = 0; </syntaxhighlight>
static int in_if     = 0; </syntaxhighlight>


; '''Prompts''' centralisés <syntaxhighlight lang=c>
; '''Prompts''' centralisés <syntaxhighlight lang=c>
static void prompt(int fd){
static void prompt(int fd){
if (in\_if)          serial\_writestr(fd, "(config-if)#\r\n");
if (in_if)          serial_writestr(fd, "(config-if)#\r\n");
else if (in\_config) serial\_writestr(fd, "(config)#\r\n");
else if (in_config) serial_writestr(fd, "(config)#\r\n");
else if (enabled)  serial\_writestr(fd, "#\r\n");
else if (enabled)  serial_writestr(fd, "#\r\n");
else                serial\_writestr(fd, ">\r\n");
else                serial_writestr(fd, ">\r\n");
} </syntaxhighlight>
} </syntaxhighlight>


; '''Bascule en mode admin''' via '''enable''' <syntaxhighlight lang=c>
; '''Bascule en mode admin''' via '''enable''' <syntaxhighlight lang=c>
if (strcmp(line, "enable") == 0) {
if (strcmp(line, "enable") == 0) {
enabled = 1; in\_config = 0; in\_if = 0;
enabled = 1; in_config = 0; in_if = 0;
prompt(fd);
prompt(fd);
continue;
continue;
Ligne 313 : Ligne 316 :
; '''Contrôle d’accès''' (refus si pas admin) <syntaxhighlight lang=c>
; '''Contrôle d’accès''' (refus si pas admin) <syntaxhighlight lang=c>
if (strcmp(line, "write erase") == 0 || strcmp(line, "erase nvram") == 0) {
if (strcmp(line, "write erase") == 0 || strcmp(line, "erase nvram") == 0) {
if (!enabled) { send\_line(fd, "% Privileged mode required"); prompt(fd); continue; }
if (!enabled) { send_line(fd, "% Privileged mode required"); prompt(fd); continue; }
send\_line(fd, "Erasing NVRAM...done"); prompt(fd);
send_line(fd, "Erasing NVRAM...done"); prompt(fd);
}
}
if (strcmp(line, "reload") == 0 || strcmp(line, "reset") == 0) {
if (strcmp(line, "reload") == 0 || strcmp(line, "reset") == 0) {
if (!enabled) { send\_line(fd, "% Privileged mode required"); prompt(fd); continue; }
if (!enabled) { send_line(fd, "% Privileged mode required"); prompt(fd); continue; }
send\_line(fd, "Proceed with reload? \[confirm]");
send_line(fd, "Proceed with reload? \[confirm]");
send\_line(fd, "Reloading..."); send\_line(fd, "OK"); prompt(fd);
send_line(fd, "Reloading..."); send_line(fd, "OK"); prompt(fd);
} </syntaxhighlight>
} </syntaxhighlight>


; '''Mode configuration''' (réaliste pour Aironet/Catalyst) <syntaxhighlight lang=c>
; '''Mode configuration''' <syntaxhighlight lang=c>
if (strcmp(line, "configure terminal") == 0 || strcmp(line, "conf t") == 0) {
if (strcmp(line, "configure terminal") == 0 || strcmp(line, "conf t") == 0) {
if (!enabled) { send\_line(fd, "% Privileged mode required"); prompt(fd); continue; }
if (!enabled) { send_line(fd, "% Privileged mode required"); prompt(fd); continue; }
in\_config = 1; in\_if = 0; prompt(fd);
in_config = 1; in_if = 0; prompt(fd);
}
}
if (strcmp(line, "interface BVI1") == 0) { in\_config = 1; in\_if = 1; prompt(fd); }
if (strcmp(line, "interface BVI1") == 0) { in_config = 1; in_if = 1; prompt(fd); }
if (strcmp(line, "no ip address") == 0)  { prompt(fd); }
if (strcmp(line, "no ip address") == 0)  { prompt(fd); }
if (strcmp(line, "end") == 0)            { in\_if = 0; in\_config = 0; prompt(fd); } </syntaxhighlight>
if (strcmp(line, "end") == 0)            { in_if = 0; in_config = 0; prompt(fd); } </syntaxhighlight>


\== Changements côté hôte ('''host\_ctrl.c''') ==
===== Changements côté hôte ('''host_ctrl.c''') =====


; '''Avant'''
; '''Avant'''
Ligne 340 : Ligne 343 :


; '''Log des commandes envoyées''' (wrapper TX) <syntaxhighlight lang=c>
; '''Log des commandes envoyées''' (wrapper TX) <syntaxhighlight lang=c>
static void writestr\_tx(int fd, const char\* s){
static void writestr_tx(int fd, const char* s){
size\_t len = strlen(s);
size_t len = strlen(s);
if (len >= 2 && s\[len-2]=='\r' && s\[len-1]=='\n') {
if (len >= 2 && s[len-2]=='\r' && s[len-1]=='\n') {
char tmp\[256]; size\_t n = len-2; if (n>255) n=255;
char tmp[256]; size_t n = len-2; if (n>255) n=255;
memcpy(tmp, s, n); tmp\[n]=0;
memcpy(tmp, s, n); tmp\[n]=0;
fprintf(stderr, "\[TX] %s\r\n\n", tmp);
fprintf(stderr, "\[TX] %s\r\n\n", tmp);
Ligne 349 : Ligne 352 :
fprintf(stderr, "\[TX] %s\n", s);
fprintf(stderr, "\[TX] %s\n", s);
}
}
serial\_writestr(fd, s);
serial_writestr(fd, s);
} </syntaxhighlight>
} </syntaxhighlight>


; '''Attente explicite du prompt admin''' (avec log clair) <syntaxhighlight lang=c>
; '''Attente explicite du prompt admin''' (avec log clair) <syntaxhighlight lang=c>
static int wait\_prompt\_hash(int fd, int tries){
static int wait_prompt_hash(int fd, int tries){
char line\[256];
char line\[256];
while (tries-- > 0) {
while (tries-- > 0) {
int n = serial\_readln(fd, line, sizeof line);
int n = serial_readln(fd, line, sizeof line);
if (n <= 0) { usleep(1000); continue; }
if (n <= 0) { usleep(1000); continue; }
// ... strip spaces/crlf ...
// ... strip spaces/crlf ...
if (strcmp(line, "#") == 0 || (line\[0]=='#' && line\[1]==0)) {
if (strcmp(line, "#") == 0 || (line[0]=='#' && line[1]==0)) {
fprintf(stderr, "\[PROMPT] # (privileged)\n");
fprintf(stderr, "\[PROMPT] # (privileged)\n");
return 1;
return 1;
Ligne 372 : Ligne 375 :


// 1) passer en mode admin
// 1) passer en mode admin
writestr\_tx(fd, "enable\r\n");
writestr_tx(fd, "enable\r\n");
if (!wait\_prompt\_hash(fd, 10)) {
if (!wait_prompt_hash(fd, 10)) {
fprintf(stderr, "\[ERR] Failed to get privileged prompt (#)\n");
fprintf(stderr, "[ERR] Failed to get privileged prompt (#)\n");
return 10;
return 10;
}
}


// 2) identifier le modèle via "show version"
// 2) identifier le modèle via "show version"
writestr\_tx(fd, "show version\r\n");
writestr_tx(fd, "show version\r\n");
// ... readline\_useful() ignore les prompts '>', '#', '(config)#' ...
// ... readline_useful() ignore les prompts '>', '#', '(config)#' ...
// ... détection "ISR4221" / "C9200" / "Aironet" ...
// ... détection "ISR4221" / "C9200" / "Aironet" ...


// 3) write erase (puis lectures)
// 3) write erase (puis lectures)
writestr\_tx(fd, "write erase\r\n");
writestr_tx(fd, "write erase\r\n");
(void)readline\_useful(fd, line, sizeof(line));
(void)readline_useful(fd, line, sizeof(line));


// 4) maintien reset (simulé par sleep)
// 4) maintien reset (simulé par sleep)
fprintf(stderr, "\[ACT] Holding reset (sim) for %d ms...\n", p->press\_ms);
fprintf(stderr, "[ACT] Holding reset (sim) for %d ms...\n", p->press_ms);
msleep(p->press\_ms);
msleep(p->press_ms);


// 5) reload (2 lectures)
// 5) reload (2 lectures)
writestr\_tx(fd, "reload\r\n");
writestr_tx(fd, "reload\r\n");
(void)readline\_useful(fd, line, sizeof(line));
(void)readline_useful(fd, line, sizeof(line));
(void)readline\_useful(fd, line, sizeof(line)); </syntaxhighlight>
(void)readline_useful(fd, line, sizeof(line)); </syntaxhighlight>


; '''Filtrage de lignes “parasites”''' (prompts et lignes vides) <syntaxhighlight lang=c>
; '''Filtrage de lignes “parasites”''' (prompts et lignes vides) <syntaxhighlight lang=c>
/\* Dans readline\_useful():
/\* Dans readline_useful():


* ignore "" (vide),
* ignore "" (vide),
Ligne 406 : Ligne 409 :
   </syntaxhighlight>
   </syntaxhighlight>


\== Exemple de trace (attendu) ==
===== Exemple de trace (attendu) =====


<pre>
<pre>
Ligne 429 : Ligne 432 :
</pre>
</pre>


\== Pourquoi c’est conforme à la consigne ==
Vous pouvez voir le resultat du test de fonctionnement dans l'image ci-dessous.
 
[[Fichier:Test simu.png|vignette|centre|Image résultat]]
* Les matériels Cisco démarrent en '''mode utilisateur (>)'''.
* Pour remettre à zéro, il faut '''passer en mode administrateur''' via '''enable''' → prompt '''#'''.
* Le simulateur reproduit fidèlement ce comportement : prompts cohérents et '''refus des commandes sensibles''' sans privilèges.
* Le contrôleur hôte respecte la procédure ('''enable → # → write erase → reload'''…), et les logs rendent le basculement **visible** pour démonstration.


== Programme préliminaire de remise à zéro ==
== Programme préliminaire de remise à zéro ==
Ligne 440 : Ligne 439 :
Joindre l'archive du programme C (avec Makefile).
Joindre l'archive du programme C (avec Makefile).


== Programme embarqué LUFA ==
== Programme embarqué LUFA==
Cette section décrit brièvement le rôle de chaque fichier du firmware et la façon dont ils interagissent pour piloter la remise à zéro Cisco (série UART) et l’interface utilisateur (boutons + LEDs).


Archive attachée avec <code>Makefile</code> intégré.
=== Arborescence ===


Vidéos de démonstration.
firmware/
  ├─ Makefile
  └─ src/
    ├─ board.h
    ├─ uart.h
    ├─ uart.c
    ├─ ios_seq.h
    ├─ ios_seq.c
    └─ main.c
 
=== '''board.h''' — Abstraction carte (LEDs & boutons) ===
 
Définit le mappage matériel :
* '''LED rouge''' = PB4 (échec)
* '''LED verte''' = PB5 (succès)
* '''LED jaune''' = PB6 (activité)
* '''B1''' = PF0, '''B2''' = PF1 ('''actifs bas''' avec pull-up)
 
Fournit des fonctions inline :
* <code>leds_init()</code> : configure les GPIO des LEDs en sorties et les éteint.
* <code>led_r_on/off()</code>, <code>led_g_on/off()</code>, <code>led_y_on/off/tgl()</code> : contrôle simple des LEDs.
* <code>buttons_init()</code> : met PF0/PF1 en entrées avec résistances pull-up.
* <code>b1_raw()</code>, <code>b2_raw()</code> : retourne 1 si le bouton est appuyé.
 
Contient la constante '''UART_BAUD = 9600''' utilisée par l’UART.
 
=== '''uart.h / uart.c''' — Lien série UART (vers l’équipement Cisco/simulateur) ===
 
Initialisation et E/S UART1 de l’AT90USB1287 :
* '''TXD1''' = PD3 (sortie), '''RXD1''' = PD2 (entrée).
* Mode 8N1 (8 bits, sans parité, 1 stop).
* '''U2X1 activé''' (double vitesse) pour réduire l’erreur de baud (robuste à 8/16 MHz).
 
Fonctions principales :
* <code>uart_init(F_CPU, baud)</code> : calcule UBRR avec U2X1, configure PD2/PD3.
* <code>uart_putc()</code>, <code>uart_write()</code> : émission caractère/chaîne.
* <code>uart_rx_ready()</code>, <code>uart_getc()</code> : réception non bloquante/bloquante.
* <code>uart_flush_rx()</code> : vide le tampon RX (utile avant <code>enable</code>).
 
Remarque : le paramètre '''F_CPU''' (8 MHz ou 16 MHz) est passé via le Makefile.
 
=== '''ios_seq.h / ios_seq.c''' — Séquences IOS (logique Cisco) ===
 
Regroupe l’algorithme haut-niveau d’échange avec l’IOS Cisco :
* '''Passage en mode admin''' : <code>ios_enter_enable()</code> envoie <code>enable</code> et attend le prompt '''#''' (détection même sans <code>\n</code>).
* '''Détection du modèle''' : <code>ios_detect_model()</code> envoie <code>show version</code> et repère <code>ISR4221</code>, <code>C9200</code>, <code>Aironet1600</code> (retourne un '''dev_t''').
* '''Effacement config''' : <code>ios_erase_startup_config()</code> tente <code>erase startup-config</code>, avec repli sur <code>write erase</code> selon la réponse.
* '''Suppléments par modèle''' :
** C9200 → <code>ios_delete_vlan_dat()</code> (''delete flash:vlan.dat'').
** Aironet → <code>ios_clear_bvi1_ip()</code> (''conf t / int BVI1 / no ip address'').
* '''Redémarrage''' : <code>ios_reload()</code> gère le <code>[confirm]</code> puis attend ''Reloading…/OK''.
 
Robustesse du parsing :
* <code>ios_read_useful()</code> ignore les lignes vides, les prompts '''>''', '''#''', et les prompts de configuration <code>(config)#</code> / <code>(config-if)#</code>.
* Détection de '''#'''/''' > ''' même s’ils arrivent sans fin de ligne.
* Timeouts paramétrés par l’appelant (en millisecondes).
 
Indicateur utilisateur : certaines fonctions font clignoter la LED '''jaune''' pendant l’attente (visuel d’activité).
 
=== '''main.c''' — Orchestrateur (boutons → séquences → LEDs) ===
 
Initialise le hardware : <code>leds_init()</code>, <code>buttons_init()</code>, <code>uart_init()</code>, <code>sei()</code>.
 
Anti-rebond compact sur B1/B2.
 
Règles d’usage :
* '''B1''' = séquence complète de remise à zéro :
::: <code>enable → show version (détecte modèle) → erase startup-config (ou write erase)</code>
::: <code>[C9200] delete vlan.dat</code> / <code>[Aironet] no ip address sur BVI1</code>
::: <code>reload</code>
* '''B2''' = raccourci redémarrage : <code>enable → reload</code>.
 
Feedback LEDs :
* '''Jaune''' : clignote pendant les envois/attentes.
* '''Verte''' : succès de la séquence.
* '''Rouge''' : échec (timeout/réponse inattendue).
 
Les '''timeouts''' sont choisis pour être réactifs (peuvent être ajustés selon l’équipement).
 
=== '''Makefile''' ===
 
Définit la cible AVR : '''MCU=at90usb1287''', '''F_CPU=8 MHz''' (ou 16 MHz), optimisation '''-Os'''.
 
Construit :
* <code>firmware.elf</code> (link),
* <code>firmware.hex</code> (image Intel HEX pour flash).
 
Commandes usuelles :
* <code>make</code> : compile et produit <code>firmware.hex</code>.
* <code>make clean</code> : supprime les artefacts.
 
=== Interaction globale ===
 
L’utilisateur appuie sur '''B1''' (ou '''B2''').
Le '''main''' lance la séquence correspondante, fait '''clignoter la LED jaune''' et délègue à '''ios_seq.c''' l’échange série.
Les fonctions '''ios_seq''' utilisent '''uart.c''' pour envoyer/recevoir et pour filtrer les prompts/réponses.
En fin de séquence :
 
succès → '''LED verte ON''' (jaune/rouge OFF),
 
échec → '''LED rouge ON''' (jaune/verte OFF).
 
=== Test de la carte avec firmware ===
 
==== Mise en mode DFU ====
Pour flasher le microcontrôleur '''AT90USB1287''' intégré à ma carte, j’utilise le bootloader matériel '''DFU''' (Device Firmware Upgrade). 
Pour passer en mode DFU :
* Maintenir le bouton '''HWB''' (connecté à la broche PE2),
* Appuyer brièvement sur le bouton '''Reset''' puis le relâcher,
* Relâcher enfin le bouton '''HWB'''.
 
À ce moment, la commande <code>lsusb</code> montre l’identifiant USB '''03eb:2ffb Atmel Corp. at90usb AVR DFU bootloader''', comme vous pouvez le voir ci-dessous.
[[Fichier:Lsusb.png|vignette||centre|lsusb effectué avant et après le passsage en mode DFU]]
 
==== Flashage du firmware ====
Une fois en mode DFU, j’utilise '''dfu-programmer''' :
 
<syntaxhighlight lang=bash>
# Compilation
make clean && make
 
# Flashage du microcontrôleur
sudo dfu-programmer at90usb1287 erase
sudo dfu-programmer at90usb1287 flash firmware.hex
sudo dfu-programmer at90usb1287 reset
</syntaxhighlight>
 
==== Fonctionnement attendu ====
* Après un appui sur '''B1''' :
** La LED '''jaune''' clignote pendant l’envoi des commandes Cisco (simulateur ou vrai équipement),
** La LED '''verte''' s’allume si la séquence s’est bien déroulée (enable → erase startup-config → commandes spécifiques → reload),
** La LED '''rouge''' s’allume en cas d’échec (timeout, absence de réponse, erreur). 
 
* Après un appui sur '''B2''' :
** La LED '''jaune''' clignote,
** La séquence envoie uniquement '''enable''' puis '''reload''',
** La LED '''verte''' s’allume si la commande a été acceptée, sinon la rouge.
 
==== Problèmes rencontrés ====
Comme vous pouvez le voir dans la video ci-dessous le test se résulte sur un échec car la LED finale allumée est la LED rouge PB4 (les LED étant toutes rouge cela peut être un peu confus) et le moniteur série de l'Arduino ne reçoit aucunes des commandes envoyées par ma carte. Je pense qu'il s'agit d'un problème lié à la façon dont je connecte mon Arduino et ma carte (liaison des pins RX et TX de l'arduino avec ceux de ma carte et mise en commun des GND avec des fils Dupont). La communication série étant prévue pour s'effectuer avec les differents ports de la carte (USB, RJ45), cette connexion avec les fils Dupont pourrait être à l'origine des problèmes de communication entre ma carte et l'Arduino. Je continue d'effectuer des tests dans l'espoir de régler ce problème.
 
 
====Vidéos de démonstration====
[[Fichier:20250827 050944.mp4|vignette|centre|Test du programme embarqué]]
 
=== Passage de Serial (D0/D1) à SoftwareSerial (D8/D9) pour recevoir des données ===
 
==== Contexte initial ====
 
Je ne recevais aucun octet sur l’Arduino lorsque ma carte AT90USB1287 envoyait ses commandes.
 
La cause probable : le port série matériel de l’Arduino (D0/D1) était déjà utilisé par le Moniteur Série USB (conflit PC ⇄ Arduino ⇄ Carte).
 
==== Objectif ====
 
Séparer la console USB (PC) et la liaison avec ma carte pour éviter les conflits.
 
Conserver l’affichage sur le Moniteur Série tout en parlant en série avec la carte.
 
==== Modification réalisée ====
 
Mise en place d’un second port série avec SoftwareSerial sur des broches libres :
 
<code>SoftwareSerial serialCarte(8, 9);</code> → RX = D8, TX = D9.
 
<code>Serial</code> reste l’USB (PC) pour observer les échanges.
 
Double lecture et double tampon :
 
<code>line</code> ← accumulation depuis <code>Serial</code> (PC),
 
<code>lineCarte</code> ← accumulation depuis <code>serialCarte</code> (carte),
 
même fonction de parsing/commande pour les deux flux (factorisation).
 
==== Câblage associé ====
 
D8 (Arduino) ← TX de ma carte (PD3).
 
D9 (Arduino) → RX de ma carte (PD2).
 
GND commun entre Arduino et la carte.
 
==== Résultat après modification ====
 
Avant : aucune réception (silence complet).
 
Après : réception présente sur D8/D9 (trames visibles), mais mal interprétées → affichage de ‘?’ (caractères non reconnus).
 
==== Code Arduino ====
<syntaxhighlight lang=cpp>
#include <SoftwareSerial.h>
 
#define DEVICE "ISR4221"  // "ISR4221", "Aironet1600", "C9200"
 
// Définition du port série logiciel pour la deuxième carte
// RX=8, TX=9 (à adapter selon ton câblage)
SoftwareSerial serialCarte(8, 9);
 
bool enabled = false;
bool inConfig = false;
bool inIf = false;
String line = "";
String lineCarte = "";
 
void setup() {
  Serial.begin(9600);        // PC (USB)
  serialCarte.begin(9600);    // Deuxième carte
  delay(200);
 
  // Message de boot envoyé aux deux sorties
  broadcast("");
  broadcast("Cisco IOS XE Software, Version 17.3.1");
  broadcast(String("Device: ") + DEVICE);
  broadcast("Type 'help' for cmds");
  prompt();
}
 
void loop() {
  // --- Lecture depuis le PC ---
  while (Serial.available()) {
    char c = Serial.read();
    handleInput(c, line);  // traite le flux du PC
  }
 
  // --- Lecture depuis la carte ---
  while (serialCarte.available()) {
    char c = serialCarte.read();
    handleInput(c, lineCarte); // traite le flux de la carte
  }
}
 
void handleInput(char c, String &buffer) {
  if (c == '\r' || c == '\n') {
    if (buffer.length() > 0) {
      handleCommand(buffer);
      buffer = "";
    }
  } else {
    buffer += c;
  }
}
 
void broadcast(String msg) {
  Serial.println(msg);
  serialCarte.println(msg);
}
 
void broadcastPrompt(String msg) {
  Serial.print(msg);
  serialCarte.print(msg);
}
 
void prompt() {
  if (inIf)          broadcastPrompt("(config-if)# ");
  else if (inConfig)  broadcastPrompt("(config)# ");
  else if (enabled)  broadcastPrompt("# ");
  else                broadcastPrompt("> ");
}
 
void handleCommand(String cmd) {
  cmd.trim();
 
  if (cmd == "help") {
    broadcast("cmds: help, enable, show version, write erase, erase nvram, erase startup-config,");
    broadcast("      reload, reset, configure terminal, conf t, interface BVI1, no ip address, end,");
    broadcast("      delete flash:vlan.dat");
  }
 
  else if (cmd == "enable") {
    enabled = true; inConfig = false; inIf = false;
  }
 
  else if (cmd == "show version") {
    broadcast(String("Cisco ") + DEVICE + " Software");
    if (String(DEVICE) == "C9200") {
      broadcast("Model: C9200 switch");
    } else if (String(DEVICE) == "Aironet1600") {
      broadcast("Model: Aironet 1600 Series");
      broadcast("Interface BVI1 present");
    } else if (String(DEVICE) == "ISR4221") {
      broadcast("Model: ISR4221 router");
    }
  }
 
  else if (cmd == "write erase" || cmd == "erase nvram") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { broadcast("Erasing NVRAM...done"); }
  }
 
  else if (cmd == "erase startup-config") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { broadcast("Erase of nvram: complete"); }
  }
 
  else if (cmd == "reload" || cmd == "reset") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else {
      broadcast("Proceed with reload? [confirm]");
      broadcast("Reloading...");
      broadcast("OK");
    }
  }
 
  else if (cmd == "configure terminal" || cmd == "conf t") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { inConfig = true; inIf = false; }
  }
 
  else if (cmd == "interface BVI1") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { inConfig = true; inIf = true; }
  }
 
  else if (cmd == "no ip address") {
    if (!enabled) { broadcast("% Privileged mode required"); }
  }
 
  else if (cmd == "end") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { inConfig = false; inIf = false; }
  }
 
  else if (cmd == "delete flash:vlan.dat") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else {
      broadcast("Delete filename [vlan.dat]?");
      broadcast("Delete flash:/vlan.dat? [confirm]");
      broadcast("File deleted");
    }
  }
 
  else {
    broadcast("?");
  }
 
  prompt();
}
 
</syntaxhighlight>
 
==== Pourquoi ce changement est important ====
 
Il supprime la concurrence entre le port USB (Moniteur Série) et la liaison vers la carte.
 
Il permet de voir dans le Moniteur Série ce que reçoit et renvoie le simulateur, tout en laissant la carte dialoguer sur une autre UART (logicielle).
 
=== État actuel & prochaines étapes ===
 
État actuel : la réception existe (progrès) mais les données sont mal décodées (apparition de '''?'''), comme vous pouvez le voir ci-dessous :
 
[[Fichier:Document 5891236542038416961.mp4|vignette|centre|test communication série]]
 
j'ai également écrit un programme LUFA que vous pouvez trouvez dans le dépôt Git du projet : https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO


== Programme Arduino Uno ==
== Programme Arduino Uno ==
Ligne 548 : Ligne 901 :


Le code Arduino est présent dans l'archive git: https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO
Le code Arduino est présent dans l'archive git: https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO
Vous pouvez voir ci-dessous la nouvelle version du code arduino permettant la simulation d'équipement Cisco et prenant en compte la commande enable et le passage en mode administrateur :
=== Sketch Arduino ===
<syntaxhighlight lang="cpp">
// sim_cisco.ino — Arduino simule un équipement Cisco ISR4221 @ 9600 bds
#define BAUD 9600
String line = "";
bool privileged = false; // false => "Router>", true => "Router#"
void printPrompt() { Serial.print(privileged ? "Router#" : "Router>"); }
void setup() {
  Serial.begin(BAUD);
  delay(300);
  printPrompt(); // prompt initial
}
void handleCommand(String cmd) {
  cmd.trim();
  if (cmd.length() == 0) { printPrompt(); return; }        // important pour DetectPrompt()
  if (cmd.equalsIgnoreCase("enable")) {
    privileged = true; Serial.print("\r\nRouter#");
  } else if (cmd.equalsIgnoreCase("terminal length 0")) {
    Serial.print("\r\n"); printPrompt();
  } else if (cmd.equalsIgnoreCase("show version")) {
    Serial.print("\r\nCisco IOS XE Software, ISR4221\r\n");
    Serial.print("Router uptime is 1 day\r\n");
    Serial.print("ISR4221 Router (revision 1.0)\r\n");
    printPrompt();
  } else if (cmd.startsWith("erase startup-config") || cmd.startsWith("write erase")) {
    Serial.print("\r\n[confirm]\r\n"); printPrompt();
  } else if (cmd.equalsIgnoreCase("reload")) {
    Serial.print("\r\nProceed with reload? [confirm]\r\n");
    // LUFA enverra juste un CR ensuite
  } else if (cmd.equalsIgnoreCase("configure terminal")) {
    Serial.print("\r\n(config)#");
  } else if (cmd.equalsIgnoreCase("interface BVI1")) {
    Serial.print("\r\n(config-if)#");
  } else if (cmd.equalsIgnoreCase("no ip address") || cmd.equalsIgnoreCase("no ip address dhcp")) {
    Serial.print("\r\n(config-if)#");
  } else if (cmd.equalsIgnoreCase("exit")) {
    Serial.print("\r\n(config)#");
  } else if (cmd.equalsIgnoreCase("end")) {
    Serial.print("\r\n"); printPrompt();
  } else {
    Serial.print("\r\n"); printPrompt();
  }
}
void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r' || c == '\n') { handleCommand(line); line = ""; }
    else { line += c; }
  }
}
</syntaxhighlight>


== Vidéos de démonstration ==
== Vidéos de démonstration ==
Après réception de la carte et afin de m'assurer de son bon fonctionnement, j'ai programmé cette dernière pour qu'une des LEDs programmables clignote une fois que le Bouton B1 est appuyé. Vous pouvez voir le résultat de cette programmation ci-dessous.
Après réception de la carte et afin de m'assurer de son bon fonctionnement, j'ai programmé cette dernière pour qu'une des LEDs programmables clignote une fois que le Bouton B1 est appuyé. Vous pouvez voir le résultat de cette programmation ci-dessous.
[[Fichier:20250824 212744.mp4|vignette|centre|test de fonctionnement]]
[[Fichier:20250824 212744.mp4|vignette|centre|test de fonctionnement]]

Version actuelle datée du 2 septembre 2025 à 22:08

Objectifs

Vous allez concevoir, réaliser et programmer un système embarqué de remise à zéro d'équipements réseau.

Les équipement réseau visés sont des éléments Cisco. Pour remettre à zéro un élément réseau Cisco, il faut passer en mode administrateur et envoyer la commande erase startup-config. Ensuite si l'équipement est un commutateur il faut effacer la liste des VLAN avec la commande del vlan.dat. Si l'équipement est un point d'accès WiFi il faut supprimer l'adresse IP sur l'interface BVI1.

L'épreuve complémentaire sera considérée comme un succès si votre système embarqué arrive à réinitialiser un ISR4221, un Aironet 1600 et un C9200.

Vous commencerez par concevoir votre carte dont le coeur doit être un microcontrôleur AVR et qui doit pouvoir converser avec un périphérique série (que ce soit via USB ou en utilisant un port RJ45). L'utilisateur doit aussi pouvoir relancer la mise à zéro via un bouton et connaitre l'état de la mise à zéro via des LED. Une réflexion doit être menée sur l'alimentation de la carte. Vous avez le droit à un budget de 50 euros pour la carte, les composants et le port.

Une fois la carte conçue et validée par l'encadrant vous pourrez vous pencher sur la programmation de la carte. Cette programmation se fera uniquement en utilisant la version AVR de gcc et l'utilitaire dfu-programmer.

Il s'agit là d'un cahier des charges initial qui peut être revu en cas d'ambigüité. Vous avez jusqu'au 1 septembre pour réaliser le travail demandé. Il vous est explicitement demandé de produire un travail régulier le plus tôt possible *et* de faire des rapports réguliers (obligatoirement au travers de ce Wiki) à votre encadrant. S'y prendre à la dernière minute avec un seul retour à quelques jours de la date limite est une garantie d'échec.

Développement et tests

Pour vous rappeler les commandes IOS Cisco vous pouver utiliser un logiciel de simulation d'éléments réseau comme Packet Tracer.

Ensuite pour tester l'algorithme haut niveau de votre programme embarqué vous pouvez :

  • écrire un programme C simulant les quelques commandes IOS Cisco nécessaires pour la remise à zéro ;
  • attacher ce programme sur un port série virtuel avec l'utilitaire socat ;
  • écrire un programme C classique qui se connecte au port série virtuel, qui communique avec le "périphérique" pour connaître son type et envoyer les ordres nécessaires pour la remise à zéro.

Pour la suite vous écrirez le vrai programme embarqué en intégrant votre algorithme haut-niveau dans le projet LUFA adapté.

Pour tester le programme sur votre carte vous pouvez la connecter à un Arduino Uno programmé pour simuler les quelques commandes Cisco nécessaire pour la remise à zéro.

Enfin vous testerez votre carte en situation reelle sur les équipements Cisco fin août.

Travail réalisé

Lien vers le dépôt Git: https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO

Avancement du 18/07

Schématique terminé

J'ai finalisé aujourd'hui une première version du schéma électronique de ma carte à base d'ATmega328P-A (boîtier TQFP-32). Tous les éléments essentiels ont été intégrés, en respectant les contraintes du cahier des charges.

Choix de l'alimentation via USB micro-B

Pour l'alimentation de la carte, j'ai opté pour un connecteur micro-USB type B, utilisé uniquement pour fournir du +5V et GND à la carte. Le 5V est ensuite filtré à l'aide d'un condensateur de 10 µF en entrée, et des condensateurs de découplage (100 nF) sont placés près du microcontrôleur pour stabiliser la tension localement.

LED de signalement d'état

J'ai intégré trois LED sur la carte pour signaler visuellement les états du système :

  • LED Témoin : Mise sous tension (Power ON)
  • LED_JAUNE : Remise à zéro en cours
  • LED_VERTE : Remise à zéro terminée avec succès
  • LED_ROUGE : Remise à zéro terminée sans succès

Chaque LED est reliée à une broche du microcontrôleur à travers une résistance de limitation de courant.

Communication série avec les équipements Cisco

J'ai choisi d'utiliser un adaptateur FT232RL (module USB ↔ UART TTL) pour assurer la communication entre ma carte et les équipements Cisco à réinitialiser. Le module FTDI sera connecté à un header 2x3 broches (J4) sur la carte (signaux TX, RX, GND, VCC), et une liaison physique sera assurée vers le port console RJ45 des équipements via un câble série.

Avancement du 19/07

Aujourd'hui, des choix techniques importants ont été faits suite au retour de M. REDON, concernant la première version du schéma électronique, pour assurer la compatibilité complète de ma carte avec tous les équipements Cisco visés (Aironet 1600, ISR4221, C9200).

Changement du microcontrôleur

J'ai remplacé le ATmega328P-A initialement utilisé, par un ATmega16U4-A (boîtier CMS TQFP-32). Ce dernier microcontrôleur possède une interface USB native, indispensable pour communiquer directement avec le Cisco C9200, qui ne dispose que d'un port USB console.

Présence de 3 boutons poussoirs

Trois boutons distincts sont intégrés au schéma :

  • Bouton RESET : connecté à la broche RESET du microcontrôleur. Il permet de redémarrer le programme en cours.
  • Bouton HWB : connecté à la broche HWB du microcontrôleur. Lorsqu'il est maintenu appuyé au moment d’un RESET, il permet de démarrer le microcontrôleur en mode bootloader DFU, pour reprogrammer le firmware via USB.
  • Bouton de remise à zéro des équipements Cisco : connecté à la broche PD5. C’est l'utilisateur qui l’actionne pour déclencher, via le firmware LUFA, l'envoi des commandes nécessaires à la remise à zéro de l’équipement cisco.

Connexions USB

Un connecteur USB micro-B est utilisé pour :

  • Alimenter la carte (5V via VBUS)
  • Communiquer avec les équipements cisco (C9200 ici) via l’interface USB native

Connexions série UART

Un connecteur 2x3 broches est conservé pour accéder aux lignes TX/RX/GND/VCC. Il servira à communiquer avec les équipements Cisco disposant d’un port console série RJ45, comme les ISR4221 et Aironet 1600.

Avancement du 22/07

Remplacement du microcontrôleur ATmega16U4-A par un AT90USB1287, afin de rendre ma carte capable de fonctionner comme un hôte USB (USB Host). Ce changement est essentiel pour pouvoir communiquer avec des équipements Cisco comme le C9200, qui n'ont qu'un port USB console.

J’ai compris que les microcontrôleurs comme le 16U4 ne peuvent agir que comme périphériques USB (USB Device), alors que le 1287 est capable d'agir comme un contrôleur USB (USB Host), ce qui est requis par le projet. Ce choix me permet d'utiliser directement la démonstration LUFA Host/ClassDriver/VirtualSerialHost.

La carte est désormais conçue pour être alimentée et programmée par un seul port USB (mâle), et elle pourra ensuite communiquer avec un équipement réseau via un second port USB (femelle), en mode hôte.

Avancement du 29/07

Ajout d'un 2e bouton utilisateur sur la carte et remplacement du connecteur 2x3 par un RJ45 (dont la référence utilisée pour le brochage est présente ci-dessous).

Brochage RJ45

Modification de la première version du routage pour prendre en compte les ajouts mentionnés ci-dessus et pour également réduire le nombre de vias et mieux placer les condensateurs du quartz comme indiqué par monsieur REDON.

Avancement du schéma KiCad – 09/08/2025

1. Correction du câblage RJ45

  • Modification du brochage conformément au retour encadrant :
    • Broches 4 et 5 reliées au GND.
    • Broche 3 du RJ45 → reliée au RX du µC.
    • Broche 6 du RJ45 → reliée au TX du µC.
  • Ajout de deux connecteurs 3 broches (type jumper/cavalier) pour inverser TX/RX selon le type de câble (droit ou croisé).
    • Position normale : Broche 3 RJ45 → RX µC et Broche 6 RJ45 → TX µC.
    • Position inversée : Broche 3 RJ45 → TX µC et Broche 6 RJ45 → RX µC.
    • Permet d’assurer la compatibilité avec câbles droits et croisés sans modification logicielle.

2. Résistances sur lignes USB (D+ et D−)

  • Ajout de résistances série 22 Ω sur D+ et D−.
    • Placées au plus près des broches USB du AT90USB1287.
    • Concernent à la fois le port USB Device (USB A mâle pour DFU) et le port USB Host (USB A femelle pour contrôle Cisco).

3. Condensateurs de découplage

  • Correction du placement : les condensateurs sont désormais placés entre chaque broche VCC/AVCC/UVCC du µC et GND (et non entre VCC et +5V).
    • Un condensateur de 100 nF céramique par broche d’alimentation.
    • Placés au plus près des broches d’alimentation du microcontrôleur.
  • Maintien d’un condensateur électrolytique de 10 µF sur la ligne d’alimentation générale pour la stabilité.

Recapitulatif Conception matérielle de la carte

La carte a été conçue sous KiCad et respecte les consignes du sujet :

  • Le cœur du système est un microcontrôleur AVR AT90USB1287 (compatibilité native USB et bonne disponibilité).
  • La carte permet de communiquer avec un équipement réseau Cisco via un port série (console), en utilisant une interface USB femelle reliée au microcontrôleur.
  • Une interface USB mâle est présente pour :
    • l’alimentation de la carte,
    • la programmation du firmware via le mode DFU intégré au microcontrôleur.
  • Des LEDs d’état sont prévues pour informer l’utilisateur du déroulement de la remise à zéro :
    • LED jaune (PB6) : remise à zéro en cours,
    • LED verte (PB5) : remise à zéro réussie,
    • LED rouge (PB4) : erreur lors de la séquence.
  • Des boutons poussoirs sont intégrés :
    • B1 (PF0) : déclenchement de la séquence complète de remise à zéro,
    • B2 (PF1) : variante (pour le moment simple reload),
    • RESET : bouton matériel de remise à zéro du microcontrôleur,
    • HWB (PE2) : bouton d’accès au mode DFU pour le flashage du firmware.
  • La carte comprend également une LED témoin d’alimentation (D4) qui s’allume dès que la carte est alimentée.
  • L’alimentation est assurée via l’USB (5 V), ce qui simplifie la conception et respecte le budget.

Résumé des fonctionnalités

  • Microcontrôleur AVR AT90USB1287 comme cœur de la carte.
  • Communication série avec les équipements Cisco via un connecteur USB femelle.
  • Programmation et alimentation via un connecteur USB mâle (mode DFU + alimentation 5 V).
  • Interface utilisateur : 2 boutons (B1, B2) pour lancer la réinitialisation, RESET et HWB pour la gestion du microcontrôleur.
  • Affichage d’état clair par 3 LEDs (rouge, verte, jaune).
  • Budget respecté : carte + composants < 50 €.

Grâce à l'accompagnement de l'encadrant M. REDON cette conception répond donc aux critères imposés :

  1. Cœur AVR,
  2. Communication série vers périphériques réseau Cisco,
  3. Déclenchement de la remise à zéro par bouton,
  4. LEDs d’indication d’état,
  5. Réflexion sur l’alimentation (USB),
  6. Budget raisonnable.

Documents Rendus

Projet KiCAD de la carte

Vous pouvez trouver ci dessous la première version du schéma électronique de ma carte réalisée le 18/07.

schematique (première version)

vous pouvez trouver ci-dessous la seconde version du schéma électronique de ma carte realisée le 19/07.

schematique (seconde version)

vous pouvez trouver ci-dessous la troisième version du schéma électronique de ma carte (avec un at90usb1287) réalisée le 20/07.

schematique (troisième version)

vous pouvez trouver ci-dessous la quatrième version du schéma électronique de ma carte réalisée le 22/07.

schematique (4e version)

vous pouvez trouver ci-dessous la 5e version du schéma électronique de ma carte réalisée le 24/07 ainsi que la 1ere version du routage.

schematique (5e version)
PCB 1ère version

vous pouvez trouver ci-dessous la 6e version du schéma électronique de ma carte réalisée le 29/07

schematique (6e version)
PCB 2e version

vous pouvez trouver ci-dessous la 7e version du schéma électronique de ma carte réalisée le 9/08

schematique (7e version)

Vous pouvez voir ci-dessous la version finale du schéma électronique ainsi que celle du routage

schema finale
routage final

Après réception de la carte les différentes phases de tests et de simiulations ont été entamés, vous pouvez voir ci-dessous la carte reçue déja soudée.

carte soudée

Commandes Cisco

Listez les commandes Cisco nécessaires pour la remise à zéro des équipements visés. show version, write erase/erase startup-config, reload

Programme de simulation d'IOS Cisco

Joindre l'archive du programme C (avec Makefile) permettant de simuler les ordres Cisco permettant la remise à zéro.

Simulation logicielle du protocole de remise à zéro

En attendant de recevoir la carte électronique, une étape de simulation a été réalisée afin de valider l’algorithme haut niveau de remise à zéro des équipements Cisco.

Objectif

  • Simuler le dialogue série entre la carte de contrôle (host) et un équipement Cisco (device).
  • Tester les séquences de commandes nécessaires à l’effacement de la NVRAM et au redémarrage.
  • Vérifier la bonne détection du modèle d’équipement et l’application des bons profils de temps (appui long sur reset).

Mise en place

  • Deux programmes C ont été développés :
    • sim_ios : émule un périphérique Cisco en répondant à un sous-ensemble de commandes IOS.
    • host_ctrl : implémente l’algorithme haut niveau qui sera intégré plus tard sur l’AT90USB1287.
  • Une paire de ports série virtuels est créée avec l’outil socat grâce à la commande  :
 # socat -d -d pty,raw,echo=0,link=/tmp/ttyHOST pty,raw,echo=0,link=/tmp/ttyDEV
  • Le simulateur est lancé sur /tmp/ttyDEV et le contrôleur sur /tmp/ttyHOST.

Commandes simulées

Le simulateur comprend les commandes suivantes :

  • help : liste les commandes supportées.
  • ID? : renvoie l’identifiant du modèle (ex. ISR4221, Aironet1600, C9200).
  • show version : affiche une version simplifiée de l’équipement.
  • write erase / erase nvram : efface la NVRAM.
  • reload / reset : simule un redémarrage de l’équipement.

Profils de reset

Trois profils de temporisation sont définis, en fonction du modèle détecté :

  • ISR4221 : appui 8 s, relâchement 0,5 s, délai post-reset 3 s.
  • Aironet1600 : appui 10 s, relâchement 0,5 s, délai post-reset 5 s.
  • C9200 : appui 12 s, relâchement 0,5 s, délai post-reset 5 s.

Fonctionnement observé

  • Au lancement, le host lit la bannière du simulateur.
  • Il envoie ID? et identifie correctement le modèle simulé.
  • Il applique ensuite la séquence :
 # envoi de write erase, réception de Erasing NVRAM...done;
 # maintien simulé du bouton reset pendant la durée définie par le profil ;
 # envoi de reset (ou reload), réception de Reloading... puis OK.
  • Le host affiche dans son terminal les lignes reçues avec l’indicateur [RX] ainsi que les étapes de la séquence.

Exemple de session

[RX] Cisco IOS XE Software, Version 17.3.1

[RX] Device: Aironet1600

[RX] Type 'help' for cmds

[INFO] Detected device: Aironet1600 -> profile press=10000ms

[RX] Erasing NVRAM...done

[ACT] Holding reset (sim) for 10000 ms...

[RX] Reloading...

[RX] OK

[OK] Reset sequence completed. Post-wait 5000 ms

vous pouvez voir ci-dessous un exemple de simulation avec un Aironet1600 comme équipement simulé.

host(terminal de gauche), commande socat (terminal au centre), simulateur (terminal de droite)


Les fichiers utilisés sont sim_ios.c, host_ctrl.c, serial.c, serial.h et un Makefile que vous pouvez retrouver dans l'archive git dont le lien est le suivant: https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO


Conclusion

Cette simulation valide :

  • la communication série host ↔ périphérique,
  • la détection automatique du modèle d’équipement,
  • l’application des séquences de reset adaptées.




Intégration du passage utilisateur (>)administrateur (#) dans ma simulation Cisco

Après un retour de l'encadrant M. REDON, qui m'a fait remarqué que mon code de simulation de communication avec un équipement cisco ne prenait pas en compte le passage du mode utilisateur au mode administrateur, j'ai entamé une phase de correction de ce problème car en effet comme me l'a bien fait comprendre M. REDON "Les matériels Cisco démarrent en mode utilisateur et pour remettre à 0 il faut passer en mode administrateur". Sur ses conseils j'ai donc effectué les modifications suivantes:

Ce chapitre résume et illustre les changements apportés à mon simulateur Cisco et à mon contrôleur hôte pour respecter la procédure réelle des équipements Cisco : démarrage en mode utilisateur (>), passage en mode administrateur (#) via la commande enable, puis exécution des commandes sensibles (write erase, reload, etc.).

Objectifs
  • Reproduire la séparation des modes : utilisateur (>) / admin (#) / config (config)# / config-if (config-if)#.
  • Interdire les commandes sensibles en mode utilisateur (message % Privileged mode required).
  • Côté hôte : envoyer `enable` d’abord, puis attendre explicitement le prompt `#` avant de lancer la séquence de remise à zéro.
  • Rendre le basculement visible dans les logs (affichage des commandes envoyées et du prompt admin détecté).
Changements côté simulateur (sim_ios.c)
Avant
Prompt unique et permissif (toujours `>`), pas de contrôle d’accès : toutes les commandes acceptées en « mode utilisateur ».
Après
Ajout d’un état de CLI et de prompts corrects, plus contrôle d’accès sur les commandes sensibles.
État du CLI
static int enabled  = 0; // 0: utilisateur '>', 1: admin '#'
static int in_config = 0;
static int in_if     = 0;
Prompts centralisés
static void prompt(int fd){
if (in_if)          serial_writestr(fd, "(config-if)#\r\n");
else if (in_config) serial_writestr(fd, "(config)#\r\n");
else if (enabled)   serial_writestr(fd, "#\r\n");
else                serial_writestr(fd, ">\r\n");
}
Bascule en mode admin via enable
if (strcmp(line, "enable") == 0) {
enabled = 1; in_config = 0; in_if = 0;
prompt(fd);
continue;
}
Contrôle d’accès (refus si pas admin)
if (strcmp(line, "write erase") == 0 || strcmp(line, "erase nvram") == 0) {
if (!enabled) { send_line(fd, "% Privileged mode required"); prompt(fd); continue; }
send_line(fd, "Erasing NVRAM...done"); prompt(fd);
}
if (strcmp(line, "reload") == 0 || strcmp(line, "reset") == 0) {
if (!enabled) { send_line(fd, "% Privileged mode required"); prompt(fd); continue; }
send_line(fd, "Proceed with reload? \[confirm]");
send_line(fd, "Reloading..."); send_line(fd, "OK"); prompt(fd);
}
Mode configuration
if (strcmp(line, "configure terminal") == 0 || strcmp(line, "conf t") == 0) {
if (!enabled) { send_line(fd, "% Privileged mode required"); prompt(fd); continue; }
in_config = 1; in_if = 0; prompt(fd);
}
if (strcmp(line, "interface BVI1") == 0) { in_config = 1; in_if = 1; prompt(fd); }
if (strcmp(line, "no ip address") == 0)  { prompt(fd); }
if (strcmp(line, "end") == 0)            { in_if = 0; in_config = 0; prompt(fd); }
Changements côté hôte (host_ctrl.c)
Avant
Parfois identification via `ID?`, pas d’assurance d’être en `#` avant d’exécuter les commandes sensibles, logs TX limités.
Après
Envoi systématique de enable, attente du prompt `#`, parsing robuste de `show version`, logs TX explicites, logs du prompt admin.
Log des commandes envoyées (wrapper TX)
static void writestr_tx(int fd, const char* s){
size_t len = strlen(s);
if (len >= 2 && s[len-2]=='\r' && s[len-1]=='\n') {
char tmp[256]; size_t n = len-2; if (n>255) n=255;
memcpy(tmp, s, n); tmp\[n]=0;
fprintf(stderr, "\[TX] %s\r\n\n", tmp);
} else {
fprintf(stderr, "\[TX] %s\n", s);
}
serial_writestr(fd, s);
}
Attente explicite du prompt admin (avec log clair)
static int wait_prompt_hash(int fd, int tries){
char line\[256];
while (tries-- > 0) {
int n = serial_readln(fd, line, sizeof line);
if (n <= 0) { usleep(1000); continue; }
// ... strip spaces/crlf ...
if (strcmp(line, "#") == 0 || (line[0]=='#' && line[1]==0)) {
fprintf(stderr, "\[PROMPT] # (privileged)\n");
return 1;
}
fprintf(stderr, "\[RX] %s\n", line); // tout ce qui arrive pendant l’attente
}
return 0;
}
Séquence côté hôte (extrait)
/\* Bannière lue ... \*/

// 1) passer en mode admin
writestr_tx(fd, "enable\r\n");
if (!wait_prompt_hash(fd, 10)) {
fprintf(stderr, "[ERR] Failed to get privileged prompt (#)\n");
return 10;
}

// 2) identifier le modèle via "show version"
writestr_tx(fd, "show version\r\n");
// ... readline_useful() ignore les prompts '>', '#', '(config)#' ...
// ... détection "ISR4221" / "C9200" / "Aironet" ...

// 3) write erase (puis lectures)
writestr_tx(fd, "write erase\r\n");
(void)readline_useful(fd, line, sizeof(line));

// 4) maintien reset (simulé par sleep)
fprintf(stderr, "[ACT] Holding reset (sim) for %d ms...\n", p->press_ms);
msleep(p->press_ms);

// 5) reload (2 lectures)
writestr_tx(fd, "reload\r\n");
(void)readline_useful(fd, line, sizeof(line));
(void)readline_useful(fd, line, sizeof(line));
Filtrage de lignes “parasites” (prompts et lignes vides)
/\* Dans readline_useful():

* ignore "" (vide),
* ignore ">" et "#",
* ignore "(config)#", "(config-if)#",
* si "> ..." ou "# ..." : on retire le prompt et on garde le contenu. \*/
Exemple de trace (attendu)
[RX] 
[RX] Cisco IOS XE Software, Version 17.3.1
[RX] Device: ISR4221
[RX] Type 'help' for cmds
[RX] >
[TX] enable\r\n
[PROMPT] # (privileged)
[TX] show version\r\n
[RX] Cisco ISR4221 Software
[RX] Model: ISR4221 router
[INFO] Detected device: ISR4221 -> profile press=8000ms
[TX] write erase\r\n
[RX] Erasing NVRAM...done
[ACT] Holding reset (sim) for 8000 ms...
[TX] reload\r\n
[RX] Proceed with reload? [confirm]
[RX] Reloading...
[OK] Reset sequence completed. Post-wait 3000 ms

Vous pouvez voir le resultat du test de fonctionnement dans l'image ci-dessous.

Image résultat

Programme préliminaire de remise à zéro

Joindre l'archive du programme C (avec Makefile).

Programme embarqué LUFA

Cette section décrit brièvement le rôle de chaque fichier du firmware et la façon dont ils interagissent pour piloter la remise à zéro Cisco (série UART) et l’interface utilisateur (boutons + LEDs).

Arborescence

firmware/ 
  ├─ Makefile 
  └─ src/ 
    ├─ board.h 
    ├─ uart.h 
    ├─ uart.c 
    ├─ ios_seq.h 
    ├─ ios_seq.c 
    └─ main.c

board.h — Abstraction carte (LEDs & boutons)

Définit le mappage matériel :

  • LED rouge = PB4 (échec)
  • LED verte = PB5 (succès)
  • LED jaune = PB6 (activité)
  • B1 = PF0, B2 = PF1 (actifs bas avec pull-up)

Fournit des fonctions inline :

  • leds_init() : configure les GPIO des LEDs en sorties et les éteint.
  • led_r_on/off(), led_g_on/off(), led_y_on/off/tgl() : contrôle simple des LEDs.
  • buttons_init() : met PF0/PF1 en entrées avec résistances pull-up.
  • b1_raw(), b2_raw() : retourne 1 si le bouton est appuyé.

Contient la constante UART_BAUD = 9600 utilisée par l’UART.

uart.h / uart.c — Lien série UART (vers l’équipement Cisco/simulateur)

Initialisation et E/S UART1 de l’AT90USB1287 :

  • TXD1 = PD3 (sortie), RXD1 = PD2 (entrée).
  • Mode 8N1 (8 bits, sans parité, 1 stop).
  • U2X1 activé (double vitesse) pour réduire l’erreur de baud (robuste à 8/16 MHz).

Fonctions principales :

  • uart_init(F_CPU, baud) : calcule UBRR avec U2X1, configure PD2/PD3.
  • uart_putc(), uart_write() : émission caractère/chaîne.
  • uart_rx_ready(), uart_getc() : réception non bloquante/bloquante.
  • uart_flush_rx() : vide le tampon RX (utile avant enable).

Remarque : le paramètre F_CPU (8 MHz ou 16 MHz) est passé via le Makefile.

ios_seq.h / ios_seq.c — Séquences IOS (logique Cisco)

Regroupe l’algorithme haut-niveau d’échange avec l’IOS Cisco :

  • Passage en mode admin : ios_enter_enable() envoie enable et attend le prompt # (détection même sans \n).
  • Détection du modèle : ios_detect_model() envoie show version et repère ISR4221, C9200, Aironet1600 (retourne un dev_t).
  • Effacement config : ios_erase_startup_config() tente erase startup-config, avec repli sur write erase selon la réponse.
  • Suppléments par modèle :
    • C9200 → ios_delete_vlan_dat() (delete flash:vlan.dat).
    • Aironet → ios_clear_bvi1_ip() (conf t / int BVI1 / no ip address).
  • Redémarrage : ios_reload() gère le [confirm] puis attend Reloading…/OK.

Robustesse du parsing :

  • ios_read_useful() ignore les lignes vides, les prompts >, #, et les prompts de configuration (config)# / (config-if)#.
  • Détection de #/ > même s’ils arrivent sans fin de ligne.
  • Timeouts paramétrés par l’appelant (en millisecondes).

Indicateur utilisateur : certaines fonctions font clignoter la LED jaune pendant l’attente (visuel d’activité).

main.c — Orchestrateur (boutons → séquences → LEDs)

Initialise le hardware : leds_init(), buttons_init(), uart_init(), sei().

Anti-rebond compact sur B1/B2.

Règles d’usage :

  • B1 = séquence complète de remise à zéro :
enable → show version (détecte modèle) → erase startup-config (ou write erase)
[C9200] delete vlan.dat / [Aironet] no ip address sur BVI1
reload
  • B2 = raccourci redémarrage : enable → reload.

Feedback LEDs :

  • Jaune : clignote pendant les envois/attentes.
  • Verte : succès de la séquence.
  • Rouge : échec (timeout/réponse inattendue).

Les timeouts sont choisis pour être réactifs (peuvent être ajustés selon l’équipement).

Makefile

Définit la cible AVR : MCU=at90usb1287, F_CPU=8 MHz (ou 16 MHz), optimisation -Os.

Construit :

  • firmware.elf (link),
  • firmware.hex (image Intel HEX pour flash).

Commandes usuelles :

  • make : compile et produit firmware.hex.
  • make clean : supprime les artefacts.

Interaction globale

L’utilisateur appuie sur B1 (ou B2). Le main lance la séquence correspondante, fait clignoter la LED jaune et délègue à ios_seq.c l’échange série. Les fonctions ios_seq utilisent uart.c pour envoyer/recevoir et pour filtrer les prompts/réponses. En fin de séquence :

succès → LED verte ON (jaune/rouge OFF),

échec → LED rouge ON (jaune/verte OFF).

Test de la carte avec firmware

Mise en mode DFU

Pour flasher le microcontrôleur AT90USB1287 intégré à ma carte, j’utilise le bootloader matériel DFU (Device Firmware Upgrade). Pour passer en mode DFU :

  • Maintenir le bouton HWB (connecté à la broche PE2),
  • Appuyer brièvement sur le bouton Reset puis le relâcher,
  • Relâcher enfin le bouton HWB.

À ce moment, la commande lsusb montre l’identifiant USB 03eb:2ffb Atmel Corp. at90usb AVR DFU bootloader, comme vous pouvez le voir ci-dessous.

lsusb effectué avant et après le passsage en mode DFU

Flashage du firmware

Une fois en mode DFU, j’utilise dfu-programmer :

# Compilation
make clean && make

# Flashage du microcontrôleur
sudo dfu-programmer at90usb1287 erase
sudo dfu-programmer at90usb1287 flash firmware.hex
sudo dfu-programmer at90usb1287 reset

Fonctionnement attendu

  • Après un appui sur B1 :
    • La LED jaune clignote pendant l’envoi des commandes Cisco (simulateur ou vrai équipement),
    • La LED verte s’allume si la séquence s’est bien déroulée (enable → erase startup-config → commandes spécifiques → reload),
    • La LED rouge s’allume en cas d’échec (timeout, absence de réponse, erreur).
  • Après un appui sur B2 :
    • La LED jaune clignote,
    • La séquence envoie uniquement enable puis reload,
    • La LED verte s’allume si la commande a été acceptée, sinon la rouge.

Problèmes rencontrés

Comme vous pouvez le voir dans la video ci-dessous le test se résulte sur un échec car la LED finale allumée est la LED rouge PB4 (les LED étant toutes rouge cela peut être un peu confus) et le moniteur série de l'Arduino ne reçoit aucunes des commandes envoyées par ma carte. Je pense qu'il s'agit d'un problème lié à la façon dont je connecte mon Arduino et ma carte (liaison des pins RX et TX de l'arduino avec ceux de ma carte et mise en commun des GND avec des fils Dupont). La communication série étant prévue pour s'effectuer avec les differents ports de la carte (USB, RJ45), cette connexion avec les fils Dupont pourrait être à l'origine des problèmes de communication entre ma carte et l'Arduino. Je continue d'effectuer des tests dans l'espoir de régler ce problème.


Vidéos de démonstration

Passage de Serial (D0/D1) à SoftwareSerial (D8/D9) pour recevoir des données

Contexte initial

Je ne recevais aucun octet sur l’Arduino lorsque ma carte AT90USB1287 envoyait ses commandes.

La cause probable : le port série matériel de l’Arduino (D0/D1) était déjà utilisé par le Moniteur Série USB (conflit PC ⇄ Arduino ⇄ Carte).

Objectif

Séparer la console USB (PC) et la liaison avec ma carte pour éviter les conflits.

Conserver l’affichage sur le Moniteur Série tout en parlant en série avec la carte.

Modification réalisée

Mise en place d’un second port série avec SoftwareSerial sur des broches libres :

SoftwareSerial serialCarte(8, 9); → RX = D8, TX = D9.

Serial reste l’USB (PC) pour observer les échanges.

Double lecture et double tampon :

line ← accumulation depuis Serial (PC),

lineCarte ← accumulation depuis serialCarte (carte),

même fonction de parsing/commande pour les deux flux (factorisation).

Câblage associé

D8 (Arduino) ← TX de ma carte (PD3).

D9 (Arduino) → RX de ma carte (PD2).

GND commun entre Arduino et la carte.

Résultat après modification

Avant : aucune réception (silence complet).

Après : réception présente sur D8/D9 (trames visibles), mais mal interprétées → affichage de ‘?’ (caractères non reconnus).

Code Arduino

#include <SoftwareSerial.h>

#define DEVICE "ISR4221"   // "ISR4221", "Aironet1600", "C9200"

// Définition du port série logiciel pour la deuxième carte
// RX=8, TX=9 (à adapter selon ton câblage)
SoftwareSerial serialCarte(8, 9);

bool enabled = false;
bool inConfig = false;
bool inIf = false;
String line = "";
String lineCarte = "";

void setup() {
  Serial.begin(9600);         // PC (USB)
  serialCarte.begin(9600);    // Deuxième carte
  delay(200);

  // Message de boot envoyé aux deux sorties
  broadcast("");
  broadcast("Cisco IOS XE Software, Version 17.3.1");
  broadcast(String("Device: ") + DEVICE);
  broadcast("Type 'help' for cmds");
  prompt();
}

void loop() {
  // --- Lecture depuis le PC ---
  while (Serial.available()) {
    char c = Serial.read();
    handleInput(c, line);   // traite le flux du PC
  }

  // --- Lecture depuis la carte ---
  while (serialCarte.available()) {
    char c = serialCarte.read();
    handleInput(c, lineCarte); // traite le flux de la carte
  }
}

void handleInput(char c, String &buffer) {
  if (c == '\r' || c == '\n') {
    if (buffer.length() > 0) {
      handleCommand(buffer);
      buffer = "";
    }
  } else {
    buffer += c;
  }
}

void broadcast(String msg) {
  Serial.println(msg);
  serialCarte.println(msg);
}

void broadcastPrompt(String msg) {
  Serial.print(msg);
  serialCarte.print(msg);
}

void prompt() {
  if (inIf)           broadcastPrompt("(config-if)# ");
  else if (inConfig)  broadcastPrompt("(config)# ");
  else if (enabled)   broadcastPrompt("# ");
  else                broadcastPrompt("> ");
}

void handleCommand(String cmd) {
  cmd.trim();

  if (cmd == "help") {
    broadcast("cmds: help, enable, show version, write erase, erase nvram, erase startup-config,");
    broadcast("      reload, reset, configure terminal, conf t, interface BVI1, no ip address, end,");
    broadcast("      delete flash:vlan.dat");
  }

  else if (cmd == "enable") {
    enabled = true; inConfig = false; inIf = false;
  }

  else if (cmd == "show version") {
    broadcast(String("Cisco ") + DEVICE + " Software");
    if (String(DEVICE) == "C9200") {
      broadcast("Model: C9200 switch");
    } else if (String(DEVICE) == "Aironet1600") {
      broadcast("Model: Aironet 1600 Series");
      broadcast("Interface BVI1 present");
    } else if (String(DEVICE) == "ISR4221") {
      broadcast("Model: ISR4221 router");
    }
  }

  else if (cmd == "write erase" || cmd == "erase nvram") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { broadcast("Erasing NVRAM...done"); }
  }

  else if (cmd == "erase startup-config") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { broadcast("Erase of nvram: complete"); }
  }

  else if (cmd == "reload" || cmd == "reset") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else {
      broadcast("Proceed with reload? [confirm]");
      broadcast("Reloading...");
      broadcast("OK");
    }
  }

  else if (cmd == "configure terminal" || cmd == "conf t") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { inConfig = true; inIf = false; }
  }

  else if (cmd == "interface BVI1") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { inConfig = true; inIf = true; }
  }

  else if (cmd == "no ip address") {
    if (!enabled) { broadcast("% Privileged mode required"); }
  }

  else if (cmd == "end") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else { inConfig = false; inIf = false; }
  }

  else if (cmd == "delete flash:vlan.dat") {
    if (!enabled) { broadcast("% Privileged mode required"); }
    else {
      broadcast("Delete filename [vlan.dat]?");
      broadcast("Delete flash:/vlan.dat? [confirm]");
      broadcast("File deleted");
    }
  }

  else {
    broadcast("?");
  }

  prompt();
}

Pourquoi ce changement est important

Il supprime la concurrence entre le port USB (Moniteur Série) et la liaison vers la carte.

Il permet de voir dans le Moniteur Série ce que reçoit et renvoie le simulateur, tout en laissant la carte dialoguer sur une autre UART (logicielle).

État actuel & prochaines étapes

État actuel : la réception existe (progrès) mais les données sont mal décodées (apparition de ?), comme vous pouvez le voir ci-dessous :

j'ai également écrit un programme LUFA que vous pouvez trouvez dans le dépôt Git du projet : https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO

Programme Arduino Uno

19/08

Simulation "Cisco" sur Arduino

Objectif

Valider les échanges de commandes de base (show version, write erase, reload) via un périphérique simulé en attendant la reception la carte finale. Ici, l' Arduino Uno joue le rôle d’un équipement Cisco simplifié accessible via le Moniteur Série de l’IDE Arduino.

Contexte / État d’avancement

  • La simulation PC↔PC (socat + programmes C) fonctionne.
  • Simulation Arduino seul via l’IDE et le Moniteur Série est opérationnelle.

Matériel & Outils

  • Arduino Uno + câble USB .
  • IDE Arduino.
  • Vitesse série : 9600 bauds, format 8N1 (par défaut).


Sketch Arduino

// sim_ios_arduino.ino — Arduino joue le rôle d’un "Cisco simplifié"

#define DEVICE "ISR4221"  // ou "Aironet1600" ou "C9200"

void setup() {
  Serial.begin(9600);
  delay(300);  // petite pause pour la connexion USB
  Serial.println();
  Serial.println("Cisco IOS XE Software, Version 17.3.1");
  Serial.print("Device: ");
  Serial.println(DEVICE);
  Serial.println("Type 'help' for cmds");
  Serial.print("> ");
}

String line = "";

void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r' || c == '\n') {
      if (line.length() > 0) {
        handleCommand(line);
        line = "";
      }
    } else {
      line += c;
    }
  }
}

void handleCommand(String cmd) {
  cmd.trim();

  if (cmd == "help") {
    Serial.println("cmds: help, ID?, show version, write erase, erase nvram, reload, reset");
  } else if (cmd == "ID?") {
    Serial.println(DEVICE);
  } else if (cmd == "show version") {
    Serial.print("Cisco ");
    Serial.print(DEVICE);
    Serial.println(" Software");
  } else if (cmd == "write erase" || cmd == "erase nvram") {
    Serial.println("Erasing NVRAM...done");
  } else if (cmd == "reload" || cmd == "reset") {
    Serial.println("Reloading...");
    delay(50);
    Serial.println("OK");
  } else {
    Serial.println("?");
  }

  Serial.print("> "); // réaffiche le prompt
}

Exemple de session (Moniteur Série, 9600 bauds)

Cisco IOS XE Software, Version 17.3.1
Device: ISR4221
Type 'help' for cmds
> help
cmds: help, ID?, show version, write erase, erase nvram, reload, reset
> show version
Cisco ISR4221 Software
> write erase
Erasing NVRAM...done
> reload
Reloading...
OK
>

Remarques

  • Commandes réelles IOS : show version, write erase/erase startup-config, reload.
  • Commande utilitaire projet : ID? (n’existe pas dans IOS, ajoutée pour simplifier l’identification).

Vous pouvez voir ci dessous les résultats des tests avec l'arduino, les commandes sont tapées (dans l'ordre help, show version, write erase, erase nvram, reload) dans le moniteur série et l'arduino simule un équipement cisco:

test Arduino

Le code Arduino est présent dans l'archive git: https://gitea.plil.fr/azongo/EC_reset_cisco_ZONGO

Vous pouvez voir ci-dessous la nouvelle version du code arduino permettant la simulation d'équipement Cisco et prenant en compte la commande enable et le passage en mode administrateur :

Sketch Arduino

// sim_cisco.ino — Arduino simule un équipement Cisco ISR4221 @ 9600 bds

#define BAUD 9600

String line = "";
bool privileged = false; // false => "Router>", true => "Router#"

void printPrompt() { Serial.print(privileged ? "Router#" : "Router>"); }

void setup() {
  Serial.begin(BAUD);
  delay(300);
  printPrompt(); // prompt initial
}

void handleCommand(String cmd) {
  cmd.trim();
  if (cmd.length() == 0) { printPrompt(); return; }         // important pour DetectPrompt()

  if (cmd.equalsIgnoreCase("enable")) {
    privileged = true; Serial.print("\r\nRouter#");
  } else if (cmd.equalsIgnoreCase("terminal length 0")) {
    Serial.print("\r\n"); printPrompt();
  } else if (cmd.equalsIgnoreCase("show version")) {
    Serial.print("\r\nCisco IOS XE Software, ISR4221\r\n");
    Serial.print("Router uptime is 1 day\r\n");
    Serial.print("ISR4221 Router (revision 1.0)\r\n");
    printPrompt();
  } else if (cmd.startsWith("erase startup-config") || cmd.startsWith("write erase")) {
    Serial.print("\r\n[confirm]\r\n"); printPrompt();
  } else if (cmd.equalsIgnoreCase("reload")) {
    Serial.print("\r\nProceed with reload? [confirm]\r\n");
    // LUFA enverra juste un CR ensuite
  } else if (cmd.equalsIgnoreCase("configure terminal")) {
    Serial.print("\r\n(config)#");
  } else if (cmd.equalsIgnoreCase("interface BVI1")) {
    Serial.print("\r\n(config-if)#");
  } else if (cmd.equalsIgnoreCase("no ip address") || cmd.equalsIgnoreCase("no ip address dhcp")) {
    Serial.print("\r\n(config-if)#");
  } else if (cmd.equalsIgnoreCase("exit")) {
    Serial.print("\r\n(config)#");
  } else if (cmd.equalsIgnoreCase("end")) {
    Serial.print("\r\n"); printPrompt();
  } else {
    Serial.print("\r\n"); printPrompt();
  }
}

void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r' || c == '\n') { handleCommand(line); line = ""; }
    else { line += c; }
  }
}

Vidéos de démonstration

Après réception de la carte et afin de m'assurer de son bon fonctionnement, j'ai programmé cette dernière pour qu'une des LEDs programmables clignote une fois que le Bouton B1 est appuyé. Vous pouvez voir le résultat de cette programmation ci-dessous.