<repo_name>MikeWellC/ESP32-CAM-car-android-app<file_sep>/app/src/main/java/com/theretrocenter/esp32_camandroidapp/UserGuideFragment.java
package com.theretrocenter.esp32_camandroidapp;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;

import com.theretrocenter.esp32_camandroidapp.utilities.Preferences;

public class UserGuideFragment  extends Fragment {
    private MainActivity mainActivity = new MainActivity();
    private Preferences preferences = Preferences.getInstance(mainActivity);
    private String sawUserGuide = preferences.getData("SawUserGuide");

    void skipUserGuide() {
        if (sawUserGuide.equals("saw")) {
            NavHostFragment.findNavController(UserGuideFragment.this)
                    .navigate(R.id.action_UserGuideFragment_to_ControlCarFragment);
        }
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState
    ) {
        // Skip user guide when viewed
        skipUserGuide();
        return inflater.inflate(R.layout.user_guide, container, false);
    }

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Set false, for not stop propagation on keyDown and keyUp events in human interfaces
        preferences.saveData("controlCar", "false");

        view.findViewById(R.id.closeUserGuide).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Save preferred UI control in preferences
                preferences.saveData("SawUserGuide", "saw");

                // Return to control car fragment layout
                NavHostFragment.findNavController(UserGuideFragment.this)
                        .navigate(R.id.action_UserGuideFragment_to_ControlCarFragment);
            }
        });
    }
}
<file_sep>/settings.gradle
include ':app'
rootProject.name = "ESP32-CAM Android App"<file_sep>/app/src/main/java/com/theretrocenter/esp32_camandroidapp/utilities/SingletonListener.java
package com.theretrocenter.esp32_camandroidapp.utilities;

public class SingletonListener
{
    private static SingletonListener instance;

    private boolean changed = false;
    private ChangeListener listener;

    public static void initInstance()
    {
        if (instance == null)
        {
            // Create the instance
            instance = new SingletonListener();
        }
    }

    public static SingletonListener getInstance()
    {
        // Return the instance
        return instance;
    }

    private SingletonListener()
    {
        // Constructor hidden because this is a singleton
    }

    public boolean isChanged() {
        return changed;
    }

    public void setChanged(boolean changed) {
        this.changed = changed;
        if (listener != null) listener.onChange();
    }

    public ChangeListener getListener() {
        return listener;
    }

    public void setListener(ChangeListener listener) {
        this.listener = listener;
    }

    public interface ChangeListener {
        void onChange();
    }
}
<file_sep>/app/build.gradle
plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"
    useLibrary "org.apache.http.legacy"

    defaultConfig {
        applicationId "com.theretrocenter.esp32_camandroidapp"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        // vectorDrawables.useSupportLibrary = true
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.navigation:navigation-fragment:2.3.5'
    implementation 'androidx.navigation:navigation-ui:2.3.5'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'io.github.controlwear:virtualjoystick:1.9.2'
    implementation 'com.github.perthcpe23:android-mjpeg-view:v1.0.9'
    implementation 'com.android.support:percent:30.0.0'
    implementation 'androidx.annotation:annotation:1.2.0'
    compile group: 'org.apache.httpcomponents' , name: 'httpclient-android' , version: '4.3.5.1'
}

<file_sep>/app/src/main/java/com/theretrocenter/esp32_camandroidapp/ControlCarFragment.java
package com.theretrocenter.esp32_camandroidapp;

import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;

import com.longdo.mjpegviewer.MjpegView;
import com.theretrocenter.esp32_camandroidapp.RemoteWIFICar.RemoteWIFICar;
import com.theretrocenter.esp32_camandroidapp.utilities.IPAddress;
import com.theretrocenter.esp32_camandroidapp.utilities.Preferences;
import com.theretrocenter.esp32_camandroidapp.utilities.SingletonListener;

import io.github.controlwear.virtual.joystick.android.JoystickView;

public class ControlCarFragment extends Fragment {
    private MainActivity mainActivity = new MainActivity();
    private RemoteWIFICar remoteWIFICar = new RemoteWIFICar();
    private Preferences preferences = Preferences.getInstance(mainActivity);
    private MjpegView viewer;
    private String lastCommand = "";
    private Boolean ligthOn = false;
    private Boolean forwardActived = false;
    private Context context;
    private ProgressDialog loading;
    private Boolean isFindingCar = false;

    // Get saved preferences
    private String carUIControl = preferences.getData("CarUIControl");
    private String remoteWIFICarIP = preferences.getData("RemoteWIFICarIP");

    public void moveCar(Integer x, Integer y) {
        String command = "";

        // Determine movement based on the index of movement of axes
        if (x > 75) {
            command = "turnright";
        } else if (x < 25) {
            command = "turnleft";
        } else if (y > 75) {
            command = "backward";
        } else if (y < 25) {
            command = "forward";
        } else {
            command = "stop";
        }

        // Execute action only when the command changes
        if (command != lastCommand) {
            remoteWIFICar.executeAction(command, remoteWIFICarIP);
            Log.i("Execute command", command);
        }

        // Set the last command
        lastCommand = command;
    }

    public void showCam() {
        // Set jpeg video url to viewer
        String mjpegSource = "http://" + remoteWIFICarIP + ":81/stream";
        Log.i("Execute command", mjpegSource);
        viewer = (MjpegView) getView().findViewById(R.id.mjpegview);
        viewer.setMode(MjpegView.MODE_FIT_WIDTH);
        viewer.setAdjustHeight(true);
        viewer.setSupportPinchZoomAndPan(true);
        viewer.setUrl(mjpegSource);
        viewer.startStream();
    }

    public void connectWithCar() {
        if (!isFindingCar) {
            // Prevent a second search instance from running
            isFindingCar = true;

            // Loading dialog while finding for IP
            loading = new ProgressDialog(context);
            loading.setCancelable(true);
            loading.setMessage(getString(R.string.loadingTextFindIP));
            loading.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            loading.show();

            // Timeout one second
            new android.os.Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                public void run() {
                    try {
                        // Get local IP
                        IPAddress ipAddress = new IPAddress();
                        String localIP = ipAddress.getLocalIP(true);

                        // Get Remote WIFI car IP
                        RemoteWIFICar remoteWifiCar = new RemoteWIFICar();
                        remoteWIFICarIP = remoteWifiCar.finderIP(localIP, mainActivity);
                        Log.i("Remote WIFI Car IP", remoteWIFICarIP);

                        // Load video jpeg
                        showCam();

                        // Hidde loading message
                        loading.hide();
                        loading.dismiss();
                        isFindingCar = false;

                    } catch(Exception ex) {
                        ex.printStackTrace();
                    }
                }
            },1000);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        try {
            // Stop viewer when rotate display
            viewer.stopStream();
            // Dismiss dialog when rotate display
            loading.hide();
            loading.dismiss();
        } catch(Exception ex) {
            //ex.printStackTrace();
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onPause() {
        super.onPause();  // Always call the superclass method first

        // Stop viewer when rotate display
        viewer.stopStream();
        // Dismiss dialog when rotate display
        loading.hide();
        loading.dismiss();
    }
    @Override
    public void onResume() {
        super.onResume();  // Always call the superclass method first

        // Find car in the network
        connectWithCar();
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState
    ) {
        context = container.getContext();

        // Determine UI control layout
        if (carUIControl.equals("joystick")) {
            // Inflate the joystick layout for this fragment
            return inflater.inflate(R.layout.controlcar_joystick, container, false);
        }

        // Inflate the buttons layout for this fragment
        return inflater.inflate(R.layout.controlcar_buttons, container, false);
    }

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Set true, for stop propagation on keyDown and keyUp events in human interfaces
        preferences.saveData("controlCar", "true");

        // Find car in the network
        connectWithCar();

        if (carUIControl.equals("joystick")) {
            // Execute car movements for a Joystick event
            final JoystickView joystickRight = (JoystickView) getView().findViewById(R.id.joystickView_right);
            joystickRight.setOnMoveListener(new JoystickView.OnMoveListener() {
                @SuppressLint("DefaultLocale")
                @Override
                public void onMove(int angle, int strength) {
                    moveCar(joystickRight.getNormalizedX(), joystickRight.getNormalizedY());
                }
            });
        } else {
            // Execute car movements for a buttons events
            view.findViewById(R.id.upButton).setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        // Pressed
                        remoteWIFICar.executeAction("forward", remoteWIFICarIP);
                        v.setBackgroundResource(R.drawable.ic_circle);
                        forwardActived = true;
                    } else if (event.getAction() == MotionEvent.ACTION_UP) {
                        // Released
                        v.setBackgroundResource(0);
                        remoteWIFICar.executeAction("stop", remoteWIFICarIP);
                        forwardActived = false;
                    }
                    return true;
                }
            });

            view.findViewById(R.id.downButton).setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        // Pressed
                        remoteWIFICar.executeAction("backward", remoteWIFICarIP);
                        v.setBackgroundResource(R.drawable.ic_circle);
                    } else if (event.getAction() == MotionEvent.ACTION_UP) {
                        // Released
                        v.setBackgroundResource(0);
                        remoteWIFICar.executeAction("stop", remoteWIFICarIP);
                    }
                    return true;
                }
            });

            view.findViewById(R.id.leftButton).setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        // Pressed
                        remoteWIFICar.executeAction("turnleft", remoteWIFICarIP);
                        v.setBackgroundResource(R.drawable.ic_circle);
                    } else if (event.getAction() == MotionEvent.ACTION_UP) {
                        // Released
                        if (forwardActived) {
                            v.setBackgroundResource(0);
                            remoteWIFICar.executeAction("forward", remoteWIFICarIP);
                        } else {
                            v.setBackgroundResource(0);
                            remoteWIFICar.executeAction("stop", remoteWIFICarIP);
                        }
                    }
                    return true;
                }
            });

            view.findViewById(R.id.rightButton).setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        // Pressed
                        remoteWIFICar.executeAction("turnright", remoteWIFICarIP);
                        v.setBackgroundResource(R.drawable.ic_circle);
                    } else if (event.getAction() == MotionEvent.ACTION_UP) {
                        // Released
                        if (forwardActived) {
                            v.setBackgroundResource(0);
                            remoteWIFICar.executeAction("forward", remoteWIFICarIP);
                        } else {
                            v.setBackgroundResource(0);
                            remoteWIFICar.executeAction("stop", remoteWIFICarIP);
                        }
                    }
                    return true;
                }
            });
        }

        view.findViewById(R.id.claxon).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    // Pressed
                    v.setBackgroundResource(R.drawable.ic_megaphone_pressed);
                    remoteWIFICar.executeAction("claxonon", remoteWIFICarIP);
                } else if (event.getAction() == MotionEvent.ACTION_UP) {
                    // Released
                    v.setBackgroundResource(R.drawable.ic_megaphone_opacity);
                    remoteWIFICar.executeAction("claxonoff", remoteWIFICarIP);
                }
                return true;
            }
        });

        view.findViewById(R.id.light_cam).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (ligthOn) {
                    remoteWIFICar.executeAction("lightoff", remoteWIFICarIP);
                    view.setBackgroundResource(0);
                    ligthOn = false;
                } else {
                    remoteWIFICar.executeAction("lighton", remoteWIFICarIP);
                    view.setBackgroundResource(R.drawable.ic_light_on);
                    ligthOn = true;
                }
            }
        });

        view.findViewById(R.id.config).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    // Go to configuration fragment layout
                    NavHostFragment.findNavController(ControlCarFragment.this)
                            .navigate(R.id.action_ControlCarFragment_to_ConfigurationFragment);

                    // Stop the viewer
                    viewer.stopStream();
                } catch(Exception ex) {
                    ex.printStackTrace();
                }
            }
        });

        SingletonListener mySingletonListener = SingletonListener.getInstance();
        mySingletonListener.setListener(new SingletonListener.ChangeListener() {
            @Override
            public void onChange() {
                try {
                    // Go to configuration fragment layout
                    NavHostFragment.findNavController(ControlCarFragment.this)
                            .navigate(R.id.action_ControlCarFragment_to_ConfigurationFragment);

                    // Stop the viewer
                    viewer.stopStream();
                } catch(Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
    }
}<file_sep>/app/src/main/java/com/theretrocenter/esp32_camandroidapp/WIFIListAdapter.java
package com.theretrocenter.esp32_camandroidapp;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.HashMap;
import java.util.List;


public class WIFIListAdapter extends ArrayAdapter<HashMap<String, String>> {

    private int resource;
    private Context context;
    private List<HashMap<String, String>> wifiList;
    private EditText ssidWifi;

    public WIFIListAdapter(
            @NonNull Context context,
            @LayoutRes int resource,
            @NonNull List<HashMap<String, String>> objects,
            @NonNull EditText ssidWIFIET) {
        super(context, resource, objects);

        // Set class vars
        this.resource = resource;
        this.context = context;
        this.wifiList = objects;
        this.ssidWifi = ssidWIFIET;
    }

    @NonNull
    @Override
    public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = layoutInflater.inflate(resource, null);
        ((TextView) view.findViewById(R.id.wifiItem)).setText(wifiList.get(position).get("ssid"));

        // Parse signal string to int
        int signal = Integer.parseInt(wifiList.get(position).get("signal"));

        // Define the signal icon based on signal lost
        if (signal < -85) {
            view.findViewById(R.id.wifiSignal).setBackgroundResource(R.drawable.ic_signal_1);
        } else if (signal < -75) {
            view.findViewById(R.id.wifiSignal).setBackgroundResource(R.drawable.ic_signal_2);
        } else if (signal < -65) {
            view.findViewById(R.id.wifiSignal).setBackgroundResource(R.drawable.ic_signal_3);
        } else {
            view.findViewById(R.id.wifiSignal).setBackgroundResource(R.drawable.ic_signal_4);
        }

        // Get security string
        String security = wifiList.get(position).get("security");

        // Define the security icon
        if (security.equals("NEED-PASS")) {
            view.findViewById(R.id.wifiSecurity).setBackgroundResource(R.drawable.ic_padlock_closed);
        } else {
            view.findViewById(R.id.wifiSecurity).setBackgroundResource(R.drawable.ic_padlock_open);
        }

        // onclick WIFI list item listener
        view.findViewById(R.id.wifiItem).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Add selected SSID to edit text
                TextView textView = (TextView) view.findViewById(R.id.wifiItem);
                String text = textView.getText().toString();
                ssidWifi.setText(text);
                ssidWifi.requestFocus();
            }
        });

        return view;
    }

    public static class UserGuideFragment {
    }
}
<file_sep>/LEAME.md

# ESP32-CAM-car-android-app

Aplicación Android para vehículo a control remoto mediante WIFI.

Esta aplicación es parte del proyecto: [ESP32-CAM-car-server](https://github.com/pablotoledom/ESP32-CAM-car-server), donde podrá montar un vehículo a control remoto usando un micro-controlador ESP32-CAM.

Si ya construyó el vehículo y desea manejarlo con esta aplicación, lea completamente este archivo ya que puede serle de gran ayuda.

[Click here for english readme](https://github.com/pablotoledom/ESP32-CAM-car-android-app/blob/main/README.md)

## Comenzando 🚀

Si no conoce el vehículo a control remoto WIFI, acá un video del resultado:

[![IMAGE Video del vehículo WIFI](https://img.youtube.com/vi/02oBJucxMBU/0.jpg)](https://www.youtube.com/watch?v=02oBJucxMBU)

Algunas capturas de esta aplicación:

![alt Screenshot port](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/capture_port.png)

![alt Screenshot land](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/capture_land.png)

### Pre-requisitos 📋

- Android Estudio
- Dispositivo físico o virtual con mínimo Android Jelly Bean 4.1 (API 16), recomendado Android Oreo 8.0 (API 26)

### Instalación del software 🔧

#### 1) clonar el proyecto desde Github

Ejecuta el siguiente comando en tu consola

```console
git clone https://github.com/pablotoledom/ESP32-CAM-car-android-app.git
```

#### 2) Abrir el proyecto con el IDE de Android Studio

Luego de abrir el proyecto verificar que las dependencias se hayan descargado correctamente, en caso contrario puede volver a correr su archivo Gradle.


## Despliegue 📦

Método 1: Puede descargar el archivo APK e instalar directamente en su dispositivo, descárguelo desde la sección [releases](https://github.com/pablotoledom/ESP32-CAM-car-android-app/releases).

Método 2: Compilar el ejecutable usando el IDE de Android Studio, este método puede resultar más útil para quienes posean conocimientos en programación de aplicaciones Android o en el lenguaje Java, también podrá realizar cambios o mejoras a esta aplicación.


## Conectarse al Vehículo 🎮

Cuando enciende el vehículo y este no logra conectarse a una red wifi dentro de un plazo de 10 segundos levantará automáticamente un punto de acceso WIFI llamado "Remote WIFI Car".

![alt Connectar WIFI](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/capture_wifi.png)

Luego que se conecte a dicha red, deberá abrir la aplicación en su dispositivo.

Si luego de 10 segundos no visualiza este punto de acceso WIFI es posible que el vehículo ya se encuentre enlazado a una red WIFI.

### La aplicación:

Al abrir la aplicación lo primero que verá es una guía de usuario en español o inglés, el lenguaje cambiará dependiendo del idioma configurado en su dispositivo.


![alt Guia de usuario](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_guide.png)

Esta guía entrega una ayuda rápida para comenzar a utilizar la aplicación. Presione el botón al final de la guía para pasar a la pantalla de control del vehículo.

Lo primero que hará la aplicación es buscar el vehículo en la red, ya sea que esté conectado al WIFI de su hogar o al WIFI del vehículo la aplicación recorrerá el rango de IPs hasta encontrar la correcta.

![alt Buscando vehículo](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_search.png)

Cuando la aplicación reconoce la IP del vehículo se habilita para poder controlarlo, la vista de control es la siguiente:

![alt Pantalla de control](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot.png)

Esta pantalla permite mover el vehículo, encender la luz, tocar la bocina y cambiar algunas configuraciones.

#### Sección de configuración:

En la sección de configuración podrá escanear las redes WIFI cercanas, una vez que visualice la suya, seleccione la e ingrese la contraseña si corresponde. Luego de guardar los cambios es necesario **reiniciar el vehículo y esta aplicación**.

 ![alt Configurar WIFI](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_wifi.png)

#### Interfaz de usuario y control del vehículo:

También podrá establecer la interfaz de usuario que más le acomode, puede elegir entre botones o joystick virtual.

![alt Configurar UI](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_ui.png)

Si posee un **gamepad o teclado** conectado al dispositivo, este se configurará en forma automática, simplemente maneje el vehículo por medio de los botones o ejes análogos.

![alt Interfaz humana](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/human-controls.png)

Botones configurados, puede usar cualquiera de los siguientes:

### Gamepad:
**Avanzar**: Análogo Arriba - Botón Arriba - R1 - R2

**Retroceder**: Análogo Abajo - Botón Abajo - L1 - L2

**Girar Izquierda**: Análogo Izquierda - Botón Izquierda

**Girar Derecha**: Análogo Derecha - Botón Derecha

**Encender / Apagar Luz**: Botón X

**Tocar Bocina**: Botón A

**Ingresar a Configuración**: Botón Start

### Teclado:
**Avanzar**: Tecla Arriba

**Retroceder**: Tecla Abajo

**Girar Izquierda**: Tecla Izquierda

**Girar Derecha**: Tecla Derecha

**Encender / Apagar Luz**: Tecla Q

**Tocar Bocina**: Tecla Espacio

**Ingresar a Configuración**: Tecla W

## Autor

<NAME>


## Licencia 📄

Este proyecto está bajo la Licencia Apache, Versión 2.0 - mira el archivo [LICENCIA](https://github.com/pablotoledom/ESP32-CAM-car-android-app/blob/main/LICENCIA) para detalles.<file_sep>/README.md
# ESP32-CAM-car-android-app

Android application for remote control vehicle via WIFI.

This application is part of the project: [ESP32-CAM-car-server](https://github.com/pablotoledom/ESP32-CAM-car-server), where you can mount a remote control vehicle using an ESP32-CAM micro-controller.

If you have already built the vehicle and want to drive it with this application, please read this file completely as it can be of great help to you.

[Click aquí para leame en español](https://github.com/pablotoledom/ESP32-CAM-car-android-app/blob/main/LEAME.md)

## Starting 🚀

If you do not know the WIFI remote control vehicle, here is a video of the result:

[![IMAGE Remote WIFI car video](https://img.youtube.com/vi/02oBJucxMBU/0.jpg)](https://www.youtube.com/watch?v=02oBJucxMBU)

Some screenshots of this application:

![alt Screenshot port](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/capture_port.png)

![alt Screenshot land](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/capture_land.png)

### Pre requirements 📋

- Android Estudio
- Physical or virtual device with minimum Android Jelly Bean 4.1 (API 16), recommended Android Oreo 8.0 (API 26)

### Installing the software 🔧

#### 1) clone the project from Github

Run the following command on your console

```console
git clone https://github.com/pablotoledom/ESP32-CAM-car-android-app.git
```

#### 2) Open the project with the Android Studio IDE

After opening the project verify that the dependencies have been downloaded correctly, otherwise you can rerun your Gradle file.

## Deployment 📦

Method 1: You can download the APK file and install it directly on your device, download it from the [releases](https://github.com/pablotoledom/ESP32-CAM-car-android-app/releases) section.

Method 2: Compile the executable using the Android Studio IDE, this method may be more useful for those who have knowledge of Android application programming or the Java language, you can also make changes or improvements to this application.

## Connect to Vehicle 🎮

When you start the vehicle and it cannot connect to a Wi-Fi network within 10 seconds, it will automatically pick up a Wi-Fi access point called "Remote WIFI Car".

![alt WIFI connect](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/capture_wifi.png)

After you connect to that network, you must open the application on your device.

If after 10 seconds you do not see this WIFI access point, it is possible that the vehicle is already linked to a WIFI network.

### The application:

When you open the application, the first thing you will see is a user guide in Spanish or English, the language will change depending on the language configured on your device.


![alt User guide](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_guide.png)

This guide provides quick help to get started with the application. Press the button at the end of the guide to go to the vehicle control screen.

The first thing the application will do is search for the vehicle on the network, whether it is connected to the WIFI of your home or the WIFI of the vehicle, the application will go through the range of IPs until it finds the correct one.

![alt Searching car](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_search.png)

When the application recognizes the vehicle's IP, it is enabled to control it, the control view is as follows:

![alt Control screen](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot.png)

This screen allows you to move the vehicle, turn on the light, honk the horn, and change some settings.

#### Configuration section:

In the configuration section you can scan the nearby WIFI networks, once you see yours, select it and enter the password if applicable. After saving the changes it is necessary to **restart the vehicle and this application**.

 ![alt WIFI Config](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_wifi.png)

#### Vehicle user interface and control:

You can also set the user interface that best suits you, you can choose between buttons or virtual joystick.

![alt UI Config](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/screenshot_ui.png)

If you have a **gamepad or keyboard** connected to the device, it will be configured automatically, simply operate the vehicle using the buttons or analog axes.

![alt Human interface](https://raw.githubusercontent.com/pablotoledom/ESP32-CAM-car-android-app/main/images/human-controls.png)

Configured buttons, you can use any of the following buttons:

### Gamepad:
**Forward**: Analog Up - Button Up - R1 - R2

**Backward**: Analog Down - Down Button - L1 - L2

**Rotate Left**: Analog Left - Left Button

**Rotate Right**: Right Analog - Right Button

**Light On / Off**: X button

**Honk**: Button A

**Enter Configuration**: Start Button


### Keyboard:
**Forward**: Up Key

**Backward**: Down Key

**Rotate Left**: Left Key

**Rotate Right**: Right Key

**Light On / Off**: Q key

**Honk**: Space Key

**Enter Configuration**: W key

## Autor

<NAME>


## Licencia 📄

This project is under the Apache License, Version 2.0 - see the file [LICENSE](https://github.com/pablotoledom/ESP32-CAM-car-android-app/blob/main/LICENSE) for details.