Menu Fermer

Communiquer entre un Arduino et un Raspberry Pi via USB (Python, Java) (2/2)

Un article sur ce sujet a été soumis à developpez.com et est maintenant disponible sous le titre Créer une communication entre Arduino et Raspberry Pi 3 via USB.

En fin d’article précédent nous avions parlé de développer une application plus complète avec:

  • Ajouter sur un Arduino Duemilanove un capteur analogique LM35 (pas possible sur un Raspberry Pi sans interface analogique);
  • Développer sur le PC un sketch Arduino pour:
    – enclencher le relais (petit chauffage) si la température est trop basse
    – recevoir la température dans la console de l’IDE
    – décider si le Pi prend le contrôle pour enclencher ou déclencher le relais;
  • Envoyer une nouvelle limite de température;
  • Développer sous Eclipse une classe Java dédié au Raspberry Pi pour faire et vérifier le travail!

Nous allons développer complètement cette application dans cette article.

Préparation sur l’Arduino

Nous commencerons évidemment par le schéma Fritzing:

et le sketch Arduino qui est préparé dans l’IDE de l’Arduino avec le PC connecté avec le câble USB. Ce dernier sera d’abord connecté au PC pour vérifier le sketch avant de passer au Raspberry Pi. Dans les deux cas, le câble USB jouera le rôle d’alimentation et de moyen de transfert avec une communication série.

Notre sketch sur l’Arduino

Et le code complet:

int tempPin = 0;
int relaisPin = 7;
double tempC;
double tempLimit = 20.5;
bool isOn = false;

int modet = 1; //mode standard

String incoming = ""; // for incoming serial string data

void setup()
{
  Serial.begin(9600);
  Serial.setTimeout(100);
  pinMode(relaisPin, OUTPUT);
  digitalWrite(relaisPin, LOW);
}

void loop()
{
  if (Serial.available() > 0)
  {
    // read the incoming:
    incoming = Serial.readString();
    Serial.flush();

    if (incoming.startsWith("on"))
    {
      modet = 0;
      digitalWrite(relaisPin, HIGH);
      delay(2500);
      isOn = true;
    }
    else if (incoming.startsWith("off"))  
    {
      modet = 0;
      digitalWrite(relaisPin, LOW);
      delay(2500);
      isOn = false;
    }
    else
    {
      double tempLimitC = incoming.toDouble();
      if (tempLimitC != 0.0)
      {
        tempLimit = tempLimitC;
        modet = 1;
      }
    }
  }
 
  tempC = analogRead(tempPin);
  tempC = tempC * 0.48828125;
 
  if (isOn) {
    tempC = tempC - 1.97; //calibration
  }

  Serial.print("Temperature (");
  Serial.print(tempLimit);
  Serial.print(") (");
  Serial.print(isOn);
  Serial.print(") (");
  Serial.print(modet);
  Serial.print(") = ");
  Serial.print(tempC);
  Serial.print("*C");
  Serial.println();

  if (modet == 1)
  {
    if (tempLimit < tempC)
    {
      digitalWrite(relaisPin, LOW);
      isOn = false;
    }
    else
    {
      digitalWrite(relaisPin, HIGH);
      isOn = true;
    }
  }
  delay(2000);
}

Le fichier Arduino lm35.ino est téléchargeable ici. Il doit être installé dans un sous-répertoire lm35 que nous pourrons alors charger dans l’IDE d’Arduino.

Après avoir monté notre circuit Arduino décrit dans le schéma Frizing ci-dessus, branché le câble USB entre l’Arduino et le PC, déterminé quel COM est utilisé, nous pourrons l’exécuter:

A présent cela nécessite quelques explications.

La série de print qui suit Serial.print(« Temperature (« ); va nous retourner la température mesurée par le LM35 accompagnée d’une série d’information, typiquement:

Temperature (20.50) (0) (1) = 21.48*C

dans notre fenêtre Moniteur série (menu Outils de l’IDE). Cette ligne de texte pourra être récupérée plus tard par un script Python ou une classe Java par un Raspberry Pi qui utilisera ce même câble USB.

La première valeur 20.50 va déterminer la température limite avec laquelle le relais sera enclenché, avec l’instruction digitalWrite(relaisPin, HIGH);, pour allumer par exemple un chauffage d’appui. Ici, avec une température de 21.48, le (0) nous sera retourné, c’est à dire la variable isOn, indiquant que le relais est déclenché.

Le second (1) est une indication du mode de travail (modet). La valeur 1 est le mode standard, c’est à dire avec la vérification de la température et de sa limite, pour ensuite activer ou désactiver le relais.

Le calcul avec la valeur 0.48828125 nous permet le calcul analogique de la température, et j’ai dû calibrer le résultat si le relais était enclenché (sans doute un problème de tension). La partie tempC = tempC – 1.97; devra sans doute être adaptée. 

Il est possible d’envoyer une nouvelle limite de température (tempLimit). Si la conversion de la valeur double donne une erreur  (incoming.toDouble()), elle est ignorée. Par contre nous pouvons envoyer un on ou un off.

Si un de ces deux strings est reçu, le relais est enclenché (on) ou déclenché (off).   Dans ces deux cas nous passons en modet = 0; et le relais restera inchangé suivant le dernier on ou off. La température continuera d’être envoyée et si un programme Python ou Java est actif, il pourrait lire la température, activer le relais avec une commande voire encore faire sa propre logique décisionnel. Pour repasser en mode normal, il faudra renvoyer une nouvelle limite de température.

Toute ces variantes peuvent être vérifiée avec l’envoi de commandes depuis le Moniteur série.  Si nous avons ceci:

Temperature (22.30) (1) (1) = 21.97*C

dans notre fenêtre de moniteur série, cela veut dire qu’une commande 22.2 a été envoyée, que le relais est enclenché, et que si la temoérature passe à 22.31, le relais sera déclenché.

Notre application Java sous Eclipse

Nous garderons notre câble connecté à l’Arduino et stopperons son IDE afin de libérer le port COM. Comme présenté à plusieurs occasions dans le livre, nous allons créer ou réutiliser un projet Eclipse, par exemple USBport, et y déposer cette classe Java, c’est à dire TemperatureRelaisArduino.java:

import java.io.IOException;
import java.io.RandomAccessFile;

public class TemperatureRelaisArduino implements Runnable {
   private Thread monThread;
   private RandomAccessFile rAF;
   private String portName;
   
   private double  temperature = 0.0;
   private String  cmd         = null;
   private boolean stopThread  = false;
   
   private boolean isTarget = true;  //Raspberry Pi

   public TemperatureRelaisArduino(String portName) {
      this.portName = portName;
      monThread = new Thread(this);
      
      if (System.getProperty("os.name").startsWith("Windows")) {
         isTarget = false;
      }
   }

   public void start() {
      try {
         rAF = new java.io.RandomAccessFile(portName, "rwd");
         rAF.writeBytes("\r\n");
      } 
      catch (Exception e) {
         System.out.println("Le port " + portName + " n'existe pas");
         System.exit(0);
      }
      monThread.start();
   }

   public void run() {
      System.out.println("Lecture du port " + portName);
      for (;;) {
         if (stopThread) {
            System.exit(0);
         }
         
         String response = null;
         try {
            response = rAF.readLine();
         } 
         catch (IOException e) {
            System.out.println(e.getMessage());
         }
         System.out.println(response);
         
         int posTemp = response.indexOf(" = ");
         
         //Temperature message:
         //Temperature (20.50) (1) = 22.42*C
         //
         if (posTemp > 0) {  //temperature received 
            temperature = Double.parseDouble(
                     response.substring(posTemp + 3, posTemp + 8));
         }
         
         if (cmd != null) {
            try {
               rAF.writeBytes(cmd);
               if (isTarget) {
                 rAF.writeBytes("\r\n"); 
               }
               cmd = null;
            } catch (IOException e) {
            }
         }
      }
   }
   
   public double getTemperature() {
      return temperature;
   }
   
   public void setCmdToSent(String cmd) {
      this.cmd = cmd;
   }
   
   public void setStopThread() {
      stopThread = true;
   }
   
   public void pause(int ms) {
      try {
      Thread.sleep(ms);
      } 
      catch (InterruptedException e) {
      }
   }

   public static void main(String[] args) {
      String portDevice = "COM3";
      if (args.length > 0) {
         portDevice = args[0];
      }
      
      TemperatureRelaisArduino tempRelArd = new
                               TemperatureRelaisArduino(portDevice);
      tempRelArd.start();
      
      for (int i = 0; i < 3; i++) {
         tempRelArd.pause(2200);
      }
      
      tempRelArd.setCmdToSent("on");
      tempRelArd.pause(2200);      
      tempRelArd.setCmdToSent("off");
      tempRelArd.pause(2200);
      tempRelArd.setCmdToSent("on");
      tempRelArd.pause(2200);      
      tempRelArd.setCmdToSent("off");
      tempRelArd.pause(2200);
      
      tempRelArd.setCmdToSent("31.3");
      tempRelArd.pause(2200);
      
      for (int i = 0; i < 3; i++) {
         tempRelArd.pause(1500);
      }
      
      tempRelArd.pause(2200);
      tempRelArd.setStopThread();
   }
}

En exécutant cette classe sous Eclipse, nous aurons le même résultat qu’avec l’IDE d’Eclipse:

Lecture du port COM3
Temperature (20.50) (0) (1) = 21.97*C
Temperature (20.50) (0) (1) = 21.97*C
Temperature (20.50) (0) (1) = 21.97*C
........

Il faudra attendre un peu pour voir la seconde partie fonctionner avec l’envoi de commandes on, off et 31.3 avec une valeur limite plutôt élevée! Pour activer l’air conditionné, il faudrait inversé la logique.

La commande DOS MODE est expliquée dans l’article précédent.

La classe TemperatureRelaisArduino est suffisamment claire et ne nécessite pas vraiment d’explications. Dans mon livre un certain nombre de composants et d’applications utilisent des Thread afin de pouvoir traiter d’autres fonctions ou composants en parallèle.  La variable isTarget a été nécessaire pour envoyer un « \r\n » qui n’était pas nécessaire sur la version Windows avec un COM. La commande stty ci-dessous a résolu les autres problèmes.

Exécuter TemperatureRelaisArduino sur le Raspberry Pi

Comme décrit dans mon livre à plusieurs occasions, nous transférerons le fichier TemperatureRelaisArduino.class depuis le Workspace d’Eclipse (répertoire bin) sur le Raspberry Pi dans le répertoire habituel /home/pi/java. Il faudra alors faire passer le câble USB du PC sur le Raspberry Pi, évidemment!

Sur le Raspberry Pi il faudra configurer correctement le port (comme déterminé dans l’article précédent) avec la commande:

stty -F /dev/ttyACM0 cs8 9600 ignbrk -brkint -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts

Nous pourrons la déposer dans un fichier setStty.sh sans oublier un chmod +x setStty.sh afin de l’exécuter avant l’application Java.

Ensuite la commande java sera ici:

java TemperatureRelaisArduino /dev/ttyUSB0

Et cela fonctionne!

Exercices

  • Adapter la classe Java TemperatureRelaisArduino pour qu’elle soit responsable de vérifier la limite de température … et non plus l’Arduino.
  • Ajouter un senseur de lumière sur l’Arduino et faire une application similaire. Le matériel du chapitre 16 peut être utile!
  • Créer une classe Java sur le Raspberry Pi pour recueillir la température mesurée par le capteur LM35 de l’Arduino, sans utilisation du relais, et faire une représentation journalière des variations de température. Définir par exemple des périodes de 5 minutes où les variations excessives seraient ignorées. Un rapport journalier pourrait être envoyé par courriel (chapitre 24 de mon livre).

Conclusion

Que ce soit l’Arduino, l’IDE de l’Arduino, la création de sketchs et de schémas Fritzing, l’utilisation d’Eclipse pour développer des classes Java, la maîtrise de Raspbian (le Linux du Raspberry Pi) ou encore du langage Python, tous ces outils, ces composants électroniques et ces langages vont permettre aux bricoleurs de s’initier dans le monde des  technologies modernes au travers, évidemment, et c’est incontournable de la programmation et de son apprentissage.

Date de la dernière modification: 12 octobre 2019