Using the UDP protocol with an ESP32 and Android Studio, Part 3

In the previous episodes of this trilogy, I showed you the general idea of using UDP (User Datagram Protocol) to send messages from an Android phone app to any device that is able to receive them, such as an ESP32. The second instalment showed you how to set up the ESP32 so that you can confirm its ability to receive data, and today it is time to fire up Android Studio and create a custom app to run your device remotely.

Android Studio is a heavyweight development environment, and when you install it and set it up, I guarantee it will intimidate you. It has so many menu options, windows, moving parts, gizmos and whatchamacallits, that at first glance it will look insurmountable. It would be very nice if block programming via Thunkable or the MIT App Inventor could provide us with UDP protocol support, but as of now, such features are not available. There are other environments such as PhoneGap / Cordova, but I couldn’t see the benefit for using them in my project. Android Studio gives it all, so I decided to bite the bullet.

Starting up Android Studio with a new project

What I did with Android Studio then was to install it, run updates as instructed, and decide to use Kotlin instead of Java. Kotlin is a slightly simplified version of Java and it is somewhat easier to understand. It is selected for any project at the project creation time, as seen here.

I won’t walk you through the Android Studio 101 phase I then took, because it is easier for you to learn the absolute basics from people who actually know what they are doing. Here’s a few good videos I used.

So, using these, I got relatively familiar with creating an app, setting up the emulator to test the app, and using two screens with one for the main program and one for the settings, before embarking on the quest for using UDP. Getting even data read from edit boxes was somewhat quirky in my mind, but that’s just me because I am from an HTML, C, Python and even Pascal background. Then again, if I can learn this, anyone can.

You will need to understand how Android apps are built using XML files for layout and Java or Kotlin files for functionality, so do watch some ofthe videos listed above. When you install my app project, you will be able to find the relevant files, MainActivity.kt, Settings.kt, activity_main.xml, and activity_settings.xml.

The only other file I had to edit is called AndroidManifest.xml, and in that one, you must insert these clauses for the system to have permission to use the Internet:

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

But these are already inserted in the project you can go and collect from GitHub.

Getting the project from GitHub

Before you can use your own phone in developer mode, ie. move APK files to it and install your own app, you need to go into Developer Mode.

I have uploaded the whole shebang of this project to my account on GitHub. GitHub is a wonderful system for delivering open source projects to users and maintaining the latest version online. I have a user account at https://github.com/HeikkiHietala and you can see many other projects besides this one.

The esp-udp-android-app project repository

The way it now works is this.

  • Go to the project repository on GitHub
  • use the Clone Project button to Download ZIP
  • unpack the project to the AndroidStudioProjects folder, usually found at c:\Users\<your-username>\AndroidStudioProjects
  • go to Android Studio
  • see the new project in the Open Project list
  • As a bonus you get both of the ESP32 C language files, ready to be edited in the Arduino IDE. They are the two INO files in the root folder of the project, so you may want to move them to your Arduino folder before editing.
The list of available projects in my Android Studio

So, you should see the project listed along whatever other projects you may have. Open it and you get to this view, or something similar in any case.

Project as opened in Android Studio

The juicy bits are well hidden in the bowels of the project. I won’t even try to claim to understand how Android Studio uses all these files, it’s just that the files to modify are located in these folders:

The files you need to edit if this setup isn’t to your liking

This setup gives you two screens in the application. They are the Main Activity and the Settings. Main Activity is the screen that collects your button pushes and constructs the string to be sent to the ESP32. Since your WLAN or shared mobile hotspot will give you a different IP, you need to use the Settings screen to edit that data. The port is set in the Arduino code, so if you don’t edit that data, it’s 1234 – all that matters is that the same port is on both the app and the ESP32.

Before you compile your app, it is possible to set the default values in the app data, but more on that in just a moment.

The layout of the project in Android Studio

In the Android operating system, all user interface elements are stored in XML format, and in XML files. All code is in Java files, or, in this case, Kotlin files, with a filename extension of .kt. All items of user interface that you have in the XML file have parts in code that correspond, and you can operate your app via this interplay. Here is the user interface as created by the XML files in this project, with the speed text and increase buttons indicated.

User interface

This is how the Increase button looks like in XML:

<Button
 android:id="@+id/btnIncrease"
 android:layout_width="160dp"
 android:layout_height="wrap_content"
 android:layout_marginStart="108dp"
 android:layout_marginTop="30dp"
 android:background="#4CAF50"
 android:onClick="increaseSpeed"
 android:text="INCREASE"
 android:textColor="#FFFFFF"
 android:textSize="30sp"
 android:textStyle="bold"
 app:layout_constraintStart_toStartOf="parent"
 app:layout_constraintTop_toBottomOf="@+id/txtSpeed" />

So, this button has an id, btnIncrease, its width is 160 dp (density independent pixel – some sort of witchcraft item), its margins etc. are given in relevant units, and when it is clicked, it calls for a button listener called increaseSpeed. XML is a very handy language for such things as layouts, because you can read it directly.

The listener, then, is in Kotlin:

btnIncrease.setOnClickListener {
mySpeed += 1
if (mySpeed > 10) mySpeed = 10
val SpeedtoShowTv = this.findViewById<TextView>(R.id.txtSpeed)
SpeedtoShowTv.text = mySpeed.toString()
myUDP = mySpeed.toString() + "," + myDirection.toString()
sendUDP(myUDP)
}

When the button is pressed, it goes to this piece of code. As this is the increase speed button, it adds one to mySpeed, and if it is going past 10 as a result, it is forced to be 10. It then updates the SpeedtoShowTv.text to show the new value in the display. And then it takes the two values, mySpeed and myDirection, makes them into string, adds a comma in between, and then goes to the function sendUDP to pass it to the ESP32.

This is just about all you need to know about the user interface, since it’s all rather self-explanatory. If you need to add or remove buttons, just go into the relevant XML file. It looks like this, if you select the Design view:

Studio in design mode

You can use the three buttons in the top right corner to select either XML view, split code/design view, or Design view. It pays to practice a little with a tutorial, before attempting to do extensive surgery on this sample project of mine.

The sendUDP function

The actual beef of this little application is the sendUDP function. I do not claim any ownership of this function, I found it on the Internet and adapted it to suit my needs. The biggest issue seemed to revolve around the data type of the message.

fun sendUDP(messageStr: String) {
// this function sends the UDP message as String
// Hack Prevent crash (sending should be done using an async task)
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
try {
//Open a port to send the package
val socket = DatagramSocket()
socket.broadcast = true
//get the message from function parameters and make it into a byte array
val sendData = messageStr.toByteArray()
//craft the packet with the parameters
val sendPacket = DatagramPacket(sendData, sendData.size, InetAddress.getByName(mySettings.RemoteHost), mySettings.RemotePort)
socket.send(sendPacket)
} catch (e: IOException) {
// Log.e(FragmentActivity.TAG, "IOException: " + e.message)
}
}

So, the message to be sent is passed to the function as an argument messageStr. At this point it is of the data type String. The Hack Prevention clause is just something that needs to be done for this to work. The structure try is a neat procedure that tries to send the message, and if it doesn’t pass properly, it will add the message to an error log at the end of the function with the catch clause.

The datagram system uses sockets, which are small interfaces into the data traffic. Therefore there is the socket, which has the methods broadcast and send. The message needs to be converted once more from string to byteArray for the sending to work, probably due to the particular type of voodoo that is happening here. If it isn’t converted, your app will crash and burn, and you will see the reason for it in the error log of Android Studio. I suggest that you don’t try to edit this function, but if you want to pass a different message, create it elsewhere and then pass it to the sendUDP as a string.

It may well be that you have a different message to pass to the UDP receiver. If that is the case, you can easily edit the structures I have here, add more or take out some of the buttons and functionalities this system has. If you look at the button listeners, it’s easy to figure out hwow they work, and you can craft your own. The global variables RemoteHost and RemotePort were best done this way, but you may want to have different values from these. The basic values I have here are related to my particular wlan setup, so these you need to edit anyway.

public class SoftOptions {
var RemoteHost: String = "192.168.10.61"
var RemotePort: Int = 1234

constructor()
init{}
}

//create a variable of this type:
val mySettings = SoftOptions()

Summing it up so far

This project has now provided you with the tools necessary to open UDP communication between any ESP32 and this application. My needs dictated that my message should be a string of the type <integer>, <integer>, but it is totally possible for you to make your own app send something completely different.

The main issues I had with creating this app were related to my inexperience in using text boxes for displaying and reading data, and converting the data between data types. I was quite prepared to drop the whole project when I was getting more error messages than I had lines of code, but tenacity saved the day.

Setting up an Android phone emulator in Android Studio is something you need to figure out using the other tutorials I listed in the beginning, and that applies to how to set up your own phone to use as the runtime environment.

The next instalment (this seems to be running into four parts even if it is a trilogy) will discuss how to build the actual system where the ESP32 governs a servo and a motor based on the strings passed to it.

Leave a Reply

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.