Bonjour,
Je continue de bosser avec la libraire Ethernet...
L'utilisant de façon intensive, je trouve pas mal d'améliorations
Mon serveur doit :
- gérer plusieurs clients
- contacter un serveur pour lire l'heure UTC
- contacter un serveur Telnet sur Raspberry Pi pour piloter un démon MPD avec une distribution Linux Moode Audio (https://www.musicpd.org/doc/html/protocol.html)
C'est ce dernier point qui me donne du fil à retordre.
Le Raspberry Pi est capricieux.
Parfois il ne répond pas et la connection échoue (timeout).
Certaines commandes du démon MPD renvoient beaucoup de données, découpées en plusieurs packets de 1024 octets.
Le démon MPD ferme brutalement la connexion Telnet après la plupart des commandes. Dans pas mal de cas il faut donc enchainer deux connections l'une à la suite (typiquement, une première pour récupérer le status avec le numéro de la chanson en cours, puis une deuxième pour demander le titre des chansons voisines)
Les buffers du W5500 sont utiles, car mon code Arduino lit les données octet par octet, jusqu'à trouver la donnée intéressante.
Il serait stupide de tout copier dans la RAM de l'Arduino.
Du coup le W5500 est mis à rude épreuve et j'arrive à avoir une erreur "bloquante" : le W5500 n'arrive pas à se connecter à un serveur car tous les sockets sont occupés.
Très étrange car mon Arduino en tant que serveur lui continue à bien fonctionner, mais quand il veut contacter un autre serveur en tant que client ça ne marche plus.
Le W5500 n'a rien à voir là dedans, c'est la bibliothèque qui est mal faite !
Première modif : la fonction connect de EthernetClient :
Deuxième modif : utiliser les 8 sockets du W5500 et pas seulement 4 :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 byte EthernetClient::connect(const char * host, uint16_t port) { DNSClient dns; // Look up the host first IPAddress remote_addr; if (sockindex < MAX_SOCK_NUM) { if (Ethernet.socketStatus(sockindex) != SnSR::CLOSED) { //Ethernet.socketDisconnect(sockindex); // TODO: should we call stop()? stop(); // Bah oui il faut appeller stop() } sockindex = MAX_SOCK_NUM; } dns.begin(Ethernet.dnsServerIP); if (!dns.getHostByName(host, remote_addr)) return CLIENT_CONNECT_ERROR_DNS; // TODO: use _timeout return connect(remote_addr, port); }
Avec cette dernière modif, je n'arrive plus à planter mon W5500 même en faisant exprès d’enchaîner les demandes d'information au démon MPC du Raspberry Pi.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 // Configure the maximum number of sockets to support. W5100 chips can have up to 4 sockets. W5200 & W5500 can have up to 8 sockets. Several bytes // of RAM are used for each socket. Reducing the maximum can save RAM, but you are limited to fewer simultaneous connections. => STUPID !!! //#if defined(RAMEND) && defined(RAMSTART) && ((RAMEND - RAMSTART) <= 2048) //#define MAX_SOCK_NUM 4 //#else #define MAX_SOCK_NUM 8 // On Arduino UNO, use 8 sockets instead of 4 sockets cost only 6 byte of flash and 36 bytes of RAM !!! //#endif // By default, each socket uses 2K buffers inside the Wiznet chip. If MAX_SOCK_NUM is set to fewer than the chip's maximum, uncommenting // this will use larger buffers within the Wiznet chip. Large buffers can really help with UDP protocols like Artnet. In theory larger // buffers should allow faster TCP over high-latency links, but this does not always seem to work in practice (maybe Wiznet bugs?) //#define ETHERNET_LARGE_BUFFERS // ETHERNET_LARGE_BUFFERS is USELESS // In pratice, 2Ko buffer is OK because with IPv4, received packets can't be higher than 1500 bytes - indeed, Telnet client send 1024 bytes packets // And also, 2ko is small for a HTML webpage, but you can send several packets to client
Très concrètement, mon serveur Arduino en tant que serveur génère une page HTML avec un formulaire qui sert à piloter le démon MPD :
La page HTML utilise à la fois des contrôles de formulaire et des liens, voici son code :
Code HTML : Sélectionner tout - Visualiser dans une fenêtre à part
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 <!DOCTYPE html><HTML> <HEAD><meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <link rel="icon" href="data:;base64,iVBORw0KGgo="> <title>Moode Audio</title> <style>td {text-align: center;}</style></HEAD> <BODY> OK 1044 songs<BR> 621 <a href="?L9=621">Moderno - Sweet Harmony </a><BR> 622 <a href="?L9=622">Moderno - Waves of Light (Exte</a><BR> 623 <a href="?L9=623">Modo - With Or Without You </a><BR> 624 <a href="?L9=624">Momento - Don't Look Back (Ext</a><BR> 625 <a href="?L9=625">Momento - I Used To Be (Extend</a><BR> 626 <b>Monika Novak - I want you back</b><BR> 627 <a href="?L9=627">Monika Novak - Lane of my life</a><BR> 628 <a href="?L9=628">Monika Novak - Living On The R</a><BR> 629 <a href="?L9=629">Monika Novak - Lovely Witch </a><BR> 630 <a href="?L9=630">Mr Zivago - Russian Paradise (</a><BR> 631 <a href="?L9=631">Mr. Fusion - New Italo Disco(2</a><BR> <BR> <form method="GET"><TABLE> <TR><TD><button name="L0" value="1" type="submit">-100</button></TD> <TD><button name="L1" value="1" type="submit">-10</button></TD> <TD><button name="L2" value="1" type="submit">+10</button></TD> <TD><button name="L3" value="1" type="submit">+100</button></TD></TR> <TR><TD><button name="F0" value="1" type="submit">Prev</button></TD> <TD><button name="F1" value="1" type="submit">Next</button></TD> <TD><input type="text" name="L8" size="4" maxlength="4" value="626"></TD> <TD><button name="F2" value="1" type="submit">Goto</button></TD></TR> <TR><TD><button name="F9" value="1" type="submit">Playing</button></TD> <TD><button name="F3" value="1" type="submit">Stop</button></TD> <TD><button name="F4" value="1" type="submit">Pause</button></TD> <TD><button name="F5" value="1" type="submit">Play</button></TD></TR> </TABLE></form><BR><a href="?A9=1">Main menu</a><BR> </BODY></HTML>
A chaque action de l'utilisateur, l'Arduino doit :
- en tant que serveur, analyser la requête du client HTML
- en tant que client, se connecter au Raspberry PI pour demander le status au démon MPD
- en tant que client, analyser la réponse du démon MPD qui est dans deux packets
- en tant que client, se connecter une deuxième fois au Raspberry PI pour demander la liste des 11 chansons à afficher
- en tant que client, analyser la réponse du démon MPD qui est dans 4 ou 5 packets de 1024 octets
- en tant que serveur, construire et envoyer une nouvelle page web en fonction des données reçues du client MPD
Bref je ne fais pas semblant![]()
Lorsque le Raspberry Pi accepte de répondre, l'ensemble des opérations que je viens de décrire (à partir du clic de souris dans le navigateur jusqu'à l'affichage de la nouvelle page web) prend moins d'une demi-seconde.
La majeure partie de cette demi seconde est passé à attendre que le démon MPD du Raspberry Pi se connecte et envoie les données qu'on lui demande.
J'ai la confirmation que les erreurs de "timeout" qui surviennent de temps en temps ne sont pas de la faute de l'Arduinomais du Raspberry Pi
En effet, lorsque je me connecte "manuellement" au démon MPD avec telnet sur mon ordinateur, je constate que de temps en temps la connection échoue, car le Raspberry Pi ne répond pas.
Bref, si le Raspberry Pi est bien plus puissant que l'Arduino, c'est un ordinateur.
Comme tout ordinateur, il a du mal à gérer les choses rapidement en toutes circonstances.
L'arduino c'est un microcontrôleur, on peut faire du temps réel.
Le Raspberry Pi est un modèle 3B+, avec HifiBerry Hat, sans écran ni clavier, juste connexion au réseau local en Wifi
La distribution est Moode Audio : https://moodeaudio.org/
Ca marcherai peut être mieux si le Raspberry Pi était connecté en Ethernet et pas en Wifi
Alors pourquoi ça ne plante plus ?
Première explication : le bug est toujours là mais avec 8 buffers au lieu de 4 il est beaucoup rare
Deuxième explication :
- le W5500 ne sait pas qu'on utilise seulement 4 de ses 8 buffers, donc lorsque les 4 utilisés sont occupés, pour lui tout va bien car il en reste encore 4 de libre...
- Le W5500 n'a aucun intérêt à libérer des sockets alors qu'ils ne sont pas tous utilisés.
- Lorsqu'on va lire le statut du W5500 il ne retournera aucune erreur par rapport à ça.
- Sauf que la bibliothèque de son côté va considérer que tout est bloqué
Dans cette bibliothèque il reste encore pas mal de "TODO" et de "FIXME" et c'est emmerdant car les commentaires ne sont pas très explicites
Certains commentaires montre que l'auteur de la bibliothèque ne comprend pas parfois ce que fait ou pas le W5500 :
Certaines fonctions sont vides car elles ne sont pas finies :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 int EthernetClient::available() { if (sockindex >= MAX_SOCK_NUM) return 0; return Ethernet.socketRecvAvailable(sockindex); // TODO: do the Wiznet chips automatically retransmit TCP ACK packets if they are lost by the network ? Someday this should // be checked by a man-in-the-middle test which discards certain packets. If ACKs aren't resent, we would need to check for // returning 0 here and after a timeout do another Sock_RECV command to cause the Wiznet chip to resend the ACK packet. }
A bientôt
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 void EthernetUDP::flush() { // TODO: we should wait for TX buffer to be emptied }
Partager