Softwareentwicklung

Der ESP32 µ-Controller steuert drei Sensoren und einen Aktor. Außerdem soll er via Smartphone mithilfe der BlynkApp bedient werden können. Zur Umsetzung des Programmcodes wurde mit der Arduino IDE gearbeitet. Hier werden die verwendeten Bibliotheken, das Setup sowie die einzelnen Methoden genauer erläutert.

Die Bibliotheken

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

Die Arduino Bibliothek stellt viele grundsätzliche Funktionen zur Verfügung. Die Wifi-Bibliothek wird benötigt um mittels Arduino WiFi-Shield mit dem Internet zu verbinden und zuletzt ist die Blynk-Bibliothek zwingend erforderlich um den µC mittels BlynkApp bedienen zu können.

Das Blynk und Wlan Setup

 const char* ssid     = "Eure_SSID";
const char* password = "Euer_WLan_Passwort";
const char* auth = "Euer_von_Blynk_generierter_Schlüssel";
WiFiServer server(80);
WidgetLED statusled(V0);
WidgetLED blockadeled(V1);

Für die Anmeldung und Authentifizierung im lokalen WLan und der Blynk App werden das Password, die SSID und der Authentifizierungscode (welcher von der Blynk App generiert und via EMail übermittelt wird) zunächst in Variablen gespeichert um später die Anmeldung durchzuführen. Desweiteren nutzen wir in der Blynk App zur Visualisierung zwei LEDs, die über die virtuellen Pins V0 und V1 angesteuert werden. die Taster der Blynk App werden später in den Methoden genauer betrachtet.

Festlegung der Pins / Globale Variablen

//PWM
int freq = 5000;
int ledChannel = 0;
int resolution = 8;
// Festlegung der GPIOS:
int betriebsleuchte = 5;
int magnetpin = 15;
int garagenpin1 = 2;
int garagenpin2 = 0;
int schrankenpin = 16;
int motorpin = 21;
int laserpin = 4;
// weitere globale Variablen
boolean torschalter = false;
boolean stopp = false;

Um eine bessere Übersicht zu schaffen werden die Pins des Lolin D32 in integer gespeichert. Außerdem wird der Motor später mit einem PWM Signal betrieben, um die Motorgeschwindigkeit steuern zu können. Auch dazu werden die benötigten Variablen zunächst festgehalten. Zuletzt werden zwei boolsche Variablen für die Betätigung der Taster benötigt.

Die Setup-Methode

void setup() {
Serial.begin(115200);
pinMode(betriebsleuchte, OUTPUT);
pinMode(magnetpin, INPUT);
pinMode(garagenpin1, OUTPUT);
pinMode(garagenpin2, OUTPUT);
pinMode(schrankenpin, INPUT);
pinMode(laserpin, OUTPUT);
pinMode(motorpin, OUTPUT);
delay(20);
// PWM:
ledcSetup(ledChannel, freq, resolution);
ledcAttachPin(21, ledChannel);
ledcWrite(ledChannel, 165);
Blynk.begin(auth, ssid, password, "iot.informatik.uni-oldenburg.de", 8080);
delay(20);
statusled.on();
blockadeled.on();
digitalWrite(garagenpin1, LOW);
digitalWrite(garagenpin2, LOW);
digitalWrite(laserpin, HIGH);
}

Die Setup-Methode dient der Initialisierung des Programms. Sie wird einmalig beim Start des µC ausgeführt. Hier wird zunächst die serielle Ausgabe mit 115200baud eingeschaltet (ist in diesem Fall optional und dient lediglich der Fehlerbehebung da unser Programm später keine Ausgaben über diese Schnittstelle tätigt). Im Anschluss werden alle benötigten Pins entsprechend ihrer Funktion als Output oder Input Pin deklariert. Auch die Stärke des PWM Signals wird im Setup zunächst festgelegt. der Wert 165 hat sich als gutes Potential herausgestellt um die Garage mit einer nicht zu schnellen Geschwindigkeit zu betreiben. Bei einem zu niedrigen Wert kann es sein das Die Volthöhe nicht ausreicht um den Motor zu starten. Je nach verwendetem Motor müsste hier der Wert also angepasst werden. Im Anschluss wird eine Verbindung zum Blynk Server hergestellt und die Status Leds auf dem Smartphone eingeschaltet. Zuletzt werden die Garagenpins, welche den Motor steuern auf LOW gesetzt und die LaserDiode eingeschaltet.

Die Blynk Methoden

BLYNK_WRITE(V2) {
int schalter = param.asInt();
if (schalter == 1) {
torschalter = true;
}
else
torschalter = false;
}

BLYNK_WRITE(V3) {
int stopschalter = param.asInt();
if (stopschalter == 1) {
stopp = true;
}
else
stopp = false;
}

Da das Smartphone zwei Taster steuern soll, braucht der Programmcode auch zwei Methoden zur Steuerung dieser Taster. Verwendet werden in diesem Fall die virtuellen Pins V2 und V3. Diese Methoden sorgen dafür, dass wenn ein Taster auf dem Smartphone gedrückt wird, die entsprechend zugeordnete boolsche Variable auf true gesetzt wird und wenn der Taster wieder losgelassen wird entsprechend auf false. Mit diesen Variablen wird im weiteren Programmcode gearbeitet.

Die Lichtschranken-Methode

boolean schranke() {
int schrankenstatus = digitalRead(schrankenpin);

if (schrankenstatus == 1) {
Serial.println("Tor blockiert");
Blynk.setProperty(V1, "color", "#D3435C");
return true;
}
else {
Serial.println("keine Blockade");
Blynk.setProperty(V1, "color", "#23C48E");
return false;
}
}

Wenn diese Methode aufgerufen wird, überprüft der Pin Schrankenstatus (angeschlossen an den Photoresistor), welche Spannung anliegt. Ist die Spannung high, so kommt das Licht der Laserdiode nicht zum Photoresistor. Die LED wird auf Rot geschaltet und ein true wird zurückgegeben. Falls ein low gemessen wird, wird die LED entsprechend grün geschaltet und ein false zurückgegeben.

Die Magnet-Methode

boolean magnet() {
int magnetstatus = digitalRead(magnetpin);
if (magnetstatus == 1) {
Serial.println("Magnet geschlossen");
Blynk.setProperty(V0, "color", "#23C48E");
return true;
}
else {
Serial.println("Magnet offen");
Blynk.setProperty(V0, "color", "#D3435C");
return false;
}
}

Ähnlich wie bei der Lichtschranken-Methode wird bei der Magnet-Methode die Spannung am Magnetpin gemessen. Je nach gemessenem Wert wird dann die Offen/Geschlossen LED grün oder rot geschaltet und die richtige boolsche Variable zurückgegeben.

Die Fahren-Methode

void fahren() {
uint32_t hochperiod = 2400L;
uint32_t runterperiod = 1800L;
if (magnet()) {
//hochfahren
ledcWrite(ledChannel, 165);
digitalWrite(garagenpin1, LOW);
digitalWrite(garagenpin2, HIGH);
for ( uint32_t tStart = millis(); (millis() - tStart) < hochperiod; ) {
}
digitalWrite(garagenpin2, LOW);
return;
}
if (!magnet() && !schranke()) {
//runter fahren
ledcWrite(ledChannel, 150);
digitalWrite(garagenpin1, HIGH);
digitalWrite(garagenpin2, LOW);
for ( uint32_t tStart = millis(); (millis() - tStart) < runterperiod; ) {
if (schranke() || stopp) {
digitalWrite(garagenpin1, LOW);
hochperiod = (millis() - tStart) * 1.4;
delay(2000);
ledcWrite(ledChannel, 165);
digitalWrite(garagenpin2, HIGH);
digitalWrite(garagenpin1, LOW);
for ( uint32_t tStop = millis(); (millis() - tStop) < hochperiod; ) {
}
digitalWrite(garagenpin2, LOW);
return;
}
}
digitalWrite(garagenpin1, LOW);
}
}

Die Fahren-Methode gliedert sich zunächst in 2 If-Abfragen. Es wird in der einen geprüft, ob das Tor geschlossen ist (Magnet-Methode). Ist das der Fall, werden die Motorsteuerungspins entsprechend geschaltet für eine Periode von 2,4 Sekunden (Umgesetzt mit einer For-Schleife). Ist das Tor offen und das Licht der Laserdiode kommt nicht beim Photoresistor an, geschieht nichts. Ist das Tor offen und nicht blockiert, fährt es herunter. Solange es herunterfährt wird durchgehend geprüft ob der Stopp Taster betätigt wird oder das Tor plötzlich blockiert ist. Ist das der Fall stoppt das Tor, wartet 2 Sekunden und föhrt dann wieder genauso weit hoch, wie es heruntergefahren ist. Da sich herausgestellt hat, dass beim Laufwerksmotor des Modells im vertikalen Zustand des Laufwerks die Schwerkraft stärker auswirkt als gedacht werden zusätzlich Zeiten und Motorgeschwindigkeit beim hoch und runterfahren angepasst um dies auszugleichen. Nach dem vollständigen Fahren wird die Methode wieder verlassen. So ist es möglich mit nur einem Taster immer die gewollte Funktionalität zu gewährleisten.

Das Hauptprogramm

void loop() {
Blynk.run();
schranke();
magnet();
if (torschalter) {
fahren();
}
}

Da aufgrund der übersichtlichkeit, alle Sensoren und Aktoren in eigene Methoden ausgelagert wurden, ist das Hauptprogramm entsprechend übersichtlich gestaltet. Hier werden lediglich die Daten zur App übertragen, ständig die Methoden Schranke und Magnet aufgerufen, um die Leds in der App möglichst in Echtzeit zu aktualisieren und falls der Fahren Taster betätigt wird, wird die Fahren-Methode aufgerufen.

Nachfolgend steht der komplette Quellcode.

 #include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

//WLAN/Blynk
const char* ssid = "eure_SSID";
const char* password = "euer_Passwort";
const char* auth = "euer_Schluessel";
WiFiServer server(80);

//PWM
int freq = 5000;
int ledChannel = 0;
int resolution = 8;

// Festlegung der GPIOS:
int betriebsleuchte = 5;
int magnetpin = 15;
int garagenpin1 = 2;
int garagenpin2 = 0;
int schrankenpin = 16;
int motorpin = 21;
int laserpin = 4;
boolean torschalter = false;
boolean stopp = false;

//BLYNK
WidgetLED statusled(V0);
WidgetLED blockadeled(V1);

void setup() {
Serial.begin(115200);
pinMode(betriebsleuchte, OUTPUT);
pinMode(magnetpin, INPUT);
pinMode(garagenpin1, OUTPUT);
pinMode(garagenpin2, OUTPUT);
pinMode(schrankenpin, INPUT);
pinMode(laserpin, OUTPUT);
pinMode(motorpin, OUTPUT);
delay(20);

// PWM:
ledcSetup(ledChannel, freq, resolution);
ledcAttachPin(21, ledChannel);
ledcWrite(ledChannel, 165);
Blynk.begin(auth, ssid, password, "iot.informatik.uni-oldenburg.de", 8080);
delay(20);
statusled.on();
blockadeled.on();
digitalWrite(garagenpin1, LOW);
digitalWrite(garagenpin2, LOW);
digitalWrite(laserpin, HIGH);
}


BLYNK_WRITE(V2) {
int schalter = param.asInt();
if (schalter == 1) {
torschalter = true;
}
else
torschalter = false;
}


BLYNK_WRITE(V3) {
int stopschalter = param.asInt();
if (stopschalter == 1) {
stopp = true;
}
else
stopp = false;
}


void loop() {
Blynk.run();
schranke();
magnet();
if (torschalter) {
fahren();
}
}


boolean schranke() {
int schrankenstatus = digitalRead(schrankenpin);
if (schrankenstatus == 1) {
Serial.println("Tor blockiert");
Blynk.setProperty(V1, "color", "#D3435C");
return true;
}
else {
Serial.println("keine Blockade");
Blynk.setProperty(V1, "color", "#23C48E");
return false;
}
}


boolean magnet() {
int magnetstatus = digitalRead(magnetpin);
if (magnetstatus == 1) {
Serial.println("Magnet geschlossen");
Blynk.setProperty(V0, "color", "#23C48E");
return true;
}
else {
Serial.println("Magnet offen");
Blynk.setProperty(V0, "color", "#D3435C");
return false;
}
}


void fahren() {
uint32_t hochperiod = 2400L;
uint32_t runterperiod = 1800L;
if (magnet()) {
//hochfahren
ledcWrite(ledChannel, 165);
digitalWrite(garagenpin1, LOW);
digitalWrite(garagenpin2, HIGH);
for ( uint32_t tStart=millis(); (millis()-tStart)<hochperiod; ) {
}
digitalWrite(garagenpin2, LOW);
return;
}
if (!magnet() && !schranke()) {
//runter fahren
ledcWrite(ledChannel, 150);
digitalWrite(garagenpin1, HIGH);
digitalWrite(garagenpin2, LOW);
for ( uint32_t tStart = millis(); (millis() - tStart) < runterperiod; ) {
if (schranke() || stopp) {
digitalWrite(garagenpin1, LOW);
hochperiod = (millis() - tStart) * 1.4;
delay(2000);
ledcWrite(ledChannel, 165);
digitalWrite(garagenpin2, HIGH);
digitalWrite(garagenpin1, LOW);
for ( uint32_t tStop = millis(); (millis() - tStop) < hochperiod; ) {
}
digitalWrite(garagenpin2, LOW);
return;
}
}
digitalWrite(garagenpin1, LOW);
}
}