Browse Source

gestion wifi/ap

master
scayac 2 weeks ago
parent
commit
c79f316464
  1. 53
      README.md
  2. 7
      data/conf.html
  3. 36
      data/conf.js
  4. 1
      platformio.ini
  5. 211
      src/main.cpp

53
README.md

@ -0,0 +1,53 @@
# Reflow Oven ESP8266
Ce projet est un contrôleur de four de refusion basé sur ESP8266, avec interface web et contrôle PID.
## Fonctionnalités
- Contrôle manuel et automatique du four
- Interface web dynamique (température, consigne, puissance SSR)
- Configuration des profils de chauffe (Preheat, Soak, Reflow)
- Visualisation en temps réel via WebSocket
- Stockage des paramètres dans `conf.json`
- Configuration WiFi client
- Mise à jour des paramètres PID (Kp, Ki, Kd) et WiFi via la page de configuration
## Structure du projet
```
├── data/
│ ├── index.html # Interface principale
│ ├── app.js # Logique client web
│ ├── style.css # Styles
│ ├── conf.html # Page de configuration
│ ├── conf.json # Paramètres persistants
├── src/
│ └── main.cpp # Firmware ESP8266
├── lib/ # Librairies PID et MAX6675
├── platformio.ini # Configuration PlatformIO
```
## Démarrage
1. Flasher le firmware avec PlatformIO
2. Uploader le système de fichiers (dossier `data/`)
3. Connecter l'ESP8266 au WiFi :
- Par défaut, le module tente de se connecter au réseau défini dans `conf.json` (modifiable via la page de configuration).
- Si la connexion échoue ou si SSID/mot de passe sont vides, le module bascule automatiquement en mode Point d'Accès (AP) avec SSID `ReflowOven` et mot de passe `12345678`.
- L'IP par défaut en mode AP est `192.168.4.1`.
4. Accéder à l'interface web via l'adresse IP affichée dans le terminal (WiFi ou AP).
## Utilisation
- **Chauffe manuelle** : Choisir la consigne avec le slider, valider, puis ajuster en temps réel
- **Chauffe automatique** : Définir les paramètres du profil, valider
- **Configuration** : Modifier SSID, mot de passe, PID, profils via `conf.html`
## Dépendances
- [ArduinoJson](https://arduinojson.org/)
- [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
- [PID_v1](https://playground.arduino.cc/Code/PIDLibrary/)
- [MAX6675](https://github.com/adafruit/MAX6675-library)
## Auteurs
- Christophe (utilisateur)
- GitHub Copilot (assistance IA)
## Licence
Ce projet est open source, licence MIT.

7
data/conf.html

@ -16,7 +16,7 @@
</div> </div>
<div class="row"> <div class="row">
<label style="width: 65px; display: inline-block;">PWD : </label> <label style="width: 65px; display: inline-block;">PWD : </label>
<input type="text" id="pwd" > <input type="password" id="password" >
</div> </div>
<div class="row"> <div class="row">
<label style="width: 65px; display: inline-block;">Kp : </label> <label style="width: 65px; display: inline-block;">Kp : </label>
@ -30,8 +30,7 @@
<label style="width: 65px; display: inline-block;">Kd : </label> <label style="width: 65px; display: inline-block;">Kd : </label>
<input type="number" id="kd" > <input type="number" id="kd" >
</div> </div>
<button id="validate" class="start">VALIDER</button> <button id="validate" class="start">VALIDER</button>
<script src="conf.js"></script>
<script src="app.js"></script>
</body> </body>
</html> </html>

36
data/conf.js

@ -0,0 +1,36 @@
document.addEventListener('DOMContentLoaded', () => {
// Charger les valeurs actuelles
fetch('conf.json')
.then(r => r.json())
.then(conf => {
for (const key in conf) {
const el = document.getElementById(key.toLowerCase());
if (el) el.value = conf[key];
}
});
// Sauvegarder à l'appui sur VALIDER
document.getElementById('validate').onclick = function() {
const data = {};
['ssid','password','kp','ki','kd'].forEach(key => {
const el = document.getElementById(key);
if (el) data[key] = el.value;
});
fetch('/save_config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(r => {
if (r.ok) alert('Configuration enregistrée !');
else alert('Erreur lors de l\'enregistrement');
});
};
});
fetch('conf.json')
.then(r => r.json())
.then(conf => {
for (const key in conf) {
const el = document.getElementById(key);
if (el) el.value = conf[key];
}
});

1
platformio.ini

@ -18,6 +18,7 @@ lib_deps =
ESP32Async/ESPAsyncWebServer ESP32Async/ESPAsyncWebServer
ESP32Async/ESPAsyncTCP ESP32Async/ESPAsyncTCP
LittleFS@^0.1.0 LittleFS@^0.1.0
ArduinoJson
; Partitionnement LittleFS (2MB pour fichiers, 1MB SPIFFS par défaut) ; Partitionnement LittleFS (2MB pour fichiers, 1MB SPIFFS par défaut)
board_build.filesystem = littlefs board_build.filesystem = littlefs

211
src/main.cpp

@ -4,10 +4,12 @@
#include <LittleFS.h> #include <LittleFS.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <max6675.h> #include <max6675.h>
#include <ArduinoJson.h>
// Wifi credentials (mis à jour depuis le fichier conf.json au démarrage)
char ssid[32] = "";
char password[64] = "";
// Wifi credentials
const char* ssid = "";
const char* password = "";
AsyncWebServer server(80); AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); AsyncWebSocket ws("/ws");
@ -17,10 +19,11 @@ const int SSR_PIN = 12; // D1 (GPIO5) par exemple
// PID variables // PID variables
double setpoint = 20, input = 0, output = 0; double setpoint = 20, input = 0, output = 0;
const int PWM_PERIOD = 2000; const int PWM_PERIOD = 2000;
double Kp = 2, Ki = 5, Kd = 1; double Kp = 2, Ki = 5, Kd = 1;//mises à jour depuis le fichier conf.json au démarrage
PID myPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT); PID myPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
// Variables globales pour le profil // Variables globales pour le profil
volatile bool wifiReconfigPending = false;
int preheatTemp = 0, preheatTime = 0; int preheatTemp = 0, preheatTime = 0;
int soakTemp = 0, soakTime = 0; int soakTemp = 0, soakTime = 0;
int reflowTemp = 0, reflowTime = 0; int reflowTemp = 0, reflowTime = 0;
@ -34,19 +37,107 @@ const int thermoCS = 4;
const int thermoSCK = 5; const int thermoSCK = 5;
MAX6675 thermocouple(thermoSCK, thermoCS, thermoSO); MAX6675 thermocouple(thermoSCK, thermoCS, thermoSO);
float lastTemp = NAN; float lastTemp = NAN;
unsigned long lastSend = 0; unsigned long lastSend = 0;
unsigned long pwmStart = 0; unsigned long pwmStart = 0;
void loadConfig() {
if (!LittleFS.exists("/conf.json")) {
Serial.println("conf.json absent, création fichier par défaut");
File f = LittleFS.open("/conf.json", "w");
f.print("{\"ssid\":\"Linksys11539\",\"password\":\"etraxbbgxr\",\"Kp\":2,\"Ki\":5,\"Kd\":1}");
f.close();
}
File file = LittleFS.open("/conf.json", "r");
if (!file) {
Serial.println("Impossible d'ouvrir conf.json");
return;
}
JsonDocument doc;
DeserializationError err = deserializeJson(doc, file);
file.close();
if (err) {
Serial.println("Erreur JSON conf.json");
return;
}
if (doc["ssid"]) {
strncpy(ssid, doc["ssid"], sizeof(ssid)-1);
ssid[sizeof(ssid)-1] = '\0';
}
if (doc["password"]) {
strncpy(password, doc["password"], sizeof(password)-1);
password[sizeof(password)-1] = '\0';
}
if (doc["Kp"]) Kp = doc["Kp"].as<double>();
if (doc["Ki"]) Ki = doc["Ki"].as<double>();
if (doc["Kd"]) Kd = doc["Kd"].as<double>();
myPID.SetTunings(Kp, Ki, Kd);
Serial.printf("Config chargée : ssid=%s, kp=%.2f, ki=%.2f, kd=%.2f\n", ssid, Kp, Ki, Kd);
}
void setup() { void setup() {
// Action arrêt (setpoint à 0°C)
server.on("/action/stop", HTTP_POST, [](AsyncWebServerRequest *request){ pinMode(SSR_PIN, OUTPUT);
setpoint = 0; digitalWrite(SSR_PIN, LOW);
autoProfileActive = false; myPID.SetMode(AUTOMATIC);
profileStep = 0; myPID.SetOutputLimits(0, PWM_PERIOD); // output = ms ON sur 2s
Serial.println("Arrêt demandé : setpoint PID à 0°C");
request->send(200, "text/plain", "OK"); Serial.begin(115200);
}); delay(100);
// Initialisation LittleFS
if (!LittleFS.begin()) {
Serial.println("Erreur LittleFS");
return;
}
Serial.println("LittleFS monté");
// Charger la config
loadConfig();
// Route POST pour enregistrer la configuration depuis le client web (AsyncWebServer, version sans crash)
static String confBody;
server.on("/save_config", HTTP_POST,
[](AsyncWebServerRequest *request){
if (confBody.length() > 0) {
// Sauvegarder l'ancienne config pour comparaison
String oldSsid = ssid;
String oldPwd = password;
File f = LittleFS.open("/conf.json", "w");
if (!f) {
request->send(500, "text/plain", "Erreur écriture fichier");
confBody = "";
return;
}
f.print(confBody);
f.close();
request->send(200, "text/plain", "OK");
Serial.println("Configuration enregistrée via POST /conf.json");
loadConfig();
// Si ssid ou password ont changé, reconfigurer le WiFi
if (oldSsid != String(ssid) || oldPwd != String(password)) {
Serial.println("Changement SSID/PWD détecté, reconfiguration WiFi demandée...");
wifiReconfigPending = true;
}
confBody = "";
} else {
request->send(400, "text/plain", "Aucun corps reçu");
}
},
NULL,
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (index == 0) confBody = "";
for (size_t i = 0; i < len; i++) confBody += (char)data[i];
}
);
// Action arrêt (setpoint à 0°C)
server.on("/action/stop", HTTP_POST, [](AsyncWebServerRequest *request){
setpoint = 0;
autoProfileActive = false;
profileStep = 0;
Serial.println("Arrêt demandé : setpoint PID à 0°C");
request->send(200, "text/plain", "OK");
});
// Action chauffe manuelle // Action chauffe manuelle
server.on("/action/preheat", HTTP_POST, [](AsyncWebServerRequest *request){ server.on("/action/preheat", HTTP_POST, [](AsyncWebServerRequest *request){
@ -89,29 +180,36 @@ void setup() {
request->send(200, "text/plain", "OK"); request->send(200, "text/plain", "OK");
}); });
pinMode(SSR_PIN, OUTPUT); // Connexion WiFi ou démarrage AP si ssid/pwd vides
digitalWrite(SSR_PIN, LOW); if (strlen(ssid) == 0 || strlen(password) == 0) {
myPID.SetMode(AUTOMATIC); Serial.println("SSID ou mot de passe vide, démarrage en mode AP");
myPID.SetOutputLimits(0, PWM_PERIOD); // output = ms ON sur 2s WiFi.mode(WIFI_AP);
Serial.begin(115200); WiFi.softAP("ReflowOven", "12345678");
delay(100); Serial.print("Point d'accès démarré : SSID=ReflowOven, PWD=12345678, IP=192.168.4.1");
Serial.println(WiFi.softAPIP());
// Connexion à un réseau WiFi défini dans les paramètres
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connexion au WiFi : ");
Serial.println(ssid);
int wifiTimeout = 0;
while (WiFi.status() != WL_CONNECTED && wifiTimeout < 20) {
delay(500);
Serial.print(".");
wifiTimeout++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connecté, IP : ");
Serial.println(WiFi.localIP());
} else { } else {
Serial.println("Échec connexion WiFi"); WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connexion au WiFi : ");
Serial.println(ssid);
int wifiTimeout = 0;
while (WiFi.status() != WL_CONNECTED && wifiTimeout < 20) {
delay(500);
Serial.print(".");
wifiTimeout++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connecté, IP : ");
Serial.println(WiFi.localIP());
} else {
Serial.println("Échec connexion WiFi, bascule en mode AP");
WiFi.disconnect();
delay(100);
WiFi.mode(WIFI_AP);
WiFi.softAP("ReflowOven", "12345678");
Serial.print("Point d'accès démarré : SSID=ReflowOven, PWD=12345678, IP=192.168.4.1");
Serial.println(WiFi.softAPIP());
}
} }
// Initialisation LittleFS // Initialisation LittleFS
@ -133,6 +231,49 @@ void setup() {
} }
void loop() { void loop() {
// Reconfiguration WiFi non bloquante
static unsigned long wifiReconfigStart = 0;
static bool wifiReconfigInProgress = false;
if (wifiReconfigPending && !wifiReconfigInProgress) {
wifiReconfigInProgress = true;
wifiReconfigStart = millis();
Serial.println("Début reconfiguration WiFi...");
WiFi.disconnect();
delay(100);
if (strlen(ssid) == 0 || strlen(password) == 0) {
WiFi.mode(WIFI_AP);
WiFi.softAP("ReflowOven", "12345678");
Serial.print("Point d'accès démarré : SSID=ReflowOven, PWD=12345678, IP=");
Serial.println(WiFi.softAPIP());
Serial.println("IP par défaut : 192.168.4.1");
wifiReconfigPending = false;
wifiReconfigInProgress = false;
} else {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connexion au WiFi : ");
Serial.println(ssid);
}
}
if (wifiReconfigInProgress) {
if (WiFi.status() == WL_CONNECTED) {
Serial.print("Connecté, IP : ");
Serial.println(WiFi.localIP());
wifiReconfigPending = false;
wifiReconfigInProgress = false;
} else if (millis() - wifiReconfigStart > 10000) { // timeout 10s
Serial.println("Échec connexion WiFi, bascule en mode AP");
WiFi.disconnect();
delay(100);
WiFi.mode(WIFI_AP);
WiFi.softAP("ReflowOven", "12345678");
Serial.print("Point d'accès démarré : SSID=ReflowOven, PWD=12345678, IP=");
Serial.println(WiFi.softAPIP());
Serial.println("IP par défaut : 192.168.4.1");
wifiReconfigPending = false;
wifiReconfigInProgress = false;
}
}
ws.cleanupClients(); ws.cleanupClients();
unsigned long now = millis(); unsigned long now = millis();

Loading…
Cancel
Save