AndroidX ←→Bluetooth, Light Up a LED Part 1: Bonding

Homan Huang
13 min readDec 26, 2020

Bluetooth SSP is one kind of Bluetooth Profile. Unlike a headset of other Bluetooth devices, you treat it like a remote USB. It’s popular because you can find this profile on the HC05/HC06 module. We can connect these modules to hardware like Arduino, Raspberry Pi. In this story, I’ll install an HC05 module to control a LED light. It’s a popular experiment online, but I want to do it myself. 🤩: It’s a DIY fun in COVID Period!

— === Menu === —

👷🏼 1. Hardware
🐋 2
. VS Code — C programming
⌨ 3
. C Programming:LED flash
🏢 4
. Android Studio: MVVM and Gradle
🔌 5
. Android Studio: Common files
🎥 6
. Android Studio:User Interface
💉7
. Android Studio:Dagger-Hilt
📡 8.
Android Studio:LifecycleObserver← Broadcast Receivers
🏄 9
. Android Studio:MainActivity &. ViewModel
🔬10
. Android Studio:Test Step 1–2–3

👷🏼 1. Hardware

← ← ← Menu

😆 : Actually, nothing is simple. It took me hours to understand the ATT command and Baudrate requirement about HC05. There is no manual attached to the module when I brought them on eBay: HM10, HC05 & HC06. Yeah, I bought more than one from different sellers to make sure one of them was not fake. Some of them are not working.

🍱 Here is the hardware list:

  • 1 x Arduino UNO
  • 1 x HC05 Bluetooth module
  • 1 x Mini jumperless board
  • 3 x Resistors: 1K, 2K, 450–500Ω
  • 1 x 3V-20MA LED
  • Jumper wires: Female-Male and Male-Male.

Assembly

🎯The red# connects to Arduino PWM digital input, except the 5V on the analog side. The green> connects to the HC05. The jumperless board has columns pass circuit, so its rows have no connection.

🚥 Arduino Uno board, PWM side:

🐋2. VS Code — C programming

← ← ← Menu

To program on Arduino, I prefer VS Code+PlatformIO:

You need to install:

Create a new project:

Edit src/main.cpp:

#include <Arduino.h>
#include <SoftwareSerial.h>
// Uno ports to HC05
#define stPin 7
#define rxPin 2 // => HC05: rx
#define txPin 3 // => HC05: tx
// GND pin
// 5V pin
#define enPin 8 //EN pin
// Bluetooth
SoftwareSerial BTserial(rxPin, txPin);
char reading = ' ';
boolean BTconnected = false;
void setup() {
// Open Bluetooth
pinMode(enPin, OUTPUT);
digitalWrite(enPin, HIGH);

delay(1000);

// Wait for Bluetooth
while (!BTconnected)
{
if (digitalRead(enPin) == HIGH) { BTconnected = true; };
}

// Uno COM at 9600
Serial.begin(9600);
Serial.println("Arduino to Host is ready");

// Bluetooth setup at 38400
BTserial.begin(38400);
Serial.println("BTserial started at 38400");
}
void loop() {
// Bluetooth to monitor
if (BTserial.available())
{
reading = BTserial.read();
Serial.write( reading );
}

// Uno to Bluetooth
if (Serial.available())
{
reading = Serial.read();
BTserial.write( reading );
}
}

😁: BTSerial represents HC05. Serial represents Uno. Hc05 is special. You only can edit the chip at Baud Rate 38400 and communicate with 9600.

platformio.ini: monitor setting.

[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 9600
monitor_flags =
--echo

Now, you need to connect the UNO to your PC, build, upload, and read the log on the monitor.

> Executing task: C:\Users\Homan\.platformio\penv\Scripts\platformio.exe device monitor <
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at http://bit.ly/pio-monitor-filters
--- Miniterm on COM5 9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Arduino to Host is ready
BTserial started at 38400
at
OK

Great! After working for hours, I’ll congratulate you if you can see the first readable words on the screen like me. 😂

You can modify the name of the device:

at+name=HomanBle01
OK

Which is my device name.

at+pswd=4343
OK

I change the password from 1234 to 4343.

at+addr
+ADDR:98d3:32:70b1c9
OK

I record the address for later purposes.

⌨ 3. C Programming:LED flash

← ← ← Menu

You’d better test the installation of your LED. So let’s open a new project in VS Code to input the code for the LED settings.

main.cpp:

#include <Arduino.h>
#include <SoftwareSerial.h>
// LED Switch
#define ledPin 13
// flash duration
const long interval = 1000;
void setup() {
// set led pin
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, HIGH );
Serial.begin(9600);
Serial.println("Arduino to Host is 9600 Baud");
}
void loop() {
digitalWrite(ledPin, LOW); // set the LED off
delay(interval); // wait for a second
digitalWrite(ledPin, HIGH); // set the LED on
delay(interval);
}

😉: Build and upload. What’ll happen?

This proves the setting is working fine. We can turn it off, now.

🏢 4. Android Studio: MVVM and Gradle

← ← ← Menu

🎨MVVM

Package:

MVVM has become a popular design pattern in the Android world. You can easily find the files and problems in a big project. I divide files into a few packages:

  • The View and ViewModel are in the same folder.
  • Model → Data: ✔️ “data” folder: empty, in case you need it.✔️ The “device” folder provides hardware data. ✔️The “di” folder helps me to inject the dependent variables.

🏒Gradle Project

buildscript {
ext.kotlin_version = "1.4.21"
repositories {
google()
jcenter()
mavenCentral()
maven { url "https://oss.jfrog.org/libs-snapshot" }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-alpha15'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// Hilt
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
// google service
classpath 'com.google.gms:google-services:4.3.4'
}
}

allprojects {
repositories {
google()
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

🏒Gradle Module

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}

android {
compileSdkVersion 30

defaultConfig {
applicationId "com.homan.huang.bletoled"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

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

buildFeatures {
dataBinding true
viewBinding true
}

useLibrary 'android.test.runner'
useLibrary 'android.test.base'
useLibrary 'android.test.mock'
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/AL2.0'
exclude 'META-INF/LGPL2.1'
exclude("META-INF/*.kotlin_module")
}
}

dependencies {
// STD

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
// Design
implementation 'com.google.android.material:material:1.2.1'
// Layout
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'

// Activity and Fragment
def activity_version = "1.2.0-rc01"
def fragment_version = "1.3.0-rc01"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
// Testing Fragments in Isolation
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"

// Test
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.ext:truth:1.3.0'
androidTestImplementation 'com.google.truth:truth:1.0'

// Kotlin coroutines
def coroutines_version = "1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
testImplementation 'org.hamcrest:hamcrest-library:1.3'
// Test: Kotlin coroutines
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9'
testImplementation 'org.hamcrest:hamcrest-library:1.3'

// Hilt
def hilt_version = '2.28-alpha'
def hilt_lifecycle_version = '1.0.0-alpha02'
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:$hilt_lifecycle_version"
kapt "androidx.hilt:hilt-compiler:$hilt_lifecycle_version"
// Tests
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"

// Lifecycle
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

// Test helpers for LiveData
def arch_version = "2.1.0"
testImplementation "androidx.arch.core:core-testing:$arch_version"
}

Please turn on your version control and commit your project. I’m using Git and GitHub as my backup service.

🔌5. Android Studio: Common files

← ← ← Menu

Loghelper.kt

const val TAG = "MLOG"
fun lgd(s:String) = Log.d(TAG, s)
fun lgi(s:String) = Log.i(TAG, s)
fun lge(s:String) = Log.e(TAG, s)

🙄: Please add a filter to make the log work.

UiHelper.kt

// Toast: len: 0-short, 1-long
fun msg(context: Context, s: String, len: Int) =
if (len > 0) Toast.makeText(context, s, LENGTH_LONG).show()
else Toast.makeText(context, s, LENGTH_SHORT).show()

Just a Toast 🍞.(cruchy, cruchy, cruchy)

ConfigHelper.kt

This file depends on your request.

object ConfigHelper {
private const val address = "98d3:32:70b1c9"
private const val pass = "4343"
fun getAddress(): String = address
fun getPass(): String = pass
}
// Bluetooth
const val BLUETOOTH_ON = "ON"
const val BLUETOOTH_OFF = "OFF"
const val BLUETOOTH_READY = "Bluetooth is ready."
const val BLUETOOTH_NOT_ON = "Bye, Bluetooth is OFF!"
const val DEVICE_NAME = "LED Control"
const val SEC = 1000L

🎥6. Android Studio:User Interface

← ← ← Menu

Simple UI. You just need to follow the picture.

💉7. Android Studio:Dagger-Hilt

← ← ← Menu

🚩 Design: We need to know what to be injected.

  • 📡 Broadcast Receivers: I insert all of the receivers into LifecycleObserver. And it needs to be injected in MainActivity.
  • BluetoothHelper👋: It connects ViewModel and Bluetooth, so it needs to be injected into MainViewModel.
  • 💱 LiveData: The system observes DeviceStatus to make actions in receivers and ViewModel.
  • Device Address 📍 : The receivers and BluetoothHelper need it to communicate with the hardware.

🚩 Application

@HiltAndroidApp
class BleLedApp : Application()

The Hilt makes it easy on the application, one line only.

🚩 AndroidManifest

Application Name + Permissions

🚩 Annotation

MainActivity.kt:

@AndroidEntryPoint 
class MainActivity : AppCompatActivity() {

MainViewModel.kt:

class MainViewModel @ViewModelInject constructor(
val bleHelper: BluetoothHelper,
val deviceStatus: MutableLiveData<DeviceStatus>
) : ViewModel() {

😄: Easy job!

🚩 Module

😍: It’s fun to type these functions. You can check whether the parameters are correct with a clickable hammer sign. 🥳 It’s party time. let’s play with the hammers: click, jump, click, jump…

🤓: Bug example,

📡 8. Android Studio:LifecycleObserver← Broadcast Receivers

← ← ← Menu

BleHc05Observer.kt

🍏 Header:

class BleHc05Observer(
private val context: Context,
devAddr: String,
devStatus: MutableLiveData<DeviceStatus>
) : LifecycleObserver {
val mRegex = "[^A-Za-z0-9 ]".toRegex()
private var hc05Address = ""

init {
hc05Address = mRegex.replace(devAddr, "").toUpperCase()
}

🍎 bleReceiver: For bonding

//region Bluetooth Discovery
private val bleFilter = IntentFilter()
private var devfound = false
private lateinit var mBleDevice: BluetoothDevice

private val bleReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {

when (intent.action) {
ACTION_DISCOVERY_STARTED -> {
lgd(tag + "Searching for device...")
}
ACTION_DISCOVERY_FINISHED -> {
if (devfound) {
devStatus.postValue(DISCOVERED)

lgd("$tag Device Found!")
mBleDevice.createBond()

lgd("$tag Device Bond state:" +
" ${mBleDevice.bondState}")

} else {
lgd("$tag Device NOT Found!")
devStatus.postValue(NOT_FOUND)
}

lgd(tag + "Finish search!!! Found? $devfound")
}
ACTION_FOUND -> {
//bluetooth device found
val device =
intent.getParcelableExtra<Parcelable>(
EXTRA_DEVICE
) as BluetoothDevice?

lgd(tag + "Found: ${device!!.name} at" +
" ${device.address}")
val mAddress = mRegex.replace(
device.address, "").toUpperCase()

// show address
lgd("$tag===> H05 -- $hc05Address ::" +
" Adv -- $mAddress")
if (mAddress == hc05Address) {
lgd("$tag!!! IcU :-) Found device !!!")
devfound = true
mBleDevice = device
getDefaultAdapter().cancelDiscovery()
}
}
ACTION_BOND_STATE_CHANGED -> {
lgd(tag + "Checking Bonded State...")
val device: BluetoothDevice =
intent.getParcelableExtra<Parcelable>(
EXTRA_DEVICE
) as BluetoothDevice

when (device.bondState) {
BOND_BONDED -> {
lgd("$tag Bonded to device")
devStatus.postValue(BONDED)
}
BOND_BONDING -> {
lgd("$tag Bonding to device")
devStatus.postValue(BONDING)
}
BOND_NONE -> {
lge("$tag Nothing has bonded.")
devStatus.postValue(FAIL)
}
}
}
}
}
}
//endregion

🍒 connectionReceiver: For connection

//region Bluetooth Connection
private val connectionFilter = IntentFilter()
private val connectionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_ACL_CONNECTED -> {
lgd(tag + "ACL: Connected!")
}
ACTION_ACL_DISCONNECTED -> {
lge(tag + "ACL: Disconnected.")
}
}
}
}
//endregion

🍔 Lifecycle events:

//region Lifecycle Event
@OnLifecycleEvent(ON_CREATE)
fun onCreate() {
lgd("$tag Register broadcast receivers...")
bleFilter.addAction(ACTION_FOUND)
bleFilter.addAction(ACTION_DISCOVERY_STARTED)
bleFilter.addAction(ACTION_DISCOVERY_FINISHED)
bleFilter.addAction(ACTION_BOND_STATE_CHANGED)
context.registerReceiver(bleReceiver, bleFilter)

connectionFilter.addAction(ACTION_ACL_CONNECTED)
connectionFilter.addAction(ACTION_ACL_DISCONNECTED)
context.registerReceiver(connectionReceiver, connectionFilter)
}

@OnLifecycleEvent(ON_RESUME)
fun onResume() {
try {
lgd("$tag ==> Registered bleReceiver.")
context.registerReceiver(bleReceiver, bleFilter)
} catch (e: IllegalArgumentException) {
lge("$tag bleReceiver has registered. Err: ${e.message}")
}

try {
lgd("$tag ==> Registered connectionReceiver.")
context.registerReceiver(connectionReceiver,
connectionFilter)
} catch (e: IllegalArgumentException) {
lge("$tag connectionReceiver has registered. Err: ${e.message}")
}
}

@OnLifecycleEvent(ON_PAUSE)
fun onPause() {
try {
lgd("$tag ==> unRegistered bleReceiver.")
context.unregisterReceiver(bleReceiver)
} catch (e: java.lang.IllegalArgumentException) {
lge("$tag bleReceiver has unregistered. Err: ${e.message}")
}
try {
lgd("$tag ==> unRegistered connectionReceiver.")
context.unregisterReceiver(connectionReceiver)
} catch (e: IllegalArgumentException) {
lge("$tag connectionReceiver has unregistered. Err: ${e.message}")
}
}

@OnLifecycleEvent(ON_DESTROY)
fun onDestroy() {
try {
lgd("$tag ==> unRegistered bleReceiver.")
context.unregisterReceiver(bleReceiver)
} catch (e: java.lang.IllegalArgumentException) {
lge("$tag bleReceiver has unregistered. Err: ${e.message}")
}
try {
lgd("$tag ==> unRegistered connectionReceiver.")
context.unregisterReceiver(connectionReceiver)
} catch (e: IllegalArgumentException) {
lge("$tag connectionReceiver has unregistered. Err: ${e.message}")
}
}
//endregion

companion object {
private const val tag = "BleObserver: "
}

🏄9. Android Studio:MainActivity &. ViewModel

← ← ← Menu

💉 MainActivity: Inject Boardcast Receivers — BleHc05Observer

private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NOTIFICATION_POLICY
)
private const val BLUETOOTH_REQUEST_CODE = 9191@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var bleObserver: BleHc05Observer

Vars:

//region vars:
private val mainVM: MainViewModel by viewModels()
private val infoTV: TextView by
lazy { findViewById(R.id.infoTV) }
private val discoveryTV: TextView by
lazy { findViewById(R.id.discoverTV) }
private val progressBar: ProgressBar by
lazy { findViewById(R.id.progressBar) }
private val onBT: Button by
lazy { findViewById(R.id.onBT) }
private val offBT: Button by
lazy { findViewById(R.id.offBT) }
//endregion

📑 MainActivity → Permission Contract

private val reqMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
permissions.entries.forEach {
lgd("MainAct-Permission: ${it.key} = ${it.value}")
}
mainVM.checkSwitch()
}

Call:

override fun onCreate(savedInstanceState: Bundle?) {
...
// check permission
reqMultiplePermissions.launch(REQUIRED_PERMISSIONS)

📑 MainActivity → Bluetooth Switch Contract

I add up the steps to bond a new Bluetooth device, the total is four.

  1. Turn on Bluetooth switch 🔄
  2. Check the bonded list📜. If found, go to step 4.
  3. Not in the bonded list. Discover🔍 near devices.
  4. Bond🤞 to device

The Bluetooth Switch contract is step 1.

> BluetoothSwitchContract.kt

class BluetoothSwitchContract:
ActivityResultContract<Int, String?>() {
override fun createIntent(p0: Context, p1: Int?): Intent {
return Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
}
override fun parseResult(resultCode: Int, intent: Intent?):
String? {
return if (resultCode == RESULT_OK)
BLUETOOTH_ON
else
BLUETOOTH_OFF
}
}

> > Call of checkSwitch() in MainViewModel.

fun checkSwitch() {
if (bleHelper.isSwitchOn()) {
lgd("$tag Turning Bluetooth switch ON...")
deviceStatus.postValue(SWITCHING)
} else {
lgd("$tag Bluetooth switch is ON.")
deviceStatus.postValue(PAIRING)
}
}

> > > Call BluetoothHelper for the result:

// check bluetooth switch
fun isSwitchOn(): Boolean {
return !bluetoothAdapter.isEnabled
}

> From MainViewModel to MainActivity:

Variable for BluetoothSwitchContract —

private val bluetoothContract = registerForActivityResult(
BluetoothSwitchContract()
) { result ->
val tag = "BluetoothContract:"
when (result) {
BLUETOOTH_ON -> {
msg(this, BLUETOOTH_READY, 1)
lgd("$tag Bluetooth Switch is ON. Move to Step 1.")
progressBar.visibility = View.VISIBLE
mainVM.checkBondedList()
}
BLUETOOTH_OFF -> {
lgd("$tag Terminate App.")
msg(this, BLUETOOTH_NOT_ON, 0)
finish()
}
}
}

The system observes deviceStatus in onCreate():

mainVM.deviceStatus.observe(
this,
{ status ->
when (status) {
SWITCHING -> {
val info = "Switching Bluetooth ON..."
infoTV.text = info
bluetoothContract.launch(BLUETOOTH_REQUEST_CODE)
}
PAIRING -> {
val info = "Searching Bonded List..."
infoTV.text = info
mainVM.checkBondedList()
progressBar.visibility = View.VISIBLE
}

> DeviceStatus:

enum class DeviceStatus {
PAIRING, BONDING, DISCOVERING, SWITCHING,
DISCOVERED, BONDED, CONNECTED,
FAIL, DISCONNECT, NOT_FOUND
}

The Bluetooth switch contract is launched at SWITCHING option. After the Bluetooth switch has turned on, the contract will continue to mainVM.checkBondedList() which is step 2.

> > MainViewModel:

fun checkBondedList() {
if (bleHelper.checkBondedList()) {
lgd("$tag Found. Move to Step 4, Bonding.")
deviceStatus.postValue(BONDING)
} else {
lgd("$tag Not Found. Move to Step 3, Discovering.")
deviceStatus.postValue(DISCOVERING)
}
}

The new device will jump to DISCOVERING. The recorded device will jump to BONDING.

> MainActivity

mainVM.deviceStatus.observe(
this,
{ status ->
when (status) {
...
DISCOVERING -> {
val info = "Discovering device in range..."
infoTV.text = info
mainVM.discovering()
}
BONDING -> {
val info = "Device Found in Record!\nBonding..."
infoTV.text = info
mainVM.bonding()
}

✔️: Next, I will run the app to test the steps from 1 to 3. Ctrl+K to save into version control.

🔬10. Android Studio:Test Step 1-2-3

← ← ← Menu

You don’t need step 4 to test. Let’s start:

☝️ Step 1: Turn off Bluetooth Switch of the phone

Allow”: Works. Bluetooth is ON.

“Deny”: The App is closed.

🖖🏻 Step 3: NOT_FOUND

This is the new BLE for my phone. Step 2 shall return false and move onto step 3. You’ll turn off the power of HC05 to test the NOT_FOUND.

NOT_FOUND -> {
lgd("MainAct: Device Not Found.")
progressBar.visibility = View.GONE
val info = "Device NOT Found!"
infoTV.setTextColor(Color.RED)
infoTV.text = info
msg(this, info, 1)
}

👍 Pass!

🖖🏻 Step 3: DISCOVERED

DISCOVERED -> {
val pass = "Your Pass: ${ConfigHelper.getPass()}"
discoveryTV.text = pass
discoveryTV.visibility = View.VISIBLE
}

👍 Pass!

🖖🏻 Step 3: Wrong Password

ACTION_BOND_STATE_CHANGED -> {
lgd(tag + "Checking Bonded State...")
val device: BluetoothDevice =
intent.getParcelableExtra<Parcelable>(
EXTRA_DEVICE
) as BluetoothDevice

when (device.bondState) {
BOND_BONDED -> {
lgd("$tag Bonded to device")
devStatus.postValue(BONDED)
}
BOND_BONDING -> {
lgd("$tag Bonding to device")
}
BOND_NONE -> {
lge("$tag Nothing has bonded.")
devStatus.postValue(FAIL)
}
}
}

> MainActivity:

FAIL -> {
val info = "FAIL to create connection!" +
"\nOr\nPassword is incorrect!"
infoTV.text = info
progressBar.visibility = View.GONE
}

Run:

🙀: Oh, no button. I forget to insert a button to get out of this situation.

NEW Button

> MainActivity:

private val tryAgainBT: Button by
lazy { findViewById(R.id.tryAgainBT) }
...
override fun onCreate(savedInstanceState: Bundle?) {
...
tryAgainBT.visibility = View.GONE
tryAgainBT.setOnClickListener {
tryAgainBT.visibility = View.GONE
progressBar.visibility = View.VISIBLE
mainVM.checkBondedList()
}
...
FAIL -> {
...
tryAgainBT.visibility = View.VISIBLE
}

Try again:

😻It’s working!

🖖🏻 Step 3: Correct Password

👌 Correct! It’s bonded.

🤗: I will continue on Part 2. Let’s take a break.

--

--

Homan Huang

Computer Science BS from SFSU. I studied and worked on Android system since 2017. If you are interesting in my past works, please go to my LinkedIn.