Travaux Pratiques n°7

Lectures préalables :  
Thèmes du TP :
  • Les threads et la consultation cyclique
  • Les requêtes HTTP
  • Le pattern Chaîne de responsabilités
  • Les assertions

 

Rappels :
Vous devez fournir des copies d'écran des diagrammes de classe de BlueJ, et la documentation générée doit faire apparaître vos commentaires sur toutes les classes, les méthodes, les paramètres, et les valeurs de retour.

Question 1. (Acquisition cyclique)

(i) Il s'agit de se connecter à un capteur qui est connecté à internet (il a sa propre URL !).

ii) Lire le source de la classe HTTPHumidity, effectuer plusieurs tests.
Ajouter une nouvelle méthode à cette classe permettant de lire la valeur de l'humidité relative fournie par ce capteur. Pour répondre à cette question, vous devez utiliser la classe java.util.StringTokenizer .

Cette méthode a la signature suivante :

public float value() throws Exception;

la valeur retournée aura un seul chiffre après la virgule, (précision 0.1 du composant)


Si le capteur n'était pas accessible, il suffirait de remplacer dans cette méthode la lecture du capteur par une affectation directe de la String par celle indiquée dans la légende de la copie d'écran ci-dessus.

 

iii) Complétez la classe CyclicAcquisition, contenant un Thread local. Ce Thread permet l'acquisition cyclique du taux d'humidité, la période doit être supérieure à 1 minute (contrainte à respecter afin de pas surcharger le serveur Web au Cnam).
Cette version néglige le temps de communication : Requête HTTP et analyse du résultat. L'information reçue est affichée sur la console par une implémentation de l'interface Command, plus particulièrement la méthode make.

L'interface Command :

public interface Command{
  public void make(float f);
}

(iv) Fournir une classe de test pour la classe AcquisitionSystem ci-dessous; un des tests sera de s'assurer que le proxy de l'esiee est bien en place avant d'effectuer la moindre requête HTTP, des tests sur la valeur de ce taux d'humidité pourront être installés (100% à l'intérieur d'un bureau ne devrait pas se produire), ...

package acquisition;

public class AcquisitionSystem implements Command{
  private String            url;
  private long              period;
  private Handler           chain;
  private CyclicAcquisition cyclic;
  
  public AcquisitionSystem(String url,long period,Handler chain){
    this.url    = url;
    this.period = period;
    this.chain  = chain;
    cyclic = new CyclicAcquisition(url,period,this);
    cyclic.start();
  }
  
  public AcquisitionSystem(Handler chain){
    this("http://lmi92.cnam.fr:8999/ds2438/",CyclicAcquisition.ONE_MINUTE,chain);
  }
  
  public void stop(){
    cyclic.stop();
  }
  
  public void make(float f){
    chain.handleRequest(f);
  }
}
AIDE :

  • Pour effectuer un premier test très simple, il est possible de créer une classe MaCommande et de ne mettre qu'un System.out.println dans la méthode make() .
  • Pour effectuer un véritable test, il faut intégrer au projet les classes Handler et TraceHandler des questions 2.i et 2.ii .

(v) Fournissez la documentation regroupant toutes les classes des questions précédentes.


Question 2. (Chaîne de responsabilités)

(i) L'information reçue, le taux d'humidité, est maintenant transmise à différents consommateurs. Les consommateurs sont chainés entre eux. Ce type de conception est issu du pattern "chaîne de responsabilités". Le principe est de transmettre l'information à une chaîne de consommateurs, chaque consommateur décide si il doit laisser passer l'information vers son successeur ou bien arrêter sa propagation.

La classe abstraite Handler adaptée, selon la bibliographie :

public abstract class Handler{
  protected Handler successor = null;
  
  public Handler(){ this.successor = null;}
  public Handler(Handler successor){ this.successor = successor;}
  public void setSuccessor(Handler successor){this.successor = successor;}
  public Handler getSuccessor(){return this.successor;}
  public boolean handleRequest(float value){
    if ( successor == null )  return false;
    return successor.handleRequest(value);
  }
}

(ii) Proposer une première "Chaîne de responsabilités" constituée de 3 "Handlers" liée à notre application de lecture cyclique du taux d'humidité

 

Les "Handlers" seront chainés comme suit TraceHandler -> MinHandler -> MaxHandler


La classe TraceHandler ci-dessous :

public class TraceHandler extends Handler{

  public TraceHandler(Handler successor){
    super(successor);
  }
  
  public boolean handleRequest(float value){
    /* affichage de la valeur */
    return super.handleRequest(value);
  }
}
  

Une Trace d'exécution possible :
value : 36.6
value : 36.9
value : 36.6
value : 36.2
value : 36.6
value : 36.2
value : 30.6
value min détectee : 30.6
value : 30.6
value min détectee : 30.6
value : 30.7
value min détectee : 30.7
value : 30.7
value min détectee : 30.7
value : 30.7
value min détectee : 30.7
value : 31.1
value min détectee : 31.1
value : 37.7
value : 37.7
value : 37.7
value : 37.7
value : 37.7
value : 37.8

 

(iii) Ajouter deux nouveaux maillons au bon endroit dans la chaîne :
AIDE :

  • Ces quelques lignes Java fournissent la date et heure courante :
        Calendar c = Calendar.getInstance();
    
        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT,Locale.FRANCE);
        DateFormat dt = DateFormat.getTimeInstance(DateFormat.SHORT,Locale.FRANCE);
        String date = df.format(c.getTime()) + "-" + dt.format(c.getTime());
  • Pour plus de détails, voir la classe Calendar.

 

(iv) Ajouter encore un maillon dans la chaîne : FileHandler, ce 'handler' est chargée de la sauvegarde sur fichier des mesures

Les "Handlers" seront chainés comme suit TraceHandler -> FileHandler -> Le reste de la chaîne

(v) Fournissez la documentation regroupant toutes les classes des questions précédentes.


OPTIONNEL* :

Question 3. (température)

(i) Complétez toutes les classes précédentes pour qu'elles gèrent la température de la pièce en plus du taux d'humidité.
Pour cela, utilisez la version longue des informations retournées par le capteur : http://lmi92.cnam.fr:8999/ds2438/?infoAll=on

AIDE :

  • Exemple de source retourné par le capteur :
    # ibutton: DS2438; Adapter: DS9097U; Port: COM2;<br>1C0000000D536B26=29.55006885501229<br><br>Humidity: 29.55006885501229 %<br>isRelative: true<br>HumidityResolution: 0.1<br>Temperature: 18.0625 °C<br>HumidityResolutions: [<0,0.1>]<br>hasSelectableHumidityResolution: false<br>hasHumidityAlarms: false<br>documentation: www.ibutton.com and this article.pdf

    Choisissez bien vos délimiteurs dans StringTokenizer pour pouvoir repérer facilement la Temperature.

(ii) Fournissez la documentation regroupant toutes les classes.


Humidité Relative : définition extraite de http://www.credo.fr/monde/page/pf_090799.htm
L'Humidité Relative (HR) exprime le rapport entre la quantité effective de vapeur d'eau dans un volume donné d'air et la quantité maximale que ce volume peut contenir à la même température.
L'eau s'évapore dans l'atmosphère jusqu'à ce que soit atteinte une proportion maximale de vapeur d'eau dans l'air, dite humidité saturante.
Quand cette condition est atteinte, la moindre chute de température provoque la condensation de la vapeur et l'apparition de minuscules gouttes d'eau. Il s'agit du phénomène de rosée.
La saturation de l'air en vapeur d'eau (ou point de rosée) correspond donc à 100 % d'humidité relative.


La quantité de vapeur d'eau dans l'air à 100 % HR est d'environ 15 grammes d'eau par mètre cube d'air.

Quand la quantité de vapeur d'eau dans l'air est en deçà de la saturation, l'humidité relative est infèrieure à 100 %.
A peu de choses près, une HR de 70 % correspond à une quantité d'eau par mètre cube d'air de 70 % de la quantité présente à saturation: environ 10,5 grammes d'eau par mètre cube d'air.


 


Annexe 1.Source de HTTPHumidity.java

// tp7-1ii : HTTPHumidity.java
package sensor;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;

import java.net.URL;
import java.net.URLConnection;

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;

/** Classe de lecture d'un capteur sur le Web
 * @version de test
 * @author jm Douin
 */
public class HTTPHumidity{

  /** l'URL associée au capteur */
  private String urlSensor;
  
  /** Constructeur d'une connexion avec un Capteur
   * @param  urlSensor l'URL du capteur sur le Web en protocole HTTP
   */
  public HTTPHumidity(String urlSensor){
    this.urlSensor = urlSensor;
  }
  
  /** Lecture de la valeur de humidité relative
   **/
  public float Value() throws Exception{
    /* Acquisition de la valeur */
  }
  
  /** lecture de l'URL
   * @return l'url associée à ce capteur
   */
  public String getUrl(){
    return this.urlSensor;
  }
  
  /** Lecture des informations issues de ce capteur
   * @param params les paramètres 
   * @return la totalité de la page lue
   * @throws Exception en cas d'erreur
   */
  public String read(Properties params) throws Exception{
    
    URL url = new URL(urlSensor);
    URLConnection connection = url.openConnection();
    
    connection.setDoInput( true );
    connection.setDoOutput(true);
    PrintWriter out = new PrintWriter(connection.getOutputStream());
    // les paramhtres (type CGI)
    for(Enumeration e = params.keys();e.hasMoreElements();){
      String key   = (String) e.nextElement();
      String value = (String) params.get(key);
      out.print(key + "=" + value);
      //System.out.println(key + "=" + value);
      if(e.hasMoreElements()) out.print("&");
    }
    //out.println();
    out.close();
      
    // lecture en retour
    BufferedReader in = new BufferedReader( new
    InputStreamReader(connection.getInputStream()));
    String result= new String("");
    String inputLine = in.readLine();
    while(inputLine != null){
      result = result + inputLine;
      inputLine = in.readLine();
    }
    in.close();
    
    return result;
   }
   
   /** Lecture des informations issues de ce capteur
   * @return la totalité de la page lue
   * @throws Exception en cas d'erreur
   */
  public String read() throws Exception{
    final Properties nil = new Properties();
    return read(nil);
   }

  /** Mise en place du proxy si nécessaire
   *  rappel à l'esiee : proxyHost=cache.esiee.fr proxyPort=3128
   *  attention, aucune vérification de la validité de l'URL transmise n'est effectuée
   *  @param proxyHost adresse du proxy
   *  @param proxyPort le port du proxy
   */
  public static void setHttpProxy(String proxyHost,int proxyPort){
    Properties prop = System.getProperties();
    prop.put("proxySet","true");
    prop.put("http.proxyHost",proxyHost);
    prop.put("http.proxyPort",Integer.toString(proxyPort));
  }
      
  // bloc d'initialisation statique pour l'esiee
  private static final boolean ESIEE_INSIDE = true;
  static { if(ESIEE_INSIDE){setHttpProxy("cache.esiee.fr",3128);} }
}


Annexe 2.Source de CyclicAcquisition.java

// tp7-1iii : CyclicAcquisition.java
package acquisition;

import sensor.HTTPHumidity;
import java.util.Calendar;
import java.text.DateFormat;

public class CyclicAcquisition implements Runnable{
  public static final long ONE_MINUTE = 60L * 1000L; 
  private Thread  local;
  private Command command;

  private HTTPHumidity sensor;
  private long         period;

  public CyclicAcquisition(String url, long period, Command command){
    assert period >= ONE_MINUTE : " respectez la période minimale (period*ms > minute) !!";
    this.sensor   = new HTTPHumidity(url);
    this.period   = period;
    this.command  = command;
    local         = new Thread(this);
  }

  public void start(){
    /* démarrer le thread */
  }

  public void stop(){
    /* interrompre le thread */ 
  }

  public synchronized void setPeriod(long period){
    this.period = period;
  }

  public void run(){
    try{
      while(!local.isInterrupted()){
        synchronized( this ){
          try{
       /* lecture du capteur et utilisation de la valeur */
          }catch( Exception e){
       /* retour d'une valeur négative */
          }
          Thread.sleep( period);
        } // synchronized
      } // while
    }catch( InterruptedException ie ){
      System.out.println( "InterruptedException : " + ie.getMessage()
    + " at " +
        DateFormat.getTimeInstance().format(Calendar.getInstance().getTime()) );
    } // catch
  } // run()
}


Annexe 3.Source de FileHandler.java

// tp7-2iv : FileHandler.java
package acquisition;

import java.io.File;

// import des classes de gestion des fichiers en ecriture
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.ObjectOutputStream;

// import des classes de gestion des fichiers en lecture
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.ObjectInputStream;

// exceptions susceptibles d'etre engendrees lors d'une operation de lecture ou d'ecriture
import java.io.IOException;
import java.io.FileNotFoundException;

// exception liee a la serialisation
import java.lang.ClassCastException;

import java.util.Collection;
/* à compléter */

import java.util.Calendar;
import java.util.Locale;
import java.util.Iterator;
import java.util.Date;

import java.text.DateFormat;

// emploi de getResource (voir java.lang.Class)
import java.net.URL;

public class FileHandler extends Handler  {
  private final static int MAX=5;
  private String    fileName;
  private int       counter;
  /* déclarer une table */
  private SortedMap table;

  
  public FileHandler(String fileName, Handler successor) {
    /* à compléter */
  }
  
  public boolean handleRequest(float value) {
    /* à compléter */
  }
  
  
   /** Cette methode genere un fichier HTML .
   */
  private void writeHTML() throws IOException{
    BufferedWriter bw = new BufferedWriter(
                          new PrintWriter(
                            new FileOutputStream(
                              new File(fileName + ".html"))));
    /* à compléter */

    bw.close();
  }
  

    /** Pour la serialisation :
     * Ecriture de la version binaire sur fichier d'une table des mesures.
     * @throws IOException Si le processus de sauvegarde echoue.
     */
    private void write() throws IOException{
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(fileName + ".ser"));
        os.writeObject(table);
        os.flush();
        os.close();
    }
}