Comme promis, la partie principale du soft :
c’est un peu long, désolé, cela peut surement être amélioré, car j’ai fait ça un peu vite. L’idéal serait de l’intégrer avec la partie générale de TASMOTA, pour tout ce qui est connexion.
Si quelqu’un est intéressé par les parties non publiées, demandez!
// V3.0 - 23 avril 2024 : ajout comptage surplus autonome
// V2.0 - avril 2024 : ajout reglage I
// V2.0 - février 2024 : modif pour rendre le soft autonome si pas de Wifi
// V1.0 - janvier 2024 : version originelle à partir de la version pour ESP32
// Utilisation d'une carte avec relais, alim 220V, 5v ou USB, avec un ESP-C3-12F pour borne EV
// Modif sur la carte : R14 supprimée (0 Ohm) car sinon le relais se colle apres le reset.
// donc plus de led en mode telechargement.
// appuyer sur inter 09 sur la carte au reset pour passer en mode téléchargement
// pas de téléchargement par USB, passer par TX/RX et une carte interface (Carte de prog ESP8266).
// Pour l'entrée analogique de mesure CP (GPIO 3) max 2,8 V en entrée donc atténuation modifiée par rapport au schéma avec ESP32
// avec la carte ESP C3 : ajout d'une résistance de 150 kOhms en // sur R5 (100 kOhms)
// carte actuelle pour IDE ARDUINO 2.1.0 : ESP32C3 DEV Module
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include <WiFi.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <PZEM004Tv30.h>
//choix de l'écran dans la bibliothèque
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE); // pour ecran OLED 1.3" 128x64
//*********************** Déclaration des constantes et variables globales utilisées *********************
//********************************************************************************************************
// ***************************************** WIFI Configuration
const char *ssid = "VotreReseau";
const char *password = "VotreMotDePasse";
IPAddress MonIP(192, 168, xx, xx); // adresse IP de la carte, suivant votre réseau
IPAddress gateway(192, 168, XX,XX); // adresse de votre Box Internet
IPAddress subnet(255, 255, 255, 0);
IPAddress dns(212, 27, 40, 240); // adresse de vos DNS, pas obligatoire
// ************************************* MQTT Configuration
IPAddress serverIPAddress(192, 168, XX, XX); Adresse de votre Broker MQTT
const int PortMQTT = 1883;
const char *clientId = "Borne_EVSE_C3";
WiFiClient espClient;
PubSubClient MQTTclient(espClient);
//Topics en emission :
const char *TopicTension = "/BorneVE/Tension";
const char *TopicCourant = "/BorneVE/Courant";
const char *TopicPuissance = "/BorneVE/Puissance";
const char *TopicEnergie = "/BorneVE/kWh";
const char *TopicAmp = "/BorneVE/Amp";
const char *TopicCosPhi = "/BorneVE/CosPhi";
const char *TopicControl = "/BorneVE/Control";
const char *TopicStatut = "/BorneVE/Statut";
// Topics en reception :
const char *TopicCommande = "/BorneVE/Commande"; // reception de commandes
const char *TopicSurplus = "/Maison/Surplus"; // pulse energie envoyée au chauffe-eau - 1Wh par pulse - ecart en ms en 2 pulses
const char *TopicConsoRout = "/Maison/ConsoRouteur";
// ******************************* entrées sorties utilisées ************************
const byte PWMOutput = 18; //sortie PWM sur GPIO 18
const byte PWMCanal = 0;
const int PWMResolution = 10; // résolution 10 bits
const int PWMFreq = 1000; // PWM à 1 kHz
const byte RelaisPin = 10; // commande du relais sur GPIO 10
const byte Bouton = 8; // Bouton de commande sur GPIO 08
const byte CP = 3; // mesure analogique sur GPIO 03;
// ecran oled :
#define SDA 4 // GPIO 4
#define SCL 5 // GPIO 5
#define LedCarte 2 // LED bleue intégrée à la carte
//******************************* Constantes globales *****************************
float CoeffTension = 0.00340; //pour afficher la valeur lue en volts (brut * coeff) normal :0.0033
// à modifer en fonction de l'alim 12 v utilisée et des tolérances des composants
int Seuil1 = 11; // si TensionLigneCP > à 11 V alors VE non connecté (normalement 12 Volts)
int Seuil2 = 8; // si TensionLigneCP entre 8 et 11 alors VE connecté et ready (normalement 9 Volts)
int Seuil3 = 5; // entre 5 et 8 VE en charge (normalement 6 Volts)
// limite rapport cyclique du PWM en fonction de l'intensité de charge voulue ( .1 = 10%)
float AmpMin = 5;
float AmpMax = 25; // limite du câblage actuel (câble de charge = 32 A)
float RappMin = 0.08;
float RappMax = AmpMax / .6 / 100; // limite du câble de charge (32A): Rapp = Amp /.6 /100;
// lignes et colonnes de l'écran (128 (colonne) x 64 (ligne))
int Lig1 = 10;
int Lig2 = 27;
int Lig3 = 44;
int Lig4 = 61;
int Col1 = 0;
int Col2 = 20;
int TempoMQTT = 5; //temps en seconde entre 2 émissions MQTT
//************************************* Variables globales *********************************
int NbLoop = 0; //compteur pour reset si perte Wifi
//static unsigned long TempsReset ;
static unsigned long Att;
static unsigned long ModifRapp; // pour temporiser l'arret charge sur courant trop fort en cas de modif de l'intensité demandée
static unsigned long IntervalleTraitePulse = millis();
static unsigned long TempoGestionSurplus;
static unsigned long CompteurSurplus = millis();
bool SurplusRecu = false;
bool ConsoRecu = false;
bool GestionSurplusAuto = false;
String msg;
String Statut; // pour indiquer l'état de la borne par MQTT (prête, en charge, etc.)
float Rapp = 0.083; // rapport cyclique du PWM au lancement (0.0833 = 5A)
float IncRapp = 0.00167; // incrément du rapport cyclique pour varier d'1 dixième d'Ampère (env 24 W)
float Volt = 0; // volts indiqués par le PZEM
float Ampere = 0; // Ampères indiquées par le PZEM
float Puiss = 0; // Puissance instantanée indiquée par le PZEM
float kWh = 0; // kWh indiqués par le PZEM
float CosPhi = 0; // CosPhi (pf) du PZEM
float TensionLigneCP = 0; // tension de la ligne CP
float brut = 0; //
int Amp = AmpMin + 1; // Ampères de réglage du chargeur, on demarre à 6A si pas de reglage (si float, modifier snprintf pour l'affichage!!)
int Surplus = 0; // Surplus de courant disponible (Cas de panneaux solaires) (en Watt)
int TalonSurplus = 300; // Surplus à laisser pour chauffe-eau ou autre (en Watt)
int ConsoRout = 0;
int ErreurMesure = 0;
int NbAjust = 0; //Nb d'ajustement du courant
bool EtatRelais = false;
bool PWM = false;
bool EnCharge = false;
bool DemandeArret = false;
//PZEM004Tv30 pzem(20, 21, 0x01);
//PZEM004Tv30 pzem(Serial2, 20, 21);
PZEM004Tv30 pzem(Serial, 20, 21);
//******************************************** Setup *******************************************
void setup() {
Att = millis();
//Serial.begin(115200);
//delay(500);
//Serial.println();
//Serial.println("Liaison Série prête");
pinMode(LedCarte, OUTPUT);
digitalWrite(LedCarte, HIGH);
pinMode(RelaisPin, OUTPUT); // Met la broche RelaisPin en sortie et à l'état bas.
digitalWrite(RelaisPin, LOW);
pinMode(Bouton, INPUT_PULLUP); // Met la broche Bouton en entrée avec résistance interne au +3V3
//*************** Ecran Oled *****************************
Wire.end();
Wire.setPins(SDA, SCL);
Wire.begin(); // Called inside of a library
u8g2.begin(); // init ecran
u8g2.enableUTF8Print(); // nécessaire pour écrire des caractères accentués
delay(2000); // delai de d'allumage de l'écran aprés la mise sous tension (sinon on ne voit pas les lignes suivantes car l'écran n'est pas encore prêt)
PrintScreen(Lig1, Col1, "Setup en cours");
// ****************** init WiFi *********************
//WiFi.mode(WIFI_ON); // le Wifi est souvent en fonction par défaut
WiFi.softAPdisconnect(true); //On arrete le serveur Wifi qui fonctionne souvent par défaut!!
WiFi.mode(WIFI_STA); // On passe en mode station, et on se connecte au réseau existant.
WiFi.config(MonIP, gateway, subnet, dns);
WiFi.begin(ssid, password);
ConnectWifi();
// **************** MQTT config *******************
// définition du serveur MQTT
MQTTclient.setServer(serverIPAddress, PortMQTT);
//appel de la procedure de connexion MQTT avec attache de la procédure de callback MQTT
if (WiFi.status() == WL_CONNECTED) ConnectMQTT(); // ne se connecte au MQTT que si le WIFI est connecté
//************** préparation du mode OTA pour pouvoir modifier le programme par WIFI
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
})
.onEnd([]() {
//Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
//Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
//Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
//Serial.println("OTA pret");
//************************************************
//Serial.println("Init sortie PWM à 1 kHz");
// Configuration du canal 0 avec la fréquence et la résolution choisie
ledcSetup(PWMCanal, PWMFreq, PWMResolution);
// Assigne le canal PWM au GPIO choisie
ledcAttachPin(PWMOutput, PWMCanal);
//Serial.print("Custom Address:");
//Serial.println(pzem.readAddress(), HEX);
//set the ADC resolution to 12 bits (0-4096)
analogReadResolution(12);
// Serial.println("Fin de setup prise BorneEV ");
if (MQTTclient.connected()) MQTTclient.publish(TopicControl, "Fin de setup BorneVE C3");
PrintScreen(Lig1, Col1, "Fin de setup");
delay(500); // pour lecture écran
ModifRapp = millis();
TempoGestionSurplus = millis();
} // fin de setup
//*************************************** Boucle principale **************************************************************************
//************************************************************************************************************************************
void loop() {
char s[20];
ArduinoOTA.handle();
MesureTension();
LitPZEM();
// *************pour test uniquement simule charge *********
//**********************************************************
//TensionLigneCP = 6;
//Ampere = 6;
// *********************************************************
//**********************************************************
if (EnCharge) {
/*
if (GestionSurplusAuto == true) { // si en charge et si Gestion Surplus est en auto, alors si on recoit du surplus on
if (SurplusRecu) { // appelle la procedure de gestion du surplus, et on remet la tempo à 0;
GestionSurplus();
TempoGestionSurplus = millis();
}
if ((millis() - TempoGestionSurplus) > 18000) { // Si apres 18 secondes, on n'a pas reçu de surplus on appelle quand même la
GestionSurplus(); // la procedure de gestion du surplus, et on remet la tempo à 0; 18s c'est env 200 W de surplus
TempoGestionSurplus = millis();
}
}
*/
if (GestionSurplusAuto == true) { // si en charge et si Gestion Surplus est en auto, alors si on recoit du surplus on
if (ConsoRecu) { // appelle la procedure de gestion du surplus,
GestionConsoRouteur();
}
ConsoRecu = false;
}
if (GestionSurplusAuto == false) {
if ((millis() - ModifRapp) > 8000) {
// ajuste le courant réel (mesuré) au courant demandé si plus faible : 3 ajustements max, sinon en fin de charge
// on ajuste en permanence puisque la voiture diminue le courant...et le PWM s'arreterait!
if ((Ampere < Amp) && (NbAjust < 3)) {
if (Ampere > 4) {
float Ecart = Amp - Ampere;
Ecart = Ecart / .6 / 100;
publie(TopicControl, Ecart, 4);
Rapp = Rapp + Ecart;
EnvoiRapport(Rapp);
ModifRapp = millis(); // attente avant la prochaine modif
NbAjust = NbAjust + 1;
}
}
}
}
}
if ((millis() - Att) > (TempoMQTT * 1000)) { // emission MQTT chaque TempoMQTT seconde
if (MQTTclient.connected()) EmissionMQTT();
Att = millis();
}
if (TensionLigneCP < Seuil3 - 2) { // si moins de 3 V, alors pb, on arrete la charge
// pas de 12V ou pb sur la ligne
ArretCharge();
PrintScreen(Lig1, Col1, "Pas de 12V !");
Statut = "Pas de 12V !";
}
// attente du branchement du véhicule, PWM à l'arret **********************
if (TensionLigneCP > Seuil1) {
EnvoiRapport(1); //arrete le PWM
//PWM = false ;
ArretRelais();
DemandeArret = false;
if (digitalRead(Bouton) == 0) { // reglage intensité de charge
Amp = Amp + 1;
if (Amp > AmpMax) Amp = AmpMin; // boucle
Rapp = Amp / .6 / 100;
//Serial.print("Ampères : ");
//Serial.println(Amp);
delay(100);
}
Statut = "Chargeur prêt";
//Serial.println("chargeur prêt");
snprintf(s, sizeof(s), "Chargeur prêt %i", WiFi.RSSI()); // Indique aussi le niveau du WiFi sur l'afficheur
PrintScreen(Lig1, Col1, s);
snprintf(s, sizeof(s), "Réglé sur %i A", Amp);
// if (MQTTclient.connected()) MQTTclient.publish(TopicControl, s);
PrintScreen(Lig4, Col1, s);
}
//véhicule branché, lancement PWM ******************************
if (TensionLigneCP > Seuil2 && TensionLigneCP < Seuil1 && !DemandeArret) {
//Serial.println("VE connecté");
snprintf(s, sizeof(s), "VE connecté %i A", Amp);
PrintScreen(Lig1, Col1, s);
//PrintScreen(Lig1, Col1, "VE connecté");
Statut = "VE connecté";
if ((EnCharge == true) && ErreurMesure < 4) { // on arrive ici si le VE a arrêté la charge
ErreurMesure = ErreurMesure + 1; // on verifie que ce n'est pas une erreur de mesure
//Serial.print("erreur mesure ");
//Serial.println(ErreurMesure);
}
if (ErreurMesure > 3) { // mesure en attendant 3 mesures avant d'arreter
ErreurMesure = 0; // la charge
ArretCharge();
}
if (!PWM) EnvoiRapport(Rapp); //demarre PWM
//MarcheRelais();
if (digitalRead(Bouton) == 0) { // arrete charge si appui sur inter poussoir
DemandeArret = true;
ArretCharge();
}
}
// véhicule pret à charger ***************************************************
// Si le VE est pret, on colle le relais HT et on passe en charge
if (TensionLigneCP > Seuil3 - 1 && TensionLigneCP < Seuil2 && !DemandeArret) {
if (!EtatRelais) { // si le chargeur est démarré avec le véhicule connecté, il ne passe pas dans le if précédent...
//Serial.println("erreur");
EnvoiRapport(Rapp); //demarre PWM
}
MarcheRelais();
EnCharge = true;
//ModifRapp = millis(); // pour ne pas sortir en erreur au demarrage.
if (digitalRead(Bouton) == 0) { // arrete charge si appui sur poussoir
DemandeArret = true;
ArretCharge();
}
//Serial.println("VE en charge");
if (!DemandeArret) {
//PrintScreen(Lig1, Col1, "VE en charge");
snprintf(s, sizeof(s), "VE en charge %i A", Amp);
PrintScreen(Lig1, Col1, s);
Statut = "en charge";
}
}
if (WiFi.status() != WL_CONNECTED) ConnectWifi();
if (!MQTTclient.connected() and (WiFi.status() == WL_CONNECTED)) ConnectMQTT();
MQTTclient.loop();
// pour ralentir la boucle sans utiliser delay():
static unsigned long Attente = millis();
do {
} while ((millis() - Attente) < 50);
//delay(50);
} // end of loop