Part 4: Creating the mobile app
We have reached the final part of our project, we can now create our mobile app to control our project, we will be creating an Android project but the same endpoints can be called from an iOS app.
We will be creating two screen, one as a login screen (this can be skipped if you assign IP addresses to your 2 ESP8266 modules) and another for our effects and colors.
Creating our Main Activity
The main activity will be our start page (the login screen), it will have a simple input for the IP address of our server and button to check for a response from the server ESP8266.
The Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="10dp"
android:background="@drawable/border">
<TextView
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="Enter IP Address"
android:padding="16dp"
android:textSize="20sp"
android:textColor="#fff"
android:background="#3F51B5"
android:layout_margin="10dp"
android:textAlignment="center"/>
<EditText
android:id="@+id/txtIp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:hint="IP Address"
android:textAlignment="center"
android:textColorHint="#a7a8aa"/>
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="5dp"
android:width="200dp"
android:layout_gravity="center"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
The Java Code
package com.project.rudolf.reactiveled;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import top.defaults.colorpicker.ColorPickerView;
public class MainActivity extends AppCompatActivity {
private EditText txtIp;
private Button btnStart;
private Activity activity;
private String ipAddress;
private RequestQueue requestQueue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtIp = (EditText) findViewById(R.id.txtIp);
btnStart = (Button) findViewById(R.id.btnStart);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!txtIp.getText().toString().equals("")) {
sendIPCheckRequest(txtIp.getText().toString());
}
}
});
ipAddress = readFromFile(getApplicationContext());
txtIp.setText(ipAddress);
requestQueue = Volley.newRequestQueue(this);
}
// Send a request to the server to see if we get a response
private void sendIPCheckRequest(String ip) {
String url = "http://" + ip;
StringRequest arrReq = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
if (response.length() > 0) {
writeToFile(ip, getApplicationContext());
Intent homeIntent = new Intent(MainActivity.this, HomeActivity.class);
homeIntent.putExtra("ip", ip);
startActivity(homeIntent);
finish();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
arrReq.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(arrReq);
}
// Save the IP address
private void writeToFile(String data, Context context) {
try {
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput("config.txt", Context.MODE_PRIVATE));
outputStreamWriter.write(data);
outputStreamWriter.close();
}
catch (IOException e) {
Log.e("Exception", "File write failed: " + e.toString());
}
}
// Retrieve our previously used IP address
private String readFromFile(Context context) {
String ret = "";
try {
InputStream inputStream = context.openFileInput("config.txt");
if ( inputStream != null ) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String receiveString = "";
StringBuilder stringBuilder = new StringBuilder();
while ( (receiveString = bufferedReader.readLine()) != null ) {
stringBuilder.append(receiveString);
}
inputStream.close();
ret = stringBuilder.toString();
}
}
catch (FileNotFoundException e) {
//Log.e("login activity", "File not found: " + e.toString());
} catch (IOException e) {
//Log.e("login activity", "Can not read file: " + e.toString());
}
return ret;
}
}
Now we can go ahead and create a new activity and layout, we’ll call this activity HomeActivty.java and the layout activity_home.xml. You can also start testing your app by compiling and running it, make sure you are connected to the same WiFi as your server ESP8266, when you enter the IP address, you should be navigated to the home activity you just created.
Creating our Home Activity
We can now code the main functionality of our app, this is where we choose the operating mode and set the colors, we will be using the ColorPickerView library by duanhong169 for our project but you can change this to your liking.
The Layout
We will use buttons that we can click to switch operating mode and a color picker for choosing the color.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeActivity"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/scrollView">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/txtCurrentEffect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#3F51B5"
android:padding="16dp"
android:text="Current Effect: "
android:textColor="#fff"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#3F51B5"
android:padding="16dp"
android:text="Simple Controls"
android:textColor="#fff"
android:textSize="20sp" />
<Button
android:id="@+id/btnNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="16dp"
android:width="200dp"
android:text="Next Effect" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#3F51B5"
android:padding="16dp"
android:text="Select Effect"
android:textColor="#fff"
android:textSize="20sp" />
<Button
android:id="@+id/btnSound"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:width="200dp"
android:text="Sound Reactive" />
<Button
android:id="@+id/btnRainbow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:width="200dp"
android:text="Rainbow" />
<Button
android:id="@+id/btnCylon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:width="200dp"
android:text="Cylon" />
<Button
android:id="@+id/btnMeteor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:width="200dp"
android:text="Meteor" />
<Button
android:id="@+id/btnStatic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="16dp"
android:width="200dp"
android:text="Static Color" />
</LinearLayout>
<LinearLayout
android:id="@+id/colorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#3F51B5"
android:padding="16dp"
android:text="Color Picker"
android:textColor="#fff"
android:textSize="20sp" />
<top.defaults.colorpicker.ColorPickerView
android:id="@+id/colorPicker"
android:layout_width="400dp"
android:layout_height="400dp"
android:layout_gravity="center" />
<Button
android:id="@+id/btnApplyColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="16dp"
android:width="200dp"
android:text="Apply" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
The Java Code
package com.project.rudolf.reactiveled;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import top.defaults.colorpicker.ColorPickerView;
/**
* Created by Rudolf Nagel on 2019/02/16.
*/
public class HomeActivity extends AppCompatActivity {
private Button btnNext;
private Button btnSound;
private Button btnRainbow;
private Button btnCylon;
private Button btnMeteor;
private Button btnApplyColor;
private Button btnStatic;
private ColorPickerView colorPickerView;
private TextView txtCurrentEffect;
private ScrollView scrollView;
private LinearLayout colorLayout;
private String ipAddress;
// Setting our initial colors
private int red = 0;
private int green = 0;
private int blue = 0;
private RequestQueue requestQueue;
private int effectId;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
ipAddress = getIntent().getStringExtra("ip");
btnNext = (Button) findViewById(R.id.btnNext);
btnSound = (Button) findViewById(R.id.btnSound);
btnRainbow = (Button) findViewById(R.id.btnRainbow);
btnCylon = (Button) findViewById(R.id.btnCylon);
btnMeteor = (Button) findViewById(R.id.btnMeteor);
btnApplyColor = (Button) findViewById(R.id.btnApplyColor);
btnStatic = (Button) findViewById(R.id.btnStatic);
txtCurrentEffect = (TextView) findViewById(R.id.txtCurrentEffect);
colorPickerView = (ColorPickerView) findViewById(R.id.colorPicker);
scrollView = (ScrollView) findViewById(R.id.scrollView);
colorLayout = (LinearLayout) findViewById(R.id.colorLayout);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendNextEffectRequest();
}
});
btnSound.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendCertainEffectRequest(0);
}
});
btnRainbow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendCertainEffectRequest(1);
}
});
btnCylon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendCertainEffectRequest(2);
}
});
btnMeteor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendCertainEffectRequest(3);
}
});
btnStatic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendCertainEffectRequest(4);
}
});
btnApplyColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendColorRequest();
}
});
requestQueue = Volley.newRequestQueue(this);
// Get the inital effect from the server
sendInitialEffectRequest();
// Get the inital colors from the server
sendInitialColorRequest();
// Set the chosen colors on the color wheel
colorPickerView.subscribe((color, fromUser, shouldPropagate) -> {
red = Color.red(color);
green = Color.green(color);
blue = Color.blue(color);
});
}
// Call initial effect endpoint
private void sendInitialEffectRequest() {
String url = "http://" + ipAddress + "/initial";
StringRequest arrReq = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
if (response.length() > 0) {
effectId = Integer.valueOf(response);
switch (effectId) {
case 0:
txtCurrentEffect.setText("Current Effect: Sound Reative");
break;
case 1:
txtCurrentEffect.setText("Current Effect: Rainbow");
break;
case 2:
txtCurrentEffect.setText("Current Effect: Cylon");
break;
case 3:
txtCurrentEffect.setText("Current Effect: Meteor");
break;
case 4:
txtCurrentEffect.setText("Current Effect: Static Color");
break;
}
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
arrReq.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(arrReq);
}
// Call initial color endpoint
private void sendInitialColorRequest() {
String url = "http://" + ipAddress + "/getcolor";
StringRequest arrReq = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
if (response.length() > 0) {
red = Integer.valueOf(response.substring(0, response.indexOf(',')));
response = response.substring(response.indexOf(',') + 1, response.length());
green = Integer.valueOf(response.substring(0, response.indexOf(',')));
blue = Integer.valueOf(response.substring(response.indexOf(',') + 1, response.length()));
int rgb = red;
rgb = (rgb << 8) + green;
rgb = (rgb << 8) + blue;
colorPickerView.setInitialColor(rgb);
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
arrReq.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(arrReq);
}
// Call the next effect endpoint
private void sendNextEffectRequest() {
String url = "http://" + ipAddress + "/next";
StringRequest arrReq = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
if (response.length() > 0) {
effectId = Integer.valueOf(response);
switch (effectId) {
case 0:
txtCurrentEffect.setText("Current Effect: Sound Reative");
break;
case 1:
txtCurrentEffect.setText("Current Effect: Rainbow");
break;
case 2:
txtCurrentEffect.setText("Current Effect: Cylon");
break;
case 3:
txtCurrentEffect.setText("Current Effect: Meteor");
break;
case 4:
txtCurrentEffect.setText("Current Effect: Static Color");
break;
}
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
arrReq.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(arrReq);
}
// Call specific effect endpoint
private void sendCertainEffectRequest(int id) {
String url = "http://" + ipAddress + "/effect?id=" + id;
StringRequest arrReq = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
switch (id) {
case 0:
txtCurrentEffect.setText("Current Effect: Sound Reative");
break;
case 1:
txtCurrentEffect.setText("Current Effect: Rainbow");
break;
case 2:
txtCurrentEffect.setText("Current Effect: Cylon");
break;
case 3:
txtCurrentEffect.setText("Current Effect: Meteor");
break;
case 4:
txtCurrentEffect.setText("Current Effect: Static Color");
break;
}
}
}
);
arrReq.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(arrReq);
}
// Call the color endpoint to set RGB values
private void sendColorRequest() {
String url = "http://" + ipAddress + "/color?r=" + red + "&g=" + green + "&b=" + blue;
StringRequest arrReq = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
if (response.length() > 0) {
effectId = Integer.valueOf(response);
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}
);
arrReq.setRetryPolicy(new DefaultRetryPolicy(60000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
requestQueue.add(arrReq);
}
}
You can now compile and run the Android app, you should be able to enter an IP address and start changing your LED effects and colors.
You can download the sample Android code at https://github.com/zu6fx/sound-reactive-led-strip-android

Be First to Comment