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