Projet domotique

Bonjour,

Je ne suis pas le premier ni ne serais le dernier à me lancer dans ce type de projet.
J'ai acheté mon premier Arduino (un Ethernet R3), il y a un mois.
J'ai déjà fait un peu de programmation en bash et VB mais jamais en C.
A part U=RI et P=UI en courant continu, je ne connais pas grand chose en électronique.
Je suis autodidacte et "google-ise" beaucoup pour m'auto-former, mais malgré cela l'aide des membres d'un forum est indispensable.
Je souhaite partager mes avancées afin qu'elles profitent à d'autres membres.

Le projet est d'installer un clavier matriciel (en l'occurrence 4x4) à chaque niveau de ma maison, soit un au garage, un au RDC et un à l'étage. Chaque clavier sera relié à un Arduino Ethernet POE. Le choix du POE permet d'avoir à amener qu'un seul câble. Un buzzer sera aussi relié à la carte afin de confirmer par un bip la bonne prise en compte de chaque appui sur les touches du clavier et de confirmer par 4 bips la bonne prise en compte de la commande par l'Arduino distant. D'autres Arduino seront positionnés à côté des organes à commander (Porte de garage, portails, volets roulants, etc...).

Le mode communication se fera par des trames UDP en broadcast sur un sous réseau uniquement occupé par les cartes Arduino et une machine sous Linux chargé de loggé les trames UDP et d'envoyer des comptes rendus d'action par SMS. Des commandes pourront aussi être passées par SMS.

Voici l'ébauche du protocole de communication dans la partie DATA des trames UDP.

ASK MaMACaddress MonTempsDeFct MonIPchoisie/Demande si adresse IP disponible
3 6 4 4 /17 bytes
CHG MaMACaddress MonTempsDeFct IPdéjàPrise MACaddressDestinataire/Réponse IP prise
3 6 4 4 6 /23 bytes
DHC MaMACaddress MonTempsDeFct/Prévenir les autres que le serveur DHCP est en ligne
3 6 4 /13 bytes
CMD MaMACaddress MonTempsDeFct N°deCommande/Passer une commande à un autre
3 6 4 3 /16 bytes
RTN MaMACaddress MonTempsDeFct MACaddressDestinataire/Retour commande exécutée
3 6 4 6 /19 bytes
VRB MaMACaddress MonTempsDeFct MonIP CommandeEffectuée/Verbose loggé sur PC
3 6 4 4 3 /20 bytes

Voici le début de l'histoire.

MonTempsDeFct est envoyé sous la forme de 4 octets avec:

octet1 : nb de jour
octet2 : nb d'heure
octet3 : nb de minute
octet4 : nb de seconde

Il s'obtient avec:

byte tempsDeFct[4];
milli2JHMS(millis(),tempsDeFct);
.............
void milli2JHMS(unsigned long depart,byte *JHMS)
{
  /*
  Pour millis() maxi = 4 294 967 295 ((2^32)-1)
  depart = 4294967295
  soit 0x31 0x11 0x02 0x2F ou
  49J 17H 2M 47S
  */
  depart=depart/1000;
  JHMS[0]=depart/86400;
  JHMS[1]=(depart%86400)/3600;
  JHMS[2]=(depart%3600)/60;
  JHMS[3]=depart%60;
}

Cela m'a permis de savoir que millis() revenait à zéro au bout d'un peu plus de 49 jours.

Je ne sais pas si cela me servira, mais dans la foulée j'ai fait la fonction inverse.

unsigned long JHMS2milli(byte JHMS[4])
{
  unsigned long depart=0;
  depart=1000*(JHMS[0]*86400+JHMS[1]*3600+JHMS[2]*60+JHMS[3]);
  return depart;
}

Toute ce qui suit était avant que je découvre la librairie EEPROM (mais ça, j'en parlerai après).

L'affectation des adresses IP est prévue grâce à un serveur DHCP (j'aime bien ce type de configuration qui permet de gérer l'ensemble de mes adresses IP dans une seule table). Seulement, que ce passe t'il si il est injoignable ou si il y a une coupure de courant. Au rétablissement du courant, les arduino auront largement finis les 15 requêtes DHCP discover avant que le serveur DHCP soit de nouveau en ligne. Je me suis dit qu'elles devaient être assez grandes pour se débrouiller toutes seules à partir du moment ou elles peuvent discuter ensemble.

D’où la première trame ASK qui dit voici mon adresse MAC, j'aimerai bien prendre cette adresse IP.

ASK MaMACaddress MonTempsDeFct MonIPchoisie/Demande si adresse IP disponible
  3         6             4                 4       /17 bytes
const byte ASK_BOUCLE=10;
byte askBoucle = ASK_BOUCLE;
IPAddress monIP(10, 0, 0, 2);
IPAddress monBroadcast(10, 255, 255, 255);
byte maMac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const unsigned int udpPort = 10666;

..............................................
void ask(void)
{
  byte i;
  byte tempsDeFct[4];

  askBoucle--;
  udp.beginPacket(monBroadcast, udpPort);
  udp.write("ASK");
  udp.write(maMac,6);
  millis2JHMS(millis(),tempsDeFct);
  udp.write(tempsDeFct,4);
  for (i=0;i<4;i++)
  {
    udp.write(monIP[i]);
  }
  udp.endPacket();  
}

Dans le setup, après 15 DHCP discover infructueux, la fonction ask est appelée un maximum de 10 fois (ASK_BOUCLE) toute les 5 secondes. Sans réponse monIP est affectée à la carte, sinon askBoucle est remise à la valeur ASK_BOUCLE et monIP passe à l'IP suivante.

IPAddress ipPlus1(IPAddress tip)
{
  tip[3]++;
  if(tip[3] > 254)
  {
    tip[3] = 1;
    tip[2]++;
    if(tip[2] > 254)
    {
      tip[2] = 0;
      tip[1]++;
      if(tip[1] > 254)
      {
        tip[3] = 2;
        tip[2] = 0;
        tip[1] = 0;
      }
    }
  }
  return tip;
}

Je suis toujours dans le cas ou le serveur DHCP n'est pas joignable. Sur une remise en tension après coupure de courant, toutes les cartes démarrent ensemble. Toutes ont par défaut l'adresse IP 10.0.0.0, l'IP 10.0.0.1 est réservée à la passerelle Linux. Lorsque une demande ASK arrive, si les deux cartes demandent la même IP, la carte compare son adresse MAC avec celle de l'autre carte, si son adresse MAC est plus petite, elle demande à l'autre carte de changer d'IP par la demande CHG.

void chg(void)
{
  byte tempsDeFct[4];
  byte i;
  
  udp.beginPacket(monBroadcast, udpPort);
  udp.write("CHG");
  udp.write(maMac,6);
  millis2JHMS(millis(),tempsDeFct);
  udp.write(tempsDeFct,4); 
  for (i=0;i<4;i++)
  {
    udp.write(sonIP[i]);
  }
  udp.write(saMac,6);
  udp.endPacket();
}

La commande CHG est aussi appliquée si la carte a déjà son IP de choisie et que l'autre carte veut la même IP.

Comparaison des deux adresses MAC.

boolean plusGrand(byte *tmac1, byte *tmac2)
{
  byte i;
  for(i=0;i<6;i++)
  {
    if ( tmac1[i] < tmac2[i] )
    {
      return false;
    }
    if ( tmac1[i] > tmac2[i] )
    {
      return true;
    }
  }
}

A ce stade, je suis tombé sur un problème puisque je commençais par affecter une première IP.

IPAddress IPTemp(10, 0, 0, 0);
IPAddress IPgateDNS(10, 0, 0, 1);
IPAddress monIP(10, 0, 0, 2);
IPAddress monBroadcast(10, 255, 255, 255);
IPAddress monMask(255, 0, 0 ,0);

Ethernet.begin(maMac, IPTemp, IPgateDNS, IPgateDNS, monMask);

Et lorsque la carte, pour une même monIP, n'avait pas reçu de réponse à ses 10 demandes ASK, je lui changeais son IP avec :

Ethernet.begin(maMac, monIP, IPgateDNS, IPgateDNS, monMask);

Lors d'envoi de nouvelles trames UDP, le programme restait bloqué sur l'instruction udp.endPacket();

Le changement d'IP à chaud ne lui faisait pas plaisir.
En regardant dans le code de la librairie EthernetUDP il y a une instruction qui n'est nulle part commentée : stop()
Il faut donc faire:

          Ethernet.begin(maMac, monIP, IPgateDNS, IPgateDNS, monMask); //changement IP à chaud
          udp.stop(); //stopper
          udp.begin(udpPort); //redémarrer

Pour faire les essais d'attribution d'IP (ASK,CHG), je charge le même programme sur deux cartes. Je suis au stade de mise au point et à chaque rechargement, il faut modifier pour chaque carte son adresse MAC, cela devient lourd même si il s'agit de commenter une ligne et en décommenter l'autre.

byte maMac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
//byte maMac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };

Grâce à vous, j'ai vu un commentaire disant que des données pouvaient être mise dans l'EEPROM. J'avais déjà vu le nom de la librairie sans jamais avoir osé regarder de plus près en me disant que cela devait être une prise de tête. Qu'elle fut ma surprise en voyant la simplicité pour écrire et lire dans cette mémoire. J'ai donc fait un sketch pour mettre en dur l'adresse MAC de chaque carte.

/* Permet de stocker une adresse MAC dans l'EEPROM de la carte.
   Depuis l'adresse 0 jusqu'à l'adresse 5.
   
   Le code suivant est à ajouter dans votre sketch pour récupérer
   l'adresse MAC stockée dans l'EEPROM.
   
   #include <EEPROM.h>
   
   byte mac[6];
   
   void setup()
   {
     for (int i = 0; i < 6; i++)
     { 
       mac[i]=EEPROM.read(i);
     }
   }
   
   void loop
   {
   }                                                             */
   
#include <EEPROM.h>

byte mac[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

void setup()
{
  Serial.begin(9600);
  delay(5000);
  
  Serial.println("Ecriture de la MAC address dans l'EEPROM");
  for (int i = 0; i < 6; i++)
  {
    //EEPROM.write(i, mac[i]); // A décommenter lorsqu'on est sur du résultat
                               /* Après l'éxecution de ce sketch, recommentez cette ligne
                                  et rechargez le sketch afin d'éviter la réécriture dans
                                  l'EEPROM à chaque reboot de la carte */
    Serial.print(i);
    Serial.print(" ");
    Serial.println(mac[i],HEX);
  }
  
  Serial.println("Vérification de l'écriture de la MAC address dans l'EEPROM");
  for (int i = 0; i < 6; i++)
  {
    Serial.print(i);
    Serial.print(" ");
    Serial.print(EEPROM.read(i),HEX);
    Serial.println();
  }  
}

void loop()
{
}

Les premiers tests du clavier sont concluants. J'ai repris le code de l'excellent site www.mon-club-elec.fr/ dont j'ai expurgé la partie afficheur LCD puisque je ne m'en sert pas. J'ai raccordé sur une broche un buzzer 5VCC/20MA.
Attention au raccordement du clavier, car les pins de mon clavier n'ont pas le même ordre:
01 02 03 04 05 06 07 08
L2 L3 C1 L4 C2 C3 C4 L1
J'ai câblé le clavier de manière à garder le même brochage sur la carte Arduino.

Sketch permettant de choisir la fréquence et la durée du bip.

#include <Keypad.h> // Librairie pour clavier matriciel

// Clavier 16 touches, 4 lignes et 4 colonnes
const byte LIGNES = 4;
const byte COLONNES = 4;

const byte C1=5; // colonne 1 sur pin 5
const byte C2=4; // colonne 2 sur pin 4
const byte C3=3; // colonne 3 sur pin 3
const byte C4=2; // colonne 4 sur pin 2

const byte L1=19; // ligne 1 sur pin 19 (A5)
const byte L2=18; // ligne 2 sur pin 18 (A4)
const byte L3=17; // ligne 3 sur pin 17 (A3)
const byte L4=16; // ligne 4 sur pin 16 (A2)

const byte BU=6; // buzzer sur pin 6

// Tableau des touches du clavier
char touches[LIGNES][COLONNES] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte BrochesLignes[LIGNES] = {L1, L2, L3, L4};
byte BrochesColonnes[COLONNES] = {C1, C2, C3, C4};

char touche;

int note=2500;
int duree=250;

// Utilisation de la librairie Keypad
// la librairie configure les pins de ligne en ENTREE avec pullup interne
// la librairie configure les pins de colonne en SORTIE
Keypad clavier = Keypad( makeKeymap(touches), BrochesLignes, BrochesColonnes, LIGNES, COLONNES );

void setup()
{
  Serial.begin(9600);
}

void loop()
{  
  touche = clavier.getKey();
  if (touche != NO_KEY)
  {
    Serial.print(touche);
    Serial.print(" ");
    Serial.print(note);
    Serial.print(" ");
    Serial.println(duree);
    tone(BU,note,duree);
    switch (touche)
    {
      case '1':
        note=note++;
        break;
      case '2':
        note=note+10;
        break;
      case '3':
        note=note+100;
        break;
      case '4':
        note=note--;
        break;
      case '5':
        note=note-10;
        break;
      case '6':
        note=note-100;
        break;
      case '*':
        duree=duree+50;
        break;
      case '#':
        duree=duree-50;
        break; 
    }
  }
}

J'ai eu quelques problèmes d'approvisionnement de 3 cartes Ethernet POE pour continuer mes tests.

Par ebay, j'ai reçu les cartes avec le port Ethernet qui ne fonctionnait pas sur chaque carte (leds du port inactives) et 2 cartes dont le bootloader semblait hs. J'ai été obligé de passer par une déclaration de litige auprès de Paypal pour me faire rembourser.

Puis par une boutique française ou les cartes étaient en rupture de stock.

J'ai donc pu faire les tests d'attribution d'IP avec 5 cartes.

Je joins quelques photos du montage, de l'injecteur et d'une copie d'écran de Wireshark et du sketch.

PacClavier.ino (4.79 KB)