View  Edit  Attributes  History  Attach  Print  Search

ACCUEIL | ARDUINO > S'INSTALLER > DEBUTER > APPROFONDIR | PROCESSING | MECATRONIQUE | MATERIEL | OUTILS | TESTS | Plus...|
Python > Shell > ATELIERS Python + Qt > PyQt apps > PyQt+Arduino | Mes Robots | RepRap | Mes lib'Arduino | Mes shields Arduino | Mes distros | Les Mini-PC |
ATELIERS ARDUINO| VIDEOS | COMPRENDRE | REFERENCES | CODER | TECHNIQUE | GNU/LINUX | LIENS | Rien à voir |

Programmer le pcDuino : Pyduino : Réseau : Serveur TCP/Http/Html/Javascript fournissant un graphique Dygraphs de 6 séries de 1000 valeurs aléatoires à partir d'un tableau Numpy, avec timeline.

Par X. HINAULT - Juillet 2013

Le graphique affiché dans le navigateur client connecté au serveur Pyduino.

Ce que l'on va faire ici

  • Dans ce code, je vous montre comment mettre en place un serveur TCP/Http/Html/Javascript fournissant un graphique à l'aide de la librairie Dygraphs : le graphique affiche ici 6 séries de 1000 valeurs aléatoires directement à partir d'un tableau Numpy. Le graphique dispose d'une timeline pour zoomer/parcourir les données.
  • Un tel graphique permet de "simuler" des série de valeurs de mesures analogique ou autre.
  • L'intérêt d'utiliser un tableau Numpy est double :
    • côté code Python (serveur), il devient possible de manipuler des séries de données avec la puissance de numpy et Scipy
    • côté client, on évite le "parsing" des données fournies au format texte, les données étant ici directement envoyées au format natif utilisé par Dygraphs, à savoir [ [ , , , ] , [ , , , ] ]

Remarquer la rapidité et fluidité de la gestion de 6000 valeurs par le serveur fourni par le mini-PC ! Remarquer également comme le code est compact comparativement au résultat obtenu !

  • On utilise ici une fonction dédiée pour fournir la page HTML+ code Javascript, rendant le code plus facile à maintenir.

Note technique

  • A nouveau, un peu à la façon des "poupées gigognes" notre code est écrit en Python...
    • qui intègre lui-même la page HTML
      • qui intègre elle-même le code Javascript qui sera exécuté côté client !
        • qui lui-même va appeler du code Javascript placé dans la librairie graphique utilisée

Principe fonctionnel

Ce qui est très très intéressant avec l'utilisation d'un miniPC, c'est qu'il est possible d'utiliser sur la même carte à la fois le code Pyduino fournissant le serveur ET un serveur http installé sur le système qui va fournir la librairie Javascript. Le "truc" va consister à utiliser 2 ports différents pour accéder aux 2 depuis le navigateur client.
Du coup, le miniPC va permettre la mise en place d'un serveur graphique autonome pouvant fonctionner totalement sur une seule plateforme (le miniPC) sans besoin de serveur externe et pouvant donc être utilisé sur un réseau local !!
Ceci est tout simplement impossible à faire avec un couple Arduino + shield Ethernet sans disposer d'un serveur sur le réseau.

Liens utiles :

Pré-requis

Schéma fonctionnel

  • Le code Pyduino communique avec l'interface réseau du système qui assure la connexion avec le client distant :
  • Noter que ce code fonctionne aussi bien en éthernet (filaire) qu'en wifi (sans fil) sous réserve que la connexion matérielle soit opérationnelle.

Le réseau utilisé

  • Note : le poste fixe peut évidemment être le poste depuis lequel vous accédez au pcDuino par VNC, au moins en phase d'essai.

Matériel nécessaire

Système

  • Le pcDuino
  • connecté à un réseau local actif associant :
    • un routeur (une box internet typiquement) +/- un switch réseau
    • d'autres postes sur le réseau, notamment un poste fixe ou une tablette.
    • voire un poste distant sur le web
  • Si on utilise le wifi, on utilisera le dongle wifi ou une clé USB wifi au lieu de l'interface réseau ethernet. Mais pour un simple test, rester en filaire, c'est plus simple !

E/S

  • Pas utilisées.

Instructions de montage

  • Simplement connecter le pcDuino au réseau, ce qui est normalement déjà le cas si vous travaillez en accès VNC !

Le montage à réaliser

Le fichier *.py

Copier/coller ce code dans l'éditeur Geany :


#!/usr/bin/python
# -*- coding: utf-8 -*-

# exemple pyDuino - par X. HINAULT - www.mon-club-elec.fr
# Juillet 2013 - Tous droits réservés - GPLv3
# voir : https://github.com/sensor56/pyDuino

# Serveur TCP générant une page HTML + Javascript affichant un graphique avec la librairie dygraphs.

from pyduino import * # importe les fonctions Arduino pour Python

import numpy as np

# entete declarative

ipLocale=Ethernet.localIP() # auto - utilise l'ip de l'interface eth0 du systeme

#ipLocale='192.168.1.25' # manuel - attention : utiliser la meme IP qu'une interface reseau du systeme
# pour connaitre les interfaces reseau sur le systeme : utiliser la commande $ ifconfig

print ipLocale # affiche l'adresse IP

port=8080 # attention port doit etre au dessus de 1024 sinon permission refusee par securite - 8080 pour http

serverHTTP=EthernetServer(ipLocale, port) # crée un objet serveur utilisant le port 8080 = port HTTP > 1024

#--- setup ---
def setup():
        global serverHTTP, ipLocale, port

        #serverHTTP.begin(10) # initialise le serveur - fixe nombre max connexion voulu
        serverHTTP.begin() # initialise le serveur - nombre max connexion par defaut = 5

        print ("Serveur TCP actif avec ip : " + ipLocale + " sur port : " + str(port) )
#--- fin setup

# -- loop --
def loop():

        global serverHTTP

        print ("Attente nouvelle connexion entrante...")
        clientDistant, ipDistante = serverHTTP.clientAvailable() # attend client entrant
        # code bloque ici tant que pas client ! Si present, on recupere d'un coup objet client ET son ip

        print "Client distant connecte avec ip :"+str(ipDistante) # affiche IP du client

        #--- requete client ---
        requete=serverHTTP.readDataFrom(clientDistant) # lit les donnees en provenance client d'un coup

        print requete # affiche requete recue

        #--- reponse serveur ---
        reponse=( # ( ... ) pour permettre multiligne..
        httpResponse() # entete http OK 200 automatique fournie par la librairie Pyduino

        # contenu page - ici date / heure du serveur et mesure nalogique A2
        +

        pageHTML() # voir la fonction separee - pour clarte du code

        +"\n") # fin reponse

        serverHTTP.writeDataTo(clientDistant, reponse) # envoie donnees vers client d'un coup

        print "Reponse envoyee au client distant : "
        #print (bytes(reponse))
        print (reponse) # affiche la reponse envoyee

        #serverHTTP.close()
        # remarque : le socket = serveur doit rester ouvert

        # quand on quitte l'application: la connexion TCP reste active un peu donc erreur si re-execution trop rapide du code
        # on peut utiliser un port voisin dans ce cas...

        delay(10) # entre 2 loop()

# -- fin loop --

#--- fonction fournissant la page HTML ---
def pageHTML():

        # code Python a executer avant envoyer page

        # donnees types
        dataGraph=""

        # utiliser options plutot que premiere ligne
        #dataGraph=(
        #"""
        #"Date,Temperature\\n" +
#""")

        # donnees sous forme d'une chaine
        # format "x,y\n"

        """
        for i in range(1000):
                # format "x,y\n"
                dataGraph=dataGraph+"\""+str(i)+","+str(i)+"\\n \"+" + "\n" # le 2eme \n est dans la page HTML, le premier dans la chaine des donnees

        dataGraph=dataGraph+"\""+str(i)+","+str(i)+"\\n \"" + "\n" # derniere ligne sans le plus
        """


        # attention : le plus est dans la chaine

        # donnees sous forme d'un tableau
        # [ [1,10,100], [2,20,80], [3,50,60], [4,70,80] ]
        # dans ce cas, utiliser l'option labels obligatoirement
        #dataGraph=dataGraph+"[ [1,10,100], [2,20,80], [3,50,60], [4,70,80] ]"

        nombreSeries=6 # nombre serie de donnees - attention definir autant de label y que de series

        data=np.zeros((1000,nombreSeries+1)) # tableau de 1000 lignes x 2 colonnes
        data[:,0] = np.arange(0,1000,1) # remplissage 1ere colonne = les x

        #data[:,1] = np.arange(0,1000,1) # remplissage 2eme colonne = les y
        for serie in range(1,nombreSeries+1): # defile serie des y de 1 à n
                data[:,serie] = (serie*5)+np.random.normal(0,1,size=1000)  # remplissage 2eme colonne = les y1

        # conversion du tableau numpy en chaine compatible Dygraphs [ [ , , ] ,  [ , , ] ]
        #dataStr=",".join(data )   # pour convertir list en str avec elements separes par ,
        dataStr=str(data.tolist()) # convertit tableau numpy pour affichage dygraph ++
        dataGraph=dataGraph+dataStr

        # options du graphique Dygraph a utiliser
        optionsGraph="""
        labels: [ "x", "y=1", "y2=", "y3=", "y4=", "y5=", "y6="], // labels series
        width : 800, // largeur
        height: 600, // hauteur
        showRangeSelector: true // affiche l'outil de selection plage voulue
        """

        # Attention, pas de , pour la derniere ligne !

        pageHTML=( # debut page HTML
"""
<!DOCTYPE html >
<html>
        <head>

        <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <!-- Encodage de la page  -->

        <script language="javascript" type="text/javascript">
        <!--

        // code javascript par X. HINAULT - www.mon-club-elec.fr - tous droits reserves - 2013 - GPL v3

        function path(jsFileNameIn) { // fonction pour fixer chemin absolu                              
                        var js = document.createElement("script");
                        js.type = "text/javascript";
                        //js.src = " http://www.mon-club-elec.fr/mes_javascripts/dygraphs/"+jsFileNameIn; // <=== serveur externe - modifier chemin ++ si besoin
                        //js.src = "http://127.0.0.1/javascript/dygraphs/"+jsFileNameIn; // serveur local
                        //js.src = "javascript/rgraph/"+jsFileNameIn; // chemin local  - fichier doit etre a la racine serveur
                        js.src = "http://"+window.location.hostname+":80"+"/javascript/dygraphs/"+jsFileNameIn; // si utilisation server http local port 80

                        document.head.appendChild(js);                                  
                        //alert(js.src); // debug

                } // fin fonction path


                //---- fichiers a charger ---                            
                path('dygraph-combined.js'); // fichier simplifiant acces a tous les fichiers de la librairie dygraph

                window.onload = function () { // au chargement de la page


                        //var valeurs = new Array(102,511,255,127,63,32); // tableau valeurs par defaut


                        colorSets = [ // definition des set de couleurs a utiliser
                        ['#284785', '#EE1111', '#8AE234'], // 1er jeu de couleur - couleur utilisee dans ordre trace des courbes
                        ['rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,0,255)'], // 2eme jeu de couleur - format rgb
                        null
                        ]

                        g = new Dygraph( // cree l'objet du graphique

                        // containing div
                        document.getElementById("graphdiv"), // objet div utilise appele par son nom

                        // CSV ou chemin fichier CSV.

                        // donnees valides au format x,y1, y2, ..., yn \n
                        // x = horodatage ou chiffre
                        // horodatages valides : voir http://dygraphs.com/data.html#csv
"""

        +
        dataGraph
        +
""",

                        //-- parametres a utiliser pour le graphique
                        {
"""

        +
        optionsGraph
        +
"""
                        } // fin parametres


                        ); // fin declaration Dygraph


                } // fin onload


        -->      
    </script>  

    <title>Test Dygraphs</title>

    </head>

    <body>

    <div id="graphdiv"></div>
    <br />
    Test de graphique Dygraphs : affichage de plusieurs s&eacute;ries de 1000 donn&eacute;es (tableau numpy)


    </body>
</html>          
"""

)  # fin page HTML
        return pageHTML # la fonction renvoie la page HTML



#--- obligatoire pour lancement du code --
if __name__=="__main__": # pour rendre le code executable
        setup() # appelle la fonction setup
        while not noLoop: loop() # appelle fonction loop sans fin
 

Ce code est également disponible ici : https://raw.github.com/sensor56/pyduino-exemples/master/Ethernet/EthernetServerHTMLJSDygraphs1000NumpyMulti.py

Utilisation

  • Connecter le mini-PC au réseau
  • Appuyer sur le bouton exécuter dans Geany
  • Une fenêtre de Terminal doit apparaître : un message indique que le serveur attend un client entrant.
  • Ouvrir alors sur le poste client un navigateur, par exemple firefox, et saisir l'adresse ip du mini-PC suivie de :8080 ce qui donne par exemple 192.168.1.13:8080. On utilise ici le port 8080 d'où le :8080...
  • A ce moment là, on doit voir sur le mini-PC les messages attestant de la connexion du client ainsi que le requête reçue
  • Puis le serveur envoie sa réponse intégrant l'entête http et la page HTML avec le code Javascript
  • Du côté client, la page HTML doit s'afficher : le code javascript va afficher un div dans la fenêtre du navigateur intégrant le graphique fourni par la librairie Dygraphs
  • Il est possible également d'éditer le source de la page permettant de s'assurer que le code HTML + javascript envoyé par le code pyduino est correct
  • Noter que le poste fixe peut simultanément accéder au mini-PC par VNC (qui utilise le port 5900) et par le navigateur client ( qui utilise le port 8080 et 80) !

Discussion technique

  • L'intérêt d'utiliser un tableau Numpy est double :
    • côté code Python (serveur), il devient possible de manipuler des séries de données avec la puissance de numpy et Scipy
    • côté client, on évite le "parsing" des données fournies au format texte, les données étant ici directement envoyées au format natif utilisé par Dygraphs, à savoir [ [ , , , ] , [ , , , ] ]
  • Noter ici également l'usage de la notation "slice" spécifique au langage Python qui permet d'extraire simplement un colonne d'un tableau 2D et la facilité avec laquelle on peut réaliser une opération sur toutes les valeurs d'un tableau Numpy en Python...