Building an Android App to Communicate With a Raspberry Pi to Detect and Record When Sound Level Exceeds a Decibel Threshold

Ingredients

Android Device
Android Studio
Fully Setup Raspberry Pi with Header (Zero W is what is used for this tutorial)
Sound Detection Module LM393
Short Wires
Breadboard
A decibel meter app such as dB Meter
Phillips Head Screwdriver

Introduction

In this project, the developer will reuse the Raspberry Pi setup from the first post and create an Android app similar to the second post which will be used to communicate with the Pi over BLE.

If you are using a different Raspberry Pi or Android device for this tutorial, be sure to follow the steps in the Raspberry Pi Setup, Pairing the Android Device with Raspberry Pi, and Setting Up and Connecting the Hardware to the Raspberry Pi sections in the first post.

The purpose of the project is to use a sound detection sensor connected to a Raspberry Pi to record a timestamp when the sound level of the environment exceeds a preset decibel limit. The project could be used in an environment where noise is a persistent problem to prevent hearing damage or to know when a noise ordinance violation is being committed.

Setting Up and Connecting the Sensor to the Raspberry Pi

The sound detection module (LM393) will be connected to the Pi using three pins. These three pins are VCC, GND, and OUT. The LM393 will have these pins on its printed circuit board. Connect a red wire to VCC (Power). Connect a black wire to GND (Ground). Connect a yellow wire to OUT (Data). Here is a picture of the wires connected to the LM393 sensor:

Each of the three wires will now need to be connected to pins on the Raspberry Pi. A diagram of the Raspberry Pi pinout can be found here: https://pi4j.com/1.2/pins/model-zerow-rev1.html

The red power wire will be connected to Pin# 17 on the Pi. The black ground wire will be connected to Pin# 25 on the Pi. The yellow data wire will be connected to Pin# 15 on the Pi. Below is a picture of the wires connected between the LM393 sensor and the Pi:

Setting Up the Python Script on the Raspberry Pi

Once the hardware is set up, the next step is to add the Python script file to the Raspberry Pi. Use your favorite text editor to add the following Python code to your Raspberry Pi. Name the file “DecibelLogger.py”.

import RPi.GPIO as GPIO
import time
import os
import datetime
import bluetooth

channel = 22
GPIO.setmode(GPIO.BCM)
GPIO.setup(channel, GPIO.IN)

data_amount = 10 #Amount of data points recorded in the log file
delay_time = 10 #Minimum delay time between each recorded event

def callback(channel):

        print( "Sound Detected!")

        #Get the current date and time
        now = datetime.datetime.now()
        now_string = str(now.year).zfill(4) + "-" + str(now.month).zfill(2) + "-" + str(now.day).zfill(2) + "   " + str(now.hour).zfill(2) + ":" + str(now.minute).zfill(2) + ":" + str(now.se$

        #Open or create the log file
        file =  open('decibel_log.txt','a+')

        if os.path.getsize("decibel_log.txt") == 0: #If file is new and empty
                file.write('1\n')
                file.write(now_string + '\n')

                #Fill in the rest of the file temporarily with '.'
                i = 1
                while i < data_amount:
                        file.write(".\n")
                        i += 1
        else:
                with open('decibel_log.txt','r') as file:
                        #Get the index of where the file was last updated
                        file.seek(0)
                        lines = file.readlines()
                        file_index = lines[0]
                        file_index_number = int(file_index)

                with open('decibel_log.txt','w') as file:
                        #If the last updated index was not the last line of the file
                        if file_index_number < data_amount:
                                file_index_number += 1
                                lines[0] = str(file_index_number) + "\n" #Update the last updated index
                                lines[file_index_number] = now_string + "\n" #Update the line with the current date and time
                        #If the last updated index was the last line of the file
                        else:
                                lines[0] = "1\n" #New last updated index is 1
                                lines[1] = now_string + "\n" #Update the line 1 with the current date and time
                        file.writelines(lines) #Write the updated lines to the file
        file.close() #Close the file
        time.sleep(delay_time) #Delay before next recorded event

GPIO.add_event_detect(channel, GPIO.FALLING, bouncetime=500)
GPIO.add_event_callback(channel, callback)

server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
server_sock.bind(("",bluetooth.PORT_ANY))
server_sock.listen(1)
port = server_sock.getsockname()[1]
uuid = "ae14f5e2-9eb6-4015-8457-824d76384ba0"
bluetooth.advertise_service( server_sock, "raspberry-pi-bt-demo", service_id = uuid, service_classes = [ uuid, bluetooth.SERIAL_PORT_CLASS ], profiles = [ bluetooth.SERIAL_PORT_PROFILE ] )


while True:

        time.sleep(1)

        print "Waiting for connection on RFCOMM channel %d" % port
        client_sock, client_info = server_sock.accept()
        print "Accepted connection from", client_info
        print "Sock number:", client_sock


        try:
                data = client_sock.recv(255)
                if len(data) == 0: 
                        break
                print "received [%s]" % data

                #Separate message and date
                message = ""
                date = ""
                for element in range(0, len(data)):
                        if (data[element] != "*"): #Create the message string
                                message = message + data[element]
                        else: #Create the date string
                                element2 = element + 1
                                for element2 in range(element2, len(data)):
                                        date = date + data[element2]
                                break

                if message == "get_decibel_log":
                        file = open("decibel_log.txt")
                        line = file.read()
                        file.close()
                        send_data = line + "#"
                elif message == "update_date_time":
                        os.system('sudo date -s %s' % date)
                        send_data = "Update Date & Time Successful#"
                else:
                        send_data = "\nInvalid Input\n"

                client_sock.send(send_data)

        except IOError:
                pass

       except KeyboardInterrupt:
                client_sock.close()
                server_sock.close()
                break

The Python code sets up the pins on the Pi, turns the Pi into a Bluetooth server, sets up the commands to communicate with the Pi, and contains the logic to create and update the log file containing the timestamps for decibel exceed events.

Each time the LM393 sensor detects that the sound level exceeds the level it was calibrated at, the Raspberry Pi will log a timestamp in the decibel_log.txt file which is contained in the same folder as the DecibelLogger.py file.

The Pi will have the ability to receive two different messages over BLE. One is the “get_decibel_log” message which will cause the Pi to send the decibel_log.txt file to the Android device. The second message the Pi can receive is the “update_date_time” message which will cause the Pi to update its internal clock to the date and time it received from the Android device. This is a useful feature for the Pi to sync its internal clock if the Pi isn’t connected to another computer over the USB connector. The capability will ensure that the Pi is recording accurate timestamps.

Next, exit out of the text editor and run the Python script by entering:

pi@raspberrypi:~ $ python DecibelLogger.py

Tuning the Sound Detection Sensor to a Certain Decibel Level

Tuning the LM393 sensor to a certain decibel threshold can be tricky because the LM393 is sensitive to background noise. The best way to do this is to put the project setup in a quiet room to calibrate the LM393 sensor. Next you will want to download a decibel detection app on the Android device like the dB Meter app. On a computer you will want to play consistent sounds at various levels in order to calibrate your sensor to the right threshold. The best way to do this is to play a sound like the one found here: https://www.youtube.com/watch?v=3FBijeNg_Gs. You will vary your computer volume to match the decibel level that you want detected by the Raspberry Pi.

On the LM393 sensor there is a small knob with a cross shape screwdriver imprint. You will use a Phillips Head screwdriver to turn the knob clockwise or counter-clockwise to calibrate the sensor. Turning the knob counter-clockwise makes the sensor less sensitive (higher decibel threshold). Turning the knob clockwise makes the sensor more sensitive (lower decibel threshold).

Modify the DecibelLogger.py script so that sound detection events will display faster during the calibration session. Change the line delay_time = 10 to delay_time = 1 in the DecibelLogger.py script. Now run the script:

pi@raspberrypi:~ $ python DecibelLogger.py

Open the dB Meter app so that you can view the decibel level at any given moment. Play a consistent sound from the computer. Adjust the volume on the computer so that the decibel level displayed on the app is equal to the decibel threshold you want triggered by the sensor. Now turn the knob until the Pi terminal goes from adding new “Sound Detected!” messages to no longer adding new “Sound Detected!” messages. Now turn the knob back slightly until new “Sound Detected!” messages are being displayed. Stop turning the knob. The sensor is now calibrated to the decibel threshold that you want to be detected.

Modify the DecibelLogger.py script back to the original delay time. Change the line delay_time = 1 to delay_time = 10 in the DecibelLogger.py script. Run the script again.

Creating an Android App Which Will Communicate Over BLE with the Raspberry Pi to Retrieve and Display When Sound Level Limits Were Exceeded

In Android Studio, create a new project and name the project whatever you would like. Once the project is created you will want to setup the user interface. For this project, there are two main features that will need to be triggered from the Android app: Get Decibel Log and Update Pi Date and Time. Create a button for each of these features. Add three TextViews: one to display the communication status with the Pi, one to display the column headers, and one to display the date and timestamps recorded by the Pi. Below is the XML code that should be contained in the activity_main.xml file.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

        <Button
            android:id="@+id/get_decibel_log"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_marginTop="15dp"
            android:layout_marginLeft="15dp"
            android:onClick="onClick"
            android:text="Get Decibel Log" />

        <Button
            android:id="@+id/update_date_time"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_marginTop="15dp"
            android:layout_marginLeft="15dp"
            android:onClick="onClick"
            android:text="Update Pi Date and Time"
            android:layout_below="@id/get_decibel_log"/>

        <TextView
            android:id="@+id/status"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_marginTop="0dp"
            android:layout_marginLeft="20dp"
            android:gravity="center"
            android:text=""
            android:maxLines = "1"
            android:textSize="15sp"
            android:textStyle="bold"
            android:layout_below="@id/update_date_time"/>

        <TextView
            android:id="@+id/tableHeader"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_marginTop="0dp"
            android:layout_marginLeft="20dp"
            android:gravity="bottom"
            android:text="         Date           Time     "
            android:maxLines = "1"
            android:textSize="15sp"
            android:textStyle="bold"
            android:layout_below="@+id/status"/>

        <TextView
            android:id="@+id/table"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="0dp"
            android:layout_marginLeft="20dp"
            android:gravity="top"
            android:text="No Data"
            android:textSize="15sp"
            android:layout_below="@+id/tableHeader"/>

</RelativeLayout>

Once the activity_main.xml file is setup, the user interface in the design editor should look like this.

The buttons won’t do anything at the moment but run the app through the debugger to make sure that the app builds with no problems. Once this is complete, it is now time to add the code to MainActivity.java which will drive the functionality of the app. Below is the Java code that should be contained in the MainActivity.java file.

package com.decibelexceedlog;

import android.os.Bundle;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import android.content.Intent;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import android.app.Activity;
import java.io.InputStream;
import android.graphics.Paint;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends Activity {

    BluetoothSocket socket;
    BluetoothDevice bt_device = null;

    final byte delimiter = 35;
    int readBufferPosition = 0;
    String data = "";
    String[] dataRows;
    String final_data = "";

    private final static int REQUEST_ENABLE_BT = 1;

    private BluetoothAdapter bluetoothAdapter;

    private Button getDecibelLog;
    private Button updateDateTime;
    private TextView status;
    private TextView tableHeader;
    private TextView table;

    private Set<BluetoothDevice> pairedDevices;
    List<BluetoothDevice> PairedDevices;

    public void sendMessage(String send_message) {

        UUID uuid = UUID.fromString("ae14f5e2-9eb6-4015-8457-824d76384ba0");

        try {

            socket = bt_device.createRfcommSocketToServiceRecord(uuid);
            if (!socket.isConnected()) {
                socket.connect();
            }

            String message = send_message;

            OutputStream btOutputStream = socket.getOutputStream();
            btOutputStream.write(message.getBytes());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setTitle("Decibel Exceed Log");

        setContentView(R.layout.activity_main);

        getDecibelLog = (Button) findViewById(R.id.get_decibel_log);
        updateDateTime = (Button) findViewById(R.id.update_date_time);
        status = (TextView) findViewById(R.id.status);
        tableHeader = (TextView) findViewById(R.id.tableHeader);
        table = (TextView) findViewById(R.id.table);

        tableHeader.setPaintFlags(tableHeader.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);

        final Handler handler = new Handler();

        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

        bluetoothAdapter = bluetoothManager.getAdapter();

        pairedDevices = bluetoothAdapter.getBondedDevices();
        PairedDevices = new ArrayList<>(pairedDevices);

        for (int i = 0; i < PairedDevices.size(); i++) {

            String name = PairedDevices.get(i).getName();

            //The name in quotes must match your device
            if (name.equals("raspberry-pi-bt-demo")) {
                bt_device = PairedDevices.get(i);
                break;
            }
        }

        //Check if Bluetooth is enabled
        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }

        final class workerThread implements Runnable {

            private String message;

            public workerThread(String msg) {
                message = msg;
            }

            public void run() {

                sendMessage(message);

                while (!Thread.currentThread().isInterrupted()) {

                    int bytesAvailable = 0;
                    boolean workDone = false;

                    try {

                        final InputStream inputStream;
                        inputStream = socket.getInputStream();
                        bytesAvailable = inputStream.available();

                        if (bytesAvailable > 0) {

                            byte[] packetBytes = new byte[bytesAvailable];
                            Log.e("Bytes received from", "Raspberry Pi");
                            byte[] readBuffer = new byte[1024];
                            inputStream.read(packetBytes);

                            for (int i = 0; i < bytesAvailable; i++) {
                                byte b = packetBytes[i];
                                if (b == delimiter) {
                                    byte[] encodedBytes = new byte[readBufferPosition];
                                    System.arraycopy(readBuffer, 0, encodedBytes, 0, encodedBytes.length);
                                    data = new String(encodedBytes, "US-ASCII");

                                    String[] copy = data.split(System.getProperty("line.separator"));

                                    //Clear extraneous characters from front of sequence
                                    String firstLine = "";
                                    for (int h = 0; h < copy[0].length(); h++)
                                    {
                                        int c = copy[0].charAt(h);
                                        if (c > 0x1F && c < 0x7F) {
                                            firstLine = firstLine + copy[0].charAt(h);
                                        }
                                    }
                                    copy[0] = firstLine;

                                    if (copy[0].equals("Update Date & Time Successful"))
                                    {
                                        handler.post(new Runnable() {
                                            public void run() {
                                                status.setText("Date & Time Update Successful!");
                                            }
                                        });
                                    }
                                    else {
                                        //Retrieve first element, remove first element and reorder from most recent to least recent
                                        int l = copy.length;
                                        int pointer = Integer.valueOf(copy[0]);
                                        dataRows = new String[l - 1];

                                        int k = 0;  //Copy array pointer
                                        for (int j = pointer; j > 0; j--) {
                                            dataRows[k] = copy[j];
                                            k++;
                                        }

                                        for (int j = l - 1; j > pointer; j--) {
                                            dataRows[k] = copy[j];
                                            k++;
                                        }

                                        final_data = "";

                                        for (int m = 0; m < dataRows.length; m++) {
                                            final_data = final_data + dataRows[m] + "\n";
                                        }

                                        readBufferPosition = 0;

                                        handler.post(new Runnable() {
                                            public void run() {
                                                status.setText("Data Collection Successful!");
                                                table.setText(final_data);
                                            }
                                        });
                                    }

                                    workDone = true;
                                    break;
                                } else {
                                    readBuffer[readBufferPosition++] = b;
                                }
                            }

                            if (workDone == true) {
                                socket.close();
                                break;
                            }

                        }
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        status.setText("Data Collection Problem!");
                        e.printStackTrace();
                    }

                }
            }
        }

        //Get Decibel Log Listener
        getDecibelLog.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {

                Log.e("Button pressed ", "Get Data");
                (new Thread(new workerThread("get_decibel_log"))).start();

            }
        });

        //Get Decibel Log Listener
        updateDateTime.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {

                SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                Date now = new Date(); //Get current date and time
                String strDate = date.format(now);  //Turn into string
                Log.e("Button pressed ", "Update Date Time");
                (new Thread(new workerThread("update_date_time"+"*"+strDate))).start();  //Add date and time string to command

            }
        });

    }
}

The app code will create the Bluetooth socket with the Raspberry Pi, get the Raspberry Pi from the list of paired devices on the Android device, send a command to the Raspberry Pi upon a button press in the app and listen for a status back from the Raspberry Pi to indicate whether the command was executed. If the Get Decibel Log button is pressed, the app will display the log of timestamps collected from the Pi. Run the app through the debugger to make sure it builds.

Final Step

The last step is to build the project through Android studio onto the device. When the app is up and running on the device, the app code will create the socket to communicate with the Pi. Press the Get Decibel Log button in the app and a log of timestamps in descending order that were collected by the Pi will be displayed in the app. A status will be returned to the app from the Pi to indicate that the data transmission was successful. Pressing the Update Pi Date and Time button will update the Pi date and time with the date and time from the Android app. A status will be returned to the app from the Pi to indicate that the date and time on the Pi was updated successfully. You have now created a way to communicate the Pi’s log and update the Pi’s date and time using BLE from an Android device!