Programmierung

  • Mikrocontroller
    • Ablauf / Anforderung an den Mikrocontroller
    • Genutzte Bibliotheken
    • WiFi Setup
    • Servo Setup
    • Request-Handling für Fernsteuerung
    • Steuerung via. direkter Interaktion

Ablauf / Anforderung an den Mikrocontroller

Die Software für den Mikrocontroller muss für unsere Zwecke folgende Dinge können:

  • Öffnen einen Access Points (“AP”)
  • Eingabe eines WiFi Kennworts
  • Steuerung des Servos
  • Loop
  • Fernsteuerung via. Request-Handling
  • Steuerung via. lokaler Website
  • Visuelles Feedback via. LED

Genutze Bibliotheken

Damit wir die Features, welche für die Umsetzung relevant sind, unterstützen können, brauchen wir entsprechende Bibliotheken:

//Bibliotheken für WifiSetup und (Fern-)Steuerung
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>        
#include <ESP8266WiFi.h>

//Bibliothek zum ansteuern des Servo
#include <Servo.h>

Diese teilen sich in 2 Kategorien auf:

  • Wifi Setup und (Fern-)Steuerung
  • Servo Steuerung

Die Bibliothek WifiManager.h verwenden wir für allgemeine Netzwerkkommunikation via WiFi.

Damit diese aufgesetzt werden kann, verwenden wir die Bibliotheken DNSServer, ESP8266WebServer, sowie ESP8266WiFi. Mehr dazu im nächsten Abschnitt.


WiFi Setup

Für das WiFi Setup werden wir einen lokalen AP öffnen, wodurch der Nutzer seinen eigenen AP via. BSSID auswählen kann und danach das entsprechende Passwort eingeben kann.

Kommen wir zuerst zu den globalen Variablen:

WiFiServer server(80);  //Server for AP
WiFiClient client;      //Client for communication
bool wifiConnected = false;

Ohne weiter auf diese einzugehen kommen wir direkt zu unserer setup()-Methode, da das Konzept anhand dieser verständlicher zu Erläutern ist:

void setup() 
{
  Serial.begin(115200);
  
  //Pins
  pinMode(servoPin, OUTPUT);
  pinMode(ledPin, OUTPUT);

  WiFiManager wifiManager;

  wifiManager.autoConnect("SoftSkillsSoSe2022");
  
  Serial.println("Connected.");
  server.begin();
  Serial.println("Server started"); 

  // Print the IP address
  Serial.print("Use this URL to connect: ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");
  wifiConnected = true;
  
  servo.attach(servoPin); //setzen des Servo auf den D-PIN 9
  digitalWrite(servoPin, LOW);
}

Wie wir hier sehen können öffnen wir einen AP mit der BSSID “SoftSkillsSoSe2022”, womit sich der Endnutzer verbinden kann.

Nachdem sich der Nutzer verbunden hat wird dieser folgendermaßen gegrüßt:

! Bild hier einfügen

Nach Auswahl des eigenen AP und Eingabe des entsprechenden Passwortes wird der AP des Mikrocontrollers geschlossen und es stehen 2 Modi zur Steuerung des Gerätes zur Verfügung.


Servo Setup

Um den Servo zu Konfigurieren und Anzusteuern braucht es lediglich die Bibliothek “Servo.h” und einen GPIO Pin (in unserem Fall PIN 9).

Zuerst brauchen wir eine globale Variable um den Servo von Software-Seite aus anzusteuern und einen GPIO PIN für die reale Verbindung zwischen Controller und Servo.

Servo servo;            //Servo for functionality
const int servoPin = 9; //(D4)

Blicken wir auf die setup()-Methode zurück fällt folgendes auf:

servo.attach(servoPin); //setzen des Servo auf den D-PIN 9
digitalWrite(servoPin, LOW);

Hier ordnen wir unsere Variablen “servo” vom Typen “Servo” den entsprechenden Pin zu und setzen diesen auf LOW um jegliche Kontraktion vorerst zu vermeiden.


Loop

Bevor wir weiter auf die Steuerung des Gerätes eingehen, werfen wir kurz einen Blick auf unsere loop()-Methode:

void loop(){
  if(!wifiConnected)
  {
    hostPage();
  }
  
  while(!client.available())
  {
    delay(1);
  }
  
  if(client.available())
  {
    requestHandling();
  }
}

Diese unterscheidet zwischen 2 Modi:

  • “Ich habe eine Verbindung und kann ferngesteuert werden”
  • “Ich habe keine Verbindung, aber man kann mich lokal steuern”

Im ersten Fall rufen wir die Methode requestHandling() auf, im zweiten Fall entsprechend die hostPage()-Methode.

Auf beide Methoden werden wir im folgenden eingehen.


Request-Handling für Fernsteuerung

Alles klar, wir haben eine Verbindung! Aber was nun?

Im Endeffekt soll der Mikrocontroller auf einen Befehl warten. Dies realisieren wir über einen lokalen Webserver, wodurch der Endnutzer diesen Port freigeben müsste, oder entsprechend einen Tunnel aufsetzen müsste.

Sehen wir davon ab ist das Konzept sehr simpel und wir unterscheiden zwischen zwei Endpunkten:

  • /COMPUTER=ON und
  • /COMPUTER=OFF

Werfen wir einen Blick in den Code, bei welchem wir auf eine Anfrage warten:

WiFiServer server(80);
WiFiClient client; 

void requestHandling()
{
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
   // Read the first line of the request
  String request = client.readStringUntil('\r');
  Serial.println(request);
  client.flush();

Insofern der Mikrocontroller den Port 80 noch nicht nutzt und eine Verbindung zu einem WLAN hat, warten wir auf eine Request von außen.

Sobald eine Request (=”Anfrage”) empfangen wird passiert folgender Abgleich:

  if (request.indexOf("/COMPUTER=ON") != -1)  
  {
     ....
  }
  if (request.indexOf("/COMPUTER=OFF") != -1)  
  {
     ....
  }

Hierdurch “weiß” der Controller was zu tun ist. Aber was genau ist denn nun zu tun?

  • Servo kontrahieren
  • LED an/aus machen
  • Visuelles Feedback via Response

Für die Kontraktion des Servos haben wir eine push()-Methode angelegt, welche wie folgt aussieht:

void push()
{
  //For Schleife zum drehen des Servos um 360°
  for(position = 0; position < 360; position++) 
  { 
    servo.write(position); //Schreiben des aktuellen Wertes der Variable 'position' (1...360)
    delay(waitTime); //Pause
  } 
}

Aufgrund unserer Umsetzung eines Gelenks (Siehe Modellierung), müssen wir lediglich eine 360° Rotation ausführen für einen Knopfdruck.

Für das Nutzer-Feedback über die LED lassen wir diese im Anschluss kurz wie folgt blinken:

void led()
{
  int delayTime = 500; 
  digitalWrite(ledPin, HIGH);
  delay(delayTime);  
  digitalWrite(ledPin, LOW);
  delay(delayTime); 
}

Hier der komplette Code d. Request-Handling (zuvor nicht gezeigt, aufgrund des hohen Anteils an HTML:

void requestHandling()
{
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  // Read the first line of the request
  String request = client.readStringUntil('\r');
  Serial.println(request);
  client.flush();
 
  int value = LOW;
  if (request.indexOf("/COMPUTER=ON") != -1)  
  {
    push();
    value = HIGH;
  }

  if (request.indexOf("/COMPUTER=OFF") != -1)  
  {
    push();
    value = LOW;
  }
  
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println("Origin: SoftSkillsSoSe2022-Gang");
  client.println(""); //  do not forget this one
  client.println("<!DOCTYPE HTML>");
  client.println("<html>");
  client.println("<body>");
  client.println("<h1>Computer is powered now: ");
  
  if(value == HIGH) 
  {
    client.print("On</h1>");
  } else {
    client.print("Off</h1>");
  }
  client.println("<br><br>");
  client.println("<a href=\"/COMPUTER=ON\"\"><button>Turn On </button></a>");
  client.println("<a href=\"/COMPUTER=OFF\"\"><button>Turn Off </button></a><br />");  
  client.println("</body></html>");
  
  delay(1);
  Serial.println("Client dicsonnected");
  Serial.println("");
}

Steuerung via. direkter Interaktion

Wie man zuvor ggf. feststellen konnte unterscheidet sich die beiden Interaktionsmodi im wesentlichen nur darin, ob eine Verbindung zum WiFi besteht, oder nicht.

Da das Konzept somit nahezu identisch ist hier die Implementierung der hostPage()-Methode:

void hostPage()
{
  WiFiClient client = server.available();   // Listen for incoming clients

  if (client) {                             // If a new client connects,
    String currentLine = "";                // make a String to hold incoming data from client
    while (client.connected()) {            // loop 
      if (client.available()) {             // if there's bytes to read
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character and the current line len is 0 we would have 2 
                                            // newline characters which indicated the end of the request, so send a response:
          if (currentLine.length() == 0) 
          {
              //Response-Header
              client.println("HTTP/1.1 200 OK");
              client.println("Content-Type: text/html");
              client.println("Origin: SoftSkillsSoSe2022-Gang");
              client.println(""); //  do not forget this one
              client.println("<!DOCTYPE HTML>");
              client.println("<html>");

              //Header
              client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
              client.println("<link rel=\"icon\" href=\"data:,\">");
              client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
              client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
              client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
              client.println(".button2 {background-color: #77878A;}</style></head>");

              //Body
              client.println("<body>");
              client.println("<h1>Computer is powered now: ");
              
              if(value == HIGH) 
              {
                client.print("On</h1>");
              } else {
                client.print("Off</h1>");
              }
              client.println("<br><br>");
              client.println("<a href=\"/COMPUTER=ON\"\"><button>Turn On </button></a>");
              client.println("<a href=\"/COMPUTER=OFF\"\"><button>Turn Off </button></a><br />");  
              client.println("</body></html>");
              
              delay(1);
              Serial.println("Client dicsonnected");
              Serial.println("");
              break;
          } else {
            currentLine = "";
          }
        } else if (c != '\r') {
          currentLine += c;
        }
      }
    }
    header = "";
  }
}