Skip to content

Building sound reactive lights controlled wireless through web service – Part 4

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


Published inESP8266IoT

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *