Eine Android App um jede RGB-LED zu steuern

Android App für RGB LED Einstellung

Vor ein paar Monaten gab es in einem großen deutschen Discounter ein unwiderstehliches Angebot: vier LED-Unterbauspots für knapp 15€. Perfekt um die stromfressenden Halogenspots in meinem Bücherregal zu ersetzen. Darüber hinaus wurde mit dem vollen RGB Farbspektrum geworben, was meine Spontankauf-Entscheidung dann vollends besiegelte.

Zuhause angekommen dann erstmal eine herbe Enttäuschung. Das Icon mit dem Farbspektrum hatte mich über das Kleingedruckte hinweglesen lassen: “1 Farbwechsel-Programm und 7 konstante Einzelfarben”. Ziemlich dürftig also. Noch dazu lassen sich die Farben nur über einen Hardware-Button am Stromkabel durchschalten. Für eine Beleuchtung die hinter den Büchern versteckt liegen soll also völlig inakzeptabel.

Nun waren die Spots aber schon mal gekauft und der Entschluss einer schicken neuen Beleuchtung war getroffen, da wollte ich mich von derartigen Kleinigkeiten nicht von meinem Vorhaben abhalten lassen. Der erste Schritt war also eine Analyse der benötigten Hardware.

 

Teil 1: Hardware

Bedarfsanalyse

Um eine Verbindung zur Android App herzustellen verwende ich den ESP8266 WiFi Chip. Glücklicherweise gibt es auch einen Arduino-Clone, der diesen Chip direkt auf dem Board integriert hat, was mir etwas Arbeit spart.

Nun muss nur noch die Frage geklärt werden, wie ich den Arduino dazu bringe, mit seinen 5V Ausgangsspannung die 12V der LEDs zu schalten. Die Antwort darauf heißt NPN Transistoren.

  • Die vollständige Hardware-Einkaufsliste lautet also:

  • WeMos D1 WiFi Board (~3,60€)

  • BC547B Transistoren (~1,50€ / 100 Stück)

  • 220Ω Widerstände (~1€ / 100 Stück)

  • Breadboard (~1,50€)

  • Jumper-Kabel male-to-male (~1€ / 40 Stück)

Die Gesamtkosten des Projekts (ausschließlich LEDs) belaufen sich also auf etwa 8,60€, wobei diese Rechnung auch etwas überdimensioniert ist, da man natürlich nicht 100 Widerstände und Transistoren benötigt, sondern lediglich drei Transistoren pro LED und drei Widerstände insgesamt.

Verkabelung

Der technische Aufbau ist vergleichweise einfach. Die NPN Transistoren haben drei Pins: Collector, Base und Emitter. Wird an die Base eine Spannung angelegt, so schaltet der Transistor den Strom vom Collector zum Emitter durch. Also wird der Collector mit der LED verbunden und der Emitter mit GND. Die Base wird mit dem Pin des Arduino verbunden, hier wird allerdings noch ein 220Ω Widerstand zwischengeschaltet.

 
LED Steuerung Schaltplan

 

Unten im Schaltbild wäre der Anschluss der LED, links und oben sind die jeweiligen Anschlüsse am Arduino vermerkt. Möchte man mehrere LEDs in der gleichen Farbe schalten, so benötigt man für jede LED drei eigene BC547B Transistoren, da ansonsten die Leuchtkraft pro LED nachlässt (die Base-Pins der jeweiligen Transistoren können dann miteinander verbunden werden, so spart man sich die zusätzlichen Wiederstände).

Diese Schaltung funktioniert für RGB-LEDs mit gemeinsamer Versorgungsspannung (Common Anode). Für RGB-LEDs mit gemeinsamer Masse (Common Cathode) ist ein etwas komplexerer Aufbau mithilfe von zusätzlichen PNP Transistoren notwendig, der hier beschrieben ist:

 

NPN pder PNP -c-kolb

 

Möchte man mehr als einen Spot bzw. einen LED-Strip über die gleichen Arduino-Pins ansteuern, so sollte man dem zweiten Anschluss jeweils eigene Transistoren verbauen, da sich ansonsten die Leuchtkraft pro LED verringert.

 

Ein Hinweis noch: Damit die Transistoren korrekt schalten ist es wichtig, dass alle Bauteile mit dem gleichen Stromkreis verbunden sind. In meinem ersten Versuch hatte ich den Arduino über das USB-Verbindungskabel meines Rechners versorgt, während die LEDs über ihr eigenes Netzteil versorgt wurden. Die Fehlersuche dauerte ewig…

 
 
Die fertige Verkabelung auf meinem Breadboard
Die fertige Verkabelung auf meinem Breadboard

 


Es werde Licht

Jetzt wird es Zeit zu testen, ob alles wie erwartet funktioniert. Hierfür habe ich mir ein kleines Arduino Programm geschrieben, das nacheinander ein paar Farben durchschaltet.

int LEDblue = D5; // Blau an Pin 5
int LEDgreen= D6; // Gruen an Pin 6
int LEDred = D7; // Rot an Pin 7

void setup() {
pinMode(LEDblue, OUTPUT);
pinMode(LEDgreen, OUTPUT);
pinMode(LEDred, OUTPUT);
}

void loop() {
setColor(255,0,0);
delay(1000);

setColor(0,255,0);
delay(1000);

setColor(0,0,255);
delay(1000);

setColor(0,0,100);
delay(1000);
}

void setColor(int red, int green, int blue){
analogWrite(LEDred, red);
analogWrite(LEDgreen, green);
analogWrite(LEDblue, blue);
}

 

Zuerst definiere ich die Konstanten für meine Pin-Belegung und lege in der Setup-Methode die entsprechenden Pins als Output-Pins fest.

Ganz unten in der Methode “setColor” schreibe ich dann über “analogWrite” die übergebenen Werte auf den jeweiligen Ausgangs-Pin. Zu beachten ist, dass ich mit analogWrite die Pulse-Width-Modulation des Arduino verwende, die Werte von 0 bis 255 übergeben bekommen kann. Mein WeMos Board hat PWM-Funktionalität für alle Pins verbaut, beim originalen Arduino Uno ist dies nicht der Fall, also achtet bei der Pin Auswahl darauf.

In der “loop” Methode schalte ich einfach vier Farben durch und lasse das Board zwischendurch jeweils eine Sekunde Pause machen. Es sollten nacheinander Rot, Grün, Blau und ein etwas dunkleres Blau erscheinen. Die ersten drei Farben zeigen mir, dass ich alle Pins korrekt angeschlossen habe, das blasse Blau bestätigt, dass die Pulse-Width-Modulation auch tatsächlich funktioniert.

Teil 2: Der Arduino-Code

Schalten der LEDs über HTTP Requests

Disclaimer: Der folgende Abschnitt basiert auf Code von dieser Demo.

Zuallererst sollte das entsprechende Plugin eingebunden werden. Dieses bietet uns die Funktionalität, um den integrierten ESP8266 Chip nutzen zu können. Es ermöglicht außerdem die Erstellung eines Server-Objekts, das wir in der nächsten Zeile auf Port 23 initialisieren.

#include <ESP8266WiFi.h>

WiFiServer server(23);

 

Bevor der Server seine Arbeit aufnehmen kann, müssen wir noch eine WLAN Verbindung aufbauen:

 

static const char* WIFI_SSID = "wlan name hier eintragen";
static const char* WIFI_PASSWORD = "wlan passwort hier eintragen";

void setupWiFi(){
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.println("Connecting to WiFi");


while(WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}


Serial.println("Local IP address is ");
Serial.println(WiFi.localIP());
}

 


Die Methode versucht eine Verbindung zum in den beiden Konstanten definierten WLAN Netzwerk aufzubauen. Sobald der Status anzeigt, dass die Verbindung steht, geben wir die lokale IP Adresse des Arduino über die serielle Schnittstelle aus.

Der Server kann dann mit einer einfachen Zeile gestartet werden:

server.begin();

Das komplette Setup unseres neuen Arduino Scripts sieht also folgendermaßen aus:

void setup() {
pinMode(LEDblue, OUTPUT);
pinMode(LEDgreen, OUTPUT);
pinMode(LEDred, OUTPUT);


setColor(0,0,0);


Serial.begin(115200);
setupWiFi();
server.begin();
Serial.println("server started");
}

 


Wir haben uns also zum Netzwerk verbunden und einen Server gestartet. Wenn wir den Code auf den Arduino flashen, sollten wir im Seriellen Monitor bereits in etwa folgende Ausgabe sehen können:

 
Arduino Code Flash

Jetzt müssen wir nur noch auf eingehende Nachrichten reagieren. Dafür wird folgender Code in die “loop” Schleife geschrieben:

static const String CONNECTED_MSG = "SOCKET_CONNECTED";

void loop() {
WiFiClient client = server.available();
if (client) {

client.println(CONNECTED_MSG);


handleClientMessages(client);

delay(100);
client.flush();
client.stop();
}
}

 

In der ersten Zeile innerhalb der Methode prüfen wir, ob ein Client zur Verbindung bereitsteht. Ist dies der Fall, schicken wir ihm eine Nachricht, um zu signalisieren, dass wir nun bereit zur Kommunikation sind.

Danach wird in der ausgegliederten Funktion “handleClientMessages” auf Nachrichten vom Client reagiert, diese folgt im nächsten Code-Block. Wurde die Verbindung zum Client getrennt, wird auch das Client-Objekt auf dem Arduino bereinigt, bevor der Server die nächste Iteration der “loop” Schleife durchläuft und auf den nächsten Client wartet.

 

static const int PING_TIME = 1000;
static const String PING_MSG = "SOCKET_PING";

void handleClientMessages(WiFiClient client){
int senttime = millis();
String rx_buffer = "";

while(client.connected()){
if(client.available()){
while(client.available()){
char c = client.read();
if(c == '\n') {
parseMessage(rx_buffer);
rx_buffer = "";
} else {
rx_buffer += c;
}
}
}

int now = millis();
if (now - senttime > PING_TIME) {
client.println(PING_MSG);
senttime = now;
}
}
}

 


Die Methode bekommt den Client als Argument übergeben. In den ersten beiden Zeilen merkt sie sich den Zeitpunkt, zu dem der Kontakt zum Client hergestellt wurde (um später den Ping auszuführen) und erstellt einen leeren String “rx_buffer”. Danach wird solange der Client verbunden ist eine while-Schleife durchlaufen, in der zuerst mit “client.available()” geprüft wird, ob eine Nachricht gesendet wurde. Ist dies der Fall wird die Nachricht Zeichen für Zeichen in die “rx_buffer” Variable geschrieben, bis vom Client das Zeichen für einen Zeilenumbruch “\n” kommt.

Sendet der Client den Zeilenumbruch, so wird alles, was bis dahin in rx_buffer geschrieben wurde an eine weitere Methode “parseMessage” weitergeleitet und die Variable “rx_buffer” wird zurückgesetzt.

Wenn gerade nichts vom Client gesendet wird, überprüft die Methode in jedem Durchlauf, ob seit dem Start bereits mehr als eine Sekunde (definiert über “PING_TIME”) vergangen ist. Wenn ja, wird eine PING Message verschickt, um die Verbindung aufrechtzuerhalten. Der Timer wird im Anschluss zurückgesetzt.

Als letztes fehlt nur noch die Methode, um die eingegangene Nachricht in RGB-Werte zu übersetzen:

void parseMessage(String msg) {

Serial.println("Set color to " + msg);

String r = msg.substring(0,3);
String g = msg.substring(3,6);
String b = msg.substring(6,9);

setColor(r.toInt(), g.toInt(), b.toInt());
}

 


Für unser Kommunikationsprotokoll sollte erstmal die simple Konvention genügen, dass ein Befehl für die gewünschte Farbe aus den entsprechenden Farbwerten besteht, die einfach hintereinander geschrieben werden, wobei die jeweiligen Werte immer aus genau drei Zeichen bestehen sollten. Ein Befehl für grünes Licht wäre demnach “000255000”.

Natürlich wäre es sinnvoll, hier ein etwas komplexeres Kommunikationsprotokoll zu entwickeln und mehr potenzielle Fehler abzufangen, aber für den Anfang sollte uns diese Implementierung genügen. Wir müssen vorerst eben selbst darauf achten, dass die übergebenen Nachrichten auch tatsächlich vom Arduino verarbeitet werden können.

Die Nachricht wird nun also einfach in drei Substrings zu jeweils drei Zeichen zerlegt. Danach werden die Substrings in ihre entsprechenden Integer Werte umgewandelt und an die bekannte “setColor” Methode übergeben.

Zu diesem Zeitpunkt sind wir schon so weit, uns mit einem beliebigen Telnet Client mit dem Arduino zu verbinden, und unsere LEDs mit einfachen numerischen Befehlen zu steuern.

 
Steuerung der LEDs durch numerische Befehle

Das ist zwar auch schon ziemlich cool, um die Kollegen zu beeindrucken, aber im Praxiseinsatz doch etwas unpraktisch. Es muss also dringend eine App her!

Teil 3: Die Android App

Setup

Zuerst wird in Android Studio eine neue App angelegt. Ich habe als minimalen API Level Android L gewählt, allerdings sind die Befehle nicht sonderlich kompliziert und sollten auch auf älteren Geräten funktionieren. Für niedrigere API Level müssen eventuell Änderungen im Code vorgenommen werden.

Um eine WLAN Verbindung aufbauen zu können, benötigt die App ein paar Berechtigungen, die in das Android Manifest eingetragen werden müssen. Fehlen diese, wird leider keine Fehlermeldung geworfen. Tragt also am besten direkt folgende Zeilen ein:

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

Vorerst brauche ich für die App nur eine einzige Ansicht, diese lasse ich mir als “Empty Activity” erstellen und gebe ihr den Namen “ColorPicker”. Anstelle einer Texteingabe für die Farbwerte hätte ich gerne eine schicke Grafische Auswahlmöglichkeit. Ich habe mich für den HoloColorPicker entschieden:

 

 

HoloColorPicker Android App

 

Damit der HoloColorPicker funktioniert, muss folgende Zeile zur Datei build.gradle (Module: app) unter dependencies hinzugefügt werden:

 

compile 'com.larswerkman:HoloColorPicker:1.5'

 

Anschließend lassen sich die Tags des HoloColorPicker in der Layout-Datei nutzen. Ich habe mich für eine Kombination von Farbrad und Saturation/Value Bar entschieden, das sollte das gesamte Farbspektrum abbilden. Zusätzlich möchte ich noch eine Info über den aktuellen Verbindungsstatus anzeigen, dies geschieht über eine einfache TextView. Der gesamte Code in activity_color_picker.xml sieht also folgendermaßen aus:

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.onepixeldiamond.ledcolorpicker.ColorPicker">

<TextView
android:id="@+id/serverStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not connected"
android:layout_margin="16dp" />

<com.larswerkman.holocolorpicker.ColorPicker
android:id="@+id/picker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="10"
android:foregroundGravity="center"
android:layout_gravity="center"/>
<com.larswerkman.holocolorpicker.SVBar
android:id="@+id/svbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="10"
android:foregroundGravity="center"
android:layout_gravity="center"/>
</LinearLayout>

 


Damit ist die Layout Datei auch schon fertig, wir können jetzt also mit dem eigentlichen Code in ColorPicker.java weitermachen. Zuallererst möchte ich ein paar grundlegende
Einstellungen vornehmen.

 

public class ColorPicker extends AppCompatActivity implements
com.larswerkman.holocolorpicker.ColorPicker.OnColorChangedListener {

TextView textStatus;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_color_picker);

textStatus = (TextView) findViewById(R.id.serverStatus);
SVBar svBar = (SVBar) findViewById(R.id.svbar);
com.larswerkman.holocolorpicker.ColorPicker picker
=
(com.larswerkman.holocolorpicker.ColorPicker)
findViewById(R.id.picker);

picker.addSVBar(svBar);
picker.setShowOldCenterColor(false);
picker.setOnColorChangedListener(this);
}

@Override
public void onColorChanged(int color) {

}

void setStatus(String s) {
textStatus.setText(s);
}
}

 


In der ersten Zeile definiere ich, dass meine Activity das interface “OnColorChangedListener” des HoloColorPicker implementieren soll. Dies zwingt mich dazu die Methode “onColorChanged” einzubauen, welche bei jeder Veränderung der eingestellten Farbe im Farbrad aufgerufen wird. Die Implementierung der Methode folgt später, vorerst möchte ich nur die Fehlermeldung verschwinden lassen.

In meiner “onCreate” Methode suche ich mir als erstes die drei Layout-Elemente zusammen, die ich in dieser Activity anzeige. Die TextView zur Status-Anzeige speichere ich in einer globalen Variable, die weiter unten in der “setStatus” Methode verwendet wird, um den angezeigten Text zu aktualisieren.

Die Elemente “ColorPicker” und “SVBar” benötige ich nur lokal in der “onCreate” Methode, dort verknüpfe ich mit “picker.addSVBar(svBar)” die Saturation/Value Bar mit dem Farbrad, deaktiviere mit “picker.setShowOldCenterColor(false)” die Anzeige der vorhergehenden Farbe in der Mitte des Farbrads und schließlich nehme ich mit “picker.setOnColorChangedListener(this)” das event binding vor, welches auf die Activity selbst verweist und dort die vom Interface vorgegebene Methode “onColorChanged” aufruft.

Herstellen der Verbindung

In der Implementierung von “onColorChanged” möchten wir jetzt den vom Frontend ausgelesenen Farbwert an den Arduino senden. Damit wir dies tun können, müssen wir aber erstmal eine Verbindung herstellen. Hierfür definieren wir eine interne Klasse “WiFiSocketTask”, die vom Interface “AsyncTask” ableitet. Ein AsyncTask kann im Hintergrund einer Activity laufen und dort Aufgaben erledigen oder Werte aktualisieren, ohne dass die UI davon blockiert wird. In unserem Fall bedeutet das, dass wir munter an unserem Farbrad drehen können ohne nach jeder Änderung darauf warten zu müssen, dass die Kommunikation mit dem Arduino abgehandelt wurde.

Da die Sub-Klasse etwas größer geworden ist, werde ich sie in mehreren Code-Schnipsel zerteilen, um jeweils Erklärungen einschieben zu können.

public class WiFiSocketTask extends AsyncTask<Void, String, Void> {
private static final String PING_MSG = "SOCKET_PING";
private static final String CONNECTED_MSG = "SOCKET_CONNECTED";
private static final String DISCONNECTED_MSG =
"SOCKET_DISCONNECTED";

private static final String address = "192.168.186.79";
private static final int port = 23;

private boolean disconnectSignal = false;

private Socket socket;
private BufferedReader inStream;
private OutputStream outStream;
private int timeout = 5000;

@Override
protected Void doInBackground(Void... arg) {

try {
setUpSocket();
verifyConnection();

while (!disconnectSignal) {
handleOutgoingMessages();
handleIncomingMessages();
}

} catch (IOException e) {
e.printStackTrace();
}

disconnect();

return null;
}

 


Als erstes werden jede Menge Konstanten und Variablen definiert. Die ersten drei sind die Meta-Nachrichten für den Auf- und Abbau der Verbindung sowie die Ping-Nachricht, wie sie auch schon im Arduino-Code definiert wurden.

Danach habe ich die Verbindungsdaten zum Arduino hard-coded implementiert. An dieser Stelle sollte ich vielleicht darauf hinweisen, dass ihr euren Router so einstellen solltet, dass er dem Arduino immer die gleiche IP Adresse gibt…

Der Bool-Wert “disconnectSignal” wird im folgenden dafür verwendet, die Verbindung bei Bedarf zu beenden.

Dann werden die Eigenschaften der eigentlichen Verbindung definiert, welche wir mittels eines Socket-Objekts realisieren. Dieses bietet einen In- und Output-Stream, welche wir in jeweils eine eigene Variable speichern, um einfacher darauf zugreifen zu können. Schließlich definieren wir noch den Timeout von fünf Sekunden.

Danach folgt die vom Interface geforderte Implementierung der “doInBackground” Methode. Diese Methode enthält den Code, der vom Task als Hintergrundaufgabe ausgeführt werden soll. Wir versuchen zuerst, mit “setUpSocket” eine Verbindung aufzubauen, die wir danach mit “verifyConnection” testen. Geht bis hierhin alles gut, soll solange kein disconnectSignal eintritt die Methoden “handleOutgoingMessages” und “handleIncomingMessages” in Dauerschleife ausgeführt werden.

Sobald die Variable “disconnectSignal” auf true gesetzt wird, verlässt das Programm die while-Schleife und wir beenden die Verbindung. Da die Socket-Verbindung das Potenzial hat, IOExceptions zu werfen, müssen wir diese mit dem Try-Catch-Block abfangen.

private void setUpSocket() throws IOException {
socket = new Socket();
socket.connect(new InetSocketAddress(address, port), timeout);
socket.setSoTimeout(timeout);

inStream = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
outStream = socket.getOutputStream();
}

 

Die Methode zum Verbindungsaufbau erstellt ein neues Socket-Objekt und versucht sich mit dem in den Konstanten definierten Server zu verbinden. Sollte dies länger als die in Timeout definierte Zeit dauern, wird eine IOException geworfen. Danach wird ein neuer gepufferter InputStreamReader aus dem InputStream des Socket erstellt und in die hierfür vorgesehene Variable gespeichert. Das gleiche passiert analog dazu für den OutputStream.

 

private void verifyConnection() throws IOException {
if (socket.isConnected()) {
long start = System.currentTimeMillis();
while (!inStream.ready()) {
long now = System.currentTimeMillis();
if (now - start > timeout) {
disconnectSignal = true;
break;
}
}
} else {
disconnectSignal = true;
}
}

 


In der “verifyConnection” Methode wird zuerst mit “socket.isConnected” überprüft, ob die Verbindung tatsächlich aufgebaut wurde. Danach wird auf den Input Stream gewartet, ist dieser nicht innerhalb der Timeout-Zeit verfügbar, wird die Verbindung über das Setzen des “disconnectSignal” Flag beendet.

Versenden der Nachrichten

Als nächstes folgt die Verarbeitung der Ein- und Ausgehenden Nachrichten auf dem Socket.

private byte[] messageBytes;

public void queueMessage(String data){
messageBytes = data.getBytes();
}

private void handleOutgoingMessages() {
if (messageBytes == null) return;

try {
outStream.write(messageBytes);
outStream.write('\n');
messageBytes = null;
} catch (Exception e) {
e.printStackTrace();
}
}

private void handleIncomingMessages() throws IOException {
String msg = inStream.readLine();
publishProgress(msg);
}

 


Der Output-Stream des Socket schreibt seine Daten als Byte-Array, daher habe ich eine entsprechende Variable erstellt, um die zu versendende Nachricht zwischenzuspeichern. Damit die Activity eine Nachricht absetzen kann, stellt der WiFiSocketTask eine öffentliche Methode “queueMessage” zur Verfügung, die einen String-Wert annimmt und in ein Byte-Array umgewandelt in die messageBytes-Variable speichert. Sollte eine zweite Nachricht eintreffen, bevor die erste Nachricht erfolgreich verschickt wurde, wird die erste Nachricht verworfen. In unserem Anwendungsfall ist das so gewollt, schließlich interessiere ich mich nicht mehr dafür, welche Lichtfarbe ich vor einigen Millisekunden noch haben wollte.

Die Methode “handleOutgoingMessages” prüft, ob sich ein Wert in der “messageBytes” Variable befindet und wenn dem so ist, wird dieser Wert in den Output-Stream geschrieben und mit einem Zeilenumbruch beendet (das Zeichen für den Arduino, dass unser Befehl abgeschlossen ist). Anschließend wird die “messageBytes” Variable zurückgesetzt, um nicht ununterbrochen die gleichen Werte zu senden.

In “handleIncomingMessages” wird der Input-Stream des Socket ausgelesen und was auch immer dort ankommt in die Methode “publishProgress” übergeben.

@Override
protected void onProgressUpdate(String... values) {
String msg = values[0];
if(msg == null) return;

if(msg.equals(CONNECTED_MSG)) {
setStatus("connected");
} else if(msg.equals(DISCONNECTED_MSG))
setStatus("disconnected");
else if(msg.equals(PING_MSG))
{}

super.onProgressUpdate(values);
}

 


Diese wird auch vom “AsyncTask” Interface vorgegeben und hier für unsere Zwecke angepasst. Wir prüfen hier lediglich ab, ob der im ersten Argument übergebene String eine der von uns definierten Meta-Nachrichten ist. Handelt es sich um eine Nachricht bezüglich des Verbindungsstatus, so wird die “setStatus” Methode der ColorPicker-Activity ausgeführt, auf die WiFiSocketTask als interne Klasse Zugriff hat. Handelt es sich um eine Ping-Nachricht, müssen wir nichts weiter tun.

Zum Schluss fehlen dem WiFiSocketTask nur noch zwei Methoden zum sauberen Beenden der Verbindung:

 

public void requestDisconnect() {
disconnectSignal = true;
}

private void disconnect() {
publishProgress(DISCONNECTED_MSG);
try {
if (socket != null) socket.close();
if (inStream != null) inStream.close();
if (outStream != null) outStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

 


Die öffentliche Methode “requestDisconnect” wird der ColorPicker-Activity bereitgestellt, um das “disconnectedSignal” Flag zu setzen und damit den Verbindungsabbau einzuleiten.

Schließlich wird in “disconnect” der Socket sowie seine beiden Streams geschlossen.

Damit ist die interne Klasse WiFiSocketTask vollständig, nun müssen wir sie nur noch beim Start der ColorPicker Activity initialisieren und jede Änderung der in HoloColorPicker eingestellten Farbe an sie weiterleiten. Der Folgende Code bezieht sich also wieder auf die ColorPicker Activity:

 

WiFiSocketTask wifiTask = null;

@Override
protected void onResume(){
super.onResume();

setStatus("Connecting...");
wifiTask = new WiFiSocketTask();
wifiTask.execute();
}

@Override
protected void onStop(){
super.onStop();

if (wifiTask == null) return;

wifiTask.requestDisconnect();
setStatus("Disconnecting...");
}

 


Wir definieren zuerst eine globale Variable, die den Task beinhalten soll. Sobald die App aktiv wird, soll automatisch eine Verbindung zum Arduino aufgebaut werden, daher überschreiben wir zum Verbindungsaufbau die Methode “onResume”. Hier erstellen wir ein neues Objekt unserer WiFiSocketTask-Klasse und starten es mit der “execute” Methode.

Analog dazu soll die Verbindung beendet werden, sobald die App inaktiv wird. Die Überladung von “onStop” ruft daher die “requestDisconnect” Methode auf.

Als letztes müssen wir nun nur noch den Methoden-Stub “onColorChanged” mit Logik befüllen. Was wir hier tun wollen, ist von der als Integer-Wert übergebenen Farbe den Rot-, Grün-, und Blau-Wert auslesen, ihn als jeweils dreistelligen Zahlencode aneinanderhängen und an die “queueMessage” Methode von WiFiSocketTask übergeben. Glücklicherweise kann uns Android bei der Bestimmung der Farbwerte weiterhelfen, daher sieht der benötigte Code nicht allzu kompliziert aus:

@Override
public void onColorChanged(int color) {
StringBuilder colorMessage = new StringBuilder();

colorMessage.append(String.format("%03d", Color.red(color)));
colorMessage.append(String.format("%03d", Color.green(color)));
colorMessage.append(String.format("%03d", Color.blue(color)));

wifiTask.queueMessage(colorMessage.toString());
}

 

 
Funktionierende RGB Steuerung als GIF

 

Damit wäre unser minimum viable product für eine RGB-LED Controller App schon fertig. Auch wenn bereits jetzt die Hauptfunktionalität für die Steuerung der LEDs gewährleistet ist, gibt es doch noch einige Verbesserungsmöglichkeiten, wie zum Beispiel die Verwaltung der Arduino-IP-Adresse innerhalb der App, ein sanfteres Überblenden der Farben vom Arduino oder die Möglichkeit mehrere Arduinos gleichzeitig anzusteuern. Ich werde auf jeden Fall weiter an der App arbeiten. Schreibt einfach in den Kommentaren, was euch am meisten Interessiert und ich schreibe eine Fortsetzung.

 

 

Ähnliche Artikel

IoT
15. November 2021 3 Min. Lesezeit
Iot – Was ist das eigentlich?
Weiterlesen
IoT, Developer Content
20. Mai 2019 4 Min. Lesezeit
Learning “Machine Learning” — Teil 2
Weiterlesen
IoT, Developer Content
15. Februar 2019 4 Min. Lesezeit
Visual Studio Code und der ESP8266
Weiterlesen
IoT, Developer Content
18. Dezember 2018 3 Min. Lesezeit
Learning “Machine Learning” Teil 1
Weiterlesen
IoT, Developer Content
10. April 2018 5 Min. Lesezeit
IoT: Speichern & Anzeigen von Temperatursensordaten mit Azure Diensten
Weiterlesen
IoT, DIY
12. März 2018 3 Min. Lesezeit
Deckenlampen per Sprachbefehl oder Wandschalter bedienen
Weiterlesen
Pfeil nach links
Pfeil nach rechts