2D Maze: DIY Maze Solver (Building a Bluetooth-Controlled Maze Game With ESP32 and Android)

by sastelvios in Circuits > Arduino

394 Views, 2 Favorites, 0 Comments

2D Maze: DIY Maze Solver (Building a Bluetooth-Controlled Maze Game With ESP32 and Android)

IMG_20230704_212229.jpg

Welcome to this step-by-step guide on creating a thrilling maze game using an ESP32 board (We have called this project 2D Maze :) ). It is inspired on Build DIY Maze Game Using Arduino by SmartTronix. We did some enhanced with Bluetooth capability to control the movement of the maze using your Android smartphone.

Instead of using an Arduino, We will be utilizing the ESP32 board, which offers additional features such as Bluetooth and WiFi connectivity. Alongside this hardware upgrade, We will integrate Bluetooth functionality and introduce a custom Android app, providing a seamless and intuitive way to control the maze solver.

Key Features:

1. ESP32 Board: The ESP32 offers a powerful microcontroller with built-in Wi-Fi and Bluetooth capabilities, making it an ideal choice for our maze-solving project. The great thing is that you can use the same code and libraries available for Arduino.

2. Bluetooth Connectivity: By incorporating Bluetooth technology, you'll be able to control the movements of the maze solver using your Android smartphone.

3. Custom Android App: We've developed a dedicated Android app that connects with the ESP32 board, allowing you to connect to the hardware, and navigate through the maze.


Software Requirements:

- Arduino IDE

- Custom Android app (provided in the project resources)


By the end of this tutorial, you'll have a fully functional maze solver that responds to your smartphone's commands, deftly maneuvering through the twists and turns of the maze. So let's gather our materials, dive into the step-by-step instructions, and embark on this thrilling journey of creating a Bluetooth-controlled maze game with an ESP32 board and Android smartphone!

Supplies

To get started, you'll need the following components:

Components

  • Power development board: ESP32 Wroom Low (you can replace with any other one that have Bluetooth capability) (see here)
  • Breadboard (see here) and Jumper wires (see here)
  • Servo Motors: Batan S1213 (see here)
  • USB type C cable
  • Android smartphone
  • Some glue (for wood and plastic)
  • Some Wood


Tools

  • Arduino IDE or VS Code with PlatformIO
  • Android Studio (or any other IDE that allows you to develop in Android SDK)
  • 3D Printer
  • Laser Cutting Machine


PS: Most of the links in this description are for a third-party so we don't have the control of it. If it's not working, try to search on Google.

Design the Hardware

We have used the tinkercad to design the 3D and test the code before uploading to the ESP32 board. And used Inkscape to design the Laser Cutting parts.

You can find our 3D parts here, and the Laser Cutting here

To generate a custom maze you can use THIS COOL PROJECT (All the credits to Aaron Rodriguez).


PS: Most of the links in this description are for a third-party so we don't have the control of it. If it's not working, try to search on Google.

Assemble the Hardware

IMG_20230704_134722.jpg
IMG_20230704_134709.jpg
IMG_20230704_104916.jpg
IMG_20230704_130450.jpg
IMG_20230704_134703.jpg
IMG_20230704_102300.jpg
IMG_20230628_211906.jpg
IMG_20230628_212206.jpg
IMG_20230628_211016.jpg
IMG_20230627_154030.jpg

Now it's time to assemble all the components. Make sure you have gathered all the necessary parts and tools before proceeding. In this project, we will be using wood and plastic glue to secure the components together, providing a sturdy and reliable construction.


  1. Begin by applying a layer of wood glue to the edges of the maze game's front case, and maze holder. The joints will make it intuitive to do so. Carefully align and place the Servo Motors and the ESP32 board onto the designated area, ensuring that the necessary pins and connectors are easily accessible.
  2. Attach the Servo Motors to the case using wood glue. Make sure to position it in a convenient location that allows easy access to the necessary connections.
  3. Connect the 3D prints using the necessary bolds. Make sure you allows all me movable parts to do so.
  4. Using blue, connect the Wood parts (Laser Cutting) and the 3D printed parts.
  5. Install the ESP32 board (on top of the breadboard) in a place you can easily connect the jumpers.
  6. Double-check all the connections and ensure that all the components are securely attached.

Software Development

Screenshot_20230704_214831.jpg
Screenshot_20230704_214836.jpg
Screenshot_20230704_214839.jpg
Screenshot_20230704_214847.jpg
Screenshot_20230704_214859.jpg

Arduino Software


First we going to develop the ESP32/Arduino code. As mentioned before, we will use ESP32.

To do so, download the Arduino IDE (here) or VS Code (here) with PlatformIO (here). We will use the VS Code and PlatformIO.

  • Connected your ESP32 board to the PC using its USB type C cable.
  • Open VS Code.
  • Copy and Paste the following code:
// Include necessary libraries
#include <Arduino.h>
#include <Servo.h>
#include <BluetoothSerial.h>
#include "UUID.h"

// Create servo objects
Servo servoX;
Servo servoY;

// Define joystick input pins. One to read the X and other to read the Y axes
int joyX = 32;
int joyY = 33;


// Initialize inicial servo angle variables.
int servoXAngle = 0;
int servoYAngle = 0;


// Define LED pin that going to turns on when the bluetooth connection is established
int ledPin = 25;


/*
*Create BluetoothSerial object.
*This is the oboject that going to manage the Bluetooth connection
*/
BluetoothSerial SerialBT;

//Deadzone Range
/*
The values of the joystick input can fluctuate even when the joystick
is not being moved due to several factors, including noise in the analog
signal or imperfections in the joystick's internal mechanism.
To minimize the effect of input fluctuations, I implement a
technique called "joystick deadzone" or "analog input deadzone."
This involves defining a small range around the center position
of the joystick where small variations in the analog readings are ignored.
In this case, all the values from the joystick between the central position
(X = 0 and Y = 0) and X = -60 or X = 60 or Y = -60 and Y -60 will be ignored, otherwise
the servos will be loving involuntary.
*/
int deadzone = 60;

void setup() {

/*
TInitialize the serial communication on the specified baud (data rate in bits per second)
rate between the ESP32 and the connected device, such as a computer. This ensures that both
the transmitting and receiving devices are synchronized and can communicate effectively at the same speed.
*/
Serial.begin(921600);
SerialBT.begin("2D Maze - by Sastelvio MANUEL"); // Set Bluetooth device name


//Attaching the servos to the ports 12 and 14 respectively
servoX.attach(12);
servoY.attach(14);


pinMode(ledPin, OUTPUT); // Set LED pin as OUTPUT

// Initialize LED status (starts OFF)
digitalWrite(ledPin, LOW);
}

void loop() {
// Read joystick values on X and Y axes
int joyXValue = analogRead(joyX);
int joyYValue = analogRead(joyY);


// Apply deadzone to joystick X value
if (abs(joyXValue - 2889) < deadzone) {
joyXValue = 2889; // Set to center position. If the fault movement are between the deadzone, ignore then and set the central value
}

// Apply deadzone to joystick Y value
if (abs(joyYValue - 2879) < deadzone) {
joyYValue = 2879; // Set to center position. If the fault movement are between the deadzone, ignore then and set the central value
}

/*
*Map joystick values to servo angles
*This allows to translate the values of the Joystick to the deserved Servo angles
*In this case, we say that the Joystick is working between 1410 and 4100,
*so these values should be translated to the angle range 70 and 130.
*This angles will have as a reference the central value of then = (70 + 130) / 2 = 100)
*/
servoXAngle = map(joyXValue, 1410, 4100, 70, 130);
servoYAngle = map(joyYValue, 1410, 4100, 70, 130);


// Turn on the LED when a Bluetooth connection is established
if (SerialBT.connected()) {
digitalWrite(ledPin, HIGH); // Turn on the LED, if the bluetooth is connected
} else {
digitalWrite(ledPin, LOW); // Turn off the LED, if the bluetooth is disconnected
}

// Check if there is data available from Bluetooth connected device
while (SerialBT.available()) {
String receivedInput = SerialBT.readStringUntil('\n'); // Read the input string

// Check the received input for X or Y axis and corresponding angle
/*
The bluetooth device send values with X and Y to identify the direction.
The substring is to get the values from the index 1 at the received String,
because the char at position 0 is the X or Y depending on the direction.
*/
if (receivedInput.charAt(0) == 'X') {
servoXAngle = receivedInput.substring(1).toInt();

} else if (receivedInput.charAt(0) == 'Y') {
servoYAngle = receivedInput.substring(1).toInt();
}

}

// Limit servo angles within valid range. This is useful to avoid the servo turns a full round
servoXAngle = constrain(servoXAngle, 0, 180);
servoYAngle = constrain(servoYAngle, 0, 180);


// Write the angles to the servo motors
servoX.write(servoXAngle);
servoY.write(servoYAngle);


// Delay between readings
delay(500);
}


You can see the Batan S1213 how to connect here.


If you use the PlatformIO to write the code, you need to set your platform.ini file as:

Environment configuration for the "upesy_wroom" board
[env:upesy_wroom]
; Platform used is Espressif32
platform = espressif32
; Board selected is "upesy_wroom"
board = upesy_wroom
; Arduino framework is used
framework = arduino
; Serial monitor speed set to 921600 baud
monitor_speed = 921600
; Library dependencies
lib_deps =
roboticsbrno/ServoESP32@^1.0.3
mbed-seeed/BluetoothSerial@0.0.0+sha.f56002898ee8
robtillaart/UUID@^0.1.5
  • Set the correct board and COM Port number and upload the code into the Arduino.


Android Software


To control the Arduino board using a Android smartphone you can use the following steps:

  • Open Android Studio
  • Start a new project, and follow the setup. You can start with an Empty Activity to avoid unnecessary Boilerplate, and for convenience, call your Activity: MainActivity in com.a2dmaze package.
  • Paste the following code on your MainActivity.java (this Android project was made using Java, if you use Kotlin do the necessary ajustments):
package com.a2dmaze;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.core.app.ActivityCompat;
import androidx.drawerlayout.widget.DrawerLayout;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;

/**
* Created by Sastelvio MANUEL on 29/06/2023.
*/
public class MainActivity extends AppCompatActivity implements JoystickView.JoystickListener {

// Declare the variable to get the list view from the xml
private ListView listView;

// Declare the Array Adapter, to adapt the view of the data (list of Bluetooth devices)
private ArrayAdapter arrayAdapter;

//Set a tag to the terminal prints, so we can associate the terminal prints relacted to our app
private static final String TAG = "FrugalLogs";

// Variable is used to request the user's permission to enable Bluetooth.
private static final int REQUEST_ENABLE_BLUETOOTH = 1;

// We will use a Handler to get the BT Connection status
UUID a2DMazeUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); // We declare a default UUID to create the global variable

//Set the necessary bluetooth variables to establish a connection
@RequiresApi(api = Build.VERSION_CODES.M)
BluetoothManager bluetoothManager;
BluetoothAdapter bluetoothAdapter;
private BluetoothSocket bluetoothSocket;
private OutputStream outputStream;
private BluetoothDevice a2DBTModule;
private String MAC_OF_ESP32_BOARD = ""; //set the MAC of your board here


//Method called when opening the app
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//Inicialize the Joystick view
JoystickView joystick = new JoystickView(this);
setContentView(R.layout.activity_main);

// Instances of BT Manager and BT Adapter needed to work with BT in Android.
bluetoothManager = getSystemService(BluetoothManager.class);
bluetoothAdapter = bluetoothManager.getAdapter();

// Instances of the Android UI elements that will be used during the execution of the app
LinearLayoutCompat btConnectionSearchView = findViewById(R.id.btConnectionSearchView);
LinearLayoutCompat btConnectionView = findViewById(R.id.btConnectionView);
TextView btDevicesText = findViewById(R.id.btDevicesText);
TextView btConnectedDevice = findViewById(R.id.btConnectedDevice);
Button searchDevices = findViewById(R.id.searchDevices);
Button disconnect = findViewById(R.id.disconnect);

//Action when the search button is pressed
searchDevices.setOnClickListener(new View.OnClickListener() {
// Display all the linked BT Devices
@Override
public void onClick(View view) {
// Check if the phone supports BT
if (bluetoothAdapter == null) {
// Device doesn't support Bluetooth
Log.d(TAG, "Device doesn't support Bluetooth");
} else {
Log.d(TAG, "Device supports Bluetooth");

// Check if Bluetooth is enabled. If disabled, we ask the user to enable BT
if (!bluetoothAdapter.isEnabled()) {
Log.d(TAG, "Bluetooth is disabled");
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "We don't have Bluetooth permissions");
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);
Log.d(TAG, "Bluetooth is enabled now");
} else {
Log.d(TAG, "We have Bluetooth permissions");
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);
Log.d(TAG, "Bluetooth is enabled now");
}
} else {
Log.d(TAG, "Bluetooth is enabled");
}

// String btDevicesString="";
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
ArrayList<String> listDeviceNames = new ArrayList<>();
if (pairedDevices.size() > 0) {
// There are paired devices. Get the name and address of each paired device.
for (BluetoothDevice device : pairedDevices) {
listDeviceNames.add(device.getName());

if (device.getAddress().equals(MAC_OF_ESP32_BOARD)) {
Log.d(TAG, "2D Maze Found: " + device.getAddress());
Toast.makeText(getApplicationContext(), "2D Maze Found", Toast.LENGTH_SHORT).show();
a2DMazeUUID = device.getUuids()[0].getUuid();
a2DBTModule = device;
} else {
Log.d(TAG, "2D Maze Not Found");
//Toast.makeText(getApplicationContext(), "2D Maze Not Found", Toast.LENGTH_SHORT).show();
}
}

btDevicesText.setVisibility(View.VISIBLE);
searchDevices.setText("Refresh paired devices");
listView = findViewById(R.id.deviceList);
arrayAdapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, listDeviceNames);
listView.setAdapter(arrayAdapter);

// Set onClick action for items in the ListView
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String selectedItem = (String) parent.getItemAtPosition(position);
if (selectedItem.equals(a2DBTModule.getName())) {
Log.d(TAG, "Valid 2D Maze");
////ProgressBar progressBar = findViewById(R.id.progressBar);
//// progressBar.setVisibility(View.VISIBLE);
if (connectToDevice(a2DBTModule)) {
btConnectionSearchView.setVisibility(View.GONE);
btConnectionView.setVisibility(View.VISIBLE);
btConnectedDevice.setText(selectedItem);
Log.d(TAG, "Connected successfully");
Toast.makeText(getApplicationContext(), "Connected successfully", Toast.LENGTH_SHORT).show();
}
} else {
Log.d(TAG, "Invalid 2D Maze");
Toast.makeText(getApplicationContext(), "Invalid 2D Maze Device", Toast.LENGTH_SHORT).show();
}
}
});
}
}
}
});

//Action when the Button Disconnect is clicked
disconnect.setOnClickListener(new View.OnClickListener() {
// Disconnect from the ESP32
@Override
public void onClick(View view) {
//check if the method disconnect returned true
if (disconnectToDevice(a2DBTModule)) {
//if true, change the view to the list of the paired devices
btConnectionSearchView.setVisibility(View.VISIBLE);
btConnectionView.setVisibility(View.GONE);

//show a message to the user that the device was disconnected
Toast.makeText(getApplicationContext(), "Disconnected successfully", Toast.LENGTH_SHORT).show();
}
}
});
}

//This method is used to connect a device from the ESP32.
private boolean connectToDevice(BluetoothDevice device) {
try {
//this line gets the Universally Unique IDentifier to attack to the connection
bluetoothSocket = device.createRfcommSocketToServiceRecord(a2DMazeUUID);
//open the connection
bluetoothSocket.connect();
//set an Output Stream to be able to send data
outputStream = bluetoothSocket.getOutputStream();
// Connection successful, you can now send commands to the ESP32
} catch (IOException e) {
// Error occurred while connecting
e.printStackTrace();
Toast.makeText(getApplicationContext(), "Unable to connect! Check if the 2D Maze is ON", Toast.LENGTH_SHORT).show();
}
//return the status in a boolean value
return bluetoothSocket.isConnected();
}

//This method is used to disconnect a device from the ESP32.
private boolean disconnectToDevice(BluetoothDevice device) {
try {
//here the connection is closed.
bluetoothSocket.close();
} catch (IOException e) {
// Error occurred while disconnecting
e.printStackTrace();
}
//return the status in a boolean value
return !bluetoothSocket.isConnected();
}

//This method is used to send the data to the ESP32 using the bluetooth connection. It receives as parameter the angle to be send
private void sendServoAngle(String angle) {
//Check if there's output Stream, meaning that theres is a connection ready to send data
if (outputStream != null) {
try { //to handle Exceptions
//Store the angle in an String and make sure that for every value a new line is created, meaning that is treated as a different value by the ESP32
String angleString = String.valueOf(angle + "\n");
//Send the data using the output stream
outputStream.write(angleString.getBytes());
} catch (IOException e) { //handle errors
// Error occurred while sending data
e.printStackTrace();
}
}
}

//This method/function it to get the movements of the Joystick (virtual)
@Override
public void onJoystickMoved(float xPercent, float yPercent, int id) {
sendServoAngle("X" + map((Math.round(xPercent) * 100), -100, 100, 70, 130));
sendServoAngle("Y" + map((Math.round(yPercent) * 100), -100, 100, 70, 130));
}

/*
*Map joystick (virtual) values to servo angles
*This allows to translate the values of the Joystick to the deserved Servo angles
*In this case, we say that the Joystick is working between 1410 and 4100,
*so these values should be translated to the angle range 70 and 130.
*This angles will have as a reference the central value of then = (70 + 130) / 2 = 100)
*/
public static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) {
return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
}
}


  • Create a class called JoystickView.java and paste the code:
package com.a2dmaze;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

/**
* Adapted by Sastelvio MANUEL on 29/06/2023.
* Created by Daniel on 7/25/2016.
*/
public class JoystickView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener
{
private float centerX;
private float centerY;
private float baseRadius;
private float hatRadius;
private JoystickListener joystickCallback;
private final int ratio = 5; //The smaller, the more shading will occur

private void setupDimensions()
{
centerX = getWidth() / 2;
centerY = getHeight() / 2;
baseRadius = Math.min(getWidth(), getHeight()) / 3;
hatRadius = Math.min(getWidth(), getHeight()) / 5;
}

public JoystickView(Context context)
{
super(context);
getHolder().addCallback(this);
setOnTouchListener(this);
if(context instanceof JoystickListener)
joystickCallback = (JoystickListener) context;
}

public JoystickView(Context context, AttributeSet attributes, int style)
{
super(context, attributes, style);
getHolder().addCallback(this);
setOnTouchListener(this);
if(context instanceof JoystickListener)
joystickCallback = (JoystickListener) context;
}

public JoystickView (Context context, AttributeSet attributes)
{
super(context, attributes);
getHolder().addCallback(this);
setOnTouchListener(this);
if(context instanceof JoystickListener)
joystickCallback = (JoystickListener) context;
}

private void drawJoystick(float newX, float newY) {
if (getHolder().getSurface().isValid()) {
Canvas myCanvas = this.getHolder().lockCanvas(); // Stuff to draw

// Set the background color
myCanvas.drawColor(Color.WHITE); // Set the desired background color

// First determine the sin and cos of the angle that the touched point is at relative to the center of the joystick
float hypotenuse = (float) Math.sqrt(Math.pow(newX - centerX, 2) + Math.pow(newY - centerY, 2));
float sin = (newY - centerY) / hypotenuse; // sin = o/h
float cos = (newX - centerX) / hypotenuse; // cos = a/h

// Calculate the maximum distance the hat can move from the center based on the base radius
float maxHatDistance = baseRadius - hatRadius;

// Calculate the actual distance of the hat from the center
float hatDistance = hypotenuse - baseRadius;

// Limit the hat distance to the maximum allowed distance
if (hatDistance > maxHatDistance) {
hatDistance = maxHatDistance;
newX = centerX + cos * (baseRadius + hatDistance);
newY = centerY + sin * (baseRadius + hatDistance);
}

// Draw the base first before shading
Paint basePaint = new Paint();
basePaint.setColor(Color.rgb(190, 190, 190));
myCanvas.drawCircle(centerX, centerY, baseRadius, basePaint);

for (int i = 1; i <= (int) (baseRadius / ratio); i++) {
Paint shadingPaint = new Paint();
shadingPaint.setColor(Color.argb(150 / i, 255, 0, 0));
myCanvas.drawCircle(newX - cos * hypotenuse * (ratio / baseRadius) * i,
newY - sin * hypotenuse * (ratio / baseRadius) * i, i * (hatRadius * ratio / baseRadius), shadingPaint);
}

// Drawing the joystick hat
for (int i = 0; i <= (int) (hatRadius / ratio); i++) {
Paint hatPaint = new Paint();
hatPaint.setColor(Color.argb(255, 3, 53, 114)); // Color(#033572)
myCanvas.drawCircle(newX, newY, hatRadius - (float) i * (ratio) / 2, hatPaint);
}

getHolder().unlockCanvasAndPost(myCanvas); // Write the new drawing to the SurfaceView
}
}

@Override
public void surfaceCreated(SurfaceHolder holder)
{
setupDimensions();
drawJoystick(centerX, centerY);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}

public boolean onTouch(View v, MotionEvent e)
{
if(v.equals(this))
{
if(e.getAction() == e.ACTION_MOVE)
{
float displacement = (float) Math.sqrt((Math.pow(e.getX() - centerX, 2)) + Math.pow(e.getY() - centerY, 2));
if(displacement < baseRadius)
{
drawJoystick(e.getX(), e.getY());
joystickCallback.onJoystickMoved((e.getX() - centerX)/baseRadius, (e.getY() - centerY)/baseRadius, getId());
}
else
{
float ratio = baseRadius / displacement;
float constrainedX = centerX + (e.getX() - centerX) * ratio;
float constrainedY = centerY + (e.getY() - centerY) * ratio;
drawJoystick(constrainedX, constrainedY);
joystickCallback.onJoystickMoved((constrainedX-centerX)/baseRadius, (constrainedY-centerY)/baseRadius, getId());
}
}
else {
drawJoystick(centerX, centerY);
joystickCallback.onJoystickMoved(0, 0, getId());
}
}
return true;
}

public interface JoystickListener
{
void onJoystickMoved(float xPercent, float yPercent, int id);

}
}

PS: The JoystickView code is inspired by EfficentIsoceles.

To create the view you can paste the following code on activity_main.xml:

<?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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingBottom="@dimen/activity_margin"
android:paddingLeft="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin"
android:gravity="top"
android:orientation="vertical"
android:background="@android:color/white"
tools:context=".MainActivity">


<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/btConnectionSearchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="@dimen/activity_margin"
android:gravity="top"
android:layout_weight="1">

<Button
android:id="@+id/searchDevices"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:fontFamily="@font/ubuntu"
android:textStyle="bold"
android:background="@drawable/button_template"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="Show Paired devices"
android:textColor="@color/colorWhite"
tools:ignore="MissingConstraints" />

<TextView
android:id="@+id/btDevicesText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15dp"
android:textStyle="bold"
android:fontFamily="@font/ubuntu"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginBottom="@dimen/activity_margin"
android:text="Paired Bluetooth Devices:"
android:visibility="invisible"/>

<ListView
android:id="@+id/deviceList"
android:fontFamily="@font/ubuntu"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>

</androidx.appcompat.widget.LinearLayoutCompat>

<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/btConnectionView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="@dimen/activity_margin"
android:gravity="top"
android:visibility="gone"
android:layout_weight="2">

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:paddingTop="0dp"
android:paddingBottom="0dp">

<TextView
android:id="@+id/btConnectedHeader"
android:layout_width="wrap_content"
android:fontFamily="@font/ubuntu"
android:layout_height="wrap_content"
android:text="Connected to:"/>

<TextView
android:id="@+id/btConnectedDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/ubuntu"
android:textColor="@color/colorPrimary"
android:layout_margin="10dp"
android:layout_marginBottom="30dp"
android:text="fff"
android:textSize="20sp"
android:textStyle="bold"/>

<Button
android:id="@+id/disconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_template"
android:fontFamily="@font/ubuntu"
android:textColor="@color/colorWhite"
android:layout_gravity="center_horizontal"
android:layout_margin="16dp"
android:text="Disconnect"
tools:ignore="MissingConstraints" />

</androidx.appcompat.widget.LinearLayoutCompat>

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:paddingLeft="40dp"
android:paddingTop="0dp"
android:paddingRight="40dp"
android:paddingBottom="0dp">

<com.sastelvio.a2dmaze.JoystickView
android:id="@+id/joystick"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</androidx.appcompat.widget.LinearLayoutCompat>

</androidx.appcompat.widget.LinearLayoutCompat>

<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:maxHeight="50dp"
android:minHeight="50dp"
android:textColor="@color/colorPrimary"
android:fontFamily="@font/ubuntu"
android:textSize="10dp"
android:textStyle="bold"
android:textAlignment="center"
android:layout_weight="2"
android:text="Developed by Sastelvio Serafim Manuel" />

</LinearLayout>

PS: customize the code according to your app variables and values, such as colors, texts, and so on.

Video Description

2D Maze: DIY Maze Solver (Building a Bluetooth-Controlled Maze Game With ESP32 and Android)

Conclusion

At this part we reached the end of the project. We hope you have enjoyed to building this with us.

If you enjoyed this article, don’t forget to like it, and share it.


Thank you!