home castoo
chapitre electronique
Electronique carte Yunshan HW622

Gestion d'un volet plus ou moins lourd
MQTT durée de luminosité

avril 2020

Le jour se lève, mon volet s'ouvre. La nuit arrive il se ferme !

Ecran Nextion 4.3 inch et 2.4 inch

Bon OK c'est du déjà vu ! Mais si l'on ajoute un suivi de la durée d'ensoleillement avec un beau petit graphique c'est plus tentant !

Remarque préliminaire : Si vous désirez simplement commander un volet qui possède déjà un moteur et des fils de commande, cet article ne concerne pas ce type de cas . Pour le cas d'un volet déjà équipé il vous suffit d'utiliser une carte HW-622 (Esp-8266 1 relai) de Yunshan décrite dans un autre article du site et pour quelques euros vous aurez un système que vous pourrez commander à distance, tout est déjà disponible sur la carte et il ne reste que du très très facile pour obtenir un résultat.
Ici nous allons nous intéresser à un volet qui ne possède aucune électronique , ni moteur, ni commande...
Que le volet ou la porte soit petite ou trés grosse cela ne changera rien ou pas grand-chose, nous allons voir deux cas avec des moteurs bien différents...

Un petit cahier des charges :
- Ouverture du rideau dès l'arrivée d'un seuil de luminosité bien précis mais réglable.
- Fermeture du rideau dès que le niveau de luminosité déterminé n'est plus atteint.
- Mémorisation des heures d'ouverture et de fermeture (auto. heures soleil) et envoi du delta à un serveur Mosquitto.
- Possibilité d'ouvertures et de fermetures forcées sans modification de la mémorisation et de l'envoie des vrais horaires à Mosquitto.
- Protection de l'état actuel du moteur dans l'eeprom pour éviter des détériorations du rideau.
- Utilisation d'une horloge RTC précise et sauvegardée pour des calculs précis.
- Affichage sur un petit écran de l'horaire d'ouverture ou de fermeture automatique.
- Réglage du mouvement moteur nécessaire à l'ouverture et à la fermeture.
- Paramétrage complet par l'utilisateur par menus (réseau, moteur, seuil de luminosité).

Retroussons nos manches, il y a du boulot !



Voyons déjà cette histoire de moteur Pas à pas

Je ne vais pas me lancer dans des grandes explications (les pas, les demi-pas, les quart de pas... la charge, les moteurs hybrides, le stator...) qui seront forcément moins bonnes que celles de l'excellent site dont je vous donne l'adresse, à voir impérativement avant de lire la suite : Ressources.univ-lemans.fr principe de fonctionnement du moteur pas à pas avec un petit dessin animé très bien fait..

Cas du moteur BYJ48

Pour notre cas, voyons tout d’abord le petit moteur : Un kit BYJ48 qui comprend le moteur qui est démultiplié par 64 (chaque fois que le moteur fera 64 tours l'axe ne fera qu'un tour) Ce moteur comporte deux enroulements et chaque point central des enroulements est relié au + 5v. Bobines moteur pàp Il n'est pas possible de commander un moteur avec les quelques milliampères disponibles à la sortie d'un Gpio de l'ESP, c'est pour cela que l'on utilise un driver qui va convertir les faibles signaux de l'ESP en signaux pouvant encaisser 500 mA, c'est donc le rôle du CI ULN2003. Chaque signal Gpio passe donc dans cet "ampli" et attaque un des fil de la bobine du moteur. Dans notre cas nous allons nous intéresser aux fils rose, orange, jaune et bleu le fil rouge étant directement raccordé au +5v.
Nous n'allons pas utiliser la librairie afin de mieux comprendre le principe.


// Pins de racordement au moteur
int mot_1 = D5; // Gpio 14
int mot_2 = D6; // Gpio 12
int mot_3 = D7; // Gpio 13
int mot_4 = D8; // Gpio 15
	

Voilà nos bobines sont raccordées à l'ESP, voyons maintenant comment nous allons alimenter ces bobines pour faire avancer (ou reculer) notre moteur. Comme vous avez consulté le site de l'UFR du Mans vous avez une petite idée... Avec nos quatre fils nous allons déterminer huit pas pour avancer et huit pour reculer pour que cela soit pratique nous allons stocker les combinaisons sous forme binaire dans un demi-octet de poids faible.
Par exemple si j'écris 0b00000110 cela signifie que j'alimente mot_2 et mot_3
si j'écris 0b00001001 cela signifie que j'alimente mot_1 et mot_4
Je vous laisse suivre le Gpio, le fil de couleur et la portion de bobine qui sont alimentés successivement (et de corriger si je me suis trompé !) OK alors, je peux donc stocker mes huit pas dans un tableau comme celui-ci (il serait bien sur possible de stocker ces valeurs en hexa, mais c'est tellement plus lisible en binaire !):


// Détermine l'ordre d'alimentation des bobines du moteur en avance et en arriere
const byte t_av[]  = {0b00001000,0b00001100,0b00000100,0b00000110,0b00000010,0b00000011,0b00000001,0b00001001};
const byte t_arr[] = {0b00000001,0b00000011,0b00000010,0b00000110,0b00000100,0b00001100,0b00001000,0b00001001};
	

Pour avancer de huit pas il suffit donc d'utiliser une boucle "for" qui va balayer le tableau t_av[], le delay(v_vit) permet de jouer sur la vitesse de changement entre chaque pas, ce qui va se répercuter sur la vitesse du moteur.


// Avance équivalent d'un pas
void av_1_tour(){ 
	for (uint8_t pas = 0; pas < 8; pas++){
		digitalWrite(mot_1, bitRead(t_av[pas],3));
		digitalWrite(mot_2, bitRead(t_av[pas],2));
		digitalWrite(mot_3, bitRead(t_av[pas],1));
		digitalWrite(mot_4, bitRead(t_av[pas],0));
		delay(v_vit);
	}
}
	

Et pour recculer de huit pas il suffit donc d'utiliser une boucle "for" qui va balayer le tableau t_arr[]...


// Arriere équivalent d'un pas
void arr_1_tour(){
	for (uint8_t pas = 0; pas < 8; pas++){
		digitalWrite(mot_1, bitRead(t_arr[pas],3));
		digitalWrite(mot_2, bitRead(t_arr[pas],2));
		digitalWrite(mot_3, bitRead(t_arr[pas],1));
		digitalWrite(mot_4, bitRead(t_arr[pas],0));
		delay(v_vit);
	}
}
	

Il faut aussi prévoir ce que l'on décide de faire lorsque le moteur n'est plus utilisé, dans notre cas nous allons simplement couper l'alimentation sur les quatre fils de notre moteur, notre volet ne va pas se mettre à tourner seul !
Par contre dans de nombreuse utilisations des moteurs il est nécessaire de conserver une alimentation de façon à ce que si une force s'exerce sur le moteur celui-ci résiste au déplacement, en principe pour réaliser cela on n'alimente qu'à un certain pourcentage les bobines (ceci est paramétrable sur les cartes de gestion un peu plus évolué que la nôtre.)


// Coupe l'alim sur le moteur
void stop_moteur(){
	digitalWrite(mot_1, LOW);
	digitalWrite(mot_2, LOW);
	digitalWrite(mot_3, LOW);
	digitalWrite(mot_4, LOW);
}
	


Cas du moteur NEMA17

Voyons maintenant le cas du moteur un peu plus gros le NEMA17 et de sa carte driver la TB6560.
Le moteur a également deux bobines
la premiere fils : bleu et rouge et la deuxième fils : vert et noir. (facile à vérifier avec un ohmmètre).
Pour le raccordement du moteur sur la carte il faut donc prévoir une alimentation indépendante de celle de l'esp.
Dans mon cas pour les tests, j'ai utilisé une alimentation stabilisée qui délivre jusqu'à 10A sous 24v, mais il est possible d'utiliser bien plus petit genre 12v 1A pour notre NEMA17. (Perso en installation finale, j'imagine bien une petite batterie 12v rechargée par un petit panneau solaire...) Dans tous les cas nous devons donc raccorder sur la carte drivers TB6560 le + et le - de cette alim.
Ensuite les fils du moteur: (attention ne pas mélanger le petit noir et le petit rouge du moteur avec ceux de l'alimentation !)
fil bleu sur B-
fil rouge sur B+
fil vert sur A-
fil noir sur A+
Coté Esp nous allons avoir besoin de trois Gpio (deux peuvent éventuellement nous suffire) :
sur la carte drivers les CLK- CW- et EN- peuvent être reliés ensemble et raccordé à la masse.
Ensuite nous allons raccorder CLK+ à Gpio14 (D5) et CW+ à Gpio12 (D6) (oublions pour l'instant EN+)
La broche D5 va être utilisée pour non pas envoyer un signal oui (1) ou non (0) mais pour envoyer un signal de type PWM.
La PWM est un signal qui oscille entre les 1 et les 0 à une fréquence déterminée mais dont on fait varier la proportion (durée) de 1 par rapport au 0 c'est pas forcément clair ! Un petit dessin :

pwm On voit ici que le 1 est toujours à 5v et le 0 à 0.
On voit ici que la fréquence n'est pas modifiée les petites flèches rouges sont sur les fronts montants et elles sont toutes alignées.
On voit ici que ce qui change c'est la partie verte (le temps ou le signal reste à 1 (ou à 0)) c'est cette variation qui se nomme PWM (Pulse Width Modulation) en français MLI (Modulation de largeur d'impulsion).
Dans notre cas (signal CLK), si nous avions besoin de faire varier la vitesse de notre moteur il faudrait changer le temps ou le signal reste haut (à 1).


// Pins de racordement au moteur
int mot_avance = D5;
int mot_sens = D6;
	

Donc CLK va nous servir à faire tourner le moteur. Pour CW il va nous permettre de choisir le sens de rotation.
Pour déterminer le nombre de pas nous allons jouer sur le temps que nous allons laisser le signal CLK actif. Si par exemple on mesure que pour faire faire un tour à notre moteur, il faut un delay de 1600 et que nous avons besoin de 5 tours il suffira de laisser le signal CLK actif pendant 5 fois 1600. Pour utiliser la PWM il faut utiliser la syntaxe spécifique "analogWrite(mot_avance,200);" 200 étant la durée haute du signal c'est donc ce chiffre à faire évoluer pour un changement éventuel de vitesse (pas besoin dans notre cas). Ces remarques nous amènent au code suivant : (C'est dans la boucle de delay, qu'il faudra prévoir la surveillance de l'intensité du moteur pour un arret d'urgence éventuel !)


// Ferme ou ouvre le rideau v_com = O Ouvre F Ferme
void action_rideau(char v_com){
	uint16_t x = 0;
	if (v_com == 'F') digitalWrite(mot_sens, HIGH); else digitalWrite(mot_sens, LOW);
	analogWrite(mot_avance,200);
	while( x < v_nbtour ){
		delay(v_vit);
		x++;
	}
	analogWrite(mot_avance,0);	
}
	

Reste à voir le paramétrage de la carte driver :

- Intensité maximum que la carte laissera passer au moteur : SW1 à SW3 + le petit S1 permet un réglage entre 0.3A jusqu'à 3A (perso 1A).
- Courant de maintien à l'arrêt (c'est ici que l'on choisi la résistance du moteur pour garder sa position lorsque qu'il n'est pas entrainé) (perso 20%). mais je pense utiliser le signal EN de façon à relacher complètement le moteur... (A préciser...)
- Pas, demi pas, quart de pas ou 8eme ou 16eme de pas (pour une même vitesse sur signal CLK vous pouvez régler la précision de déplacement au détriment de la vitesse) (perso 1/8eme).
- Decay setting : Façon dont le courant circule dans le moteur (ce paramètre est à régler à l'oreille de façon à ce que le moteur fasse le moins de bruit possible !).


L'interface homme machine (IHM)

- L'écran LCD Oled SD1306 est déjà bien documenté dans ce site, vous trouverez les quatre fichiers à intégrer dans le même répertoire que le code du volet dans la partie électronique.(ATTENTION CA C'ETAIT AVANT QUE JE NE COMPTE VRAIMENT LES GPIO DE L'ESP ! LIRE LA SUITE...)
- L'encodeur rotatif est également documenté dans le site, c'est lui qui permet d'interagir avec l'écran LCD, il est géré dans les class ci-dessous.
Les fichiers sai_oled.cpp et sai_oled.h contiennent une class C++ pour gérer la saisie de texte.
Les fichiers menu_oled.cpp et menu_oled.h contiennent une class C++ pour gérer les menus.
L'encodeur sera donc le seul bouton, il permettra de se déplacer dans les menus de paramètres , mais surtout il permettra de gerer l'ouverture ou la fermeture forcée du volet.
On est en pleine journée, le système a ouvert le volet à 6h du matin, mais sur le coup de 14h vous avez envie d'une petite sieste !
C'est là qu'intervient la fermeture forcée, ensuite, à la fin de la sieste, soit vous rouvrez le volet en ouverture forcée, soit le système le soir arrivé s'apercevra que le volet a été fermé dans l'après-midi. Il faut donc qu'il ne cherche pas à le refermer de nouveau (risque de casse du volet), il ne faut pas non plus que ces ouvertures ou fermetures forcées modifient le calcul de luminosité ! Par contre même si le système ne modifie pas l'état du volet il faut quand même qu'il envoie l'information d'ensoleillement à Mosquitto (MQTT).
Le système ne rouvrira le volet que le lendemain matin...
Pour faciliter l'ouverture ou la fermeture forcée sans être obligé de regarder l'écran et de faire défiler des menus ce choix est placé en tête de menu, donc un double appuie sans même regarder l'écran ouvre où ferme le rideau.


Utilisation de l'eeprom de l'Esp-8266

L'utilisation de l'eeprom va permettre de mémoriser les paramètres wifi, serveur MQTT, nombre de tour de moteur pour ouvrir ou fermer, à voir seuil de luminosité...
Je vais également prendre la précaution de stocker dès l'ouverture ou la fermeture la date et l'heure de façon a ce que s’il y a une coupure de courant le système puisse s'y retrouver : Le volet est ouvert/fermé donc je ne casse pas tout en insistant dans le mauvais sens !


Le circuit d'horloge DS3231

J'aurais bien sur pu récupérer l'heure par internet avec la connexion wifi, mais au cas ou il n'y a pas d'internet de disponible j'ai préféré utiliser un circuit d'horloge sous I2C le DS3231, c'est pas chère, c'est très précis et fiable et en plus sur I2c comme on a déjà le LCD Oled on a pas besoin de Gpio supplémentaire. La librairie spécifique n'est pas nécessaire ici nous n'utilisons ni le générateur de fréquence ni l'eeprom du circuit ni les alarmes.
Le circuit comporte une petite pile CR2032 qui vous assure la tranquilité pour plusieurs années...
Il faut par contre enlever la petite résistance (voir sur photo) pour la recharge de la batterie qui n'est pas utilisée avec le type de pile CR2032 livrée avec le circuit.
Nous n'avons pas de problème de conflit d'adresse sur le bus I2C, le LCD, le RTC et à voir le INA219 n'ont pas les mêmes adresses par défaut.
Pour initialiser la date et l'heure il faut dé-commenter la ligne suivante dans le setup lors de la compilation, puis repasser en commentaire la ligne pour les compilations suivantes.
// RTC.adjust(DateTime(__DATE__, __TIME__));
A voir... je vais prévoir la mise à jour de la date et de l'heure dans les paramètres... DS3231 avec pile CR2032


La chasse aux Gpio !

Comme j'en avais peur, il me manque des E/S sur l'Esp-8266 pour gérer le projet comme je l'ai défini !
1 entrée analogique pour la photo-résistance.
2 E/S pour l'interface I2C SCL SDA.
3 ou 4 sorties pour le moteur.
3 entrées pour l'encodeur rotatif.
BILAN 9 E/S sachant que sur l'Esp-8266 il est déconseillé d'utiliser D3, D4 et D8 qui sont réservés pour le démarrage (bootloader) ou pour flasher un nouveau "firmware" (certains les utilisent, mais il faut ajouter des transistors, des condensateur, des résistances pour polariser ces e/s et attendre que le boot soit complet, trop compliqué pour moi !). Certains utilisent aussi les signaux Tx et Rx, mais c'est pas simple non plus !
Ce projet est conçu pendant le confinement et l'approvisionnement en composant est compliqué et long sinon j'aurais commandé un circuit qui ajoute des E/S sur le bus I2C, c'est tout prêt et la bonne librairie est disponible...
Dans cette période spéciale je fais donc le tour de mes fonds de tiroir, j'y trouve des circuits I2C qui sont conçus pour le raccordement des anciens LCD 2 lignes genre "1602". Ces petites cartes sont à base de PCF8574 un composant qui permet d'avoir 8 E/S accessible avec I2C. C'est top !
De toute façon je ne les aurai plus utilisé, les écrans 2 lignes c'était avant, on a mieux et moins chere aujourd'hui.
Le fonctionnement est super simple il n'y a qu'un registre et nous avons vu comment utiliser l'interface I2C ici avec le MCP9808.
Dans de nombreux montage ou j'utilise un écran oled et un encodeur rotatif je regrette de devoir consacrer 5 gpio pour l'IHM. Donc je vais utiliser la petite carte code 4585 équipée du PCF8574 pour raccorder mon encodeur ce qui me permettra d'avoir une IHM de seulement les 2 fils de l'I2C.
Bon un peu plus haut je disais que je n'avais plus besoin de me soucier de l'IHM que j'avais des class C++ toutes prêtes ! Et bien c'est raté mais l'adaptation à l'I2C ne me demandera que peu de temps, les nouveaux fichiers à joindre au projet sont donc :
Les fichiers sai_oled_i2c.cpp et sai_oled_i2c.h contiennent une class C++ pour gérer la saisie de texte sous I2C.
Les fichiers menu_oled_i2c.cpp et menu_oled_i2c.h contiennent une class C++ pour gérer les menus sous I2C.

Extesion entrée sortie sur i2c Extesion entrée sortie sur i2c
Côté programme voilà ce qui change la ligne :


digitalRead(this->PinDT)
	

est remplacé par un appel de la petite fonction


this->li_rot_dt()
	

ci-dessous :


// retourne etat du signal DT
bool sai_oled::li_rot_dt(){
	uint8_t lect_octet;
	Wire.requestFrom(adr_i2c_4585, 1);
	lect_octet = Wire.read();
	Wire.endTransmission();	
	return bitRead(lect_octet, this->PinDT);
}
	

La modification est vraiment mineure et je suis maintenant plus serein, chaque fois que j'ai besoin d'une entrée ou d'une sortie, il me suffit d'ajouter ce genre de petit appel ! Il me reste maintenant 4 E/S de disponible pour des besoins éventuels !

Le menu général avec en premier choix, la marche forcée. Gestion d'un volet menu général Le menu pour adapter le moteur au rideau, volet ou... Gestion d'un volet menu moteur Le menu réseau pour l'utilisation de Wifi et de Mqtt. Gestion d'un volet menu réseau
Le menu général est organisé de façon à ce qu'un double appui sur le bouton permet d'utiliser l'ouverture où la fermeture forcée.
Le 1er réglage permet de choisir le nombre de pas pour que le moteur réalise un tour complet.
Le 2eme réglage permet de choisir le nombre de tour pour fermer ou ouvrir le volet.
Ces deux réglages doivent permettre de s'adapter à n'importe quel moteur pas à pas et à n'importe quel type de volet.
Dans l'exemple ci-dessous, on aperçoit sur l'oscillo les signaux PWM envoyés au moteur, des tests sont réalisés sur des valeurs "bidon" de durées jour pour vérifier la chaine MQTT (Mosquitto, NodeRed). Suivant le type de volet il est possible d'imaginer tous les types d'entrainement...



Le schéma

Schéma gestion de volet


Le boitier 3D

Il est prévu, mais que dans un futur que j'espère proche !

Le programme

recuperer lib ecran oled
Attention pour la librairie de l'écran Oled "SSD1306Wire.h", c'est celle-ci qu'il faut télécharger sur le site "github", elle est différente de celle qui est disponible dans l'environnement "Arduino".
On la reconnait au "w" de Wire qui doit dans notre cas être une majuscule. (Merci à Guy, pour cette précision qui peut éviter pas mal de galère !).
Merci également à tous les contributeurs(46) de ce petit package qui contient plusieurs librairies. Vous devez charger le zip complet comme montré sur l'image ci-contre.


Le programme n'est qu'un premier jet ! il n'integre pas la sécurité du controle de l'intensité sur le moteur, alors attention pour l'instant il ne faut faire que des tests et ne surtout pas laisser un doigt dans le volet !!!
---------------- A UTILISER AVEC PRUDENCE ! ----------------
Le code de base qui doit vous permettre de démarrer pour vos propres projets...


// gest_volet.ino
// Ouverture / Fermeture auto du rideau, d'un volet ou d'une porte
// en fonction de la lumiere
// config pour un Wemos 8266 D1 mini
// version avec une carte driver T6560 et un moteur NEMA17
// castoo.fr
// novembre 2020
// V1.6

#include "ESP8266WiFi.h"
#include "PubSubClient.h"
#include "Wire.h"
#include "SSD1306Wire.h"
#include "sai_oled_i2c.h"
#include "menu_oled_i2c.h"
#include "ESP_EEPROM.h"
#include "RTClib.h"

// Mettre en commentaire cette ligne après débugage
//#define debug_anemo

// Pins de racordement au moteur
int mot_avance = D5;
int mot_sens = D6;
// Pins de racordement I2C (OLED et RTC)
int i2c_sda = D2;
int i2c_scl = D1;
// Pins de racordement de l'encodeur rotatif sur l'extention I2C (economie de gpio voir explication sur site castoo.fr)
int encod_dt = 1; // P1 => broche 5 de l'adaptateur I2C LCD
int encod_clk = 2; // P2 => broche 6 de l'adaptateur I2C LCD
int encod_sw = 0; // P0 => broche 4 de l'adaptateur I2C LCD
// Adresse I2C du 4585 par défaut il est (en principe) à 0x3F si ce n'est pas le cas 
// il faut modifier dans les deux class le #define adr_i2c_4585 0x3F

// Création instance objet écran OLED
SSD1306Wire  display(0x3c, i2c_sda, i2c_scl); // I2C sur un esp8266 donc D2 > SDA & D1 > SCL

// init menu oled (clk, dt, sw et nom de l'objet LCD)
menu_oled mes_menus_oled(encod_clk, encod_dt, encod_sw, display); 

// init saisie (clk, dt, sw et nom de l'objet LCD)
sai_oled mon_sai_oled(encod_clk, encod_dt, encod_sw, display); 

// Création instance objet RTC
RTC_DS3231 RTC;      // Instance du module RTC de type DS3231 sur I2C

WiFiClient espClient; // déclaration Objet wifi

PubSubClient client(espClient); // déclaration objet mosquitto

int photo_resist = A0; // Modele GL 5528 avec résistance de rappel de 10k à la masse
int v_lumiere = 0; // Valeur lue de la photo-résistance
const String s_jour[] = {"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"};
bool force_ouvert = false; // Positionné à true si bouton utilisé pour ouverture
bool force_ferme = false; // Positionné à true si bouton utilisé pour fermeture
int d_soleil; // Calcul durée ensoleillement de la journée minute
char msg_envoye[50]; // Message MQTT
DateTime h_actuel; // heure actuelle
int d_actuel; // date actuelle
int v_dsoir; // date derniere mesure nuit
int v_dmatin; // date derniere mesure matin
bool v_nuit = false; // indicateur jour / nuit

// gestion de l'eeprom
struct s_memo_rom {
	DateTime matin; // Heure ouverture avec soleil
	DateTime soir; // Heure fermeture fin soleil
	bool rid_ouvert; // Derniere modification de l'état du rideau auto ou manuelle
	char wifi_ssid[50]; // Box hebergement "Broker" serveur mqtt
	char wifi_passe[25]; // passe serveur
	char ip_mqtt[15]; // IP mosquitto le port 1883 est en dur dans le code
	char login_mqtt[25]; // Login mosquitto
	char passe_mqtt[25]; // Passe mosquitto
	int nbpas1tour; // temps du signal SCK+ pour faire un tour (specifique au moteur et au réglage de pas (1/8 1/2...))
	int nb_tour_ouvre; // Nb de tour pour ouverture
	int nb_tour_ferme; // Nb de tour pour fermeture
	int seuil_lum; // Valeur du seuil de luminosité (suivant type de photo-résistance)
} memo_rom, v_ram;
int adr_deb_memo_rom = 0;

// Choix selectionné dans menu
int A_menu; 
const int nb_ch_menuA = 6;
const String ch_menuA[] = {"Ouverture / Fermeture", "Param nb tour moteur", "Param date heure", "Param Réseaux", "Param seuil lumiere", "Retour"};
String _ch_menuA = * ch_menuA;
int B_menu; 
const int nb_ch_menuB = 2;
const String ch_menuB[] = {"Vérif. Date Heure", "Retour"}; // A completer prochaine version...
String _ch_menuB = * ch_menuB;
int C_menu; 
const int nb_ch_menuC = 6;
const String ch_menuC[] = {"Temps par tour", "Nb tour ouvre", "Test ouvre", "Nb tour ferme", "Test ferme", "Retour"};
String _ch_menuC = * ch_menuC;
int D_menu; 
const int nb_ch_menuD = 6;
const String ch_menuD[] = {"Login Wifi", "Adresse IP mosquitto", "Login mosquitto", "Passe mosquitto", "Aff. param Wifi", "Retour"};
String _ch_menuD = * ch_menuD;
String ch_result;

// Initialise des valeurs standard pour le LCD
void prep_aff_LCD(uint8_t typ_pol) {
	display.clear();
	if(typ_pol == 16) display.setFont(ArialMT_Plain_16);
	else if(typ_pol == 24) display.setFont(ArialMT_Plain_16);
	else display.setFont(ArialMT_Plain_10);
	display.setTextAlignment(TEXT_ALIGN_LEFT);
}

// Ferme ou ouvre le rideau v_com = O Ouvre F Ferme
void action_rideau(char v_com){
	#ifdef debug_anemo
		Serial.println("Entree dans action_rideau : " + v_com);
	#endif
	uint16_t x = 0;
	prep_aff_LCD(16);
	if (v_com == 'F'){
		digitalWrite(mot_sens, HIGH); 
		display.drawString(20, 20, "Fermeture");
		display.drawString(20, 40, "en cours");
	}else{ 
		digitalWrite(mot_sens, LOW);
		display.drawString(20, 20, "Ouverture");
		display.drawString(20, 40, " en cours");
	}
	display.display();
	analogWrite(mot_avance,200);
	while( x < v_ram.nb_tour_ouvre ){
		delay(v_ram.nbpas1tour);
		x++;
	}
	analogWrite(mot_avance,0);	
}

// ------------------- Cnx au serveur Raspberry Mosquitto ------------------
void setup_wifi() {
	#ifdef debug_anemo
		Serial.println("Entree dans setup_wifi");
	#endif
	delay(10);
	WiFi.begin(v_ram.wifi_ssid, v_ram.wifi_passe);
	prep_aff_LCD(10);
	display.drawString(10, 30, "Recherche wifi :");
	display.display();
	while (WiFi.status() != WL_CONNECTED) { 
		prep_aff_LCD(10);
		display.drawString(30, 30, "    WIFI    ");
		display.display();
		delay(450); 
		prep_aff_LCD(10);
		display.drawString(30, 30, "<<<<...>>>>");
		display.display();
		delay(50); 
		if(!mes_menus_oled.li_rot_sw()){
			prep_aff_LCD(10);
			display.drawString(10, 10, "Erreur cnx wifi");
			display.drawString(10, 20, "Memo nouveau SSID");
			display.drawString(10, 30, "Relancer wifi M/A");
			display.display();
			delay(300);
			while(mes_menus_oled.li_rot_sw()){delay(10);}
			return;
		}
	}
	prep_aff_LCD(16);
	display.drawString(25, 30, "Wifi OK");
	display.display();
	delay(500); 
	randomSeed(micros());
}

// ----------------------------- Cnx à mosquitto ------------------------
void reconnect() {
	#ifdef debug_anemo
		Serial.println("Entree dans reconnect");
	#endif
	while (!client.connected()) {
		prep_aff_LCD(10);
		display.drawString(10, 30, "Recherche mosquitto :");
		display.display();
		String clientId = "ESP8266Ensoleillement-";  // Creatation d'un ID aléatoire (identification ESP sur mosquitto)
		clientId += String(random(0xffff), HEX);
		if (!client.connect(clientId.c_str(), v_ram.login_mqtt, v_ram.passe_mqtt)) {
			prep_aff_LCD(10);
			display.drawString(20, 30, "     MQTT     ");
			display.display();
			delay(4000); 
			prep_aff_LCD(10);
			display.drawString(20, 30, "<<<<...>>>>");
			display.display();
			delay(1000); 
		}
		if(!mes_menus_oled.li_rot_sw()){
			prep_aff_LCD(10);
			display.drawString(10, 10, "Erreur cnx MQTT");
			display.drawString(10, 20, "Memo nouveau param");
			display.drawString(10, 30, "Relancer wifi M/A");
			display.display();
			delay(300);
			while(mes_menus_oled.li_rot_sw()){delay(10);}
			return;
		}
	}
	prep_aff_LCD(16);
	display.drawString(10, 30, "Mosquitto OK");
	display.display();
	delay(500); 
}

// Converti la date en string
String affiche_date(DateTime datetime){
	String jour = "";
	jour = s_jour[datetime.dayOfTheWeek()] + " " + String(datetime.day())+ "/" + String(datetime.month())+ "/" + String(datetime.year(),DEC);
	return jour;
}

// Converti l'heure en string
String affiche_heure(DateTime datetime){
	String heure = "";
	heure  = String(datetime.hour())+ ":" + String(datetime.minute())+ ":" + String(datetime.second());
	return heure;
}

// Affiche compte-rendu enregistrement eeprom param wifi
void aff_param_wifi(){
	delay(200);
	prep_aff_LCD(10);
	display.drawString(0, 0, "Parametre Wifi");
	display.drawString(0, 10, "ssid: " + String(v_ram.wifi_ssid));
	display.drawString(0, 20, "pass: " + String(v_ram.wifi_passe));
	display.drawString(0, 30, "ip : " + String(v_ram.ip_mqtt));
	display.drawString(0, 40, "logMqtt : ");
	display.drawString(40, 40, String(v_ram.login_mqtt));
	display.drawString(0, 50, "pasMqtt : " + String(v_ram.passe_mqtt));
	display.display();
	delay(300);
	while(mes_menus_oled.li_rot_sw()){delay(10);}
}

// Affiche compte-rendu enregistrement eeprom
void aff_eeprom(bool lect = true, bool memo_ok = false){
	delay(200);
		prep_aff_LCD(10);
		if(lect) display.drawString(20, 0, "Lecture EEPROM"); else display.drawString(20, 0, "Ecriture EEPROM");
		display.drawString(0, 10, "Matin : " + affiche_heure(v_ram.matin));
		display.drawString(0, 20, affiche_date(v_ram.matin));
		display.drawString(0, 30, "Soir : " + affiche_heure(v_ram.soir));
		display.drawString(0, 40, affiche_date(v_ram.soir));
		display.drawString(0, 50, "Rideau : ");
		if(v_ram.rid_ouvert) 	display.drawString(35, 50, "Ouvert");
		else 			display.drawString(35, 50, "Fermé");
		display.display();
	delay(1000);
}

// Lecture eeprom
void lecture_eeprom(){
	EEPROM.begin(sizeof(s_memo_rom));
	prep_aff_LCD(16);
	display.drawString(15, 30, "Lecture eeprom");
	display.display();
	delay(300);
	EEPROM.get(adr_deb_memo_rom, memo_rom);
	delay(300);
	// Si les dates mémorisées sont > à la date du jour il y a un problème ! 
	//if(memo_rom.matin > RTC.now()) v_ram.matin = RTC.now(); else v_ram.matin = memo_rom.matin;
	v_ram.matin = memo_rom.matin;
	//if(memo_rom.soir > RTC.now()) v_ram.soir = RTC.now(); else v_ram.soir = memo_rom.soir;
	v_ram.soir = memo_rom.soir;
	v_ram.rid_ouvert = memo_rom.rid_ouvert;
	v_ram.nbpas1tour = memo_rom.nbpas1tour;
	v_ram.nb_tour_ouvre = memo_rom.nb_tour_ouvre; // v_ram.nb_tour_ouvre = memo_rom.nb_tour_ferme; A mettre en oeuvre si necessaire
	v_ram.seuil_lum = memo_rom.seuil_lum;
	strcpy(v_ram.wifi_ssid, memo_rom.wifi_ssid);
	strcpy(v_ram.wifi_passe, memo_rom.wifi_passe);
	strcpy(v_ram.ip_mqtt, memo_rom.ip_mqtt);
	strcpy(v_ram.login_mqtt, memo_rom.login_mqtt);
	strcpy(v_ram.passe_mqtt,memo_rom.passe_mqtt);
	EEPROM.end();
	delay(300);
	aff_eeprom();
}

// Ecriture eeprom
void ecriture_eeprom(){
	EEPROM.begin(sizeof(s_memo_rom));
	prep_aff_LCD(16);
	display.drawString(15, 30, "Ecriture eeprom");
	display.display();
	delay(300);
	memo_rom.matin = v_ram.matin;
	memo_rom.soir = v_ram.soir;
	memo_rom.rid_ouvert = v_ram.rid_ouvert;
	memo_rom.nbpas1tour = v_ram.nbpas1tour;
	memo_rom.nb_tour_ouvre = v_ram.nb_tour_ouvre;
	memo_rom.nb_tour_ferme = v_ram.nb_tour_ouvre;
	memo_rom.seuil_lum = v_ram.seuil_lum;
	strcpy(memo_rom.wifi_ssid, v_ram.wifi_ssid);
	strcpy(memo_rom.wifi_passe,v_ram.wifi_passe);
	strcpy(memo_rom.ip_mqtt,v_ram.ip_mqtt);
	strcpy(memo_rom.login_mqtt, v_ram.login_mqtt);
	strcpy(memo_rom.passe_mqtt, v_ram.passe_mqtt);
	EEPROM.put(adr_deb_memo_rom, memo_rom);
	delay(300);
	bool ok = EEPROM.commit();
	delay(300);
	aff_eeprom(false, ok); // Parametre pas utilisé dans ce code
	EEPROM.end();
	delay(300);
}

// 	Saisie du temps necessaire au moteur pour faire 1 tour
void saisie_temps_1_tour_moteur(){
	prep_aff_LCD(10);
	display.drawString(10, 10, "Saisir le temps");
	display.drawString(10, 20, "nécéssaire au moteur");
	display.drawString(10, 30, "pour faire un tour");
	display.drawString(10, 40, "Défaut : 1600");
	display.display();
	while(mes_menus_oled.li_rot_sw()){delay(10);}
	mon_sai_oled.init(); // Initialisation ecran OLED pour saisie
	String temps_tour = mon_sai_oled.aff_menu_Alpha(); // On lance la saisie
	if(temps_tour.length() > 2){
		v_ram.nbpas1tour = temps_tour.toInt();
		ecriture_eeprom();
	}else{
		prep_aff_LCD(16);
		display.drawString(20, 20, "Valeur");
		display.drawString(10, 40, "non valide");
		display.display();
		delay(300);
		while(mes_menus_oled.li_rot_sw()){delay(10);}
	}
}

// 	Saisie du nombre de tour moteur (ouverture/fermeture) (des choix différents ouv/ferm seront dev. si necessaire)
void saisie_nb_tour_moteur(){
	prep_aff_LCD(10);
	display.drawString(10, 10, "Saisir le nombre");
	display.drawString(10, 20, "de tour moteur");
	display.drawString(10, 30, "pour ouverture");
	display.drawString(10, 40, " et fermeture");
	display.drawString(10, 50, " Défaut : 5");
	display.display();
	while(mes_menus_oled.li_rot_sw()){delay(10);}
	mon_sai_oled.init(); // Initialisation ecran OLED pour saisie
	String nb_tour = mon_sai_oled.aff_menu_Alpha(); // On lance la saisie
	if(nb_tour.toInt() > 0 && nb_tour.toInt() < 10){ // Limitation à 10 tours a augmenter pour besoins spécifiques
		v_ram.nb_tour_ouvre = nb_tour.toInt();
		ecriture_eeprom();
	}else{
		prep_aff_LCD(16);
		display.drawString(20, 20, "Valeur");
		display.drawString(10, 40, "non valide");
		display.display();
		delay(300);
		while(mes_menus_oled.li_rot_sw()){delay(10);}
	}
}

// Ferme ou ouvre le rideau pour test nb_tour v_com = O Ouvre F Ferme
void test_rideau(char v_com, uint8_t t_nb_tour){
	uint16_t x = 0;
	prep_aff_LCD(16);
	if (v_com == 'F'){
		digitalWrite(mot_sens, HIGH); 
		display.drawString(20, 0, "Fermeture");
	}else{ 
		digitalWrite(mot_sens, LOW);
		display.drawString(20, 0, "Ouverture");
	}
	display.drawString(10, 20, String(t_nb_tour) + " 1/8 pas");
	display.drawString(0, 40, "Bouton STOP !");
	display.display();
	analogWrite(mot_avance,200);
	while( x < t_nb_tour ){
		for(int y = 0; y < 10; y++){
			if(!mes_menus_oled.li_rot_sw()){
				analogWrite(mot_avance,0);	
				return;
			}
			delay(v_ram.nbpas1tour / 10);
		}
		x++;
	}
	analogWrite(mot_avance,0);	
}

// Saisie du seuil de luminosité (détermine la quantité de soleil pour ouvrir ou fermer en automatique)
void saisie_seuil_lumiere(){
	prep_aff_LCD(10);
	display.drawString(10, 0, "Saisir le seuil");
	display.drawString(10, 10, "de luminosite");
	display.drawString(10, 20, "ouverture/fermeture");
	display.drawString(10, 30, "Défaut : 300");
	display.drawString(10, 40, "Actuel : " + String(v_ram.seuil_lum));
	display.display();
	while(mes_menus_oled.li_rot_sw()){delay(10);}
	mon_sai_oled.init(); // Initialisation ecran OLED pour saisie
	String seuil_luminosite = mon_sai_oled.aff_menu_Alpha(); // On lance la saisie
	if(seuil_luminosite.toInt() > 0){
		v_ram.seuil_lum = seuil_luminosite.toInt();
		ecriture_eeprom();
	}else{
		prep_aff_LCD(16);
		display.drawString(20, 20, "Valeur");
		display.drawString(10, 40, "non valide");
		display.display();
		delay(300);
		while(mes_menus_oled.li_rot_sw()){delay(10);}
	}
}

// Découverte des réseaux wifi accessibles
void decouverte_wifi() {
	String ssid_wifi[9];
	delay(100);
	WiFi.disconnect(true);
	delay(100);
	prep_aff_LCD(10);
	display.drawString(2, 0,  "RAZ wifi en cours...");
	display.drawString(2, 10,  ">>> Patience ... <<<");
	display.drawString(8, 30, "Attention !");
	display.drawString(0, 40, "Max 5 réseaux affichés");
	display.display();
	delay(200);
	int n = 0;
	for(int nb_rel = 1; nb_rel < 6; nb_rel++){
		n = WiFi.scanNetworks();
		if( n == -1 ){
			display.drawString(0, 50, "Tentative trouver réseau : " + nb_rel);
			display.display();
		}else{
			break;
		}
		if(nb_rel > 1) delay(400);
	}
	if( n == -1 ){
		prep_aff_LCD(10);
		display.drawString(0, 20, "Aucun réseau Wifi en vue !");
		display.drawString(0, 40, "Relancez la recherche...");
		display.display();
		while(mes_menus_oled.li_rot_sw()){delay(10);}
	}else{
		if(n > 5) n = 5;
		for (uint8_t i = 0; i < n+1; ++i) {
			if(WiFi.SSID(i).length() >= 25)
				ssid_wifi[i] = WiFi.SSID(i).substring(0, 24);
			else
				ssid_wifi[i] = WiFi.SSID(i);
				delay(20);
		}
		prep_aff_LCD(10);
		int W_menu = 0;
		W_menu = mes_menus_oled.init(n, *ssid_wifi);
		ssid_wifi[W_menu+1].toCharArray(v_ram.wifi_ssid, ssid_wifi[W_menu+1].length()+1);
		if(v_ram.wifi_ssid != ""){
			prep_aff_LCD(10);
			display.drawString(2, 10,  "Saisir le mot de passe");
			display.drawString(2, 20,  "du reseau :");
			display.drawString(2, 30,  v_ram.wifi_ssid);
			display.display();
			delay(500);
			while(mes_menus_oled.li_rot_sw()){delay(10);}
			mon_sai_oled.init();
			ch_result = mon_sai_oled.aff_menu_Alpha();
			prep_aff_LCD(10);
			if(ch_result.length() > 2){
				ch_result.toCharArray(v_ram.wifi_passe, ch_result.length()+1);
				ecriture_eeprom();
			}else{
				display.drawString(2, 0,  "Mot de passe non valide");
				display.drawString(2, 20,  "Le passe est init à NULL");
				display.display();
				v_ram.wifi_passe[0] =  '\0';
				while(mes_menus_oled.li_rot_sw()){delay(10);}
			}
			prep_aff_LCD(10);
		}
	}
}

void setup(){
	#ifdef debug_anemo
		Serial.begin(115200);
		Serial.println("Entrée setup");
	#endif	
	// Ini pin moteur en sortie
	pinMode(mot_avance, OUTPUT);
	pinMode(mot_sens, OUTPUT);
	display.init(); // Ini de l'écran Oled
	display.flipScreenVertically();
	prep_aff_LCD(10);
	lecture_eeprom(); // Récup des parametres dans l'eeprom
	//while(mes_menus_oled.li_rot_sw()){delay(10);} // Attente visualisation param dates, seuil...
	aff_param_wifi(); // Affache param wifi
	//while(mes_menus_oled.li_rot_sw()){delay(10);} // Attente visualisation param connexions (wifi et MQTT)
	setup_wifi(); // cnx wifi
	delay(500); 
	client.setServer(v_ram.ip_mqtt, 1883); // cnx mosquitto
	delay(2000);
	Wire.begin(); // Init I2C
	mon_sai_oled.init(); // Initialisation ecran pour saisie OLED
	mon_sai_oled.sortie_deb(false); // Invalide sortie balayage aprés chaque saisie (+ rapide mais perso, j'aime pas !) à tester à true...
	RTC.begin();
	// Initialise la date et le jour au moment de la compilation 
	// La ligne qui suit sert à définir la date et l'heure afin de régler le module, 
	// pour les redémarrages suivants, il ne faut PAS la décommenter, sinon à chaque démarrage 
	// le module se réinitialisera à la date et heure de compilation ce qui n'est pas vraiment grave !
	//					RTC.adjust(DateTime(__DATE__, __TIME__));
	//
}

// Boucle principale
void loop(){
	v_lumiere = analogRead(photo_resist);
	d_soleil = 0; // Calcul durée ensoleillement de la journée minute
	msg_envoye[50]; // Message MQTT
	h_actuel = RTC.now(); // heure actuelle
	d_actuel = h_actuel.unixtime() / 86400; // date actuelle
	v_dsoir = v_ram.soir.unixtime() / 86400; // date derniere mesure nuit
	v_dmatin = v_ram.matin.unixtime() / 86400; // date derniere mesure matin
	#ifdef debug_anemo
		Serial.println("Entree loop" );
		Serial.println("Val unix date actuelle : " + String(d_actuel));
		Serial.println("Val unix date Ram Matin : " + String(v_dmatin) );
		Serial.println("Val unix date Ram Soir : " + String(v_dsoir) );
		Serial.println(" ROM Matin : " + affiche_date(v_ram.matin) + " " + affiche_heure(v_ram.matin));
		Serial.println(" ROM Soir : " + affiche_date(v_ram.soir) + " " + affiche_heure(v_ram.soir));
		Serial.print("Rideau : "); if(v_ram.rid_ouvert) Serial.println("Ouvert"); else	Serial.println("Fermé");
		Serial.println("Seuil de luminosité : " + String(v_ram.seuil_lum));
		Serial.println("Luminosité mesurée : " + String(v_lumiere));
		Serial.println("Nombre de pas moteur pour un tour : " + String(v_ram.nbpas1tour));
		Serial.println("Nombre de tour moteur Ouv/Ferm : " + String(v_ram.nb_tour_ouvre));
		Serial.println("Wifi ssid: " + String(v_ram.wifi_ssid));
		Serial.println("Wifi passe: " + String(v_ram.wifi_passe));
		Serial.println("MQTT ip : " + String(v_ram.ip_mqtt));
		Serial.println("MQTT login : " + String(v_ram.login_mqtt));
		Serial.println("MQTT passe : " + String(v_ram.passe_mqtt));
	#endif	
	// Commande par le système en automatique suivant le seuil lumineux
	// Passage mode nuit 
	if((v_lumiere < v_ram.seuil_lum) && (!v_nuit)){
			v_nuit = true;
			if(v_ram.rid_ouvert){ // Si c'est la nuit que le rideau est ouvert
				action_rideau('F');
				v_ram.rid_ouvert = false;
				force_ouvert = false;
				force_ferme = false;
			}
			delay(1000);
			// Meme si le rideau etait déjà fermé en forcé on envoi quand même l'info d'ensoleillement
			v_ram.soir = h_actuel;
			ecriture_eeprom();
			delay(1000);
			// Envoie vers Mosquitto de la durée d'ensoleillement
			d_soleil = (v_ram.soir.unixtime() - v_ram.matin.unixtime())/60;
			// cnx une seule fois par jour alors vérif toujours ok
			if (WiFi.status() != WL_CONNECTED) {
				setup_wifi(); // cnx wifi
				delay(500); 
				client.setServer(v_ram.ip_mqtt, 1883); // cnx mosquitto
				delay(2000);
			}
			if (!client.connected()) { reconnect(); }
			client.loop();
			snprintf (msg_envoye, 50, "Ensoleillement %d minutes", d_soleil);
			client.publish("Soleil", msg_envoye);
	// Passage mode jour
	}else if((v_lumiere >= v_ram.seuil_lum) && (v_dmatin < d_actuel)){
			v_nuit = false;
			if(!v_ram.rid_ouvert){ // Si c'est le jour et que le rideau est fermé
				action_rideau('O');
				v_ram.rid_ouvert = true;
				force_ouvert = false;
				force_ferme = false;
			}
			delay(1000);
			v_ram.matin = h_actuel;
			ecriture_eeprom();
	}
	if(v_lumiere < v_ram.seuil_lum  && force_ferme)  force_ferme  = false;
	if(v_lumiere >= v_ram.seuil_lum && force_ouvert) force_ouvert = false;
	// Commande à partir du menu de paramètre
	if(!mes_menus_oled.li_rot_sw()){
		delay(300);
		A_menu = 0;
		A_menu = mes_menus_oled.init(nb_ch_menuA, _ch_menuA);
		delay(300);
		switch (A_menu){
			// Menu général
			case 0:
				// Marche forcée
				if (v_ram.rid_ouvert){
					action_rideau('F');
					v_ram.rid_ouvert = false;
					force_ferme = true;
					force_ouvert = false;
				}else{
					action_rideau('O');
					v_ram.rid_ouvert = true;
					force_ouvert = true;
					force_ferme = false;
				}
				ecriture_eeprom();
				break;
			case 1:
				// Menu gestion du moteur
				delay(300);
				C_menu = 0;
				C_menu = mes_menus_oled.init(nb_ch_menuC, _ch_menuC);
				delay(300);
				switch (C_menu){
					case 0: 
						// Saisie du temps necessaire au moteur pour faire 1 tour
						saisie_temps_1_tour_moteur();
						break;
					case 1: 
						// Saisie du nombre de tour pour ouvrir
						saisie_nb_tour_moteur();
						break;
					case 2: 
						// test d'ouverture
						test_rideau('O', v_ram.nb_tour_ouvre);
						break;
					case 3: 
						// Saisie du nombre de tour pour fermer
						saisie_nb_tour_moteur();
						break;
					case 4: 
						// test de fermeture
						test_rideau('F', v_ram.nb_tour_ouvre);
						break;
				}
				break;
			case 2: 
				// Gestion date heure
				// A venir...
				delay(300);
				B_menu = 0;
				B_menu = mes_menus_oled.init(nb_ch_menuB, _ch_menuB);
				delay(300);
				switch (B_menu){
					case 0: 
						// Vérification date et heure actuel dans l'esp
						prep_aff_LCD(16);
						display.drawString(0, 0, "Dat./Heur. actuel.");
						display.drawString(0, 25, "le " + affiche_date(RTC.now()));
						display.drawString(25, 45, "il est " + affiche_heure(RTC.now()));
						display.display();
						while(mes_menus_oled.li_rot_sw()){delay(10);}
						break;
				}
				break;
			case 3:
				// Gestion réseaux
				delay(300);
				D_menu = 0;
				D_menu = mes_menus_oled.init(nb_ch_menuD, _ch_menuD);
				delay(300);
				switch (D_menu){
					case 0: 
						// Saisie du ssid Wifi et son mot de passe
						decouverte_wifi();
						break;
					case 1: 
						// Saisie de l'adresse IP mosquitto
						mon_sai_oled.init();
						ch_result = mon_sai_oled.aff_menu_Alpha();
						if(ch_result.length() > 2){
							ch_result.toCharArray(v_ram.ip_mqtt, ch_result.length()+1);
							ecriture_eeprom();
						}
						break;
					case 2:
						// Saisie du Login mosquitto
						mon_sai_oled.init();
						ch_result = mon_sai_oled.aff_menu_Alpha();
						if(ch_result.length() > 2){
							ch_result.toCharArray(v_ram.login_mqtt, ch_result.length()+1);
							ecriture_eeprom();
						}
						break;
					case 3: 
						// Saisie du Passe mosquitto
						mon_sai_oled.init();
						ch_result = mon_sai_oled.aff_menu_Alpha();
						if(ch_result.length() > 2){
							ch_result.toCharArray(v_ram.passe_mqtt, ch_result.length()+1);
							ecriture_eeprom();
						}
						break;
					case 4:
						// Affichage parametre
						aff_param_wifi();
				}
				break;
			case 4:
				// Saisie du seuil de luminosité (détermine la quantité de soleil pour ouvrir ou fermer en automatique)
				saisie_seuil_lumiere();
				break;
		}
		delay(300);
	}//else stop_moteur(); // ici mettre commande enable moteur pour stopper alim
	prep_aff_LCD(16);
	if(v_lumiere >= v_ram.seuil_lum){
		display.drawString(10, 0, "Mode Jour");
		display.drawString(0, 25, "le " + affiche_date(v_ram.matin));
		display.drawString(30, 45, "à " + affiche_heure(v_ram.matin));
	}else{
		display.drawString(10, 0, "Mode nuit");
		display.drawString(0, 25, "le " + affiche_date(v_ram.soir));
		display.drawString(30, 45, "à " + affiche_heure(v_ram.soir));
	}
	display.display();
	delay(10);
}
	



Outil de debug

Si vous souhaitez modifier le programme, vous avez la possibilité de voir ce qui se passe en enlevant le commentaire devant la ligne de #define debug_anemo en début de fichier, vous aurez donc accès aux infos suivantes sur l'interface série réglée à 115200 bauds.
Exemple :
debug volet


Les class C++ a ajouter dans le répertoire de compilation
du programme de gestion du volet

Télécharger sai_oled_i2c.h
Télécharger sai_oled_i2c.cpp
Télécharger menu_oled_i2c.h
Télécharger menu_oled_i2c.cpp


Petite remarque sur le circuit DS3231 si vous souhaitez utiliser ce composant pour générer des fréquences comme cela semble possible il faut veiller à la référence qui est inscrite sur le composant 3231M c'est pas bon (pas de géné de fréquence possible !) si 3231SN c'est gagné vous avez le bon !!! (J’étais tombé sur un mauvais lors de mes premiers tests, j'ai cherché un moment !!!)
Attention composant ds3231
Attention composant ds3231M pas de générateur de fréquence Attention composant ds3231M pas de générateur de fréquence
Attention composant ds3231SN c'est bon il y a bien le générateur de fréquence Attention composant ds3231SN c'est bon il y a bien le générateur de fréquence

Quelques informations complèmentaires

Un site qui explique bien la PWM PWM.
De la documentation sur la carte de commande moteur TB6560 : TB6560 et TB6560.
De la documentation sur le petit moteur BYJ48 : Moteur BYJ48.
De la documentation sur le CI ULN2003 : Drivers ULN2003.
De la documentation sur la carte DS3231 RTC (avec eeprom) : DS3231 RTC.
De la documentation sur les moteurs pas à pas : Doc moteur pas à pas.
Le schema du 4585 / i2c pour lcd1602 : 4585 i2c.
La datasheet du PCF8574 : PCF8574.



Début de la réalisation :

Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet Réalisation volet



accueil electronique

Bricolage Robotique Informatique Peinture Voyage
Téléc. portail Le robot "mécano" Astuces informatique Henri Bertrou Auvergne
Bat. Iphone 6S Le robot "solaire" Réseau couche app. Jean-Michel Castille Floride
Robot piscine Servo et IR" Réseau Les couches New York
Xiaomi M365 Le robot "thymio" Réseaux Outils L'Ouest américain
Mac Mini Le robot "Rovio" Unités grandeur inf. L'Ile Maurice
Putty SSH Windows L'Italie / Venise
Bases Raspberry Tunisie
Termius IPhone/IPad Grece
Le vieux ZX 81
...
Navigation La Rochelle CNC / Imp3D Electronique Programmation
Rencontre dauphins Les Minimes Construction CNC Alim. TPL 5110 Doc. programme
Analyse NMEA 0183 Le Vieux port CNC du commerce Carte ESP8266 Indent programme
graph. NMEA 0183 L'Ile de Ré Martyr CNC ESP8266 1 relai Prog. objet
Analyse trames AIS A visiter Réa. imp. 3D ESP8266 Alarme Prog. procédurale
Analyse AIS TCP-IP Cura impression 3D ESP8266 MQTT
Sortie en ketch Plateau CR10 ESP8266 Temp.
Echange GPS C80 Anémomètre.
HP Sun-Odyssey CNC / 3D en vrac MCP9808 Librairie
LCD yanmar Saisie Oled
Testeur nmea esp1 i2c