KOLANKY - Chargeur voiture électrique connecté 6A-16A Tuya à moins de 90€

Attention, le schéma est pour une carte ESP32, ma première version. Les indications en haut à gauche sont pour la carte ESP C3 actuelle (Un ESP 32 avec moins de broches).
J’ai mis un ESP C3, car j’avais besoin de l’ESP32 pour un autre montage, et il était un peu riche pour ce montage, qui n’utilise que peu de GPIOs.

Mais le schéma reste le même, seules les GPIO changent. (et une résistance, R5)
Sur la carte ESP32:
Le PWM sort de GPIO 19
La mesure CP est sur GPIO 34
l’interrupteur sur GPIO 2
le relais sur GPIO 16
l’écran OLED sur GPIO 22 et 25
le PZEM sur GPIO 30 et 31 (liaison série)

CP n’a qu’un seul fil, le deuxième fil, c’est la masse, qui doit relié à la terre, qui sert de référence.

1 « J'aime »

Merci beaucoup pour les précisions

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

Bonjour et merci bcp pour ton partage, m j’ai un wallbox copper mais ça me tente bien d’essayer la wallbox diy pour recharger ma Spring, j’ai quasi tout les composants.
J’ai une carte qui ressemble a la tienne : https://fr.rs-online.com/web/p/modules-de-developpement-de-communication-et-sans-fil/2863995?gb=s

Quand tu parles ‹ parties non publiées › tu penses a quoi?

Il y a certaines parties du code qui ne sont pas dans la partie principale. Regarde le code en détail, tu verras…

Pour ta carte, cela devrait fonctionner, il faut juste adapter les entrées / sorties.

Merci beaucoup @jeunegeo pour le partage
Je vais regarder pour faire un proto.
Je posterai en fonction de l’avancement.
Bonne journée
Stéphane

Tu parles des .h au début? C’est pas des librairies qu’on trouve sur l’ide Arduino ?

lit le code et essaye de le comprendre…

Je ne trouve pas mais ce n’est pas grave (mes connaissances en arduino se limitent à allumer des leds, un ecran oled et récupérer qq info ou piloter un relais mais je ne connais rien en mqtt), je verrais quand j’aurais la carte et un peu plus de temps (je suis sur mon téléphone)
merci quand même et bon WE.

Mqtt permet de dialoguer avec HA
J’utilise l’intégration Mosquitto qui permet d’utiliser le protocole Mqtt.
C’est plutôt réactif.
Maintenant le code pour un débutant c’est pas évident même si il est bien commenté.
Je suis pas expert non plus mais je vais regarder pour comprendre l’algo.

Un exemple d’un Switch de mon fichier mqtt.yaml.
Je pilote le gpio18 d’un esp8266 pour faire varier la vitesse de la pompe de ma piscine.
Tu as également des sensors et des binary_sensors.

Merci pour le partage @jeunegeo .

Depuis mai les api tesla sont limité, plus possible de passer par l’intégration tesla pour gérer la charge en fonction du surplus de production.

Je partais sur une openevse mais ta solution me plaît beaucoup plus :smiley: J’ai déjà un esp32 en stock, un relai, une alim et un câble type 2 et c’est parti ( ~100€ ) sans oublier l’écran ( que j’ai en stock pour un opendtu :smiley: ).

Bon par contre j’ai 0 connaissance en programmation …

Salut @Legeantbleu
Je vais également essayer de faire une borne.
J’ai reçu une prise t2 pour faire des tests.
Je pense utiliser un esp8266 car j’ai du stock.
Il faut que je regarde les composants et surtout l’alimentation si possible double 12v et 5v si j’ai bien compris.
Si quelqu’un a une sujestion de référence je suis preneur.
Après il faut que je me remette à la programmation des esp car ça fait un moment que j’ai pas pratiqué.
Je partagerai des que j’avance un peu.
A suivre
Stéphane

J’ai trouvé ce module qui fait du 5v et du 12v 1.8€ 9% OFF|AC DC 110 220V Schalt netzteil modul AC DC isolation eingang ausgang 5V /12V /100mA /500mA| | - AliExpress

Ok merci je vais regarder

Bonjour,
Va y avoir de l’émulation, j’ai aussi 1 vieux esp82, je tente aujourd’hui de communiquer avec lui avec ha en mqtt…

J’ai déjà utilisé un ESP8266 pour sauver mon robot piscine. Il communique maintenant avec HA via mqtt
J’ai utilisé l’IDE ARDUINO :

Je vais faire des tests avec le code de @jeunegeo et je vous tiens au jus
Encore merci @jeunegeo :+1: :+1:

bonjour
1er essais et échec, en effet, il manque bcp de fonction (MesureTension, LitPZEM, connexion mqtt et wifi …)
jeunegeo : pourrais-tu partager toutes ces fonctions ?

Hello,
Idem pour moi je galère avec le code et les fonctions non définies :


Si @jeunegeo peut nous donner un coup de main
Merci par avance

Bonjour,
ledcSetup est une fonction pour ESP32, pas pour esp8266…