====== 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: - Reine Hardware-Schaltung - Hardware-Schaltung mit Embedded Software: Interne Steuerung - 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: \\ \\ {{tutorial01_step01_01.png?600|}} ==== Schritt 2: Theoretisches ON-Szenario ==== Das geplante ON-Szenario soll wie folgt aussehen: \\ \\ {{tutorial01_step01_02.png?600|}} ==== Schritt 3: Praktische Umsetzung ==== Benötigtes Material: * 9V Batterie * Kabel * 5V LED * 220Ω Drahtwiderstand * Kabel-Set * Optional: Kippschalter für erleichtertes manuelles Schalten 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: \\ \\ {{tutorial01_step02_01.png?600|}} \\ \\ ==== Schritt 2: Theoretisches ON-Szenario ==== Das geplante ON-Szenario soll wie folgt aussehen: \\ \\ {{tutorial01_step02_02.png?600|}} \\ \\ ==== Schritt 3: Praktische Umsetzung ==== Benötigtes Material: * 9V Batterie * Kabel * 5V LED * 220Ω Drahtwiderstand * Kabel-Set * Breadboard * [[https://www.adafruit.com/product/3213|Adafruit Feather HUZZAH with ESP8266: Version "stacking headers"]] * [[https://www.adafruit.com/product/3191|Adafruit Power Relay FeatherWing]] Pinout-Diagramm des HUZZAH Mikrocontroller-Boards: \\ {{huzzah_esp8266_pinout_v1.2.pdf |}} Als Erstes werden am Relay-Board die gewünschten Pads gemäss [[https://learn.adafruit.com/adafruit-power-relay-featherwing/pinouts|Hersteller-Anleitung]] miteinander verbunden, in diesem Beispiel werden die Pads für Pin-Nr. 14 (im folgenden Bild rot markiert) verwendet: \\ \\ {{feather_jumpers.jpg?600|}} \\ \\ 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 [[https://www.arduino.cc/en/Main/Software|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: \\ \\ ''File'' → ''Preferences'' 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 ''Tools'' → ''Boards'' → ''Boards Manager'' die ''ESP8266''-Prozessorfamilie installieren. \\ \\ Die benötigten Treiber/Libraries sind nun zum Arbeiten in den Arduino-Applikation verfügbar. \\ Unter ''Tools'' → ''Boards'' 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 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 [[https://docs.oracle.com/javase/8/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/package-summary.html|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: \\ \\ {{tutorial01_step03_01.png?600|}} \\ ==== Schritt 2: Theoretisches ON-Szenario ==== Das geplante ON-Szenario soll wie folgt aussehen: \\ \\ {{tutorial01_step03_02.png?600|}} \\ \\ ==== Schritt 3: Praktische Umsetzung ==== Benötigtes Material: * 9V Batterie * Kabel * 5V LED * 220Ω Drahtwiderstand * Kabel-Set * Breadboard * [[https://www.adafruit.com/product/2821|Adafruit Feather HUZZAH with ESP8266]] * [[https://www.adafruit.com/product/3191|Adafruit Power Relay FeatherWing]] * PC/Notebook mit WiFi-Anbindung, statischer IP-Adresse, //Java//-Entwicklungsumgebung, z.B. [[https://www.eclipse.org/downloads/|Eclipse]] und mit entsprechend konfigurierter Firewall für erlaubte eingehende Webserver-Verbindungen (defaultmässig auf Port 80/TCP). 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 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 = ""; const char* password = ""; const char* host = "..."; 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(, , , ); IPAddress gateway(, , , ); IPAddress subnet(, , , ); // 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 //.java//-Datei → ''Export...'' → ''Java''-Order Dropdown öffnen → ''Runnable JAR file'' auswählen → ''Next'' → Jetzt folgende Einstellungen wählen: \\ {{jarexportsettings.png?300|}} \\ → ''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: - **instanceswitchON.bat** \\ mit folgendem Inhalt: @echo off echo status=on > ./instance.txt - **instanceswitchOFF.bat** \\ mit folgendem Inhalt: @echo off echo status=off > ./instance.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: [{{switchfilespath01.png?300|Variante .jar}}] [{{switchfilespath02.png?300|Variante Eclipse}}] - **instanceswitchON.sh** \\ mit folgendem Inhalt: echo "status=on" > ./instance.txt - **instanceswitchOFF.sh** \\ mit folgendem Inhalt: echo "status=off" > ./instance.txt Nun die beiden Skript-Dateien ausführbar machen: chmod +x instanceswitchON.sh chmod +x instanceswitchOFF.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 A//dafruit Feather HUZZAH with ESP8266// Board besitzt eine Konsolenausgabe, welche in Arduino via ''Tools'' → ''Serial Monitor'' geöffnet werden kann (Achtung: Korrekte Baudrate ''115200 baud'' wählen!): \\ {{serialmonitor.png?400|}} ===== 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: \\ \\ {{tutorial01_step03_01_alt.png?600|}} \\ ==== Schritt 2: Theoretisches ON-Szenario ==== Das geplante ON-Szenario soll wie folgt aussehen: \\ \\ {{tutorial01_step03_02_alt.png?600|}} \\ \\ ==== Schritt 3: Praktische Umsetzung ==== Benötigtes Zusatzmaterial (Empfohlenes Produkt, es kann auch ein anderer Akku verwendet werden.): * [[https://www.adafruit.com/product/328|Adafruit Lithium Ion Polymer Battery - 3.7v 2500mAh]] Wichtig: Dem Analog-Pin des Mikrocontroller-Board kann eine maximale Spannung von 1V zugeführt werden. Die passenden Vorwinderstände sind [[https://learn.adafruit.com/adafruit-feather-huzzah-esp8266/power-management|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 [[https://www.adafruit.com/product/259|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 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 = ""; const char* password = ""; const char* host = "..."; 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(, , , ); IPAddress gateway(, , , ); IPAddress subnet(, , , ); // 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. ===== {{htmlmetatags>metatag-robots=()}}