How to advertise AdvertiseData with BluetoothLeAdvertiser on android using BLE 2M PHY - android

I'm currently building a chat app based on Bluetooth Low Energy Advertisments. I tried to build a client following this tutorial by android, but my BluetoothLeScanner doesnt seem to retrieve any BLE 2M PHY scanResults. I was able to achieve the desired result by using the nRF Connect app, so it's probably not a hardware issue
class BluetoothAdvertiser(private var bluetoothAdapter: BluetoothAdapter) {
private val TAG: String = BluetoothAdvertiser::class.java.simpleName
private var advertiseUUID: ParcelUuid =
ParcelUuid(UUID.fromString("e889813c-5d19-49e2-8bc4-d4596b4f5250"))
private var advertiser: BluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser
private var maxDataLength: Int = 0
private lateinit var currentAdvertisingData: AdvertiseData
private lateinit var currentAdvertisingSet: AdvertisingSet
fun init(): Boolean {
Log.d(TAG, "init: ")
if (!this.checkDeviceHardware()) {
return false
}
this.initAdvertiser("123")
return true
}
private fun checkDeviceHardware(): Boolean {
if (!this.bluetoothAdapter.isLe2MPhySupported) {
Log.e(TAG, "init: 2M PHY not supported")
return false
}
if (!this.bluetoothAdapter.isLeExtendedAdvertisingSupported) {
Log.e(TAG, "init: LE Extended Advertising not supported")
return false
}
this.maxDataLength = this.bluetoothAdapter.leMaximumAdvertisingDataLength
Log.d(TAG, "init: leMaximumAdvertisingDataLength: " + this.maxDataLength)
return true
}
private fun initAdvertiser(message: String): Boolean {
val setParameters: AdvertisingSetParameters = AdvertisingSetParameters.Builder()
.setLegacyMode(false)
.setInterval(AdvertisingSetParameters.INTERVAL_HIGH)
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM)
.setPrimaryPhy(BluetoothDevice.PHY_LE_CODED)
.setSecondaryPhy(BluetoothDevice.PHY_LE_2M)
.build()
val advertiseData: AdvertiseData = AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(advertiseUUID)
.addServiceData(advertiseUUID, message.encodeToByteArray())
.build()
val periodicParameters: PeriodicAdvertisingParameters = PeriodicAdvertisingParameters.Builder()
.setInterval(800)
.build()
return try {
this.advertiser.startAdvertisingSet(
setParameters,
advertiseData,
advertiseData,
periodicParameters,
advertiseData,
this.advertisingSetCallback
)
true
} catch (e: SecurityException) {
Log.w(TAG, "initAdvertiser: ",e)
false
}
}
fun changeAdvertisingData(message: String){
try{
Log.d(TAG, "changeAdvertisingData: message: $message" )
this.currentAdvertisingSet.setAdvertisingData(AdvertiseData.Builder().addServiceData(this.advertiseUUID,message.encodeToByteArray()).build())
} catch (e: SecurityException) {
Log.w(TAG, "initAdvertiser: ",e)
}
}
private var advertisingSetCallback: AdvertisingSetCallback = object : AdvertisingSetCallback() {
override fun onAdvertisingSetStarted(
advertisingSet: AdvertisingSet,
txPower: Int,
status: Int
) {
Log.i(
TAG, "onAdvertisingSetStarted(): txPower: $txPower , status: $status "
)
currentAdvertisingSet = advertisingSet
}
override fun onAdvertisingSetStopped(advertisingSet: AdvertisingSet) {
Log.i(TAG, "onAdvertisingSetStopped():")
}
}

The code above is working as intended, but I didn't set up the BluetoothLeScanner for non-legacy advertisements, below is my now working scanner
class BluetoothScanner(private var bluetoothLeScanner: BluetoothLeScanner) : ScanCallback() {
private val TAG: String = BluetoothScanner::class.java.simpleName
private var scanning = false
private var advertiseUUID: ParcelUuid =
ParcelUuid(UUID.fromString("e889813c-5d19-49e2-8bc4-d4596b4f5250"))
private var scanSettings: ScanSettings
private var scanFilter: ScanFilter
init {
this.scanSettings = ScanSettings.Builder()
.setLegacy(false)
.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
.build()
this.scanFilter = ScanFilter.Builder()
.setServiceUuid(advertiseUUID)
.build()
}
fun startScan(): Boolean {
Log.d(TAG, "startScan: ")
if (scanning) {
this.stopScan()
}
if (!this.scanning) {
return try {
this.bluetoothLeScanner.startScan(listOf(this.scanFilter),this.scanSettings, this)
this.scanning = true
true
} catch (e: SecurityException) {
Log.w(TAG, "startScan: ", e)
false
}
}
return false
}

Related

how do i get the set notification in BLE nordic

i am trying to set notification callback in BLE nordic, where i am using the Android BLE library (Nordic Github). But i not ablet to get the notification event when i am changing the value of the characteristics.
`
class BleManagerHP1T(context: Context) : BleManager(context) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, GattService.TAG, message)
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
private var rxCharacteristic: BluetoothGattCharacteristic? = null
#SuppressLint("MissingPermission")
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(GattService.MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(GattService.MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
Log.d(TAG, "isRequiredServiceSupported: notify ${(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)}")
rxCharacteristic = service?.getCharacteristic(GattService.MyServiceProfile.RX_CHARACTERISTIC_UUID)
val obj = JSONObject()
obj.put("OPCODE","PROVISION")
rxCharacteristic?.value = obj.toString().encodeToByteArray()
val rxRead = gatt.writeCharacteristic(rxCharacteristic)
Log.d(TAG, "isRequiredServiceSupported: Read $rxRead")
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
enableNotifications(myCharacteristic).enqueue()
requestMtu(260).enqueue();
setNotificationCallback(myCharacteristic).with { _, data ->
Log.d(TAG, "initialize: TX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: TX char value $value")
}
}
requestMtu(260).enqueue();
enableNotifications(rxCharacteristic).enqueue()
setNotificationCallback(rxCharacteristic).with { _, data ->
Log.d(TAG, "initialize: RX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: RX char value $value")
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
override fun readCharacteristic(characteristic: BluetoothGattCharacteristic?): ReadRequest {
return Request.newReadRequest(characteristic)
}
}
`
gatt connection is establishing perfectly fine, using this code.
`
val bleManager = BleManagerHP1T(this#ControllerActivity)
synchronized (this) {
bleManager.connect(deviceMainList[position]).useAutoConnect(false).enqueue()
}
`
here is the gatt service file .
`
class GattService : Service() {
private val defaultScope = CoroutineScope(Dispatchers.Default)
private lateinit var bluetoothObserver: BroadcastReceiver
private var myCharacteristicChangedChannel: SendChannel<String>? = null
private val clientManagers = mutableMapOf<String, ClientManager>()
// val connect = BleManager()
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
// Setup as a foreground service
val notificationChannel = NotificationChannel(
GattService::class.java.simpleName,
resources.getString(R.string.gatt_service_name),
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationService =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationService.createNotificationChannel(notificationChannel)
val notification = NotificationCompat.Builder(this, GattService::class.java.simpleName)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(resources.getString(R.string.gatt_service_name))
.setContentText(resources.getString(R.string.gatt_service_running_notification))
.setAutoCancel(true)
startForeground(1, notification.build())
// Observe OS state changes in BLE
bluetoothObserver = object : BroadcastReceiver() {
#SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val bluetoothState = intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
-1
)
when (bluetoothState) {
BluetoothAdapter.STATE_ON -> enableBleServices()
BluetoothAdapter.STATE_OFF -> disableBleServices()
}
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.d(TAG, "Bond state changed for device ${device?.address}: ${device?.bondState}")
when (device?.bondState) {
BluetoothDevice.BOND_BONDED -> addDevice(device)
BluetoothDevice.BOND_NONE -> removeDevice(device)
}
}
}
}
}
registerReceiver(bluetoothObserver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
registerReceiver(bluetoothObserver, IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
// Startup BLE if we have it
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) enableBleServices()
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(bluetoothObserver)
disableBleServices()
}
override fun onBind(intent: Intent?): IBinder? =
when (intent?.action) {
DATA_PLANE_ACTION -> {
DataPlane()
}
else -> null
}
override fun onUnbind(intent: Intent?): Boolean =
when (intent?.action) {
DATA_PLANE_ACTION -> {
myCharacteristicChangedChannel = null
true
}
else -> false
}
/**
* A binding to be used to interact with data of the service
*/
inner class DataPlane : Binder() {
fun setMyCharacteristicChangedChannel(sendChannel: SendChannel<String>) {
myCharacteristicChangedChannel = sendChannel
}
}
#SuppressLint("MissingPermission")
private fun enableBleServices() {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) {
Log.i(TAG, "Enabling BLE services")
bluetoothManager.adapter.bondedDevices.forEach { device -> addDevice(device) }
} else {
Log.w(TAG, "Cannot enable BLE services as either there is no Bluetooth adapter or it is disabled")
}
}
private fun disableBleServices() {
clientManagers.values.forEach { clientManager ->
clientManager.close()
}
clientManagers.clear()
}
private fun addDevice(device: BluetoothDevice) {
if (!clientManagers.containsKey(device.address)) {
val clientManager = ClientManager()
clientManager.connect(device).useAutoConnect(true).enqueue()
clientManagers[device.address] = clientManager
}
}
private fun removeDevice(device: BluetoothDevice) {
clientManagers.remove(device.address)?.close()
}
/*
* Manages the entire GATT service, declaring the services and characteristics on offer
*/
companion object {
/**
* A binding action to return a binding that can be used in relation to the service's data
*/
const val DATA_PLANE_ACTION = "data-plane"
const val TAG = "gatt-service"
}
private inner class ClientManager : BleManager(this#GattService) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, TAG, message)
Log.d(TAG, "log: $message")
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_READ != 0) &&
(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
setNotificationCallback(myCharacteristic).with { _, data ->
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
defaultScope.launch {
myCharacteristicChangedChannel?.send(value)
}
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
}
object MyServiceProfile {
val MY_SERVICE_UUID: UUID = UUID.fromString("8d67d51a-801b-43cb-aea2-bbec9d1211fd")
val MY_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51c-801b-43cb-aea2-bbec9d1211fd")
val RX_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51b-801b-43cb-aea2-bbec9d1211fd")
}
}
`

How to connect BT barcode reader correctly?

I have two type of BT reader on of them is a Symbol CS3070 and the other one is a Datalogic DBT6400.
For the DBT6400 device I found an Android SDK with which the connection is very simple. But for the first one there is no supported SDK.
So I decided to connect to these device directly without SDK. Everything works fine,until the device disconnected because of sleep mode or because of I switch from one to another device.
In this case I get a lot of exception inside the while loop, because of the socket is closed. I have a tip for this why it's happen, maybe because of the coroutine, if it is possible.
UPDATE: So I found the problem, a put the catch block this line of code: reader=0, and there is only 1 error, so it's seems good. But I still don't know, is it a good solution?
My CustomReader class
class CustomReader(
private val deviceName: String,
onBarcodeRead: (barcode: String?) -> Unit
) : BarcodeReader(onBarcodeRead), CoroutineScope {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
private var pairedDevices: Set<BluetoothDevice>? = null
private var device: BluetoothDevice? = null
private lateinit var uuid: UUID
private var socket: BluetoothSocket? = null
private var connected: Boolean = false
private val localJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + localJob
override fun init(context: Context) {
pairedDevices = bluetoothAdapter.bondedDevices
}
override fun connect(address: String) {
try {
bluetoothAdapter.startDiscovery()
uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
device = pairedDevices?.firstOrNull { x -> x.name.startsWith(deviceName) }
if(device == null){
Timber.d("CustomReader: $deviceName: Device not found")
return
}
initSocket()
launch {
run()
}
} catch (e: Exception) {
Timber.e(e)
}
}
override fun disconnect() {
try {
socket?.close()
socket = null
} catch (e: IOException) {
Timber.d("CustomReader: $deviceName: Socket closed")
}
localJob.cancel()
}
override fun destroy() {
try {
socket?.close()
socket = null
} catch (e: IOException) {
Timber.d("CustomReader: $deviceName: Socket closed")
}
localJob.cancel()
}
override fun getAddress(): String? {
return device?.address
}
override fun isConnected(): Boolean {
return connected
}
private suspend fun run() {
try {
socket?.let { socket ->
socket.connect()
connected = true
val buffer = ByteArray(1024)
var read = 0
do {
try {
read = socket.inputStream.read(buffer)
val data = String(buffer, 0, read)
onBarcodeRead.invoke(data)
} catch (e: Exception) {
Timber.e(e)
}
} while (read > 0)
CoroutineScope()
}
}catch (e: Exception){
Timber.d("CustomReader: $deviceName Disconnected")
connected = false
}
}
fun initSocket(){
if(socket != null)
return
socket = device?.createRfcommSocketToServiceRecord(uuid)
bluetoothAdapter.cancelDiscovery()
}
fun closeSocket(){
try {
socket?.close()
}catch (e: Exception){
Timber.d("CustomReader: $deviceName: Socket closed")
}
}
}
**BletoothHandlar class:**
object BluetoothHandler {
#JvmStatic
private var currentReader: BarcodeReader? = null
#JvmStatic
private val barcodeLiveData = MutableLiveData<EventWrapper<String?>>()
#JvmStatic
fun getData(): LiveData<EventWrapper<String?>> = barcodeLiveData
init {
fixedRateTimer(
name = "blueooth-reconnect",
daemon = true,
initialDelay = 0L,
period = 5000
) {
if (currentReader == null) {
Timber.d("No reader device connected")
return#fixedRateTimer
}
if (currentReader?.isConnected() == true) {
Timber.d("reader connected, doing nothing")
} else if (currentReader?.isConnected() == false) {
Timber.d("reader not connected, reconnecting")
currentReader?.connect(currentReader!!.getAddress()!!)
}
}
}
#JvmStatic
fun connectToDevice(device: BluetoothDeviceCustom, context: Context): BarcodeReader? {
if (device.address == currentReader?.getAddress() && currentReader?.isConnected() == true) {
Timber.d("device already connected")
return currentReader
}
//ha változott a kiválasztott olvasó
if (device.address != currentReader?.getAddress()) {
currentReader?.disconnect()
}
val reader = createReader(device)
if (reader == null) {
currentReader?.disconnect()
} else {
reader.init(context)
reader.connect(device.address)
}
currentReader = reader
return reader
}
#JvmStatic
fun disconnect() {
currentReader?.disconnect()
currentReader?.destroy()
currentReader = null
}
#JvmStatic
fun getConnectedDevice(): BarcodeReader? {
return currentReader
}
#JvmStatic
private fun createReader(bluetoothDeviceCustom: BluetoothDeviceCustom): BarcodeReader? {
return CustomReader(bluetoothDeviceCustom.name) {
barcodeLiveData.postValue(EventWrapper(it))
}
/*return if (bluetoothDeviceCustom.name.startsWith("DBT6400")) {
DatalogicDBT6400 {
barcodeLiveData.postValue(EventWrapper(it))
}
} else if (bluetoothDeviceCustom.name.startsWith("CS3070")) {
CustomReader(bluetoothDeviceCustom.name) {
barcodeLiveData.postValue(EventWrapper(it))
}
} else {
null
}*/
}
}
So, here it is my solution, I don't think it is the best, but it works.
const val UUID_STRING = "00001101-0000-1000-8000-00805F9B34FB"
class CustomReader(
onBarcodeRead: (barcode: String?) -> Unit
) : BarcodeReader(onBarcodeRead), CoroutineScope {
private val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
private var pairedDevices: Set<BluetoothDevice>? = null
private var device: BluetoothDevice? = null
private var socket: BluetoothSocket? = null
private val localJob: Job = Job()
private var deviceName = ""
private val uuid = UUID.fromString(UUID_STRING)
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + localJob
override fun init(context: Context) {
pairedDevices = bluetoothAdapter.bondedDevices
}
private val singleExecutor: ExecutorCoroutineDispatcher =
Executors.newFixedThreadPool(1).asCoroutineDispatcher()
override fun connect(address: String) {
try {
device = pairedDevices?.firstOrNull { x -> x.address == address }
if(device == null){
Timber.d("CustomReader: $deviceName: Device not found")
return
}
deviceName = device?.name ?: "N/A"
initSocket()
launch {
withContext(singleExecutor){
run()
}
}
} catch (e: Exception) {
Timber.e(e)
}
}
override fun disconnect() {
closeSocket()
localJob.cancel()
}
override fun getAddress(): String? {
return device?.address
}
override fun isConnected(): Boolean {
return socket?.isConnected ?: false
}
private suspend fun run() {
try {
socket?.let { socket ->
socket.connect()
val buffer = ByteArray(1024)
var read = 0
do {
try {
read = socket.inputStream.read(buffer)
val data = String(buffer, 0, read)
onBarcodeRead.invoke(data)
} catch (e: Exception) {
Timber.d("CustomReader: $deviceName Socket disconnected")
read = -1
closeSocket()
}
} while (read > 0)
}
}catch (e: Exception){
Timber.d("CustomReader : $deviceName Disconnected")
}
}
private fun closeSocket(){
try {
socket?.close()
}catch (e: IOException){
Timber.d("CustomReader: $deviceName Socket closed")
}
socket = null
}
private fun initSocket(){
if(socket != null)
return
socket = device?.createRfcommSocketToServiceRecord(uuid)
}
}

android Kotlin socket scanner.hasNext() has No response

Server-side language: Python
Client-side language: Kotlin, Android
It receives data from different lines at one line.
class SampleActivity : AppCompatActivity() {
private val TAG = "SAMPLE_ACTIVITY_TAG"
private lateinit var editTextMessage: EditText
private val IPs = listOf("192.168.100.30")
private val PORTs = listOf(5050)
private val INDEX_SERVER = 0
private var socket: Socket? = null
private val IP = IPs[INDEX_SERVER]
private val PORT = PORTs[INDEX_SERVER]
private lateinit var outPrintWriter: PrintWriter
private val CONNECTION_TOKEN = "fab2bd65f67193d761c06b07a708d3232f0d8569"
private val TAG_CONNECT_START = "[CONNECT]"
private val TAG_CONNECT_END = "[/CONNECT]"
private val TAG_TRANSFER_START = "[TRANSFER]"
private val TAG_TRANSFER_END = "[/TRANSFER]"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
editTextMessage = findViewById(R.id.editTextMessage)
CoroutineScope(Dispatchers.IO).launch {
connect()
}
buttonSubmit.setOnClickListener {
if(editTextMessage.text.toString().trim().isEmpty()){
return#setOnClickListener
}
CoroutineScope(Dispatchers.IO).launch {
send(messageConverter(TAG_TRANSFER_START + editTextMessage.text.toString().trim() + TAG_TRANSFER_END))
}
}
}
override fun onDestroy() {
outPrintWriter.close()
super.onDestroy()
}
private fun messageConverter(text: String): String {
Log.w(TAG, "[CONVERTING] \nMessage: $text\nAscii: ${text.toASCII()}")
return text.toASCII() // Defined in extention.kt
}
private suspend fun connect() {
try {
Log.w(TAG, "[CONNECTING] Connecting to: $IP:$PORT")
socket = Socket(IP, PORT)
socket?.receiveBufferSize = 1024
socket?.soTimeout = 1 * 60 * 60
outPrintWriter = PrintWriter(socket!!.getOutputStream())
send(messageConverter(TAG_CONNECT_START + CONNECTION_TOKEN + TAG_CONNECT_END))
receive()
} catch (uHE: UnknownHostException) {
Log.e(TAG, "[ERROR] UnknownHostException, ${uHE.message}", uHE)
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] IOException, ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] Exception, ${e.message}", e)
}
}
private suspend fun send(text: String) {
try {
Log.w(TAG, "[SENDING] Message: $text")
outPrintWriter.print(text)
outPrintWriter.flush()
Log.w(TAG, "[SENDING] finished .")
clearMessageBox()
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] IOException, ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] IOException, ${e.message}", e)
}
}
private suspend fun receive() {
try {
val scanner = Scanner(
socket?.getInputStream()
)
val lines = ArrayList<String>()
var line: String?
while (scanner.hasNextLine()) {
Logger.w(TAG, "[RECEIVING] Scanner hasNext")
line = scanner.nextLine()
if (!line.isNullOrEmpty()) {
lines.add(line)
}
}
Logger.w(TAG, "[RECEIVING] Exit While loop : ${lines}")
} catch (eIO: IOException) {
Log.e(TAG, "[ERROR] Receive IOException error: ${eIO.message}", eIO)
} catch (e: Exception) {
Log.e(TAG, "[ERROR] Receive Exception error: ${e.message}", e)
}
}
private suspend fun clearMessageBox() {
withContext(Main) {
editTextMessage.text.clear()
}
}
}
Logs:
[CONNECTING] Connecting to: 192.168.100.30:5050
[CONVERTING]
Message: [CONNECT]fab2bd65f67193d761c06b07a708d3232f0d8569[/CONNECT]
Ascii: 91,67,79,78,78,69,67,84,93,102,97,98,50,98,51,50,102,48,100,49,55,56,99,48,54,55,48,56,53,55,97,100,54,53,102,54,54,98,48,100,51,50,57,49,57,51,100,55,54,91,47,67,79,78,78,69,67,84,93
[SENDING] Message: 91,67,79,78,78,69,67,84,93,102,97,98,50,98,51,50,102,48,100,49,55,56,99,48,54,55,48,56,53,55,97,100,54,53,102,54,54,98,48,100,51,50,57,49,57,51,100,55,54,91,47,67,79,78,78,69,67,84,93
[SENDING] finished.
[RECEIVING] Scanner hasNext
[RECEIVING] Exit While loop : [OK1OK2]
Log: **[RECEIVING] Exit While loop :** shall be `[OK1, OK2]`
Server send messagesin every second twice
I just checked some questions and samples b, unfortunately in my case, no one solved the above issue

Unable to read data via bluetooth successfully

I'm trying to create an App which can receive data and send data to the microcontroller (ESP32). But for some reason, I'm unable to receive data from microcontroller successfully.
The app is written in Kotlin, and I already tried some examples mentioned on StackOverflow, but none of them actually works on my code.
I can successfully send data to the microcontroller via Bluetooth, but I can't receive data from Bluetooth. (The method I used in the microcontroller is just simply "ESP_BT.println("Check");"
In the code snippet, the function relates to my receiving data is called "receiveBluetooth"
class ControlActivity: AppCompatActivity() {
companion object {
val myUUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
var myBluetoothSocket: BluetoothSocket? = null
lateinit var myProgress: ProgressDialog
lateinit var myBluetoothAdapter: BluetoothAdapter
var myIsConnected: Boolean = false
lateinit var myAddress: String
val mmInStream: InputStream? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.control_layout)
myAddress = intent.getStringExtra(SelectDeviceActivity.EXTRA_ADDRESS)
ConnectToDevice(this).execute()
val btnShow = findViewById<Button>(R.id.btnShow)
var inputRPM: String
//Read in value and store it as String
btnShow.setOnClickListener{
inputRPM = receiveInput()
sendCommand(inputRPM)
}
//Read RPM from microcontroller (bluetooth)
val showCountTextView = findViewById<TextView>(R.id.textView)
btnRefresh.setOnClickListener {
//showCountTextView.text = receiveBluetooth()
receiveBluetooth(showCountTextView)
}
control_disconnect.setOnClickListener{
disconnect()
}
}
private fun receiveInput(): String {
val input = findViewById<EditText>(R.id.editText)
return input.text.toString()
}
private fun sendCommand(input: String) {
if (myBluetoothSocket != null) {
try{
myBluetoothSocket!!.outputStream.write(input.toByteArray())
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun receiveBluetooth(input: TextView) {
val buffer = ByteArray(256)
val bytes:Int
var tmpIn: InputStream? = null
if (myBluetoothSocket != null) {
try {
tmpIn = myBluetoothSocket!!.inputStream
val mmInStream = DataInputStream(tmpIn)
bytes = mmInStream.read(buffer)
val readMessage = String(buffer, 0, bytes)
input.text = readMessage
//input.text="123"
} catch (e:IOException) {
e.printStackTrace()
}
}
}
private fun disconnect() {
if (myBluetoothSocket != null) {
try {
myBluetoothSocket!!.close()
myBluetoothSocket = null
myIsConnected = false
} catch (e: IOException) {
e.printStackTrace()
}
}
finish()
}
private class ConnectToDevice(c: Context) : AsyncTask<Void, Void, String> () {
private var connectSuccess: Boolean = true
private val context: Context
init {
this.context = c
}
override fun onPreExecute() {
super.onPreExecute()
myProgress = ProgressDialog.show(context, "Connecting", "Please wait")
}
override fun doInBackground(vararg params: Void?): String? {
try {
if (myBluetoothSocket == null || !myIsConnected) {
myBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device: BluetoothDevice = myBluetoothAdapter.getRemoteDevice(myAddress)
myBluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(myUUID)
BluetoothAdapter.getDefaultAdapter().cancelDiscovery()
myBluetoothSocket!!.connect()
}
} catch (e: IOException) {
connectSuccess = false
e.printStackTrace()
}
//Needs be fixed
return null
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
if (!connectSuccess) {
Log.i("data", "couldn't connect")
} else {
myIsConnected = true
}
myProgress.dismiss()
}
}
}
I expect the text will show exactly "Check", but instead, my text will only show the initial value that I assigned.
Maybe you should use a library. For me works fine RxAndroidBle library:
Gradle:
implementation "com.polidea.rxandroidble2:rxandroidble:1.8.1"
Implementation:
In my project with Android Java and ESP32 too, I read some characteristics or values with simple implementations, for example:
public void setupNotification() {
if (isConnected()) {
final Disposable disposable = connectionObservable
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(charactSensorDataUuid))
.doOnNext(notificationObservable -> { notificationHasBeenSetUp(); })
.flatMap(notificationObservable -> notificationObservable)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onNotificationReceived, this::onNotificationSetupFailure);
compositeDisposable.add(disposable);
}
}
public void readSensorConfig(){
if (isConnected()) {
final Disposable disposable = connectionObservable
.firstOrError()
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(charactConfigUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSensorConfigRead, this::onReadFailure);
compositeDisposable.add(disposable);
}
}
public void readSensorData(){
if (isConnected()) {
final Disposable disposable = connectionObservable
.firstOrError()
.flatMap(rxBleConnection -> rxBleConnection.readCharacteristic(charactSensorDataUuid))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSensorDataRead, this::onReadFailure);
compositeDisposable.add(disposable);
}
}
The complete Java implementation is here:
https://github.com/kike-canaries/android-hpma115s0/blob/master/app/src/main/java/hpsaturn/pollutionreporter/common/BLEHandler.java
The migration to Kotlin should be simple, also on this library the main target is Bluetooth BLE, and they have many samples on Kotlin

Android: How to detect Bluetooth connection status

I want to detect the connection status of a paired Bluetooth headset
to the phone.
In Android 3.0 (API level 11) "BluetoothHeadset" class has
"isAudioConnected()" method.
I don't know how to create (initialize) a "BluetoothHeadset" object.
It seems that I need to use
"getProfileProxy ()" but I need a sample code to find out how I need
to create and pass the parameters.
Thanks,
Hos
You need to implement BluetoothProfile.ServiceListener :
BluetoothProfile.ServiceListener b = new BlueToothListener();
boolean profileProxy = BluetoothAdapter.getDefaultAdapter()
.getProfileProxy(Handler.bot, b, BluetoothProfile.HEADSET);
public class BlueToothListener implements ServiceListener {
public static BluetoothHeadset headset;
public static BluetoothDevice bluetoothDevice;
#Override
public void onServiceDisconnected(int profile) {// dont care
headset = null;
}
#Override
public void onServiceConnected(int profile,
BluetoothProfile proxy) {// dont care
try {
Debugger.test("BluetoothProfile onServiceConnected "+proxy);
if (proxy instanceof BluetoothHeadset)
headset = ((BluetoothHeadset) proxy);
else// getProfileProxy(Handler.bot, b, BluetoothProfile.HEADSET);
return;// ^^ => NEVER
List<BluetoothDevice> connectedDevices = proxy
.getConnectedDevices();
for (BluetoothDevice device : connectedDevices) {
Debugger.log("BluetoothDevice found :" + device);
bluetoothDevice = device;
int connectionState = headset.getConnectionState(bluetoothDevice);
Debugger.log("BluetoothHeadset connectionState "+connectionState);//2 == OK
boolean startVoiceRecognition = headset
.startVoiceRecognition(device);
if (startVoiceRecognition) {
Debugger
.log("BluetoothHeadset init Listener OK");
return;
}
else
Notify.popup("Bluetooth headset can't start speech recognition");
}
} catch (Exception e) {
// }
}
}
}
`
Monitoring of the BT status can be done indeed by polling. Here's how I did it (full sample here) . Note that it's just a sample and you should manage the polling better:
manifest
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
gradle
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.work:work-runtime-ktx:2.7.1"
MainActivityViewModel.kt
#UiThread
class MainActivityViewModel(application: Application) : BaseViewModel(application) {
private val bluetoothAdapter: BluetoothAdapter =
context.getSystemService<BluetoothManager>()!!.adapter
private var bluetoothHeadsetProfile: BluetoothProfile? = null
val connectedDevicesLiveData =
DistinctLiveDataWrapper(MutableLiveData<ConnectedDevicesState>(ConnectedDevicesState.Idle))
val bluetoothTurnedOnLiveData = DistinctLiveDataWrapper(MutableLiveData<Boolean?>(null))
val isConnectedToBtHeadsetLiveData = DistinctLiveDataWrapper(MutableLiveData<Boolean?>(null))
private val pollingBtStateRunnable: Runnable
init {
updateBtStates()
pollingBtStateRunnable = object : Runnable {
override fun run() {
updateBtStates()
handler.postDelayed(this, POLLING_TIME_IN_MS)
}
}
// Establish connection to the proxy.
val serviceListener = object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, bluetoothProfile: BluetoothProfile) {
this#MainActivityViewModel.bluetoothHeadsetProfile = bluetoothProfile
handler.removeCallbacks(pollingBtStateRunnable)
pollingBtStateRunnable.run()
}
override fun onServiceDisconnected(profile: Int) {
handler.removeCallbacks(pollingBtStateRunnable)
updateBtStates()
}
}
bluetoothAdapter.getProfileProxy(context, serviceListener, BluetoothProfile.HEADSET)
onClearedListeners.add {
this.bluetoothHeadsetProfile?.let { bluetoothProfile ->
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothProfile)
}
handler.removeCallbacks(pollingBtStateRunnable)
}
}
fun initWithLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
pollingBtStateRunnable.run()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
handler.removeCallbacks(pollingBtStateRunnable)
}
})
}
#UiThread
private fun updateBtStates() {
// Log.d("AppLog", "updateBtStates")
val isBlueToothTurnedOn = bluetoothAdapter.state == BluetoothAdapter.STATE_ON
bluetoothTurnedOnLiveData.value = isBlueToothTurnedOn
if (!isBlueToothTurnedOn) {
connectedDevicesLiveData.value = ConnectedDevicesState.BluetoothIsTurnedOff
isConnectedToBtHeadsetLiveData.value = false
return
}
val isConnectedToBtHeadset = try {
bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothAdapter.STATE_CONNECTED
} catch (e: SecurityException) {
null
}
isConnectedToBtHeadsetLiveData.value = isConnectedToBtHeadset
val bluetoothProfile = bluetoothHeadsetProfile
if (bluetoothProfile != null) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
val connectedDevicesSet = bluetoothProfile.connectedDevices.toHashSet()
val previousConnectedDevices =
(connectedDevicesLiveData.value as? ConnectedDevicesState.GotResult)?.connectedDevices
if (previousConnectedDevices == null || previousConnectedDevices != connectedDevicesSet)
connectedDevicesLiveData.value =
ConnectedDevicesState.GotResult(connectedDevicesSet)
} else {
connectedDevicesLiveData.value =
ConnectedDevicesState.NeedBlueToothConnectPermission
}
} else {
connectedDevicesLiveData.value = ConnectedDevicesState.Idle
}
}
companion object {
private const val POLLING_TIME_IN_MS = 500L
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
viewModel.initWithLifecycle(lifecycle)
viewModel.bluetoothTurnedOnLiveData.observe(this) {
Log.d("AppLog", "MainActivity bluetoothTurnedOnLiveData BT turned on? $it")
}
viewModel.isConnectedToBtHeadsetLiveData.observe(this) {
Log.d("AppLog", "MainActivity isConnectedToBtHeadsetLiveData BT headset connected? $it")
}
viewModel.connectedDevicesLiveData.observe(this) {
Log.d("AppLog", "MainActivity connectedDevicesLiveData devices: $it")
}
findViewById<View>(R.id.grantBtPermission).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestPermissions(arrayOf(Manifest.permission.BLUETOOTH_CONNECT), 1)
}
}
}
}
ConnectedDevicesState.kt
sealed class ConnectedDevicesState {
object Idle : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG)
return "Idle"
return super.toString()
}
}
class GotResult(#Suppress("MemberVisibilityCanBePrivate") val connectedDevices: Set<BluetoothDevice>) : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG) {
return "GotResult: connectedDevices:${
connectedDevices.map {
try {
it.name
} catch (e: SecurityException) {
it.address
}
}
}"
}
return super.toString()
}
}
object BluetoothIsTurnedOff : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG)
return "BluetoothIsTurnedOff"
return super.toString()
}
}
object NeedBlueToothConnectPermission : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG)
return "NeedBlueToothConnectPermission"
return super.toString()
}
}
}
DistinctLiveDataWrapper.kt
class DistinctLiveDataWrapper<T>(#Suppress("MemberVisibilityCanBePrivate") val mutableLiveData: MutableLiveData<T>) {
#Suppress("MemberVisibilityCanBePrivate")
val distinctLiveData = Transformations.distinctUntilChanged(mutableLiveData)
var value: T?
#UiThread
set(value) {
mutableLiveData.value = value
}
get() {
return mutableLiveData.value
}
#AnyThread
fun postValue(value: T) {
mutableLiveData.postValue(value)
}
fun observe(lifecycleOwner: LifecycleOwner, observer: Observer<in T>) {
distinctLiveData.observe(lifecycleOwner, observer)
}
}
BaseViewModel.kt
/**usage: class MyViewModel(application: Application) : BaseViewModel(application)
* getting instance: private lateinit var viewModel: MyViewModel
* viewModel=ViewModelProvider(this).get(MyViewModel::class.java)*/
abstract class BaseViewModel(application: Application) : AndroidViewModel(application) {
#Suppress("MemberVisibilityCanBePrivate")
var isCleared = false
#Suppress("MemberVisibilityCanBePrivate")
val onClearedListeners = ArrayList<Runnable>()
#Suppress("unused")
#SuppressLint("StaticFieldLeak")
val context: Context = application.applicationContext
#Suppress("unused")
val handler = Handler(Looper.getMainLooper())
override fun onCleared() {
super.onCleared()
isCleared = true
onClearedListeners.forEach { it.run() }
}
}

Categories

Resources