
ESP-RTC mit NTP
Für meine Software-Projekte musste ich mit das Thema Uhrzeit aus dem Internet holen und die interne ESP-RTC setzen.Das alles habe ich hier zusammengetragen, damit es
Wir besitzen eine funkgesteuerte Markise, die aber keine wetterabhängige Regelung besitzt.
Darum habe ich mit dem ESP32S3 eine Regelung bebaut, welche die Sonneneinstrahlung und den Wind misst. Daraus resultiert dann das Auf- und Zufahren der Markise. Wie das nun programmiert ist und welche Hardware ich dazu benötigt habe, ist im folgenden zu lesen.
Auf dem Schaltbild ist zu sehen, dass der ESP32S3, ein Display, und 3 Relais ansteuert. Des weiteren wird über den I2C-Bus der Lichtsensor (BH1750) und der Temperatursensor BMP280 angesteuert.
Das Anemometer
wird über eine Steckerleiste angeschlossen, wie auch die anderen Sensoren und die Taster der Markisenfunksteuerung. Ein Netzteil komplettiert die Schaltung.
Die Relais werden über jeweils einen Transistor BC337 angesteuert, um genügend Leistung für das Relais zu haben. Die Relais sind mit den Tastern in der Funksteuerung verbunden und simulieren so den jeweiligen Tastendruck (AUF, ZU, STOPP), da ich keinen Zugang zum Funkprotokoll habe.
Das Signal des Anemometers wird mit einem Operationsverstärker LM358 verstärkt.
3 LEDs zeigen zusätzlich den Status der Markise an.
Die Hardware habe ich in eine passende Abzweigdose eingebaut, welche mir einfache Anschlüsse der externen Komponenten wie Sensoren und Spannungsversorgung bietet.
Im Bild sind gut die verwendeten Steckerleisten für die externen Komponenten zu sehen.
Auch ist die Verdrahtung zum Funkschalter, der auf dem Deckel der Abzweigdose montiert ist, gut zu erkennen.
Für die Regelung benötige ich mindestens 2 Werte, das sind Sonne und Wind. Beide Werte haben Einfluss auf die Regelung. In der Softwarebeschreibung gehe ich darauf weiter ein. Hier soll nur die Positionierung der beiden Sensoren gezeigt werden.
Oben sehen wir das schwarze Anemometer und weiter unten den Lichtsensor mir Temperaturfühler.
Auf dem ESP32S3 läuft die gesamte Software, welche sich in die Regelersoftware und in das Web-Interface aufteilt. Diese Aufteilung spiegelt sich auch in der Aufteilung auf die beiden Kerne wieder. Arduino-SW mit dem Webserver läuft auf Kern 1 und die Markisensteuerung auf Kern 0.
Die Daten werden zentral im der struct MarkiseObject gehalten, welche im EEPROM-Bereich des ESP32S3 abgelegt ist.
struct MarkiseObject
{
uint16_t statusEEPROM_Values; // Status der Werte in der EEPROM, 0 = keine Werte, EEPROM_VALID_FLAG = Werte vorhanden
uint16_t sonneSollWert; // Schwellwert für Sonne
uint16_t windSollWert; // Schwellwert für Wind
uint16_t windSicherheitsZeitSekunden; // Zeit, die die Markise bei starkem Wind eingefahren bleibt, um Schäden zu vermeiden in Sekunden
uint16_t markiseLaufzeitSekunden; // Zeit, die die Markise zum Ein- oder Ausfahren benötigt in Sekunden
uint16_t sonneIstWert; // aktueller Wert für Sonne
uint16_t windIstWert; // aktueller Wert für Wind
uint16_t markiseStatus; // 0 = eingefahren, 1 = ausgefahren, 2 = Einfahren, 3 = Ausfahren
uint16_t markiseBleibtZuZeit; // Zeitintervall für die "bleibt zu Zeit" in Sekunden
uint16_t counterMarkiseAufAbtastung; // Aktueller Counter Wert für die Sonne-Auf-Abtastung der Markise
uint16_t counterMarkiseZuAbtastung; // Aktueller Counter Wert für die Sonne-Zu-Abtastung der Markise
uint16_t counterWindZuAbtastung; // Aktueller Counter Wert für die Wind-Zu-Abtastung der Markise
uint16_t markiseAufAbtastungSekunden; // Zeitintervall für die Auf-Abtastung der Sensoren in Sekunden
uint16_t markiseZuAbtastungSekunden; // Zeitintervall für die Zu-Abtastung der Sensoren in Sekunden
uint16_t windAbtastungSekunden; // Zeitintervall für die Zu-Abtastung der Windstärke in Sekunden
uint16_t countMarkiseAufAbtastung; // Soll für die Auf-Abtastung der Lichtstärke
uint16_t countMarkiseZuAbtastung; // Soll für die Zu-Abtastung der Lichtstärke
uint16_t countWindZuAbtastung; // Soll für die Zu-Abtastung der Windstärke
uint16_t automatikStatus; // Status der Automatik Aus=0, EIN=1
};
Die Haupttask „MarkiseTask“ steuert den Markise. „TaskMarkiseAuf“ und „TaskMarkiseZu“ übernehmen die zeitaufwendigen Funktionen zum bedienen der Hardware.
„TaskWindSensor“ ist getrennt vom Sonnensensor, da Wind häufiger abgefragt wird. Die anderen Tasks stellen weitere Werte zur Verfügung. „TaskDisplay“ übernimmt die wechselnde Anzeige der Werte.
„MarkiseTask“ kommuniziert mit „TaskMarkiseAuf“ und „TaskMarkiseZu“ über je ein Semaphore.
TaskHandle_t MarkiseTask;
TaskHandle_t TaskMarkiseAuf;
TaskHandle_t TaskMarkiseZu;
TaskHandle_t TaskWindSensor;
TaskHandle_t TaskLichtMessen;
TaskHandle_t TaskBME280Messen;
TaskHandle_t TaskDisplay;
SemaphoreHandle_t MarkiseZuSemaphore = NULL;
SemaphoreHandle_t MarkiseAufSemaphore = NULL;
Die Regelung hat 3 State machines:
1. Markise zu: Warten auf Sonne ohne Wind
2. Markise auf: Prüfe auf Sonne und Wind
3. Markise fährt AUF/ZU: Warten
Die Regelung erfolgt über die Soll-und IstWerte von Sonne und Wind, die in den Werten „counterMarkiseAufAbtastung“und „counterMarkiseZuAbtastung“ mit den Schwellwerten „countMarkiseAufAbtastung“ und „countMarkiseZuAbtastung“ verglichen werden. Hierdurch werden die Zeitglieder der Regelung erstellt.
Der Wind fließt im gleichen System mit „counterWindZuAbtastung“ und „countWindZuAbtastung“ in die Regelung mit ein.
Die Sonnenwerte werden in dieser Task verarbeitet, die Windwerte werden aber in der Wind-Task aufbereitet, da die Abtastung häufiger erfolgt als bei der Sonne.
void Markise( void * pvParameters ){
Serial.println("Starte Task Markise");
CheckLichtSensor(&markiseObject); // Init Lichtwert
FahreMarkiseZu(&markiseObject,true); // Nach Programmstart
//int windStatus=KEIN_WIND;
for(;;){
// Markise eingefahren
Serial.println("wdt_markise_task==="+String(wdt_markise_task));
wdt_markise_task = WDT_TIMEOUT; //Trigger watchdog
if(markiseObject.automatikStatus == EIN){
if(markiseObject.markiseStatus == EINGEFAHREN){
display_ctrl.setStatus("MARKISE-ZU", String(markiseObject.sonneIstWert)+"lx");
Serial.println("Markise eingefahren warten auf Sonne ohne Wind");
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp("Markise zu").c_str());
digitalWrite(LED_ROT, LOW); // turn the LED on Markise zu
vTaskDelay((markiseObject.markiseAufAbtastungSekunden*1000) / portTICK_PERIOD_MS);
if(CheckLichtSensor(&markiseObject) == true) // Sonne Zähler hochzählen
markiseObject.counterMarkiseAufAbtastung++;
else if(markiseObject.counterMarkiseAufAbtastung>0) // Sonne wieder weg, Zähler runterzählen
markiseObject.counterMarkiseAufAbtastung--;
if(markiseObject.counterMarkiseAufAbtastung >markiseObject.countMarkiseAufAbtastung)
markiseObject.counterMarkiseAufAbtastung=markiseObject.countMarkiseAufAbtastung;// Max-Wert begrenzen
if(markiseObject.counterMarkiseAufAbtastung == markiseObject.countMarkiseAufAbtastung &&
markiseObject.counterWindZuAbtastung <= 2) { // Viel Sonne und fast kein Wind?
FahreMarkiseAuf(&markiseObject, false);
markiseObject.counterMarkiseAufAbtastung=0;
}
String cnt = "cntMarkAuf=" + String(markiseObject.counterMarkiseAufAbtastung) +
" Sonne="+ String(markiseObject.sonneIstWert) + " Wind="+ String(markiseObject.windIstWert);
Serial.println(cnt);
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp(cnt).c_str());
}
// Markise offen
if(markiseObject.markiseStatus == AUSGEFAHREN){
display_ctrl.setStatus("MARKISE-AUF", String(markiseObject.sonneIstWert)+"lx");
Serial.println("Markise ausgefahren warten auf Wind und Sonne");
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp("Markise Auf").c_str());
vTaskDelay((markiseObject.markiseZuAbtastungSekunden*1000) / portTICK_PERIOD_MS);
if(CheckLichtSensor(&markiseObject) == false)
markiseObject.counterMarkiseZuAbtastung++;
else if(markiseObject.counterMarkiseZuAbtastung>0) // Sonne wieder da, Zähler runterzählen
markiseObject.counterMarkiseZuAbtastung--;
if(markiseObject.counterMarkiseZuAbtastung >markiseObject.countMarkiseZuAbtastung)
markiseObject.counterMarkiseZuAbtastung=markiseObject.countMarkiseZuAbtastung;// Max-Wert begrenzen
if(markiseObject.counterMarkiseZuAbtastung == markiseObject.countMarkiseZuAbtastung ||
markiseObject.counterWindZuAbtastung>=markiseObject.countWindZuAbtastung) {
FahreMarkiseZu(&markiseObject,false);
markiseObject.counterMarkiseZuAbtastung=0;
}
String cnt = "cntMarkZu=" + String(markiseObject.counterMarkiseZuAbtastung) +
" Sonne="+ String(markiseObject.sonneIstWert)+ " Wind="+ String(markiseObject.windIstWert);
Serial.println(cnt);
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp(cnt).c_str());
}
while(markiseObject.markiseStatus == FAHRE_AUF || markiseObject.markiseStatus == FAHRE_ZU){
Serial.println("Markise fährt AUF oder ZU -- Warten");
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp("Markise fährt AUF oder ZU -- Warten").c_str());
vTaskDelay((10000) / portTICK_PERIOD_MS); // 10 Sek warten
}
}
else {
vTaskDelay((markiseObject.markiseAufAbtastungSekunden*1000) / portTICK_PERIOD_MS);
Serial.println("Automatik ist aus");
}
}
}
In der WindTask werden die eingelesenen Werte des AD-Wandlers auf die Werte 0-100 mit der map-Funktion umgesetzt.
Dieser Wert wird dann mit dem „windSollWert“ verglichen, um festzustellen, ob es ein böiger Wind oder ein normaler wind ist.
Bei Böen wird ein Offset auf „counterWindZuAbtastung“ aufaddiert.
Mit „counterWindZuAbtastung“ wir die Winderfassung in die Regelung eingespeist, welche dann entscheidet ob die Markise überhaupt AUF-fährt oder ggf. ZU-fährt wird.
void TaskWind(void * pvParameters){
Serial.println("Starte TaskWind");
for(;;){
Serial.println("wdt_wind_task==="+String(wdt_wind_task));
wdt_wind_task = WDT_TIMEOUT; //Trigger watchdog
vTaskDelay((markiseObject.windAbtastungSekunden*1000) / portTICK_PERIOD_MS);
int wert=0;
wert = analogRead(WIND_AEROMETER);
//Serial.println("Wert="+ String(wert));
wert= map(wert,0,4096,0,100);
#ifdef TESTHW
wert = windSensorWert;
#endif
Serial.println("Analoger WIND-Wert= "+ String(wert));
//Serial.println("CheckWindSensor->AktuellerWindWert="+ String(windSensorWert)+"km/h");
markiseObject.windIstWert = wert;
// Starker WIND?
if(wert >= markiseObject.windSollWert+10){
Serial.println("Markise zufahren Starker Wind");
markiseObject.counterWindZuAbtastung=markiseObject.countWindZuAbtastung; // Starker Wind zählt mehr
markiseObject.counterWindZuAbtastung+=4; // Starker Wind zählt mehr
Serial.println("WindSensor stark->>windIstWert="+ String(markiseObject.windIstWert));
Serial.println("WindSensor stark->>counterWindZuAbtastung="+ String(markiseObject.counterWindZuAbtastung));
continue;
}
// Normaler Wind oder kein Wind
else if(wert>= markiseObject.windSollWert)
markiseObject.counterWindZuAbtastung++;
else if(markiseObject.counterWindZuAbtastung>0) // Wind wieder weg, Zähler runterzählen
markiseObject.counterWindZuAbtastung--;
Serial.println("WindSensor normal->>windIstWert="+ String(markiseObject.windIstWert));
Serial.println("WindSensor normal->>counterWindZuAbtastung="+ String(markiseObject.counterWindZuAbtastung));
}
}
Unspektakulär wird hier jeweils das Semaphore gesetzt, was die Markise dann AUF oder ZU fahren lässt in jeweils einer Task.
void FahreMarkiseAuf(MarkiseObject *markise, bool manuell){
if(markise->markiseStatus == EINGEFAHREN || manuell==true){
Serial.println("Fahre Markise Auf (Set Semaphore)");
markise->markiseStatus = FAHRE_AUF;
xSemaphoreGive(MarkiseAufSemaphore);
}
}
void TaskFahreMarkiseAuf(void * pvParameters){
Serial.println("Starte TaskFahreMarkiseAuf");
for(;;){
//Semaphore abfragen
if (xSemaphoreTake(MarkiseAufSemaphore, portMAX_DELAY)) {
display_ctrl.setStatus("MARKISE", "--> AUF");
Serial.println("Task-->Fahre Markise Auf -- Start");
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp("Task-->Fahre Markise Auf").c_str());
digitalWrite(LED_GELB, LOW);
digitalWrite(LED_ROT, HIGH); // turn the LED off
digitalWrite(LED_GRUEN, LOW); // turn the LED on
// Schalte Relais für Markise auf
digitalWrite(PIN18AUF, HIGH);
vTaskDelay((500) / portTICK_PERIOD_MS); //500MS Tastendruck
digitalWrite(PIN18AUF, LOW);
vTaskDelay((markiseObject.markiseLaufzeitSekunden*1000) / portTICK_PERIOD_MS);
digitalWrite(PIN20STOPP, HIGH);
vTaskDelay((500) / portTICK_PERIOD_MS); //500MS Tastendruck "Relais Stopp"
digitalWrite(PIN20STOPP, LOW);
markiseObject.counterMarkiseAufAbtastung=0;
markiseObject.counterMarkiseZuAbtastung=0;
markiseObject.markiseStatus = AUSGEFAHREN;
Serial.println("Task-->Fahre Markise Auf -- End");
digitalWrite(LED_ROT, HIGH); // turn the LED off
digitalWrite(LED_GRUEN, LOW); // turn the LED on
digitalWrite(LED_GELB, HIGH);
}
}
}
//**************************************************
//**************************************************
void FahreMarkiseZu(MarkiseObject *markise, bool manuell){
digitalWrite(LED_ROT, LOW); // turn the LED on
if(markise->markiseStatus == AUSGEFAHREN || manuell==true){
Serial.println("Fahre Markise ZU (Set Semaphore)");
markise->markiseStatus = FAHRE_ZU;
xSemaphoreGive(MarkiseZuSemaphore);
}
}
void TaskFahreMarkiseZu(void * pvParameters){
Serial.println("Starte TaskFahreMarkiseZu");
for(;;){
//Semaphore abfragen
if (xSemaphoreTake(MarkiseZuSemaphore, portMAX_DELAY)) {
Serial.println("Task-->Fahre Markise Zu -- Start");
display_ctrl.setStatus("MARKISE", "--> ZU");
if(trace_on == true) appendFile(LittleFS, "/trace.txt", get_time_stamp("Task-->Fahre Markise Zu").c_str());
digitalWrite(LED_GELB, LOW);
digitalWrite(LED_GRUEN, HIGH);
digitalWrite(LED_ROT, LOW);
// Schalte Relais für Markise zu
digitalWrite(PIN19ZU, HIGH);
vTaskDelay((500) / portTICK_PERIOD_MS); //500MS Tastendruck "Relais zu"
digitalWrite(PIN19ZU, LOW);
vTaskDelay((markiseObject.markiseLaufzeitSekunden*1000) / portTICK_PERIOD_MS);
digitalWrite(PIN20STOPP, HIGH);
vTaskDelay((500) / portTICK_PERIOD_MS); //500MS Tastendruck "Relais Stopp"
digitalWrite(PIN20STOPP, LOW);
markiseObject.markiseStatus = EINGEFAHREN;
markiseObject.counterMarkiseAufAbtastung=0;
markiseObject.counterMarkiseZuAbtastung=0;
Serial.println("Task-->Fahre Markise Zu -- End");
digitalWrite(LED_ROT, LOW);
digitalWrite(LED_GRUEN, HIGH);
digitalWrite(LED_GELB, HIGH);
}
}
}
Hier im Setup() sieht man das anlegen der Tasks und Semaphores sowie das Aufsetzen des LittleFS, welches für das Traceing und den Webserver benötigt wird. Auch ist zu sehen, dass die Uhrzeit über NTC geholt wird und die interne Uhr (rtc) gesetzt wird.
Die Semaphore werden gestartet sowie alle Tasks.
Auch ein Watchdog-Timer für die Tasks wird initialisiert.
Das Starten des Webservers leitet dann über zum Web-Interface welches auch auf dem ESP32S3 gehostet ist.
void setup() {
Serial.begin(115200);
//Wire.setPins(SDA, SCL);
Wire.begin(SDA,SLC);
pinMode(LED_GELB, OUTPUT);
pinMode(LED_ROT, OUTPUT);
pinMode(LED_GRUEN, OUTPUT);
pinMode(PIN18AUF, OUTPUT);
pinMode(PIN19ZU, OUTPUT);
pinMode(PIN20STOPP, OUTPUT);
digitalWrite(LED_GELB, HIGH); // turn the LED off
digitalWrite(LED_ROT, HIGH); // turn the LED off
digitalWrite(LED_GRUEN, HIGH); // turn the LED off
digitalWrite(PIN18AUF, LOW); // Turn Transistor closed
digitalWrite(PIN19ZU, LOW); // Turn Transistor closed
digitalWrite(PIN20STOPP, LOW); // Turn Transistor closed
lightMeter.begin();
/*bool status = bme.begin();
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}*/
sensor.begin(Bme280TwoWireAddress::Primary); // Also for BMP
sensor.setSettings(Bme280Settings::weatherMonitoring());
DisplayMutex = xSemaphoreCreateMutex();
if (DisplayMutex == NULL) {
Serial.println("Failed to create DISPLAY mutex!");
while (1);
}
EEPROM.begin(EEPROM_SIZE);
int eepromSize = EEPROM.length();
Serial.println("EEPROM_SIZE=="+ String(eepromSize));
uint16_t statusEEPROM_Values;
EEPROM.get(0, statusEEPROM_Values);
Serial.println("statusEEPROM_Values==="+ String(statusEEPROM_Values));
EEPROM.get(0, markiseObject);
PrintMarkiseStruct(&markiseObject);
if (statusEEPROM_Values == uint16_t(EEPROM_VALID_FLAG)) // Überprüfen, ob Werte im EEPROM gültig sind
{
EEPROM.get(0, markiseObject);
Serial.println("EEPROM holen= "+ String(markiseObject.statusEEPROM_Values));
PrintMarkiseStruct(&markiseObject);
}
else
{
InitMyData(&markiseObject);
//EEPROM.put(0, markiseObject);
//EEPROM.commit();
//EEPROM.get(0, markiseObject);
//markiseObject.statusEEPROM_Values= EEPROM_VALID_FLAG;
Serial.println("EEPROM schreiben= "+ String(markiseObject.statusEEPROM_Values));
PrintMarkiseStruct(&markiseObject);
}
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
u8g2_for_adafruit_gfx.begin(display); // connect u8g2 procedures to Adafruit GFX
u8g2_for_adafruit_gfx.setFont(u8g2_font_helvR14_tf); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
u8g2_for_adafruit_gfx.setFontMode(1); // use u8g2 transparent mode (this is default)
u8g2_for_adafruit_gfx.setFontDirection(0); // left to right (this is default)
u8g2_for_adafruit_gfx.setForegroundColor(WHITE); // apply Adafruit GFX color
//print_display("WAIT FOR", "COMMAND");
timer= timerBegin(1000000000); // Set IR pulse counter to 1MS resolution
initWiFi();
initLittleFS();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
if(!getLocalTime(&timeinfo)){
Serial.println("Zeit konnte nicht geholt werden");
return;
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
rtc.setTimeStruct(timeinfo);
display_ctrl.setTime(rtc.getTime(), ""); // Set time for display
MarkiseZuSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
if (MarkiseZuSemaphore == NULL) {
Serial.println("Failed to create MarkiseZuSemaphore!");
while (1);
}
MarkiseAufSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
if (MarkiseAufSemaphore == NULL) {
Serial.println("Failed to create MarkiseAufSemaphore!");
while (1);
}
xTaskCreatePinnedToCore(
TaskFahreMarkiseZu, /* Funktion */
"TaskFahreMarkiseZu", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
2, /* Priorität */
&TaskMarkiseZu, /* Task-Handle */
0); /* Kern 1 */
xTaskCreatePinnedToCore(
TaskFahreMarkiseAuf, /* Funktion */
"TaskFahreMarkiseAuf", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
2, /* Priorität */
&TaskMarkiseAuf, /* Task-Handle */
0); /* Kern 1 */
xTaskCreatePinnedToCore(
TaskWind, /* Funktion */
"TaskWind", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
2, /* Priorität */
&TaskWindSensor, /* Task-Handle */
0); /* Kern 1 */
xTaskCreatePinnedToCore(
TaskMesseLicht, /* Funktion */
"LichtMessen", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
2, /* Priorität */
&TaskLichtMessen, /* Task-Handle */
0); /* Kern 1 */
xTaskCreatePinnedToCore(
TaskCheck_BME280, /* Funktion */
"BME280Messen", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
2, /* Priorität */
&TaskBME280Messen, /* Task-Handle */
0); /* Kern 1 */
xTaskCreatePinnedToCore(
Markise, /* Funktion */
"MarkiseTask", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
1, /* Priorität */
&MarkiseTask, /* Task-Handle */
0); /* Kern 1 */
xTaskCreatePinnedToCore(
TaskChangeDisplay, /* Funktion */
"DisplayTask", /* Name */
10000, /* Stackgröße */
NULL, /* Parameter */
2, /* Priorität */
&TaskDisplay, /* Task-Handle */
0); /* Kern 1 */
// Activate watchdog with system reset after timeout
//esp_task_wdt_init(&twdt_config);
Watchdog = xTimerCreate("WatchdogTimer",
1000/portTICK_PERIOD_MS, // 1 Sek period
pdTRUE,
NULL,
WDTCallback);
if (Watchdog == NULL) {
Serial.println("Failed to create Watchdogtimer!");
while (1);
}
xTimerStart(Watchdog, 0); // Starte Watchdog
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(LittleFS, "/index.html", "text/html", false);
});
server.serveStatic("/", LittleFS, "/");
//Response for all states
server.on("/states", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Response for all states");
String json = getOutputStates();
request->send(200, "application/json", json);
json = String();
});
// Response for current sun/wind values
server.on("/current", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Response for current sun/wind values");
String json = getOutputStates2();
request->send(200, "application/json", json);
json = String();
});
//GET request to /update?output=
Der asynchrone Webserver hostet die Web-Site, welche dem User die Möglichkeiten gibt, die Automatik Ein-/Auszuschalten, die Markise auf_/zuzufahren und die Sollwerte für Sonne und Wind zu setzen.
Auch werden die aktuellen Werte für Wind und Sonne angezeigt, dies erfolgt alle 10 Sekunden. Ein Gesamtupdate aller Werte erfolgt nach 30 Sekunden.
Markise
Markisen-Steuerung
Automatik
Wenn Sie die Markise nur von Hand steuern möchten, schalten sie die Automatik aus.
Status: ????
Handbetrieb
Hier können Sie die Markise manuell steuern. Der Status der Automatik verändert sich nicht
Status: ????
Sollwerte Sonne/Wind
Mit diesen Werten bestimmen Sie das Öffnen/Schließen der Markise bei Sonne und Wind.
Sonne-Istwert: ????
Wind-Istwert: ????
window.addEventListener('load', getStates);
var plus = 31;
var plusSon = 25000;
var auto ="Ein";
var Hand ="Markise zu";
var dummyJSON = '{"sonne_ist":"20000","wind_ist":"22","wind_max":"11", "sonne_soll":"15000", "auto":"EinEin", "manu":"Markise zuzu"}';
var dummyJSON2 = '{"sonne_ist":"30000","wind_ist":"11"}';
var myTimerVar = setInterval(getStates, 30000);
var myTimer2Var = setInterval(myTimer, 10000);
var timerPlus = 0;
function getStates() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myObj = JSON.parse(this.responseText);
//var myObj = JSON.parse(dummyJSON);
console.log(myObj)
//console.log("Hallo aus der Funktion getStates ");
var val_auto = myObj.auto;
var val_manu = myObj.manu;
var val_wind = myObj.wind_max;
var val_sonne = myObj.sonne_soll;
var val_wind_ist = myObj.wind_ist;
var val_sonne_ist = myObj.sonne_ist;
document.getElementById("windWert").innerHTML ="Wind-Istwert:" +val_wind_ist +"km/h" ;
document.getElementById("sonnenWert").innerHTML ="Sonnen-Istwert:" +val_sonne_ist +"Lux" ;
document.getElementById("windInput").value=val_wind;
document.getElementById("sonneInput").value=val_sonne;
document.getElementById("stateAuto").innerHTML = "Status: " + val_auto;
document.getElementById("stateManu").innerHTML = "Status: " + val_manu;
}
};
xhr.open("GET", "/states", true);
xhr.send();
}
/*function getStatesTest(){
//document.getElementById("stateSonne").innerText = "2500Lux";
//document.getElementById("stateWind").innerText = plus +"km/h";
// setTimeout(myFunction, 3000);
plus= plus + 1;
plusSon = plusSon + 1000;
if (auto == "Ein"){
auto = "Aus";
}else{
auto = "Ein";
}
if (Hand == "Markise zu"){
Hand = "Markise auf";
}else{
Hand = "Markise zu";
}
// let text = document.getElementById("windWert").textContent;
document.getElementById("windWert").innerHTML ="Wind-Istwert: " +plus +"km/h" ;
document.getElementById("sonnenWert").innerHTML ="Sonnen-Istwert: " +plusSon +"Lux" ;
document.getElementById("stateAuto").innerHTML = "Status: " + auto;
document.getElementById("stateManu").innerHTML = "Status: " + Hand;
//alert("Sonnen-Sollwert wurde geändert");
} */
function myTimer() {
console.log("Hallo aus der Funktion myTimer ");
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myObj = JSON.parse(this.responseText);
//var myObj = JSON.parse(dummyJSON2);
console.log(myObj)
//var val_wind_ist = +myObj.wind_ist + timerPlus;
//var val_sonne_ist = +myObj.sonne_ist + timerPlus;
var val_wind_ist = +myObj.wind_ist;
var val_sonne_ist = +myObj.sonne_ist;
timerPlus = timerPlus + 1;
document.getElementById("windWert").innerHTML ="Wind-Istwert: " +val_wind_ist +"km/h" ;
document.getElementById("sonnenWert").innerHTML ="Sonnen-Istwert: " +val_sonne_ist +"Lux" ;
}
};
xhr.open("GET", "/current", true);
xhr.send();
}
// Send Requests to HW
function sendControls (element) {
var xhr = new XMLHttpRequest();
if (element.id == "button-auf"){
xhr.open("GET", "/update?manu=auf", true);
document.getElementById("stateManu").innerHTML = "Status: Markise auf";
}
if (element.id == "button-zu"){
xhr.open("GET", "/update?manu=zu", true);
document.getElementById("stateManu").innerHTML = "Status: Markise zu";
}
if (element.id == "button-ein"){
xhr.open("GET", "/update?auto=ein", true);
document.getElementById("stateAuto").innerHTML = "Status: Ein";
}
if (element.id == "button-aus"){
xhr.open("GET", "/update?auto=aus", true);
document.getElementById("stateAuto").innerHTML = "Status: Aus";
}
if (element.id == "button-sonne"){
xhr.open("GET", "/update?sonne=" + document.getElementById("sonneInput").value, true);
}
if (element.id == "button-wind"){
xhr.open("GET", "/update?wind=" + document.getElementById("windInput").value, true);
}
xhr.send();
//getStates(); // Alles aktualisieren nach Änderung
}
Hier noch die beiden Programmteile im ESP32 Code, die die Json-Daten für das Update der Website zur Verfügung stellen.
Beide Programm werden im Setup() aufgerufen ab Zeile 186. Dort befindet sich auch der Programmteil, der das Setting der Werte aus dem Webinterface erledigt.
//*********************************************************************
//*********************************************************************
// Return JSON with all Output States for Webpage init
String getOutputStates() {
JSONVar myArray;
String var ="";
myArray["sonne_ist"]= String(markiseObject.sonneIstWert);
myArray["wind_ist"]= String(markiseObject.windIstWert);
myArray["wind_max"]= String(markiseObject.windSollWert);
myArray["sonne_soll"]= String(markiseObject.sonneSollWert);
if (markiseObject.automatikStatus == EIN) {
var="EIN";
}else {
var = "AUS";
}
myArray["auto"]= var;
switch (markiseObject.markiseStatus) {
case EINGEFAHREN: var= "Markise ZU"; break;
case AUSGEFAHREN: var = "Markise AUF"; break;
case FAHRE_AUF: var = "Markise -> AUF";break;
case FAHRE_ZU: var = "Markise -> ZU";break;
}
myArray["manu"]= var;
String jsonString = JSON.stringify(myArray);
Serial.print(jsonString);
return jsonString;
}
//*********************************************************************
//*********************************************************************
// Return JSON2 with current sun/wind Output States
String getOutputStates2() {
JSONVar myArray;
String var ="";
myArray["sonne_ist"]= String(markiseObject.sonneIstWert);
myArray["wind_ist"]= String(markiseObject.windIstWert);
String jsonString = JSON.stringify(myArray);
Serial.print(jsonString);
return jsonString;
}

Für meine Software-Projekte musste ich mit das Thema Uhrzeit aus dem Internet holen und die interne ESP-RTC setzen.Das alles habe ich hier zusammengetragen, damit es

Was soll hier gezeigt werden? Für mich war die ESP-IDF neu, darum habe ich folgendes ausprobiert, um festzustellen, wie elegant sich das lösen lässt. Folgende

Die Idee ist, Steckdosen mit Timer extern steuern zu können. Dafür wurde ein ESP32-S3 und eine Echtzeituhr DS3231 mit I2C Interface genutzt. Ein 4×20 LCD-Display