Ovládanie ESP32 mobilnou apkou III. – vývoj Android aplikácie

Námetom miniseriálu je ovládanie zariadenia s mikrokontrolérom ESP32 pomocou mobilnej aplikácie, ktorá komunikuje s ESP 32 cez bluetooth. V predchádzajúcich dvoch častiach sme riešili jednoduché ovládanie dvoch LED. Ako dočasné riešenie na zadávanie povelov z mobilnej aplikácie do zariadenia hotovú aplikáciu Serial Bluetooth Terminal, ktorú môžete použiť aj na ladenie v úvodnej fáze vývoja aplikácie pre ESP32. Samozrejme skôr, či neskôr je potrebné vytvoriť špecializovanú aplikáciu šitú na mieru na ovládanie príslušného zariadenia.

Video s príkladom, kde je ukázaný postup vytvorenia:

Z technického pohľadu má Android aplikácia na ovládanie zariadenia s ESP32 tri úlohy:

  • Ponúknuť používateľovi zoznam párovaných Bluetooth zariadení
  • Pripojiť sa k vybranému zariadeniu
  • Komunikovať so zariadením, posielať povely a parametre a prijímať údaje zo zariadenia

Tak ako sme krok za krokom ukázali vývoj jednoduchej aplikácie pre ESP32 na ovládanie niekoľkých LED diód, ukážeme aj kompletný postup Android aplikácie na komunikáciu s ESP 32, tak aby ste mohli aplikáciu vytvoriť a spustiť. V pokračovaní vysvetlíme fungovanie kódu.

K vývojárskemu počítaču môžete mať súčasne pripojený Android smartfón aj zariadenie s ESP32 a mať spustené vývojové prostredia Android Studio a Arduino IDE

Vo vývojom prostredí Android Studio vytvorte aplikáciu a z ponuky typov aktivít vyberte Empty Activity.

V príklade budeme používať programovací jazyk Java. Aplikáciu sme pomenovali BluetoothTest3.

Pre jednoduchosť tento úvodný príklad využíva len jednosmernú komunikáciu z aplikácie do zariadenia s ESP32. Neskôr ho rozšírime, aby aplikácia mohla prijímať údaje zo zariadenia.

Aplikácia bude využívať dve aktivity. Aktivita je určená k tomu, aby zobrazovala používateľské rozhranie a zachytávala interakcie používateľa cez toto rozhranie. Cez aktivitu sa implementuje viac, alebo menej komplexná čiastková úloha, ktorú má používateľ realizovať. Po spustení aplikácie sa zobrazí prvá aktivita, ktorá umožní vybrať zo zoznamu spárovaných zariadení zariadenie, s ktorým bude aplikácia komunikovať. V našom prípade to bude zariadenie ovládané mikrokontrolérom ESP32.

Po výbere zariadenia sa zobrazí druhá aktivita, ktorá obsahuje používateľské rozhranie. Aktivita je definovaná v dvoch súboroch. V súbore s príponou .xml je definované používateľské rozhranie a v súbore s príponou .java programový kód.

Začneme definíciou používateľského rozhrania aktivity na výber spárovaného zariadenia.

Je to hlavná aktivita, ktorej súbory boli vytvorené zároveň s projektom. Treba ich upraviť takto

Kompletný súbor activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:textStyle="bold"
        android:text="Párované zariadenia" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Zobraz párované zariadenia"
        android:id="@+id/button"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true" />

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/listView"
        android:layout_centerHorizontal="true"
        android:layout_above="@+id/button"
        android:layout_below="@+id/textView" />
</RelativeLayout>
Kompletný súbor MainActivity.java
package com.example.bluetoothtest3;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.content.Intent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Set;

public class MainActivity extends AppCompatActivity
{
    Button btnParovane;
    ListView zoznamZariadeni;

    private BluetoothAdapter adapterBluetooth = null;
    private Set<BluetoothDevice> parovaneZariadenia;
    public static String EXTRA_ADDRESS = "device_address";

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //prvky UI
        btnParovane = (Button)findViewById(R.id.button);
        zoznamZariadeni = (ListView)findViewById(R.id.listView);
        //podporuje zariadenie BT?
        adapterBluetooth = BluetoothAdapter.getDefaultAdapter();
        if(adapterBluetooth == null)
        {
            Toast.makeText(getApplicationContext(), "BT nepodporované !", Toast.LENGTH_LONG).show();
            finish();
        }
        else if(!adapterBluetooth.isEnabled())
        {
            //OS výzva: aby používateľ zapol BT
            Intent turnBTon = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(turnBTon,1);
        }

        btnParovane.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v)
            {
                parovaneZariadenia();
            }
        });
    }

    private void parovaneZariadenia()
    {
        parovaneZariadenia = adapterBluetooth.getBondedDevices();
        ArrayList list = new ArrayList();

        if (parovaneZariadenia.size()>0)
        {
            for(BluetoothDevice bt : parovaneZariadenia)
                list.add(bt.getName() + "\n" + bt.getAddress()); //nazov a adresa
        }
        else
            Toast.makeText(getApplicationContext(), "Párované BT zariadnia nenájdené !", Toast.LENGTH_LONG).show();

        final ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1, list);
        zoznamZariadeni.setAdapter(adapter);
        zoznamZariadeni.setOnItemClickListener(klikNaPolozku); //obsluha kliknutia na polozku
    }

    private AdapterView.OnItemClickListener klikNaPolozku = new AdapterView.OnItemClickListener()
    {
        public void onItemClick (AdapterView<?> av, View v, int arg2, long arg3)
        {
            //MAC addresa zaridenia je posledných 17 znakov
            String info = ((TextView) v).getText().toString();
            String address = info.substring(info.length() - 17);
          // intent na štart aktivity Ovladanie
            Intent i = new Intent(MainActivity.this, Ovladanie.class);
            //prepnutie na aktivitu Ovladanie, odovzdá sa adresa BT zariadenia
            i.putExtra(EXTRA_ADDRESS, address);
            startActivity(i);
        }
    };
}

Aktivitu Ovládanie vytvoríte v AndroidStudiu cez menu File – New – Activity – Empty Activity. Následne prepíšte jej xml a Java súbory. Okrem tlačidiel na ovládanie LED má táto aktivita aj posuvný ovládací prvok na ovládanie periférií kde sa nastavujú parametre, napríklad na nastavenie intenzity svietenia LED, otáčok motora a podobne.

Kompletný súbor activity_ovladanie.xml

</pre>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;RelativeLayout
    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"
    tools:context=".Ovladanie"&gt;

    &lt;TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Ovládanie zariadenia s ESP32"
        android:textStyle="bold"
        android:id="@+id/textView2"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"/&gt;

    &lt;TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button4"
        android:layout_alignParentStart="true"
        android:layout_marginTop="42dp"
        android:text="Motor"
        android:textAppearance="?android:attr/textAppearanceLarge" /&gt;

    &lt;Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView2"
        android:layout_alignEnd="@+id/seekBar"
        android:layout_alignParentStart="true"
        android:layout_marginTop="30dp"
        android:backgroundTint="#F44336"
        android:onClick="prepniCervenuLED"
        android:text="Červená" /&gt;

    &lt;Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button2"
        android:layout_alignEnd="@+id/button2"
        android:layout_alignParentStart="true"
        android:layout_marginTop="15dp"
        android:backgroundTint="#2196F3"
        android:onClick="prepniModruLED"
        android:text="Modrá" /&gt;

    &lt;Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button3"
        android:layout_alignEnd="@+id/button3"
        android:layout_alignParentStart="true"
        android:layout_marginTop="15dp"
        android:backgroundTint="#4CAF50"
        android:onClick="prepniZelenuLED"
        android:text="Zelená" /&gt;

    &lt;SeekBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/seekBar"
        android:max="255"
        android:progress="1"
        android:indeterminate="false"
        android:layout_below="@+id/textView3"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"/&gt;

    &lt;TextView
        android:id="@+id/lumn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/seekBar"
        android:layout_alignTop="@+id/textView3"
        android:layout_alignParentEnd="true"
        android:layout_marginStart="6dp"
        android:layout_toEndOf="@+id/textView3" /&gt;
&lt;/RelativeLayout&gt;</pre>
<pre>

Kompletný súbor Ovladanie.java

package com.example.bluetoothtest3;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.AsyncTask;
import java.io.IOException;
import java.util.UUID;

public class Ovladanie extends AppCompatActivity {
    SeekBar brightness;
    TextView tvMotor;
    String adresaBtZariadenia = null;
    private ProgressDialog priebehPripajania;
    BluetoothAdapter adapterBluetooth = null;
    BluetoothSocket btSocket = null;
    private boolean pripojenieBT = false;
    static final UUID myUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ovladanie);
        Intent newint = getIntent();
        adresaBtZariadenia = newint.getStringExtra(MainActivity.EXTRA_ADDRESS); //adresa BT zariadenia

        brightness = (SeekBar)findViewById(R.id.seekBar);
        tvMotor = (TextView)findViewById(R.id.lumn);

        new PripojenieBT().execute(); //trieda na pripájanie

        brightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser==true)
                {
                    tvMotor.setText(String.valueOf(progress));
                    try { btSocket.getOutputStream().write(String.valueOf(progress).getBytes()); }
                    catch (IOException e) { }
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }

    public void prepniLED(String s)
    {
        if (btSocket!=null)
        {
            try { btSocket.getOutputStream().write(s.getBytes()); }
            catch (IOException e) { oznamToast("Error"); }
        }
    }

    public void prepniCervenuLED(View v) { prepniLED("C"); }

    public void prepniModruLED(View v) { prepniLED("M"); }

    public void prepniZelenuLED(View v) { prepniLED("Z"); }

    private void oznamToast(String s)
    {
        Toast.makeText(getApplicationContext(),s,Toast.LENGTH_LONG).show();
    }

    private class PripojenieBT extends AsyncTask&amp;lt;Void, Void, Void&amp;gt;  //thread
    {
        private boolean jePripojene = true;

        @Override
        protected void onPreExecute()  //progres dialog počas pripájania na pozadí
        {
            priebehPripajania = ProgressDialog.show(Ovladanie.this, "Pripájanie...", "Čakajte!!!");
        }

        @Override
        protected Void doInBackground(Void... devices)
        {
            try
            {
                if (btSocket == null || !pripojenieBT)
                {
                    adapterBluetooth = BluetoothAdapter.getDefaultAdapter();
                    //je zariadenie s touto adresou k dispozícii
                    BluetoothDevice btZar = adapterBluetooth.getRemoteDevice(adresaBtZariadenia);
                    //vytvorenie RFCOMM (SPP) pripojenia
                    btSocket = btZar.createInsecureRfcommSocketToServiceRecord(myUUID);
                    BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
                    btSocket.connect();
                }
            }
            catch (IOException e) { jePripojene = false; }
            return null;
        }
        @Override
        protected void onPostExecute(Void result) //kontrola po pripájaní
        {
            super.onPostExecute(result);

            if (!jePripojene)
            {
                oznamToast("Chyba BT pripojenia.");
                finish();
            }
            else
            {
                oznamToast("Pripojené.");
                pripojenieBT = true;
            }
            priebehPripajania.dismiss();
        }
    }
}

Aby aplikácia mohla využívať bluetooth komunikáciu, je potrebné túto funkcionalitu povoliť. Do manifestu aplikácie AndroidManifest.xml, ktorý je v zložke manifests je potrebné pridať oprávnenie pre bluetooth (pridajte podčiarknuté riadky).

&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.bluetoothtest3"&amp;gt;

    &amp;lt;uses-permission android:name = "android.permission.BLUETOOTH_ADMIN"/&amp;gt;
    &amp;lt;uses-permission android:name = "android.permission.BLUETOOTH"/&amp;gt;

    &amp;lt;application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BluetoothTest3"&amp;gt;
        &amp;lt;activity
            android:name=".Ovladanie"
            android:exported="true" /&amp;gt;
        &amp;lt;activity
            android:name=".MainActivity"
            android:exported="true"&amp;gt;
            &amp;lt;intent-filter&amp;gt;
                &amp;lt;action android:name="android.intent.action.MAIN" /&amp;gt;
                &amp;lt;category android:name="android.intent.category.LAUNCHER" /&amp;gt;
            &amp;lt;/intent-filter&amp;gt;
        &amp;lt;/activity&amp;gt;
    &amp;lt;/application&amp;gt;

&amp;lt;/manifest&amp;gt;

Teraz môžete aplikáciu spustiť a vyskúšať.

Ako úplne prvý krok k tomu aby ste mohli vytvoriť a spustiť aplikáciu na vašom zariadení, bez ohľadu na to, či sa jedná o smartfón, alebo tablet musíte aplikovať tento postup.  V aplikácii Nastavenie aktivujte položku Informácie o zariadení a následne vyhľadajte položku Číslo zostavy. V niektorých nadstavbách Androidu je táto položka vnorená na obrazovke Informácie o softvéri. Ďalší postup sa vám bude zdať značne prvoaprílový, ale ubezpečujeme vás, že nie je.  Začnite na položku Číslo zostavy rýchlo ťukať. Musíte kliknúť až 7x. Ak napríklad po šiestom ťuknutí prestanete, Android vás povzbudí, že sa jedná o seriózny postup, aby sa z vás stal vývojár a zobrazí vám oznam „Teraz ste už len jeden krok od toho aby z vás bol vývojár“, ktorý vám avizuje koľkokrát je potrebné ešte ťuknúť. Po poslednom ťuknutí sa zobrazí oznam Vývojársky režim bol zapnutý a v nastavení pribudne položka Možnosti pre vývojárov.

Vývojársky režim totiž umožňuje priame zavedenie aplikácie z vývojárskeho PC do Android zariadenia cez USB a to môže byť v prípade aplikácie z neznámeho, až podozrivého zdroja hodne riskantné. V prípade vývoja vlastnej aplikácie nič neriskujete, viete presne akú aplikáciu ste vytvorili a čo bude robiť.

V budúcom pokračovaní vysvetlíme kľúčové časti kódu.


Článek byl převzat z webu Nextech se souhlasem autora.