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:
Das Tutorial ist aufbauend gestaltet, es sollten also alle Übungen und Schritte nacheinander durchgeführt werden, ohne etwas auszulassen!
Bereits mit wenig Aufwand und wenig Hardware kann eine simple, leicht verständliche und manuell zu bedienende Schaltung aufgebaut werden.
Benötigtes Material:
Alle Komponenten wie abgebildet miteinander verbinden.
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.
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:
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 <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.
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.
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
@echo off echo status=on > ./instance<meineInstanzNummer>.txt
@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:
Version Linux
echo "status=on" > ./instance<meineInstanzNummer>.txt
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 Tools
→ Serial Monitor
geöffnet werden kann (Achtung: Korrekte Baudrate 115200 baud
wählen!):
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.
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