IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

Optimisation librairie Ethernet


Sujet :

Arduino

  1. #1
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 002
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 002
    Par défaut Optimisation librairie Ethernet
    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 :

    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);
    }
    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
    // 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
    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.

    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 :

    Nom : MoodeControlHTML.jpg
Affichages : 507
Taille : 95,2 Ko

    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'Arduino mais 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 :

    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.
    }
    Certaines fonctions sont vides car elles ne sont pas finies :

    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
    }
    A bientôt

  2. #2
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 921
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 921
    Par défaut
    on dirait que la communauté va bénéficier d'une nouvelle bibliothèque Ethernet un peu plus costaude

  3. #3
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 002
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 002
    Par défaut
    Citation Envoyé par Jay M Voir le message
    on dirait que la communauté va bénéficier d'une nouvelle bibliothèque Ethernet un peu plus costaude
    Merci, mais je dois préciser que j'ai optimisé cette bibliothèque pour Arduino UNO et le Shield Ethernet II (celui avec le W5500)

    Elle sera donc limité à ce matériel, néanmoins certaines modifs pourront être reprises dans la bibliothèque "générique" ; il faudra que je documente bien mes modifications

    Pour aller plus loin j'ai tenté de lire la datasheet du W5500 mais elle est très "bas niveau" c'est ardu...
    L'écriture de la bibliothèque a certainement demandé énormément de travail, c'est pour ça qu'on ne trouve pas de fork
    Mon optimisation reste du reverse engineering avec plusieurs de zones d'ombres, malheureusement.

    PS : merci pour l'astuce "[ CODE=HTML ]" je cherchais comment le faire sans m'en souvenir - Ca serait bien si cette fonction de choix du langage était accessible avec les icônes d'édition du forum

  4. #4
    Membre chevronné Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    1 002
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 1 002
    Par défaut
    Bonjour,

    Voici quatre autres remarques :

    1) Ce petit morceau de HTML dans le HEADER est important, car sinon le navigateur fait toujours une deuxième requête au serveur pour obtenir une icône (la aussi, bug très chiant qu'on comprend uniquement en épluchant les échanges avec Wireshark)

    Code HTML : Sélectionner tout - Visualiser dans une fenêtre à part
    <link rel="icon" href="data:;base64,iVBORw0KGgo=">

    2) Il n'y a pas UNE mais DEUX fonctions EthernetClient::connect() à modifier en remplaçant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Ethernet.socketDisconnect(sockindex);
    par

    Celle que j'ai montrée est la fonction connect() qui utilise une URL (l'adresse IP du serveur est obtenue avec DNS)

    Mais il y a une autre fonction connect() qui utilise directement une adresse IP ; c'est cette fonction là qu'on utilise quand le serveur est un object connecté sur le réseau local (un autre Arduino ou un Raspberry Pi)

    3) Wireshark peut montrer un packet unique alors qu'il est fragmenté

    Ou alors, quand j'utilise Telnet avec mon PC, le Raspberry Pi envoi des paquets plus gros que quand j'utilise Telnet avec mon Arduino

    Là aussi, c'est extrêmement difficile à comprendre...

    La solution, c'est de prévoir dans le code Arduino des fonctions de débogage, typiquement afficher dans les résultats la valeur retournée par

    4) Pour recevoir plusieurs fragments, il faut s'y prendre de la façon suivante :
    - "vider" le fragment reçu en lisant tous les caractères
    - ensuite, attendre de nouveau que des caractères soient disponibles, avec un timeout

    Il semblerait que le W5500 envoi une commande au serveur lorsque tous les caractères sont lus, ce qui permet au serveur d'envoyer le fragment éventuel suivant.

    Cela est très surprenant, car à priori le W5500 ne gère pas la fragmentation de packets

    C'est peut être lié au fonctionnement du Raspberry Pi, qui envoi sagement les fragments dans l'ordre, en attendant que le fragment envoyé soit lu avant d'envoyer le suivant.

    Voyez le code de la fonction Example_Application_MoodeAudio_ExtractData_Close() ci-dessous :

    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
    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
    byte Example_Application_MoodeAudio_OpenAndSendCmd(int len) {
    	byte website_ip[] = {192,168,123,28}; // Moode Audio IP
    	byte err = client2.connect(website_ip, 6600);
    	if (err==CLIENT_CONNECT_OK) client2.write(Buffer_HTTP, len); // WARNING - connect() FUNCTION IS CHANGED TO RETURN CUSTOM ERROR CODES
    	return err;
    }
    void Example_Application_MoodeAudio_Close() { 
    	client2.stop();
    }
    #define MOODE_TIMEOUT 1000
    int Example_Application_MoodeAudio_GetDataLen() {
    	int len=0;
    	unsigned long t = millis();
    	while(len==0){ 
    		len = client2.available();
    		if (millis() -t > MOODE_TIMEOUT) break; 
    	}
    	return len;
    }
    int Example_Application_MoodeAudio_PrepareToRead() { 
    	int len;
    	int i;
    	byte k;
    	for (k=0;k<2;k++) {
    		len = Example_Application_MoodeAudio_GetDataLen();
    		if ((len>0)&&(k==0)) {
    			for (i=0;i<len;i++) {
    				client2.read(); // We don't need the first fragment data, it just contains "OK MPD 0.22.0"
    			}
    		} else {
    			return len;
    		}
    	}
    }
    void Example_Application_MoodeAudio_ExtractStatus_Close() {
    	// Moode audio return several text lines :
    	int len;
    	char c;
    	byte k = 0;
    	byte j = 0;
    	int SongID = 0;
    	int PlaylistLen = 0;
    	len = Example_Application_MoodeAudio_PrepareToRead();
    	#define Max_Char_For_Int 5
    	while (len>0) {
    		c = client2.read();
    		len--;
    		if (c==10) {
    			k=0;
    			j++;
    		} else {
    			if (j==7) { // Line 7 is "playlistlength: 1044"
    				if ((k>15)&&(k<15+Max_Char_For_Int)) {
    					PlaylistLen = PlaylistLen * 10 + c-48;
    				}
    				k++;
    			}
    			if (j==11) { // Line 3 is "songid: 513"
    				if ((k>7)&&(k<7+Max_Char_For_Int)) {
    					SongID = SongID * 10 + c-48;
    				}
    				k++;
    			}
    		}
    	}
    	Example_Application_MoodeAudio_Close();
    	MoodeSongID = SongID;
    	MoodePlaylistLen = PlaylistLen;
    }
    void Example_Application_MoodeAudio_ExtractData_Close() {
    	char c;
    	byte k;
    	byte t = 0;
    	byte j = 0;
    	byte s = 0;
    	int len = Example_Application_MoodeAudio_PrepareToRead();
    	bool other_fragment = len == 1024; // ONLY OK FOR MOODE AUDIO 
    	while (len>0) {
    		c = client2.read();
    		len--;
    		if (c==10) { // Line feed
    			k=0;
    			if (j<11) { 
    				j++;
    			} else { // Song data takes 11 lines - We reached the next song:
    				j = 0;
    				t = 0;
    				s ++;
    			}
    		} else {
    			if (j==3) { // Line 3 is "title: Like my fire"
    				if (k>6) { 
    					if ((s < MOODE_LIST_SONG_NB)&&(t < MOODE_SONG_TITLE_LEN)) {
    						Buffer_HTTP[Buffer_HTTP_Len - (MOODE_LIST_SONG_NB - s)*MOODE_SONG_TITLE_LEN + t] = c;
    					}
    					t++;
    				}
    				k++;
    			}
    		}
    		// Moode Audio Telnet MPD cuts data into 1024 bytes fragments
    		// W5500 has 2048 bytes buffer
    		// But we ask MOODE_LIST_SONG_NB songs data it's about 4ko
    		// that is to say, 3 or 4 1024 bytes fragments
    		if ((len==0)&&(other_fragment)) {
    			len = Example_Application_MoodeAudio_GetDataLen();
    			other_fragment = len == 1024; // ONLY OK FOR MOODE AUDIO
    		}
    	}
    	Example_Application_MoodeAudio_Close();
    }

Discussions similaires

  1. Réponses: 4
    Dernier message: 10/08/2010, 21h20
  2. Librairie d'optimisation pour VB.net
    Par Faladin dans le forum Débuter
    Réponses: 6
    Dernier message: 12/05/2008, 20h23
  3. Optimisation de fft sous la librairie FFTW
    Par Mer.cury dans le forum Bibliothèques
    Réponses: 1
    Dernier message: 20/09/2007, 09h03
  4. [Avancé][Optimisation] Charger des librairies dynamiquement
    Par Wookai dans le forum Général Java
    Réponses: 12
    Dernier message: 12/08/2005, 16h34

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo