Interfacing hc-05 bluetooth module with Android Application is one of the very basic task. Whenever we need to communicate with our Arduino based devices or any other embedded system project where we need to control devices with bluetooth. Bluetooth modules are one of the very important and basic wireless communication protocol which we can easily implement due to the HC-05 module. There are also other bluetooth modules available like HC-06 any other HC series bluetooth module variations.
Learning to interface the Bluetooth module in your Android Kotlin based application will allow you to make native applications for your next automation projects. Either you are planning to collect sensors data in your Android application or controlling appliances through your android application over bluetooth this will help you.
In this tutorial we are going to explain how to communicate with your hc-05 bluetooth module or any other similar bluetooth module through android application. For the demonstration purpose we are using the Kotlin code but the similar approach could be implemented in Java language as well. We will explain how to take permissions for bluetooth in your android application. How to send data over bluetooth in your android application. Last but not the least, one of the most complex looking thing which is to receive data from bluetooth module in your android application. So let’s dive in.
Step1: Create a New Application in Android Studio
First of all you need to create a new project in your android studio. We are going to implement the basic lagacy xml layout style activity and previous gradle build system. So look into the image below and carefully create a new project in your android studio for the bluetooth communication. Once you click on create new project it will take you to the Activity selection. You should Select the Blank activity but no the one which have the jetpack compose icon in it. In this application we are not demonstrating the jetpack compose. Rather we are focusing on the XML based layout.
Step2: Select Groovy DSL(gradle.builde)
For the build system you should select the Groovy DSL (gradle.build) system. You can choose the previous kotlin based default system but just to follow along select this one.
Step3: Add Permissions in AndroidMenifest.xml file
Now you need to go to your android menifest file and copy the following permissions for your bluetooth. Which you also have to ask permissions for in the next step.
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Code language: HTML, XML (xml)
Step4: Create a new file PermissionManager
Because in higher android versions we have to ask the user consent or the permissions required on the runtime. Also the user have control over to allow or disallow the permissions. We need to ask for permissions. There are many ways to do this. Also there are easypermission library for this purpose. But I preffer this custom solution making own permission manager class. This solution is not efficient or complete but it do the task. I test this application in my Techno 10 Pro mobile running on Android 13. So let’s create a new Kotlin class, name it `PermissionManager` and copy this code which is taken from this GitHub repository.
package com.example.myapplication2
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
class PermissionManager(private var context: Context) {
private val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CALL_PHONE,
Manifest.permission.RECORD_AUDIO,
)
} else {
TODO("VERSION.SDK_INT < S")
}
private val PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED
fun requestPermissions() {
for (permission in permissions) {
if(ActivityCompat.checkSelfPermission(context, permission) != PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(context as Activity, permissions, 5)
}
else {
Log.d("PERMISSIONS", "Permission $permission Granted")
}
}
}
}
Code language: HTML, XML (xml)
Step 4: Ask Permissions
Now that you have your permission manager class you can ask user for permissions but first let’s create the instance of this permission manager class in your main activity. You can simple do this by writing the following code.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
PermissionManager(this).requestPermissions()
}
Code language: JavaScript (javascript)
Also make sure to suppress the missing permission warnings which will help you a lot for complex version check warnings which we are not implementing here for the sake of simplicity. Here is how you can do this
@SuppressLint("MissingPermission")
class MainActivity : AppCompatActivity() {
}
Code language: CSS (css)
Create the RF Socket
We need to create a socket from where we can communicate to the Bluetooth device. For this purpose let’s create a separate class or you can implement it inside the same activity for the sake of simplicity. Here is the example of doing this.
package com.example.myapplication2
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.util.Log
import java.io.IOException
import java.util.UUID
@SuppressLint("MissingPermission")
class BluetoothHandler(
private val bluetoothAdapter: BluetoothAdapter,
private val macAddress: String
) : Thread() {
fun createSocket(device: BluetoothDevice): BluetoothSocket {
val uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
return device.createRfcommSocketToServiceRecord(uuid)
}
override fun run() {
var bluetoothSocket: BluetoothSocket? = null
if(bluetoothSocket == null || !bluetoothSocket.isConnected) {
val device = bluetoothAdapter.getRemoteDevice(macAddress)
try {
bluetoothSocket = createSocket(device)
}catch (e: IOException) {
Log.d("Connect Error", "Error Creating Socket!")
}
if(bluetoothSocket != null && bluetoothSocket.isConnected) {
Log.d("Connecting", "Connecting to ${device.name}")
} else {
bluetoothSocket?.close()
}
}
}
}
Get the Input and Output Streams
For the transmission purpose and receiving data, you need to get the outputstream and input stream from your socket. for this you can create a `lateinit` variable for and can instantiate this later from your Bluetooth adapter. Here is how you can do this.
private lateinit var outputStream: OutputStream
private lateinit var inputStream: InputStream
Code language: PHP (php)
Once you connected to your hc-05 module you can simply check if the device is connected you can get the streams like this.
// Set up InputStream after BluetoothSocket is connected
if (bluetoothSocket.isConnected) {
inputStream = bluetoothSocket.inputStream
outputStream = bluetoothSocket.outputStream
}
Code language: JavaScript (javascript)
Send Data to HC-05 Bluetooth Module
Now that we have the input and output streams we can utlize it for send data and receiving data as well. First of all we are simply demonstrating how to transmit data over bluetooth because that is the simplest part and will help you to grab the basics of handling streams. Here is a function which we created for simple data transmission over bluetooth rf socket stream.
private fun sendToHC05(valToSend: String) {
try {
// Check if the BluetoothSocket is connected
if (bluetoothSocket.isConnected) {
// Convert the string to bytes and send
outputStream.write(valToSend.toByteArray())
outputStream.flush()
} else {
// Handle the case where the BluetoothSocket is not connected
// Maybe show an error message or try to reconnect
Toast.makeText(baseContext,"Not Connected to any device",Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Code language: PHP (php)
Here you can see we simple used the outputstream.write()
function to write our desired string by converting it to ByteArray with the help of extension function in the String Class in Kotlin. The reason for converting it to ByteArray is because this is the datatype which the stream accept and return in reading case.
Receive data from Bluetooth
Now that we are done with the data transmission we are ready to receive the data. The data receiving is an ongoing process where we have to wait for the data. This is a polling method which we cannot implement on main UI thread. So we need to create a seperate thread for this purpose just the same way we do for creating a connection with the HC-05 device. Here is how we can implement the data receiving thread in Kotlin.
// Set up InputStream after BluetoothSocket is connected
if (bluetoothSocket.isConnected) {
inputStream = bluetoothSocket.inputStream
// Start a thread to continuously read data from InputStream
Thread {
val buffer = ByteArray(1024)
var bytes: Int
while (true) {
try {
// Read from the InputStream
bytes = inputStream.read(buffer)
// Convert the bytes to a string (assuming it's text data)
var receivedData = String(buffer, 0, bytes)
receivedData = receivedData.trim()
// Handle received data here, e.g., update UI or perform actions
runOnUiThread {
val lastValue = getLastValue(receivedData)
val intValue: Float =
(lastValue.toFloatOrNull() ?: 0.0).toFloat() // Convert string to integer, default to 0 if conversion fails
resultTextView.text = "Smoke: $lastValue ppm"
val thresholdValue:Float =
(edtThresholdValue.text.toString().toFloatOrNull() ?: 3.0).toFloat()
if (intValue > thresholdValue ) {
val phoneNumber = edtNumberToCall.text.toString()
val callIntent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$phoneNumber"))
// Check if the CALL_PHONE permission is granted before making the call
if (checkSelfPermission(android.Manifest.permission.CALL_PHONE) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
startActivity(callIntent)
} else {
// Request the CALL_PHONE permission
requestPermissions(arrayOf(android.Manifest.permission.CALL_PHONE), 1)
}
}
// Toast.makeText(baseContext, "Get:$receivedData",Toast.LENGTH_SHORT).show()
}
} catch (e: IOException) {
// Handle exceptions when reading from InputStream
e.printStackTrace()
break
}
}
}.start()
} else {
// Handle the case where BluetoothSocket is not connected
// ...
Toast.makeText(baseContext,"Input Stream not connected",Toast.LENGTH_SHORT).show()
}
Code language: JavaScript (javascript)