Communication entre les deux Raspberry Pi

Comme je l’ai expliqué dans un précédent article qui sert de référence, mon installation est composée, entre autre, de deux Raspberry Pi que je nomme comme suit:

  1. Le RPi frontal
  2. Le DomoPi

Dans cet article je vais expliquer comment faire communiquer les deux RPi, en particulier comment configurer le DomoPi pour recevoir des commandes, le tout à base d’un petit protocole maison simplissime et extensible.

Le RPi frontal doit pouvoir envoyer des commandes au DomoPi qui héberge l’ensemble de ce que j’appelle mes services domotiques. Il s’agit, par exemple:

  • Actionner (On/Off) un radiateur
  • Activer ou désactiver un programme de chauffage prédéfini
  • Actionner (On/Off) une lampe
  • Consulter la valeur d’une sonde de température
  • Envoyer un SMS
  • Consulter la boîte de réception des SMS
  • etc… en fonction des divers projets que je rajouterai bientôt à mon installation

On trouve sur l’excellent site d’Idleman plusieurs manières de faire communiquer des RPi, via par exemple une communication radio RF/433MHz.  En l’occurrence, mes deux RPi étant tous deux connectés à mon réseau IP domestique, le plus logique était de les faire communiquer via une simple connexion TCP.

Le principe


Le RPi frontal établit une connexion TCP sur le port 6868 du DomoPi (en réalité, cela passe par de la redirection de port sur le routeur wifi, comme expliqué dans mon premier article). La connexion TCP peut être réalisée en ligne de commande à des fins de test, ou via n’importe quel langage de programmation. Sur le RPi frontal, j’utilise PHP pour l’ensemble du site web, c’est donc depuis diverses pages (scripts) PHP qu’est établie la connexion TCP vers le DomoPi.

Exemple de connexion en ligne de commande (mode verbeux):

pi@raspberrypi ~ $ nc -vvv 192.168.0.10 6868
Connection to 192.168.0.10 6868 port [tcp/*] succeeded!

Exemple de connexion en PHP:

// Connect to the DomoPi services
$client = stream_socket_client("tcp://192.168.0.10:6868", $errno, $errorMessage);

Une fois la connexion établie:

  • le DomoPi attend de recevoir une commande sous la forme d’un simple texte prédéfini
  • le RPi frontal envoie la commande
  • le DomoPi exécute la commande
  • optionnellement, selon la commande:
    • le DomoPi renvoie le résultat de la commande sur la connexion
    • le RPi frontal lit et stocke le résultat
  • le RPi frontal ferme la connexion TCP

Exemple en ligne de commande, ou le RPi frontal envoie la commande « readTemperature » et ou le DomoPi répond avec la température courante:

pi@raspberrypi ~ $ nc -vvv 192.168.0.10 6868
Connection to 192.168.0.10 6868 port [tcp/*] succeeded!
readTemperature
19.7

 Configuration du DomoPi


Je suis un grand fan du démon Xinetd qui permet de créer facilement un serveur réseau en gérant des connexions entrantes sur un système, et en reléguant la gestion des données à une application tierce, comme par exemple un script bash. En gros, Xinetd apporte des fonctionnalités réseau (sockets TCP et UDP) à des applications qui n’ont qu’à gérer le flux de données sur leurs entrées/sorties standard, sans se soucier de la partie réseau. Xinetd offre d’autres fonctionnalités et avantages mais ça n’est pas l’objet de cet article.

Étape 1:

On commence par déclarer un nom de service pour le port TCP/6868, ce qui se fait en éditant le fichier « /etc/services » :

pi@domopi ~ $ sudo nano /etc/services

Rajouter la ligne suivante :

domoservices    6868/tcp                        # Arno's Domotic Services

Étape 2:

Après avoir installé Xinetd (voir un précédent article), on crée un fichier de configuration spécifique à nos besoins :

pi@domopi ~ $ sudo nano /etc/xinetd.d/domoservices

Ajouter les lignes suivantes :

service domoservices
{
   disable = no
   socket_type = stream
   protocol = tcp
   wait = no
   user = pi
   server = /home/pi/Domotique/domoServices.sh
   nice = 10
}

Attention à bien utiliser le même nom pour le service que celui qu’on a précédemment déclaré dans le fichier « /etc/services ». Si l’explication détaillée de ces options vous intéresse, je vous invite à consulter le manuel Xinetd. En gros, ce fichier définit un nouveau service, activé, en écoute sur le port TCP/6868 et qui relègue la gestion des données au script « /home/pi/Domotique/domoServices.sh », que nous créerons par la suite. Le script sera exécuté avec l’utilisateur « pi ».

On va ensuite redémarrer le service Xinetd:

pi@domopi ~ $ sudo service xinetd restart

Étape 3:

Il reste à créer le script bash qui est au cœur du dispositif de communication. C’est in-fine ce script qui va lire les commandes sur son entrée standard, agir selon la commande en invoquant d’autres scripts ou applications, et finalement renvoyer un résultat. A titre d’exemple, je  propose ici une version simplifiée, mais parfaitement fonctionnelle, de mon script actuel. Ce script évolue avec les nouveaux services domotiques que je rajoute. A chaque nouveau service domotique correspond une ou plusieurs nouvelles commandes à traiter. Il suffit alors de rajouter les nouvelles commandes dans ce script bash, en y associant les actions à réaliser.

Le script peut être créé dans n’importe quel emplacement à condition de refléter cet emplacement dans le fichier de configuration de Xinetd précédemment créé.

On crée le script en l’ouvrant un éditeur de texte :

pi@domopi ~ $ sudo nano /home/pi/Domotique/domoServices.sh

Puis entrer le code suivant (NB: j’ai pris l’habitude de coder et de commenter mon code en anglais, désolé) :

!/bin/bash
# Author: Arno0x0x
# Description:  This script executes some domotic orders based on the command passed as argument.
#       		The network wrapper is Xinetd on port 6868/tcp (domoservices)

#-----------------------------------------------------------------------
# Initialize main variables
#-----------------------------------------------------------------------
_rootDir="/home/pi/Domotique"
_domoServicesLogFile="${_rootDir}/domoservices.log"

#-----------------------------------------------------------------------
# Commands available
#-----------------------------------------------------------------------
_readTemperature="${_rootDir}/readTemperature.sh"

#-----------------------------------------------------------------------
# Read the command passed on stdin
read _command

case ${_command} in
	readTemperature)
		echo $($_readTemperature)
		[[ $? -eq 0 ]] || echo "[ERROR]"  >> ${_domoServicesLogFile}
		;;
	*) echo "[ERROR] UNKNOWN COMMAND"
	;;
esac

 Conclusion


On a vu dans cet article comment configurer le DomoPi pour recevoir des commandes, via une connexion réseau. Le script présenté n’effectue en lui même aucune action domotique, il se contente de recevoir les commandes, et pour les commandes qu’il connait, d’invoquer d’autres scripts ou applications. Ainsi couplé à Xinetd, le script agit comme un serveur, rendant accessible à distance (via le réseau IP) l’ensemble des actions domotiques locales possibles sur le DomoPi.

Vous aimez cet article ? Faites le savoir avec quelques bitcoins !

8 réflexions sur “Communication entre les deux Raspberry Pi

  1. Georges-Michel 25 janvier 2017 / 19 h 13 min

    Bonjour,
    J’ai suivi votre tuto pour relier mes 2 RPI.
    Mais quand je lance la commande :
    nc -vvv 192.168.0.20 6868

    J’ai bien le retour :
    Connection to 192.168.0.20 6868 port [tcp/*] succeeded!

    Mais je ne peut pas taper la commande :
    J’ai l retour de -bash que la commande est introuvable.

    Mes programmes de gestion domotique sont en Python3 sur le Client, quelle commande passer pour interroger le serveur ?
    Mon but est que le Client interroge le serveur domotique (température) et la stocke dans un fichier.

    Merci d’avance pour votre retour,

    Gm

    J’aime

  2. DarkChyper 10 janvier 2016 / 19 h 45 min

    Bonjour,

    Article très intéressant, j’ai cependant quelques questions pour bien utiliser ce tuto :

    * Je n’arrive pas a lancer une commande directement à la suite de la commande nc, je suis obligé de mettre le texte a envoyé dans un fichiuer et de faire : nc -vvv addr 6868 &1 pour lancer l’execution et récupérer la réponse

    * Comment faire passer une liste d’arguments ? j’ai un script générique qui prend 3 paramètres et j’aimerai que ca ne soit pas le PiDomo qui choisisse ces paramètres, comment faire ?

    Cordiazlement

    J’aime

    • arno0x0x 11 janvier 2016 / 20 h 10 min

      Bonjour et merci pour votre commentaire.

      Concernant votre problème avec l’utilitaire netcat (nc) je ne suis pas sûr de comprendre quel est le problème. Est-ce un netcat standard ? Sur quelle plateforme ? En principe, netcat permet d’interagir directement en ligne de commande.

      Pour ce qui est de passer une liste d’arguments, c’est très simple, il y a plusieurs façons de faire, mais la plus simple est probablement la suivante:
      – Du côté qui envoie les commandes, il suffit de mettre les arguments sur autant de lignes à la suite de la commande elle même, donc avec le caractère ‘newline’ (\n), ou simplement en tapant sur la touche ‘Enter’ si c’est en interactif avec netcat. Par exemple:

      myCommand
      argument1
      argument2
      argument3

      – Du côté du script qui reçoit la commande et les arguments, on peut faire quelque chose comme ça:

      case ${_command} in
      myCommand)
      read argument1
      read argument2
      read argument3
      scriptGenerique $argument1 $argument2 $argument3
      ;;
      *) echo « [ERROR] UNKNOWN COMMAND »
      ;;
      esac

      Voilà, je ne sais pas si j’ai été bien clair, il est possible de faire des choses plus dynamiques avec par exemple, un nombre d’argument non fixé à l’avance, ou encore d’envoyer un argument de taille variable dont on précise en paramètre la taille en octets etc… Cela revient à inventer un petit protocole de communication, en script shell, sans avoir à se coltiner la partie communication réseau laissée à Xinetd.

      Cordialement,
      Arno

      J’aime

      • DarkChyper 23 janvier 2016 / 17 h 48 min

        Bonjour,

        Je ne sais pas quelle version de netcat j’ai sur mon rpi, mais lorsque je lance une commande j’ai le message comme quoi la connexion est réussi et elle se coupe directement, je n’ai pas la possibilité de taper quoique ce soit, et du coup je n’ai pas de retour de mes scripts non plus :/

        darkchyper:~$ netcat -vvv 10.0.0.11 6868
        Connection to 10.0.0.11 6868 port [tcp/*] succeeded!
        darkchyper:~$

        Pour le passage d’arguements, voici ce que j’ai fait :

        source envi.sh
        source fnct.sh

        # define split separator
        IFS= »  »

        # read the incoming command
        read IN

        # split IN into arr[]
        # 0 is the command
        # 1 to n are args
        arr=($IN)

        case ${arr[0]} in
        takeShot)
        if [[ ${#arr[@]} -ne 4 ]]
        then
        echo « [ERROR] takeShot needs 3 arguments : .width .height .pathToSave »
        DHSLog ERROR « takeShot command received with arguments error » « $IN »
        else
        takeShot ${arr[1]} ${arr[2]} ${arr[3]}
        [[ $? -eq 0 ]] || DHSLog ERROR « takeShot ends with error » $?
        fi
        ;;

        takeVideo)
        if [[ ${#arr[@]} -ne 5 ]]
        then
        echo « [ERROR] takeVideo needs 4 arguments : .width .height .duration .pathToSave »
        DHSLog ERROR « takeVideo command received with arguments error » « $IN »
        else

        takeVideo ${arr[1]} ${arr[2]} ${arr[3]} ${arr[4]}
        [[ $? -eq 0 ]] || DHSLog ERROR « takeVideo ends with error » $?
        fi
        ;;

        *)
        echo « [ERROR] UNKNOWN COMMAND »
        DHSLog ERROR « UNKNOWN COMMAND » « $IN »
        ;;
        esac

        J’aime

Laisser un commentaire