Arduino et prises commandées (4 – les essais)

 Arduino philie  Commentaires fermés sur Arduino et prises commandées (4 – les essais)
Mai 112014
 

Arduino

Commande de prises électriques, éclairages…

par transmission 2.4GHz

Premiers essais

 

Les premiers essais ont été menés pour vérifier la portée de la transmission. Deux modules Arduino UNO et deux modules nRF24L01 de base (modèle avec antenne en L) ont été mis en œuvre; après chargement, le premier modules, alimenté par une pile 9V. m’a accompagné, tandis que le second module restait connecté sur le port USB de l’ordinateur.

Le premier module est connecté à un bouton poussoir; on émet l’état du bouton vers le second module; ce dernier reçoit la transmission, lit l’information et la réémet aussitôt vers le premier; le premier module reçoit la transmission et commande une led. Cela permet de tester la transmission dans les deux sens à la fois.

Il y a également les « print » qui vont bien pour suivre le fonctionnement sur la console.

Les 2 programmes à charger :

// L’émetteur……………………..

// Essai de transmission 2.4 GHz avril 2014

// Philippe Redoutey sur bases issues d’internet

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

const int buttonPin = 2; // le N° de l’entrée digitale qui reçoit le bouton
const int ledPin = 3; // le N° de la sortie numérique qui commande la led
int buttonState = 0; // variable pour mémoriser l’état du bouton
byte valeur_octet[1]; // contient la valeur découpée en octet pour l’envoi (pour le test = 1 octet
byte Etatrecu = 0;

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

Mirf.cePin = 8; // CE sur D8
Mirf.csnPin = 7; // CSN sur D7
Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
Mirf.init(); // Initialisation du SPI

Mirf.channel = 0; // On va utiliser le canal 0 pour communiquer (128 canaux disponible, de 0 à 127)
Mirf.payload = sizeof(unsigned byte); // = 1, ici il faut déclarer la taille du « payload » soit du message qu’on va transmettre, au max 32 octets

// RF_SETUP=0000abcd : a=1–>2Mb/s, a=0–>1Mb/s;
// puissance émission bc=00–>-18 dBm, bc=01–>-12dBm, bc=10–>-6dBm, bc=11–>0dBm;
// d=0 pas de gain sur le bruit en réception

Mirf.configRegister(RF_SETUP, 0x06); // 1 Mb/s et 0 dBm (puissance maximum)

Mirf.config(); // Tout est bon ? Ok let’s go !

Mirf.setTADDR((byte *) »nrf02″); // Le 1er module va envoyer ses info au 2eme module
Mirf.setRADDR((byte *) »nrf01″); // On définit ici l’adresse du 1er module

pinMode(ledPin, OUTPUT); // Initialisation de la sortie digitale pour la led

pinMode(buttonPin, INPUT); // Initialisation de l’ebtrée digitale pour le bouton
digitalWrite(buttonPin, HIGH); // met la résistance de tirage au +

Serial.println(« Go ! »);
}

void loop(){

buttonState = digitalRead(buttonPin); // lecture de l’état du bouton
valeur_octet[0] = buttonState;

unsigned long time = millis(); // On stocque le temps actuelle retourné par millis() dans time

// Mirf.send((byte *)&time); // On envoi time en utilisant l’astuce du cast de pointeur sur adresse

Mirf.send(valeur_octet); // On envoie les octets, 1 octet pour le test

while(Mirf.isSending()); // On boucle (attend) tant que le message n’as pas était envoyé

Serial.print(« Octet envoye « ); // impression pour le contrôle du fonctionnement
Serial.print(valeur_octet[0]);
Serial.print( » « );
delay(10);

while(!Mirf.dataReady()){ // On attend de recevoir quelque chose
if ( ( millis() – time ) > 1000 ) { // Si on attend depuis plus d’une seconde
Serial.println(« =(« ); // C’est le drame …
return; // ce cas ne devrait pas se produire !!!
}
}

// Mirf.getData((byte *) &time); // On récupére le message recu

if (!Mirf.isSending() && Mirf.dataReady()){ // Si un message a été recu et qu’un autre n’est pas en cours d’emission
Mirf.getData(valeur_octet); // on récupére le méssage
Serial.print(« Recu : « );
Serial.println(valeur_octet[0]);
}
Etatrecu = valeur_octet[0]; // on récupère l’octet reçu
digitalWrite(ledPin, Etatrecu); // et on commande la led en conséquence
delay(100);
}
Serial.print(« Octet envoye « ); // impression pour le contrôle du fonctionnement
Serial.print(valeur_octet[0]);
Serial.print( » « );
delay(10);

while(!Mirf.dataReady()){ // On attend de recevoir quelque chose
if ( ( millis() – time ) > 1000 ) { // Si on attend depuis plus d’une seconde
Serial.println(« =(« ); // C’est le drame …
return; // ce cas ne devrait pas se produire !!!
}
}

// Mirf.getData((byte *) &time); // On récupère le message reçu

if(!Mirf.isSending() && Mirf.dataReady()){ // Si un message a été reçu et qu’un autre n’est pas en cours d’emission
Mirf.getData(valeur_octet); // on récupère le message
Serial.print(« Recu : « );
Serial.println(valeur_octet[0]);
}
Etatrecu = valeur_octet[0]; // on récupère l’octet reçu
digitalWrite(ledPin, Etatrecu); // et on commande la led en conséquence
delay(100);
}

 

// Le récepteur 2.4 GHz

// Essai de transmission avril 2014
// Philippe Redoutey sur bases issues d’internet

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

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

Mirf.cePin = 8; // CE sur D8
Mirf.csnPin = 7; // CSN sur D7
Mirf.spi = &MirfHardwareSpi; // On veut utiliser le port SPI hardware
Mirf.init(); // on initialise le SPI

Mirf.channel = 0; // On va utiliser le canal 0 pour communiquer (128 canaux disponible, de 0 à 127)
Mirf.payload = sizeof(unsigned byte); // = 1, ici il faut déclarer la taille du « payload » soit du message qu’on va transmettre, au max 32 octets
Mirf.config(); // Tout est bon ? Ok let’s go !

Mirf.setTADDR((byte *) »nrf01″); // Le 2eme module va envoyer ses info au 1er module
Mirf.setRADDR((byte *) »nrf02″); // On définit ici l’adresse du 2eme module

// RF_SETUP=0000abcd : a=1–>2Mb/s, a=0–>1Mb/s;
// puissance émission bc=00–>-18 dBm, bc=01–>-12dBm, bc=10–>-6dBm, bc=11–>0dBm;
// d=0 pas de gain sur le bruit en réception

Mirf.configRegister(RF_SETUP, 0x06); // 1 Mb/s et 0 dBm (puissance maximum)

Serial.println(« Go ! »);
}

void loop(){
byte data[Mirf.payload]; // Tableau de byte qui va stocker le message recu

if (!Mirf.isSending() && Mirf.dataReady()){ // Si un message a été reçu et qu’un autre n’est pas en cours d’émission
Serial.println(« Recu… « );
Mirf.getData(data); // on récupère le message
Mirf.send(data); // et on le renvoit tel quel
Serial.println(« Et renvoye! »);
delay(10);
}
}

Les résultats sont moyennement concluants; si la transmission fonctionne dès le premier essai, la portée n’est pas très importante : le second module étant au sous-sol, la transmission s’arrête quand je suis entre le rez-de-chaussée et le premier étage avec le premier modules (maison en pierres pour le sous-sol et briques au dessus); quand je sors de la maison, la transmission s’arrête à quelques mètres.

 

Seconds essais

Les essais suivants sont destinés d’une part, à améliorer la portée (en utilisant un autre modèle de carte nRF24L01) et d’autre part, à optimiser le code pour fiabiliser les transmissions.

==> suivre…

 

 

 

 

Arduino et prises commandées (3 – protocole de transmission)

 Arduino philie  Commentaires fermés sur Arduino et prises commandées (3 – protocole de transmission)
Mai 012014
 

Arduino

Commande de prises électriques, éclairages…

par transmission 2.4GHz

Protocole de transmission

 

A l’issue de tous ces choix de base et quel que soit le mode de fonctionnement retenu, il est nécessaire de définir la façon dont les informations circulent.

Prérequis :

  • différencier les différents modules avec des plages d’adresses suffisamment étendues (prise, éclairage, capteur de température, thermostat, alarme…).
  • permettre d’identifier le type de commande (programmation, commande directe, demande de statut…),
  • en cas de programmation pour les A/M des prises, permettre plusieurs programmations quotidiennes et/ou hebdomadaires,
  • en cas de programmation pour les éclairages, permettre de commander soit des niveaux directs, soit des plages – temps de variation,
  • … et j’en ai surement oublié,
  • tenir compte du fait que les modules 2.4GHz (nRf24l01) ne permettent de transférer que 32 octets, il faut donc être économe dans l’utilisation de ces octets ou utiliser d’autres astuces. NB : cette valeur de 1 à 32 octets, c’est le Mirf.payload que l’on initialise dans le programme.

J’avais donc opté pour le découpage suivant :

  • Octet 1 (numéroté 0 dans la programmation) : 1ère partie de l’adresse du module ; permet surtout de différencier les types d’interfaces ; de 1 à 255 (on évite d’utiliser la valeur 0) ;
  • Octet 2 : 2ème partie de l’adresse du module ; permet d’identifier le module dans son type ; de 1 à 255 (on évite d’utiliser la valeur 0) ;
  • Octet 3 : type de commande (programmation, commande directe, lecture du statut du module, reset des plages horaires des modules, remise à l’heure, si nécessaire, des horloges des modules… entre l’utilisation des bits à l’unité et les combinaisons possibles, il reste beaucoup de fonctions imaginables) ;
  • Octet 4 : état de la commande directe (0 ou 1 pour une prise, de 0 à 255 pour une lampe) ;
  • Octet 5 : utilisé pour la commande de lampe, sur commande directe, de 1 à 255 pour une variation sur le nombre de secondes indiquées ;

Sur cette première base, il s’avère que 5 octets sont utilisés, il en restait donc 27 de disponibles.

NB : en s’inspirant d’une structure telle que celle du X10 (16 codes maison x 16 codes modules), on aura pu se limiter à un byte d’adresse, soit « seulement » 256 modules ; cela m’a paru trop limitatif et je suis donc resté sur 2 octets pour l’adresse.

Tableau récap pour les 5 premiers octets :

Octet[0]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Utilisation

0

0

0

0

0

0

0

0

Inutilisé

x

x

x

x

x

x

x

x

Adresse première partie

 

Octet[1]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Utilisation

0

0

0

0

0

0

0

0

Inutilisé

x

x

x

x

x

x

x

x

Adresse deuxième partie

Si par exemple, on décide que les prises sont à l’adresse haute 1, le premier module prise portera l’adresse basse 1 et donc l’adresse globale 11 en hexa (soit 00000001 00000001 en binaire).

Si les éclairages sont à l’adresse haute 2, le premier module éclairage sera à l’adresse globale 21…

Octet[2]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Action

0

0

0

0

0

0

0

0

Aucune

0

0

0

0

0

0

0

1

Programmation

0

0

0

0

0

0

1

0

Commande directe

0

0

0

0

0

1

0

0

Lecture statut

x

x

x

x

x

0

0

0

Option à venir

1

0

0

0

0

0

0

0

Remise à l’heure des RTC des modules

1

1

1

1

1

1

1

1

Remise à zéro (notamment les plages horaires programmées pour les modules prises)

 

Octet[3]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Action

0

0

0

0

0

0

0

0

Commande A/M

(prise)

0

0

0

0

0

0

0

1

x

x

x

x

x

x

x

x

Niveau d’éclairement

(lampe)

A noter que la commande de volet – écran, peut se faire sans problème avec une fonction A/M sous réserve d’avoir un relais mécanique en sortie.

Octet[4]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Action

x

x

x

x

x

x

x

x

Temps de variation lumineuse en secondes

(entre valeur actuelle et valeur demandée dans l’octet[3])

Si ma prise est à l’adresse basse 1 et que je veux lui envoyer un ordre direct de marche, je vais émettre 01h, 01h, 02h, 01h, 00h.

Pour les prises, j’étais parti sur 2 ou 3 programmation quotidiennes possibles mais je me suis rendu compte que je bloquais la possibilité d’avoir des programmations différentes, par exemple, pour la semaine et le WE.

J’étais donc reparti sur le fait de prévoir le maximum de possibilités en utilisant les 27 octets restants ; néanmoins, il me fallait un octet pour définir le ou les jour(s) de la semaine et 4 octets (compactables en 3 avec du concaténage) pour définir une heure de début (marche) et une heure de fin (arrêt).

J’avais donc redéfini le mode de programmation des heures, découpés de la façon suivante :

  • le premier octet contient un numéro de jour ou une combinaison : 1 octet = 8 bits, 1 semaine = 7 jours, ce qui permet toutes les combinaisons possibles, y compris des commandes répétitives, la valeur 0 permet au module de comprendre qu’il ne gère pas les octets suivants,
  • le second octet contient la valeur de l’heure pour l’allumage,
  • le troisième octet contient la valeur des minutes pour l’allumage,
  • le quatrième octet contient la valeur de l’heure pour l’extinction,
  • le cinquième octet contient la valeur des minutes pour l’extinction.

Et là, je vous entends me dire : reste 27 octets, 5 octets par commande, cela ne fait plus que 5 programmations possibles, c’est pas assez… Et je suis d’accord !

Donc, il faut non pas envoyer toutes les plages horaires lors d’une transmission mais envoyer les plages horaires à raison de 1 par transmission et les mémoriser dans le module ; il faut donc faire attention dans ce cas de ne pas « exploser » la capacité mémoire du module et de savoir mémoriser les données ; dans ce but, on va utiliser la partie EEPROM du module Arduino NANO qui fait 512 octets (pour la version ATmega168) et 1ko (pour la version ATmega368) ; du coup, on se retrouve avec la possibilité de programmer 512 / 5 = 102 pages horaires.

Dans la mesure où il sera intéressant de pouvoir récupérer ces info (avec le mode « lecture du statut ») et de les afficher, il faudra être raisonnable. De mon point de vue, 4 à 5 programmations différentes par semaine doit représenter la majorité des besoins.

Tableau récap pour les octets correspondants aux jours et heures:

Octet[5]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Jour

0

0

0

0

0

0

0

0

Inutilisé

0

0

0

0

0

0

1

0

Lundi

0

0

0

0

0

1

0

0

Mardi

0

0

0

0

1

0

0

0

Mercredi

1

0

0

0

0

0

0

0

Dimanche

1

1

0

0

0

0

0

0

Samedi + dimanche

1

1

1

1

1

1

1

0

Semaine complète

0

0

1

1

1

1

1

0

Du lundi au vendredi

 

Octet[6]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Heure de début

0

0

0

0

0

0

0

0

00hxx

0

0

0

0

0

0

0

1

01hxx

0

0

0

0

0

0

1

0

02hxx

0

0

0

0

0

1

0

0

04hxx

0

0

0

0

0

0

1

0

0

1

0

18hxx

0

0

0

..

0

0

0

1

0

1

1

1

23hxx

 

Octet[7]

Byte 7

Byte 6

Byte 5

Byte 4

Byte 3

Byte 2

Byte 1

Byte 0

Minutes de début

0

0

0

0

0

0

0

0

xxh00

0

0

0

0

0

0

0

1

xxh01

0

0

0

0

0

0

1

0

xxh02

0

0

0

0

0

1

0

0

xxh04

0

0

0

0

1

0

1

0

1

0

xxh42

0

0

0

..

0

0

1

1

1

0

1

1

xxh59

Idem pour octet[8] pour l’heure de fin et pour octet[9] pour les minutes de fin.

Au total, par transmission, on n’enverrait donc que 10 octets (Mirf.payload = 10).

Comme les modules récepteurs sont intelligents et stockent les informations, on n’envoie ces informations que lorsque l’on veut modifier la programmation, c’est-à-dire pas souvent.

Gestion du statut : cette fonction permet de récupérer l’état d’un module prise ou d’un module lampe, de lire la programmation horaire pour un module prise, de récupérer une valeur, par exemple pour un module thermomètre…

Adressage des modules : dans un premier temps, je vais préparer mes modules avec les adresses programmées en dur ; ultérieurement, j’aimerais faire en sorte qu’un module soit en phase d’auto-apprentissage pendant, par exemple 30 secondes après sa mise sous tension (et sous réserve qu’il n’ait pas déjà une adresse, auquel cas, il faut d’abord passer par une commande de remise à zéro).

 

Ce n’est donc pas terminé car je voudrais encore améliorer plusieurs points :

1) permettre d’utiliser un module Arduino pour commander plusieurs prises différentes; cela signifie qu’un module prise ne répond pas qu’à une seule adresse mais à plusieurs;

2) rendre possible le fait de reseter les modules;

3) reprogrammer un module (après l’avoir reseter ou à la première utilisation); il faut donc gérer le fait que l’adresse est « vide » pour être alors en mode programmation puis ne plus le faire quand l’adresse est enregistrée.

Suite : voir article sur les premiers essais