/*******************************************************************
* Code ESP32-C3 + DS3231 + Carte SD + Servo + BLE + LEDs + Bouton
* Log très détaillé (SAV).
*
* - Bouton : GPIO19 (pull-up)
* - LED Verte : GPIO7
* - LED Rouge : GPIO6
* - Servo : GPIO9
* - Carte SD : CS=2, SCK=10, MISO=0, MOSI=1
* - DS3231 : SDA=GPIO3, SCL=GPIO4
*
* Fonctionnalités :
* - Appui court (<25s) => lance un cycle servo
* - Appui long (>=25s) => reset compteur
* - LED Rouge clignote pendant l'appui, s'arrête 1s avant action
* - LED Verte signale l'état ON + clignote sur actions servo
* - Log très détaillé (chaque étape du setup, appuis, servo, BLE).
* - Lecture du log via BLE (caractéristique READ).
*******************************************************************/
#include <Arduino.h>
#include <Wire.h>
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
#include <ESP32Servo.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// -----------------------------------------------------------
// CONFIGURATION DES BROCHES ET CONSTANTES
// -----------------------------------------------------------
// Bouton poussoir (GPIO19) : INPUT_PULLUP (LOW = appuyé)
const int PIN_BUTTON = 19;
// Servo : GPIO9
const int PIN_SERVO = 9;
// Carte SD : CS=2, SCK=10, MISO=0, MOSI=1
const int PIN_SD_CS = 2;
// LEDs : Verte (GPIO7), Rouge (GPIO6)
const int PIN_LED_VERTE = 7;
const int PIN_LED_ROUGE = 6;
// Seuil appui long : 25s
const unsigned long LONG_PRESS_THRESHOLD = 25000;
// Nom du fichier log
const char* LOG_FILENAME = "/log.txt";
// -----------------------------------------------------------
// VARIABLES GLOBALES
// -----------------------------------------------------------
// Indique si la carte SD est OK
bool sdInitialized = false;
// Gestion du bouton
unsigned long pressStartTime = 0;
bool buttonWasPressed = false;
// Compteur de cycles servo
unsigned int cycleCount = 0;
// Indique si un client BLE est connecté
bool bleConnected = false;
// Clignotement LED Rouge pendant l'appui
bool redLedState = false;
unsigned long lastRedBlink = 0;
const unsigned long RED_BLINK_MS = 300;
// Objet RTC DS3231
RTC_DS3231 rtc;
// Objet Servo
Servo myServo;
// -----------------------------------------------------------
// FONCTIONS DE LOG AVEC TIMESTAMP DS3231
// -----------------------------------------------------------
/**
* Retourne une chaîne "[YYYY-MM-DD HH:MM:SS]" depuis le DS3231.
*/
String getTimestamp() {
DateTime now = rtc.now();
char buffer[22];
snprintf(buffer, sizeof(buffer),
"[%04d-%02d-%02d %02d:%02d:%02d]",
now.year(), now.month(), now.day(),
now.hour(), now.minute(), now.second());
return String(buffer);
}
/**
* Log un événement dans /log.txt, précédé d'un timestamp DS3231
* + tag de contexte.
*/
void logEvent(const String &message, const String &tag = "INFO") {
// Affichage local (Serial)
String fullMsg = getTimestamp() + " [" + tag + "] " + message;
Serial.println(fullMsg);
// Enregistrement sur SD si dispo
if (!sdInitialized) return; // On sort si SD pas initialisée
File logFile = SD.open(LOG_FILENAME, FILE_APPEND);
if (!logFile) {
Serial.println("[WARN] Impossible d'ouvrir le log.txt !");
logEvent("Impossible d'ouvrir le log.txt !", "WARM");
return;
}
logFile.println(fullMsg);
logFile.close();
}
// -----------------------------------------------------------
// GESTION DES LEDs
// -----------------------------------------------------------
void blinkGreen(unsigned int times, unsigned int intervalMs = 200) {
for (unsigned int i = 0; i < times; i++) {
digitalWrite(PIN_LED_VERTE, LOW);
delay(intervalMs);
digitalWrite(PIN_LED_VERTE, HIGH);
delay(intervalMs);
}
}
void blinkRed(unsigned int times, unsigned int intervalMs = 300) {
for (unsigned int i = 0; i < times; i++) {
digitalWrite(PIN_LED_ROUGE, HIGH);
delay(intervalMs);
digitalWrite(PIN_LED_ROUGE, LOW);
delay(intervalMs);
}
}
// -----------------------------------------------------------
// GESTION SERVO
// -----------------------------------------------------------
/**
* Cycle servo : 20° -> 2s -> 165° -> 10s -> retour 20°.
* On loggue chaque étape pour le SAV.
*/
void activateServoCycle() {
logEvent("Activation du servo (cycle)", "USER");
// Petit clignotement LED Verte avant le mouvement
blinkGreen(2, 150);
logEvent("Servo position = 20° (début)", "SERVO");
myServo.write(20);
delay(2000);
logEvent("Clignotement vert intermédiaire", "SERVO");
blinkGreen(3, 150);
logEvent("Servo position = 165°", "SERVO");
myServo.write(165);
delay(10000);
logEvent("Clignotement vert de fin", "SERVO");
blinkGreen(2, 150);
logEvent("Servo retour = 20°", "SERVO");
myServo.write(20);
delay(500);
cycleCount++;
logEvent("Fin cycle servo, cycleCount = " + String(cycleCount), "SERVO");
}
/**
* Reset du compteur de cycles.
*/
void resetCycleCount() {
logEvent("Reset du compteur (appui long)", "ADMIN");
cycleCount = 0;
// Clignote LED rouge 3 fois
blinkRed(3, 300);
logEvent("Compteur remis à zéro", "ADMIN");
}
// -----------------------------------------------------------
// GESTION DU BLE
// -----------------------------------------------------------
#define SERVICE_UUID "12345678-1234-1234-1234-1234567890AB"
#define LOG_CHARACTERISTIC_UUID "87654321-4321-4321-4321-BA0987654321"
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) override {
bleConnected = true;
logEvent("Client BLE connecté", "BLE");
}
void onDisconnect(BLEServer* pServer) override {
bleConnected = false;
logEvent("Client BLE déconnecté", "BLE");
}
};
class LogCharacteristicCallbacks : public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) override {
if (!bleConnected) {
pCharacteristic->setValue("Not connected!");
logEvent("Lecture BLE refusée (pas connecté)", "BLE");
return;
}
if (!sdInitialized) {
pCharacteristic->setValue("SD error!");
logEvent("Lecture BLE refusée (SD non init)", "BLE");
return;
}
File logFile = SD.open(LOG_FILENAME, FILE_READ);
if (!logFile) {
pCharacteristic->setValue("Log not found!");
logEvent("Lecture BLE : impossible d'ouvrir log.txt", "BLE");
return;
}
// Lire tout le fichier (attention à la taille)
String logData;
while (logFile.available()) {
logData += (char)logFile.read();
}
logFile.close();
pCharacteristic->setValue(logData.c_str());
logEvent("Log envoyé par BLE, taille=" + String(logData.length()), "BLE");
}
};
void setupBLE() {
BLEDevice::init("VESPINOX");
BLEServer* pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService* pService = pServer->createService(SERVICE_UUID);
BLECharacteristic* pLogCharacteristic = pService->createCharacteristic(
LOG_CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ
);
pLogCharacteristic->addDescriptor(new BLE2902());
pLogCharacteristic->setCallbacks(new LogCharacteristicCallbacks());
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->start();
logEvent("BLE démarré, nom = VESPINOX", "BLE");
}
// -----------------------------------------------------------
// SETUP
// -----------------------------------------------------------
void setup() {
Serial.begin(115200);
delay(100);
// I2C + RTC DS3231
Wire.begin(3, 4);
if (!rtc.begin()) {
Serial.println("Erreur : DS3231 non détecté !");
logEvent("Erreur : DS3231 non détecté !", "WARM");
blinkRed(5, 200);
} else {
if (rtc.lostPower()) {
Serial.println("DS3231 a perdu l'alimentation. Réglage heure compile...");
logEvent("DS3231 a perdu l'alimentation. Réglage heure compile...", "WARM");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
Serial.println("RTC DS3231 OK");
logEvent("RTC DS3231 OK", "SYSTEM");
}
// Bouton & LEDs
pinMode(PIN_BUTTON, INPUT_PULLUP);
pinMode(PIN_LED_VERTE, OUTPUT);
pinMode(PIN_LED_ROUGE, OUTPUT);
digitalWrite(PIN_LED_VERTE, HIGH);
digitalWrite(PIN_LED_ROUGE, LOW);
// Carte SD
SPI.begin(10, 0, 1, PIN_SD_CS);
if (SD.begin(PIN_SD_CS, SPI)) {
sdInitialized = true;
Serial.println("Carte SD OK");
logEvent("Carte SD OK", "SYSTEM");
} else {
sdInitialized = false;
Serial.println("ERREUR init SD !");
logEvent("ERREUR init SD", "WARM");
blinkRed(5, 200);
}
// Création ou ouverture du log
if (sdInitialized) {
logEvent("===== DEMARRAGE SYSTEME =====", "SYSTEM");
}
// Servo
myServo.attach(PIN_SERVO);
myServo.write(20);
logEvent("Servo initialisé (position 20°)", "SYSTEM");
// BLE
setupBLE();
// Indiquer qu'on a fini le setup
blinkGreen(2, 100);
logEvent("Setup terminé, cycleCount=" + String(cycleCount), "SYSTEM");
// État initial bouton
buttonWasPressed = false;
}
// -----------------------------------------------------------
// LOOP
// -----------------------------------------------------------
void loop() {
bool buttonState = (digitalRead(PIN_BUTTON) == LOW);
// Clignotement LED rouge si bouton appuyé
if (buttonState) {
unsigned long now = millis();
if (now - lastRedBlink >= RED_BLINK_MS) {
lastRedBlink = now;
redLedState = !redLedState;
digitalWrite(PIN_LED_ROUGE, redLedState ? HIGH : LOW);
}
} else {
// Bouton pas appuyé
if (buttonWasPressed) {
// On vient de relâcher
digitalWrite(PIN_LED_ROUGE, LOW);
redLedState = false;
// Durée d'appui
unsigned long pressDuration = millis() - pressStartTime;
// Log durées, par ex.
logEvent("Bouton relâché après " + String(pressDuration) + " ms", "USER");
delay(1000); // la fameuse 1s d'intervalle
// Décider action
if (pressDuration < LONG_PRESS_THRESHOLD) {
logEvent("=> Action : cycle servo (appui court)", "USER");
activateServoCycle();
} else {
logEvent("=> Action : reset compteur (appui long)", "USER");
resetCycleCount();
}
}
}
// Si bouton passe de inactif -> actif
if (buttonState && !buttonWasPressed) {
pressStartTime = millis();
logEvent("Bouton appuyé (debut)", "USER");
}
buttonWasPressed = buttonState;
// Petit delay anti-bruit
delay(10);
}