Table of Contents

Arduino/Java Embedded Projekt: Wi-FLight

Dieses simple Kleinprojekt soll auf Basis von Embedded Hardware in drei aufeinander aufbauenden Schritten den fliessenden Übergang einer reinen Elektronik-Umgebung zu einer softwarekombinierten Elektronik-Umgebung veranschaulichen. Kurz gesagt: Den Grundbaustein von Systemtechnik/Informatik aufzeigen.
Alle drei Schritte haben dasselbe Ziel: Eine LED zum Leuchten bringen.
Der wesentliche Unterschied der drei Schritte ist der Weg zum Ziel. Folgende Varianten werden dabei behandelt:

  1. Reine Hardware-Schaltung
  2. Hardware-Schaltung mit Embedded Software: Interne Steuerung
  3. Hardware-Schaltung mit Embedded Software und externer Applikation: Externe Steuerung

Das Tutorial ist aufbauend gestaltet, es sollten also alle Übungen und Schritte nacheinander durchgeführt werden, ohne etwas auszulassen!

Variante 1: Reine Hardware-Schaltung

Bereits mit wenig Aufwand und wenig Hardware kann eine simple, leicht verständliche und manuell zu bedienende Schaltung aufgebaut werden.

Schritt 1: Theoretisches OFF-Szenario

Das geplante OFF-Szenario soll wie folgt aussehen:

Schritt 2: Theoretisches ON-Szenario

Das geplante ON-Szenario soll wie folgt aussehen:

Schritt 3: Praktische Umsetzung

Benötigtes Material:

Alle Komponenten wie abgebildet miteinander verbinden.

Variante 2: Hardware-Schaltung mit Embedded Software: Interne Steuerung

Die nächste Variante zeigt eine bereits viel komfortablere, automatisierte Lösung: Mit wenig Zusatzaufwand und entsprechender Zusatz-Hardware (Mikrocontroller) wird das Projekt um simple "Intelligenz" künstlich ergänzt.
Beim Mikrocontroller-Board handelt es sich um ein Low-Power Embedded-Entwicklerboard mit integriertem WiFi-Modul. Es verfügt -zusätzlich zum vorhandenen Mikrocontroller- über einen Bootloader-Chip, welcher es ermöglicht, den Mikrocontroller ohne zusätzlichen Debugger via Adruino zu programmieren.

Schritt 1: Theoretisches OFF-Szenario

Das geplante OFF-Szenario soll wie folgt aussehen:



Schritt 2: Theoretisches ON-Szenario

Das geplante ON-Szenario soll wie folgt aussehen:



Schritt 3: Praktische Umsetzung

Benötigtes Material:

Pinout-Diagramm des HUZZAH Mikrocontroller-Boards:
huzzah_esp8266_pinout_v1.2.pdf

Als Erstes werden am Relay-Board die gewünschten Pads gemäss Hersteller-Anleitung miteinander verbunden, in diesem Beispiel werden die Pads für Pin-Nr. 14 (im folgenden Bild rot markiert) verwendet:



Danach kann das modulare Power Relay adaptiv aufgesteckt und die geplante physische Schaltung aufgebaut werden.

Via USB-Mini Kabel wird der Mikrocontroller an einen PC mit installierter Arduino-Applikation angeschlossen. Jetzt müssen der Arduino-Applikation die Treiber/Libraries für den entsprechenden Mikrocontroller zur Verfügung gestellt werden:

In der Arduino-Applikation unter:

FilePreferences im Feld Additional Boards Manager URLs

folgende URL einfügen:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Mit OK bestätigen.

Jetzt unter ToolsBoardsBoards Manager die ESP8266-Prozessorfamilie installieren.

Die benötigten Treiber/Libraries sind nun zum Arbeiten in den Arduino-Applikation verfügbar.
Unter ToolsBoards kann nun das entsprechende Adafruit Feather HUZZAH ESP8266 Board ausgewählt werden, der entsprechende COM-Port muss auch noch ausgewählt werden.

Für ein automatisiertes Ein-/Ausschalten der LED wird folgender Arduino-Code angewendet:

/*
   Project: Wi-FLight 
   Version: 1.0
   Author: Di Taranto
   Date: 20.02.2019
*/
 
#include <ESP8266WiFi.h>
const int   switchPin = 14;
 
void setup() {
  // Set switchPin as an output pin
  pinMode (switchPin, OUTPUT);
} // setup
 
void loop() {
  // Switch relay ON
  digitalWrite (switchPin, HIGH);
  delay (1000);
  // Switch relay OFF
  digitalWrite (switchPin, LOW);
  delay (1000);
} // loop

Die LED wird nach dem Code-Upload in Sekundentakt automatisiert ein-/ausgeschaltet.

Variante 3: Hardware-Schaltung mit Embedded Software und externer Applikation: Externe Steuerung

Mit ein wenig Mehraufwand und etwas tieferen Programmierkenntnissen ist es möglich, auf relativ einfache Weise zusätzlichen Komfort zu integrieren: Manuelles Ein-/Ausschalten der LED von einem extern PC via WiFi für mehrere Relais-Instanzen. Für die Umsetzung werden bereits vorhandene Java-Libraries -u.a. ein Java basierter Webserver- eingesetzt.

Der bestehende physische Aufbau aus Variante 2 bleibt bestehen, es muss diesbezüglich nichts geändert werden.

Schritt 1: Theoretisches OFF-Szenario

Das geplante OFF-Szenario soll wie folgt aussehen:


Schritt 2: Theoretisches ON-Szenario

Das geplante ON-Szenario soll wie folgt aussehen:



Schritt 3: Praktische Umsetzung

Benötigtes Material:

Folgender Arduino-Code wird verwendet (Entsprechende Werte für Parameter ssid, password, host, ip, gateway, subnet anpassen):

/*
   Project: Wi-FLight
   Version: 1.0
   Author: Di Taranto
   Date: 20.02.2019
*/
 
#include <ESP8266WiFi.h>
 
const int   switchPin = 14;
const int   instanceID = 1;
const int   instanceStatusPin = 4;
int         onOffValue = -1;
const int   offRelaisPin = 12;
const int   onRelaisPin = 14;
const char* ssid = "<mySSID>";
const char* password = "<myPassword>";
const char* host = "<xxx>.<xxx>.<xxx>.<xxx>";
 
void setup() {
 
  // Set switchPin as an output pin
  pinMode (switchPin, OUTPUT);
 
  // Initialize the serial interface
  Serial.begin(115200);
  Serial.setTimeout(2000);
 
  // Set static client IP address
  IPAddress ip(<xxx>, <xxx>, <xxx>, <xxx>);
  IPAddress gateway(<xxx>, <xxx>, <xxx>, <xxx>);
  IPAddress subnet(<xxx>, <xxx>, <xxx>, <xxx>);
 
  // Wake up the WiFi adapter and disable its cache
  WiFi.forceSleepWake();
  delay(1);
  WiFi.persistent(false);
 
  // Initialize the WiFi adapter
  WiFi.mode(WIFI_STA);
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(ssid, password);
 
  // Start connecting to the WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  } // while
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
 
} // setup
 
void loop() {
 
  // Check whether relay is already switched on/off
  onOffValue = digitalRead(switchPin);
 
  // Connect to the webserver host (Use WiFi client class to create a TCP connection)
  Serial.print("connecting to ");
  Serial.println(host);
  WiFiClient client;
  const int httpPort = 80;
  if (client.connect(host, httpPort)) {
 
    // Create a URI for the request
    String url = String("/instance") + String(instanceID) + String(".txt") + String("?") + String(onOffValue);
    Serial.print("Requesting URL: ");
    Serial.println(url);
 
    // Send the request to the server
    client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
    delay(500);
 
    // Read all the lines of the reply from server and print them to the serial interface
    while (client.available()) {
      String line = client.readStringUntil('\n');
      Serial.print ("Line: '");
      Serial.print (line);
      Serial.println ("'");
      String value = "";
      int pos = line.indexOf ("status=");
      if (pos != -1) {
        value = line.substring (pos + 7);
        value.trim();
        // As data transfer is now finished...
 
        // ...print value data
        Serial.print ("Status value: '");
        Serial.print (value);
        Serial.println ("'");
        Serial.println (value.length());
 
        if (value == "on") {
          Serial.println ("Trigger ON relay");
          digitalWrite (switchPin, HIGH);
        }
        else if (value == "off") {
          Serial.println ("Trigger OFF relay");
          digitalWrite (switchPin, LOW);
        } // else
      } // if
    } // while
  } // if
  delay(2000);
} // loop
 
/* ----- End of program ----- */

Für den Java-Webserver wird folgender Code verwendet:

/*Instance Web-Server
 * 
 * Project: Wi-FLight
 * Version: 1.0
 * Author: Di Taranto
 * Date: 20.02.2019
 * 
 * */
 
import java.io.IOException;
 
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.util.Scanner;
import java.io.*;
 
public class InstanceWebServer {
 
	private static final int port = 80;
	private static final String context = "/";
	private static final String path = "./";
	private static int requestedInstanceNumber = -1;
 
	public static void main(String[] args) throws Exception {
		try {
			System.out.println("Creating Http Server ...");
			HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
			server.createContext(context, new MyContentHandler());
			server.setExecutor(null);
			System.out.println("Starting Http Server ...");
			server.start();
			System.out.println("Http Server started.");
		} catch (Exception e) {
			System.out.println("Unable to start Http Server");
			e.printStackTrace();
		} // try/catch
	} // main
 
	static class MyContentHandler implements HttpHandler {
		public void handle(HttpExchange t) throws IOException {
			System.out.println("Request arrived!");
			Headers headers = t.getResponseHeaders();
			headers.set("Content-Type", "text/txt");
			String instance = t.getRequestURI().getPath(); 
			int pos = instance.indexOf(".");
			String parseStr = instance.substring(9, pos);		
			requestedInstanceNumber = Integer.parseInt(parseStr);
			System.out.println("Requested Instance-Number: " + requestedInstanceNumber); // Output test
			Scanner scanner = new Scanner(new File(path + instance.substring(1, pos) + ".txt"));		
			String relayState = t.getRequestURI().getQuery();
			System.out.println("Relay state: " + relayState); // Output test
			String result = "";
			while (scanner.hasNext()) {
				String response = scanner.next(); // Scanner is listening from now on for any incoming signal
				if ("off".equals(response.substring(7)) && relayState.equals("1")) {
					result += response;
				} // if
				else if ("on".equals(response.substring(7)) && relayState.equals("0")) {
					result += response;
				} // else if
			} // while
			scanner.close(); // Close Scanner so it will not listen anymore for incoming signals.
			// Send the result
			int len = result.length();
			System.out.println("Response: '" + result + "'");
			t.sendResponseHeaders(HttpURLConnection.HTTP_OK, len);
			OutputStream outputStream = t.getResponseBody();
			outputStream.write(result.getBytes());
			outputStream.flush();
			outputStream.close();
 
		} // handle
	} // MyContentHandler
 
} // InstanceWebServer
 
// End of program


Der programmierte Java-Webserver kann in Eclipse wie folgt als ausführbare .JAR-Datei exportiert und dann ausgeführt werden:

Rechtsklick auf die <meinJavaWebserver>.java-Datei → Export…Java-Order Dropdown öffnen → Runnable JAR file auswählen → Next → Jetzt folgende Einstellungen wählen:

Finish klicken

Alternativ kann der Webserver auch direkt in Eclipse gestartet werden (Was den Vorteil mit sich bringt, dass man in der Eclipse-Konsole die Ausgabe-Logs sehen kann (Hilfreich für das Troubleshooting, z.B. bei Verbindungsproblemen.)).

Wichtig: Per Default müssen im im selben Verzeichnis, aus dem die der Java-Webserver gestartet wird, folgende zwei Dateien manuell angelegt werden:

Version Windows

  1. instance<meineInstanzNummer>switchON.bat
    mit folgendem Inhalt:
    @echo off
    echo status=on > ./instance<meineInstanzNummer>.txt
  2. instance<meineInstanzNummer>switchOFF.bat
    mit folgendem Inhalt:
    @echo off
    echo status=off > ./instance<meineInstanzNummer>.txt

Wie bereits erwähnt, müssen sich die Files im selben Verzeichnis befinden, aus dem der Java-Webserver gestartet wird. Folgend die beiden Varianten, je nachdem, ob der Java-Webserver über ein .jar-File oder direkt in Eclipse gestartet wird:

Variante .jar
Variante Eclipse

Version Linux

  1. instance<meineInstanzNummer>switchON.sh
    mit folgendem Inhalt:
    echo "status=on" > ./instance<meineInstanzNummer>.txt
  2. instance<meineInstanzNummer>switchOFF.sh
    mit folgendem Inhalt:
    echo "status=off" > ./instance<meineInstanzNummer>.txt

Nun die beiden Skript-Dateien ausführbar machen:

chmod +x instance<meineInstanzNummer>switchON.sh
chmod +x instance<meineInstanzNummer>switchOFF.sh

Die beiden Skript-Dateien können nun zum Ein-/Ausschalten des Relais ausgeführt werden. Der Webserver liest den Inhalt der lokalen .TXT-Datei und entscheidet aufgrund dessen, ob er als http-response einen Ein- oder Ausschalt-Befehl dem anfragenden Client zurücksendet.

Tipp: Auch das Adafruit Feather HUZZAH with ESP8266 Board besitzt eine Konsolenausgabe, welche in Arduino via ToolsSerial Monitor geöffnet werden kann (Achtung: Korrekte Baudrate 115200 baud wählen!):

Erweiterte Zusatzlösung

Die folgende erweiterte Lösung ermöglicht einen USB-Kabel unabhängigen Betrieb des Mikrocontrollers/Relais. Mikrocontroller und Relais werden nun nicht mehr via USB, sondern via 4.2V LiPo Akkumulator gespiesen, was Mobilität ermöglicht.

Wichtig: Ist kein LiPo-Akku mit integriertem Tiefen-Entladeschutz vorhanden, muss in der Arduino-Applikation die Spannung ständig überprüft und entsprechend reagiert werden!
Die folgende Lösung hat das Messen der Akku-Spannung integriert.

Zusätzlich beinhaltet der Java-Code der folgenden Zusatzlösung ein regelmässiges File-Logging der jeweils aktuell herrschenden Akku-Spannung in eine lokale .CSV-Datei. Die Ist-Daten der aktuellen Akku-Spannung erhält der Java-Webserver hierfür bei jedem http-request als Zusatzinfo zugesandt.

Schritt 1: Theoretisches OFF-Szenario

Das geplante OFF-Szenario soll wie folgt aussehen:


Schritt 2: Theoretisches ON-Szenario

Das geplante ON-Szenario soll wie folgt aussehen:



Schritt 3: Praktische Umsetzung

Benötigtes Zusatzmaterial (Empfohlenes Produkt, es kann auch ein anderer Akku verwendet werden.):

Wichtig: Dem Analog-Pin des Mikrocontroller-Board kann eine maximale Spannung von 1V zugeführt werden. Die passenden Vorwinderstände sind gemäss Hersteller (Abschnitt Measuring Battery) zu wählen.
Entsprechend muss ein Umrechnungsfaktor in den Arduino-Code eingefügt werden, um den tatsächlichen Ist-Spannungswert zu erhalten. Der in diesem Projekt verwendete Akku wird mit 3.7V gekennzeichnet, kann jedoch mit einem speziell vom Hersteller verfügbaren Ladegerät eine Spannung von maximal 4.2V erreichen. In diesem Projekt deshalb von einer 4.2V Spannung ausgegangen.

Folgender Arduino-Code wird verwendet (Entsprechende Werte für Parameter ssid, password, host, ip, gateway, subnet anpassen):

/*
   Project: Wi-FLight
   Version: 1.0
   Author: Di Taranto
   Date: 20.02.2019
*/
 
#include <ESP8266WiFi.h>
 
const int   switchPin = 14;
const int   instanceID = 1;
const int   instanceStatusPin = 4;
int         onOffValue = -1;
const int   offRelaisPin = 12;
const int   onRelaisPin = 14;
const char* ssid = "<mySSID>";
const char* password = "<myPassword>";
const char* host = "<xxx>.<xxx>.<xxx>.<xxx>";
const int   batteryLevelPin = A0;
int         batteryLevel = 0;
const int   batteryLevelStageOne = 940; // 3.85V ≙ ~940
 
/*
   Additional information:
   Battery level reference (fully loaded): 4.2V ≙ ~1024
   Battery cuts off automatically by built-in physical discharge protection @ 2.75V ≙ ~650
 
*/
 
void setup() {
 
  // Set switchPin as an output pin
  pinMode (switchPin, OUTPUT);
 
  // Initialize the serial interface
  Serial.begin(115200);
  Serial.setTimeout(2000);
 
  // Set static client IP address
  IPAddress ip(<xxx>, <xxx>, <xxx>, <xxx>);
  IPAddress gateway(<xxx>, <xxx>, <xxx>, <xxx>);
  IPAddress subnet(<xxx>, <xxx>, <xxx>, <xxx>);
 
  // Wake up the WiFi adapter and disable its cache
  WiFi.forceSleepWake();
  delay(1);
  WiFi.persistent(false);
 
  // Initialize the WiFi adapter
  WiFi.mode(WIFI_STA);
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(ssid, password);
 
  // Start connecting to the WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  } // while
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
 
} // setup
 
void loop() {
 
  // Check whether relay is already switched on/off
  onOffValue = digitalRead(switchPin);
 
  // Handle battery level
  batteryLevel = analogRead(batteryLevelPin); //read analog input pin to get battery level value
  Serial.println();
  Serial.print("Battery-Level: ");
  Serial.print(batteryLevel);
 
  // Connect to the webserver host (Use WiFi client class to create a TCP connection)
  Serial.print("connecting to ");
  Serial.println(host);
  WiFiClient client;
  const int httpPort = 80;
  if (client.connect(host, httpPort)) {
 
    // Create a URI for the request
    String url = String("/instance") + String(instanceID) + String(".txt") + String("?") + String(batteryLevel) + String("&") + String(onOffValue);
    Serial.print("Requesting URL: ");
    Serial.println(url);
 
    // Send the request to the server
    client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
    delay(500);
 
    // Read all the lines of the reply from server and print them to the serial interface
    while (client.available()) {
      String line = client.readStringUntil('\n');
      Serial.print ("Line: '");
      Serial.print (line);
      Serial.println ("'");
      String value = "";
      int pos = line.indexOf ("status=");
      if (pos != -1) {
        value = line.substring (pos + 7);
        value.trim();
        // As data transfer is now finished...
 
        // ...print value data
        Serial.print ("Status value: '");
        Serial.print (value);
        Serial.println ("'");
        Serial.println (value.length());
 
        if (value == "on") {
          Serial.println ("Trigger ON relay");
          digitalWrite (switchPin, HIGH);
        }
        else if (value == "off") {
          Serial.println ("Trigger OFF relay");
          digitalWrite (switchPin, LOW);
        } // else
      } // if
    } // while
  } // if
  delay(2000);
} // loop
 
/* ----- End of program ----- */

Für den Java-Webserver wird folgender Code verwendet:

/*Instance Web-Server
 * 
 * Project: Wi-FLight
 * Version: 1.0
 * Author: Di Taranto
 * Date: 20.02.2019
 * 
 * */
 
import java.io.IOException;
 
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.util.Scanner;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
 
public class InstanceWebServer {
 
	private static final int port = 80;
	private static final String context = "/";
	private static final String path = "./";
	private static int requestedInstanceNumber = -1;
	private static String[] httpQueryValues = new String[2];
 
	public static void main(String[] args) throws Exception {
		try {
			System.out.println("Creating Http Server ...");
			HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
			server.createContext(context, new MyContentHandler());
			server.setExecutor(null);
			System.out.println("Starting Http Server ...");
			server.start();
			System.out.println("Http Server started.");
		} catch (Exception e) {
			System.out.println("Unable to start Http Server");
			e.printStackTrace();
		} // try/catch
	} // main
 
	static class MyContentHandler implements HttpHandler {
		public void handle(HttpExchange t) throws IOException {
			System.out.println("Request arrived!");
			Headers headers = t.getResponseHeaders();
			headers.set("Content-Type", "text/txt");
			String instance = t.getRequestURI().getPath(); 
			int pos = instance.indexOf(".");
			String parseStr = instance.substring(9, pos);		
			requestedInstanceNumber = Integer.parseInt(parseStr);
			System.out.println("Requested Instance-Number: " + requestedInstanceNumber); // Output test
			Scanner scanner = new Scanner(new File(path + instance.substring(1, pos) + ".txt"));		
			String batteryQuery = t.getRequestURI().getQuery();			
			httpQueryValues = batteryQuery.split("&");
			System.out.println("Battery-Level: " + httpQueryValues[0]);
			System.out.println("Instance-Status: " + httpQueryValues[1]);
			String result = "";
			while (scanner.hasNext()) {
				String response = scanner.next(); // Scanner is listening from now on for any incoming signal
				if ("off".equals(response.substring(7)) && httpQueryValues[1].equals("1")) {
					result += response;
				} // if
				else if ("on".equals(response.substring(7)) && httpQueryValues[1].equals("0")) {
					result += response;
				} // else if
			} // while
			scanner.close(); // Close Scanner so it will not listen anymore for incoming signals.
			// Logging battery level including timestamp into a local .CSV file
			DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
			LocalDateTime now = LocalDateTime.now();
			System.out.println("Date: " + dtf.format(now) + " ");
			if (httpQueryValues[1].equals("0")) {
				PrintStream myConsole = new PrintStream(
						new FileOutputStream("BatteryLevel Instance-No." + requestedInstanceNumber + ".csv", true));
				myConsole.append(httpQueryValues[0] + ";" + dtf.format(now) + "\r\n");
				myConsole.close();
			} // if
			// Send the result
			int len = result.length();
			System.out.println("Response: '" + result + "'");
			t.sendResponseHeaders(HttpURLConnection.HTTP_OK, len);
			OutputStream outputStream = t.getResponseBody();
			outputStream.write(result.getBytes());
			outputStream.flush();
			outputStream.close();
 
		} // handle
	} // MyContentHandler
 
} // InstanceWebServer
 
// End of program


Ende des Tutorials.