I need to connect to a bluetooth device which acts as a server. I know its UUID (at least the device's documentation contains it). However, I get an exception when I try to connect to it. The discovery part takes place successfully.
In the following, I cite the relevant code parts.
Here is the discovery. After I successfully found my device, I try to connect to it.
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
private val bluetoothReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action: String = intent.action
when (action) {
BluetoothDevice.ACTION_FOUND -> {
val foundDevice: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
Log.i("NAME", foundDevice.name)
if (foundDevice.name.startsWith("RN487")) {
bluetoothAdapter?.cancelDiscovery()
device = foundDevice
val connectThread = ConnectThread(device)
connectThread.start()
}
}
}
}
}
private lateinit var device: BluetoothDevice
The ConnectThread class is here:
private inner class ConnectThread(device: BluetoothDevice) : Thread() {
private val mSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
device.createRfcommSocketToServiceRecord(UUID)
}
override fun run() {
bluetoothAdapter?.cancelDiscovery()
mSocket?.use { socket ->
socket.connect()
toast("Connected!")
}
}
fun cancel() {
try {
mSocket?.close()
} catch (e: IOException) {
Log.e(TAG, "Could not close the client socket", e)
}
}
}
The UUID was given as
private val UUID = nameUUIDFromBytes("49535343-...".toByteArray())
Thanks for your time and expertise!
As one of my eagle-eyed colleagues pointed out, the bluetooth description begins with the "oldschool" version on the official android developers site. Later, the bluetooth low energy is described, which I need for my project.
Related
I have been developing this Android app for some time and have realized that my Activities are way too bloated so I have been trying to switch to MVVM with Clean Architecture but am running into an issue. I have this BluetoothLe service that provides the data to my application and it seems like I can only bind to my service from my Activities which is a problem because I am trying to separate the presentation from the data layer.
Here is some code from my activity which uses the service:
#AndroidEntryPoint
class DeviceActivity: AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
// No NightMode allowed.
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
super.onCreate(savedInstanceState)
binding = ActivityDeviceBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setSupportActionBar(binding.myToolbar)
if (intent.extras!!.get("btDeviceName") != null) {
deviceName = intent.extras!!.get("btDeviceName").toString()
binding.deviceTitle.text = deviceName
}
setupActionBar()
// Binding to the service via gattServiceIntent.
val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
...
}
// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName?, service: IBinder?) {
bluetoothService = (service as BluetoothLeService.LocalBinder).getService()
bluetoothService?.let { bluetooth ->
// Call functions on service to check connection and connect to devices
if (!bluetooth.initialize()) {
Log.e("blah", "Unable to initialize Bluetooth.")
finish()
}
// Perform device connection
if (bluetoothDevice != null) {
bluetooth.connect(bluetoothDevice!!)
}
}
}
override fun onServiceDisconnected(name: ComponentName?) {
bluetoothService = null
}
}
Then I listen for the data via update receiver:
// Connect to device after registering for the receiver.
private fun registerBluetoothLeServiceReceiver() {
try {
registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
if (bluetoothService != null) {
connectToBluetoothDevice()
}
} catch (e: Exception) {
Log.d(TAG, "onResume: $e Couldn't register receiver.")
}
}
private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothLeService.ACTION_GATT_CONNECTED -> {
connected = true
updateConnectionState(context.resources.getString(R.string.connected))
}
BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
connected = false
updateConnectionState(context.resources.getString(R.string.disconnected))
leaveActivity()
binding.progressBarCyclic.visibility = GONE
}
// When services are discovered on the device, we request gatt to update the mtu size.
BluetoothLeService.ACTION_MTU_UPDATED -> {
bluetoothService?.enableNotification()
}
// We write the characteristic only when notifications have been enabled.
BluetoothLeService.ACTION_NOTIFICATION_ENABLED -> {
bluetoothService?.writeGattCharacteristic()
}
// This is where the data stream is received.
BluetoothLeService.ACTION_DATA_AVAILABLE -> {
val byteArr = intent.getByteArrayExtra("byteArray")
Log.d(TAG, "onReceive: ${byteArr.contentToString()}")
if (byteArr != null) {
// Firmware versions greater than 3634 have different data streams.
binding.recyclerviewDevices.visibility = View.VISIBLE
if (isLessThan3634 && boardVersion == 2.1) {
dataProcessor.processData(byteArr)
} else {
dataProcessor.processData(byteArr, 1)
}
}
}
}
}
}
After this is a bunch of data manipulation and processing which I would much rather do in my ViewModel or another class for separation purposes.
Is there a way that I can get my service to bind to a repository or something that will allow me to separate the presentation and data layers so I can listen for data in something other than my Activity?
I think you will need to add a new android module and name it "device" that implements your Bluetooth datasource and inject this module in your data layer.
This article could help you
https://five.agency/android-architecture-part-1-every-new-beginning-is-hard/
Hello am new in Android development I was reading about android bluetooth Here on Android development documentation.
I was able to setup bluetooth, find the bonded device and to connect but am having an issue on transfer data between them
Here is the bluetooth server socket code that listen to bluetooth connection request.
class BluetoothActivity : AppCompatActivity() {
private lateinit var listen: Button
private lateinit var msgBox:TextView
private lateinit var status:TextView
private lateinit var sendButton: Button
private lateinit var writeMsg:EditText
private lateinit var listDevice:Button
private lateinit var listView: ListView
private val handler = Handler()
private var bluetoothDevices = arrayListOf<BluetoothDevice>()
private var deviceName = arrayListOf<String>()
private inner class ServerAcceptThread:Thread(){
private val mmServerSocket:BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE){
bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(myName,myUUID)
}
override fun run() {
//Keep listen until error occured or socket is returned
var shouldKeepListen = true
while (shouldKeepListen){
val socket:BluetoothSocket? = try {
mmServerSocket?.accept()
}catch (e:IOException){
Log.e("bluetoothSocket","ServerSocket failde",e)
shouldKeepListen = false
null
}
if (socket!= null){
val connected = ConnectedThread(socket)
connected.start()
}
}
}
//Close server socket and cause the thread to finish
fun cancel(){
try {
mmServerSocket?.close()
}catch (e:IOException){
Log.e("ConnectionFailed!", "Connection close failed",e)
}
}
}
And down below is the code for Bluetooth client that connect to bluetooth server socket.
private inner class ClientConnectThread(device: BluetoothDevice):Thread(){
private val mmSocket:BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE){
device.createRfcommSocketToServiceRecord(myUUID)
}
public override fun run() {
//Cancel the discovery process because it slow down the connection
bluetoothAdapter?.cancelDiscovery()
mmSocket?.let { socket ->
socket.connect()
}
}
fun cancel(){
try {
mmSocket?.close()
}catch (e:IOException){
Log.e("Socket", "Could not close the client socket",e)
}
}
}
And Then I have bluetooth service that read and write data to send to remote device (client). which take BluetoothSocket as parameter, were the server is listening to
private inner class ConnectedThread(private val mmSocket:BluetoothSocket):Thread(){
private val mmInPutStream:InputStream = mmSocket.inputStream
private val mmOutPutStream:OutputStream = mmSocket.outputStream
private val mmBuffer:ByteArray = ByteArray(1024)
override fun run() {
var numByte:Int //number of bytes returns from read()
//keep listen to the InputStream until an error occured
while (true){
//Read from inputStream
numByte = try {
mmInPutStream.read(mmBuffer)
}catch (e:IOException){
Log.e(TAG,"InputStream was disconnected",e)
break
}
//Send the message to Ui activity
val readMsg = handler.obtainMessage(
MESSAGE_READ,numByte,-1,mmBuffer
)
readMsg.sendToTarget()
}
}
//Call this function to mainActivity to send data to remote device
fun write(byte:ByteArray){
try {
mmOutPutStream.write(byte)
}catch (e:IOException){
Log.e(TAG,"Error occured during send messge",e)
//Send the failed message back to activity
val writeErrorMessage = handler.obtainMessage(MESSAGE_TOAST)
val bundle = Bundle().apply {
putString("Toast","could not send the data")
}
writeErrorMessage.data = bundle
handler.sendMessage(writeErrorMessage)
return
}
//Share the sent message with UI activity
val writtenMsg = handler.obtainMessage(
MESSAGE_WRITE, -1,-1,mmBuffer
)
writtenMsg.sendToTarget()
}
//Call this method to activity to shut socket
fun cancle(){
try {
mmSocket.close()
}catch (e:IOException){
Log.e(TAG,"Connection closed failed!")
}
}
}
}
And I have also implement the listener for UI to start listen to a connection request, list Bonded device and connect to remote device and transfer data through each other.
fun implementsListeners(){
listDevice.setOnClickListener {
val pairedDevice: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
var index:Int = 0
val pairedDevice: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
if (pairedDevice != null){
var listDeviceName = arrayListOf<String>()
try {
pairedDevice.forEachIndexed { index, device ->
listDeviceName.add(index, device.name)
bluetoothDevices.add(device)
}
}catch (e:IndexOutOfBoundsException){
Log.e(TAG, "indexOutOfBond",e)
}
val arrayAdapter:ArrayAdapter<String> = ArrayAdapter(
this,android.R.layout.simple_list_item_1,listDeviceName
)
listView.adapter =arrayAdapter
}
listen.setOnClickListener {
val serverClass = ServerAcceptThread()
serverClass.start()
}
listView.setOnItemClickListener { parent, view, position, id ->
val client = ClientConnectThread(bluetoothDevices[position])
client.start()
status.text = "Connecting..."
}
sendButton.setOnClickListener {
val client = BluetoothService(Handler())
//Call the write() method to write data
}
My Question is how can I access the write() method and read() that is on ConnectedThread. I have tried to Instantiate ConnectedThread but it's take BluetoothSocket as parameter I can't access the socket outside client or server class. Method Any help or suggestion on. I would Appreciate
I'm trying to wrap my head around the android bluetooth API by adapting this half-finished example project to get it working on a BLE heart rate peripheral (the stock HR example from Espressif, running on an ESP32 dev board).
My problem is that I am unable to bind the Service that manages the BLE connection; calling bindService always returns false (see commented line in initBLEService in code snippet below). I am unable to understand why, nor how to get the service running properly. Help?
Here's how I'm managing the BLE connection:
object BLEConnectionManager {
private val TAG = "BLEConnectionManager"
private var mBLEService: BLEService? = null
private var isBind = false
private val mServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, service: IBinder) {
mBLEService = (service as BLEService.LocalBinder).getService()
Log.i(TAG, "BLEConnectionManager.onServiceConnected mBLEService = $mBLEService")
if (!mBLEService?.initialize()!!) {
Log.e(TAG, "Unable to initialize")
}
}
override fun onServiceDisconnected(componentName: ComponentName) {
mBLEService = null
}
}
fun initBLEService(context: Context) {
try {
if (mBLEService == null) {
val gattServiceIntent = Intent(context, BLEService::class.java)
if (context != null) {
// BELOW LINE ALWAYS RETURNS false. WHY?
isBind = context.bindService(gattServiceIntent, mServiceConnection,
Context.BIND_AUTO_CREATE)
Log.i(TAG, "BLEConnectionManager.initBLEService isBind = $isBind")
}
}
} catch (e: Exception) {
Log.e(TAG, e.message)
}
}
fun connect(deviceAddress: String): Boolean {
var result = false
Log.i(TAG, "BLEConnectionManager.connect (to $deviceAddress) and mBLEService is $mBLEService")
if (mBLEService != null) result = mBLEService!!.connect(deviceAddress)
return result
}
// ...etc
And here's what's going on in the main activity onCreate:
if (!BLEDeviceManager.isEnabled()) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
BLEConnectionManager.initBLEService(this#MainActivity)
And I attempt to connect with a button in the main activity:
private fun connectDevice() {
Handler().postDelayed({
BLEConnectionManager.initBLEService(this#MainActivity)
if (BLEConnectionManager.connect(mDeviceAddress)) {
Toast.makeText(this#MainActivity, "DEVICE CONNECTED", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this#MainActivity, "DEVICE CONNECTION FAILED", Toast.LENGTH_SHORT).show()
}
}, 1000)
}
The BLEService class is unchanged from the original code.
Ugh. I didn't realize that the service must be declared in the manifest file to be available to the component for binding. Add:
<service android:name=".blemodule.BLEService"></service>
I want to connect multiple Android devices via Network Service Discovery. I want to use a star topology. so on device should discover and the others a registering a service which should found by the discoverer. For this I took the NSD Chat (https://github.com/gauravcanon/NsdChat ) NsdHelper and changed it only a bit, so a callback gets called after resolving a service which establish a connection. This works in mostly if I am using three devices. two advertiser and one discoverer. The services getting resolved and a connection estabished. If I get an additional third advertiser it is crashing in 80% of all attempts. The reason is that the serviceInfo which is passed in the onServiceResolved function in the ResolveListener contains the ip address of the resolving phone and not of the advertising one. The port is correct and the servicename also. That is such a strange behaviour, I don't know how to debug this. I'm using the bonjour browser to see all registered services and the registration of all services is fine. All service infos containing the right ip address and port. I also tried the the pure discovering process without establishing a connection. Same failure. Sometimes the ip address of the resolver is in the serviceinfo. It can also happen at the first discovery and the second, but most likely it is on the third one.
I will post my NsdHelper code below. I made some edits right now because i tried to start the discovery process again after resolving, so there are more differences to the NSD Chat, but the error persists.
Is someone using the NSD implementation of Android via the NSDManager with multiple devices? Is it working for you? What are you doing different?
This topic is related there someone had the same problems ( Android, NSD/DNS-SD: NsdManager unreliable discovery and IP resolution ). I cant imagine that this error is still a thing 3 years ago?
I'm thankful for every hint!
class NsdHelper(private var mContext: Context, private val createClient: (InetAddress, Int)->Unit, val mHandler: Handler) {
internal var mNsdManager: NsdManager = mContext.getSystemService(Context.NSD_SERVICE) as NsdManager
internal lateinit var mResolveListener: NsdManager.ResolveListener
private var mDiscoveryListener: NsdManager.DiscoveryListener? = null
private var mRegistrationListener: NsdManager.RegistrationListener? = null
var mServiceName = "Wizard"
var chosenServiceInfo: NsdServiceInfo? = null
internal set
val mServices = mutableListOf<NsdServiceInfo>()
fun initializeNsd() {
stopDiscovery()
tearDown()
initializeResolveListener()
}
fun reset(){
initializeResolveListener()
discoverServices()
}
fun initializeDiscoveryListener() {
mDiscoveryListener = object : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(regType: String) {
Log.d(TAG, "Service discovery started")
}
override fun onServiceFound(service: NsdServiceInfo) {
Log.d(TAG, "Service discovery success$service")
when {
service.serviceType != SERVICE_TYPE -> Log.d(TAG, "Unknown Service Type: " + service.serviceType)
service.serviceName == mServiceName -> Log.d(TAG, "Same machine: $mServiceName")
service.serviceName.contains("Verzauberte") -> {
if (mServices.none { it.serviceName == service.serviceName })
mNsdManager.resolveService(service, mResolveListener)
}
}
}
override fun onServiceLost(service: NsdServiceInfo) {
Log.e(TAG, "service lost$service")
}
override fun onDiscoveryStopped(serviceType: String) {
Log.i(TAG, "Discovery stopped: $serviceType")
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.e(TAG, "Discovery failed: Error code:$errorCode")
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.e(TAG, "Discovery failed: Error code:$errorCode")
}
}
}
fun initializeResolveListener() {
mResolveListener = object : NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.e(TAG, "Resolve failed$errorCode")
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
Log.e(TAG, "Resolve Succeeded. $serviceInfo")
if (serviceInfo.serviceName == mServiceName) {
Log.d(TAG, "Same IP.")
return
}
chosenServiceInfo = serviceInfo
mHandler.post(Runnable {
createClient(
serviceInfo.host,
serviceInfo.port
)
})
mServices.add(serviceInfo)
reset()
}
}
}
fun initializeRegistrationListener() {
mRegistrationListener = object : NsdManager.RegistrationListener {
override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
mServiceName = NsdServiceInfo.serviceName
Log.d(TAG, "Service registered: $mServiceName")
}
override fun onRegistrationFailed(arg0: NsdServiceInfo, arg1: Int) {
Log.d(TAG, "Service registration failed: $arg1")
}
override fun onServiceUnregistered(arg0: NsdServiceInfo) {
Log.d(TAG, "Service unregistered: " + arg0.serviceName)
}
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.d(TAG, "Service unregistration failed: $errorCode")
}
}
}
fun registerService(port: Int) {
tearDown() // Cancel any previous registration request
initializeRegistrationListener()
val serviceInfo = NsdServiceInfo().apply {
serviceType = SERVICE_TYPE
serviceName = "Verzauberte[$port]"
setPort(port)
}
mNsdManager.registerService(
serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener
)
}
fun discoverServices() {
stopDiscovery() // Cancel any existing discovery request
initializeDiscoveryListener()
Log.d(this.toString(), "Start discovering")
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener
)
}
fun stopDiscovery() {
if (mDiscoveryListener != null) {
try {
mNsdManager.stopServiceDiscovery(mDiscoveryListener)
} finally {
}
mDiscoveryListener = null
}
}
fun tearDown() {
if (mRegistrationListener != null) {
try {
mNsdManager.unregisterService(mRegistrationListener)
} finally {
}
mRegistrationListener = null
}
}
companion object {
val SERVICE_TYPE = "_votinginteractions._tcp."
val TAG = "NsdHelper"
}
}
I'm writing a chat app with the server and Android client written in Kotlin. I create a background service that constantly reads from the socket connected to the server and sends notifications when a message arrives. Everything works fine until user taps 'x' button and closes the app. Connection with server fails during executing cleanUp code posted below. Server had gotten EOF before service managed to send EXIT request and close streams. Then, service is recreated but when it tries to connect to the server it gets ConnectException (connection refused). It happens only when battery saving mode is on. When it's off or phone is connected to my laptop with USB and charging there's no problem.
The ss command lists that there is someone listening on the specified port, so it's not that problem. I've tried to connect in a loop, i. e. try to connect 5 times every 10 seconds, but it got refused every time. I've tried listening on two different ports, but both failed even if one of them wasn't used before. Docs say that default backlog is 50, so I guess it's not that either. I tried to set a SO_REUSEADDR flag on the server socket, but still nothing. And the strange thing is, that when service is started from the app when I launch it for the second time it can connect again. So I've created a broadcast receiver that starts the service the same way as the app in case it crashes, but it's not helping either.
I really was googling it for over a week but it's my first attempt at using both Kotlin and sockets and I'm running out of ideas. If someone has a clue to what might be going on, I'd really appreciate some help.
Here is the service onStartCommand:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
activeConversation = intent?.getStringExtra(CONV_NAME) ?: ""
login = intent?.getStringExtra(LOGIN) ?: login
if (thread?.isAlive != true) {
thread = thread(start = true) {
synchronized(lock) {
try {
socket = Socket(SERVER_IP, SERVICE_PORT)
output = ObjectOutputStream(socket?.getOutputStream())
input = ObjectInputStream(socket?.getInputStream())
output?.writeObject(Request(START_SERVICE, mutableMapOf(LOGIN to login)))
} catch (e: IOException) {
e.printStackTrace()
return#thread
}
}
handleMessages() //contains input?.readObject() in infinite loop
}
}
return START_STICKY
}
In onDestory() and onTaskRemoved() I call this function:
private fun cleanUp() {
synchronized(lock) {
thread(start = true) {
try {
output?.writeObject(Request(EXIT, mutableMapOf(LOGIN to login)))
output?.close()
input?.close()
socket?.close()
nullStreams()
thread?.join()
println("SERVICE: thread joined")
} catch(e: IOException) {
e.printStackTrace()
return#thread
} finally {
println("Service sends broadcast to ask for recreation")
val restartIntent = Intent(this, ServiceRestarter::class.java)
restartIntent.putExtra(LOGIN, login)
sendBroadcast(restartIntent)
}
}.join()
}
}
ServiceRestarter:
class ServiceRestarter : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val login = intent?.getStringExtra(LOGIN)
println("SERVICE RESTARTER: receiving restart request from $login")
val serviceIntent = Intent(context, MessengerService::class.java)
serviceIntent.putExtra(LOGIN, login)
context.startService(serviceIntent)
}}
Part of my server responsible for listening:
val clientsSocket = ServerSocket(CLIENTS_PORT)
val serviceSocket = ServerSocket(SERVICE_PORT)
serviceSocket.setReuseAddress(true)
println("Server socket ready!")
println("Service socket port: ${serviceSocket.localPort}")
thread(start = true) {
while(true) ClientThread(clientsSocket.accept(), loggedInUsers, pendingRequests).start()
}
thread(start = true) {
while(true) ServiceThread(serviceSocket.accept(), loggedInUsers).start()
}
And ServiceThread:
class ServiceThread(val socket: Socket,
val loggedInUsers: HashMap<String, UserConnection>) : Thread() {
private var login = ""
private val input = ObjectInputStream(socket.getInputStream())
private val output = ObjectOutputStream(socket.getOutputStream())
override fun run() {
var request = input.readObject() as Request
login = request.content[LOGIN] as String
var userConn: UserConnection?
synchronized(loggedInUsers) {
userConn = loggedInUsers[login]
if(request.action == START_SERVICE) {
println("SERVICE THREAD: New socket conn from $login")
userConn?.run {
println("SERVICE THREAD: putting $login output to logged in users")
serviceStream = output
if(pendingMessage != null) {
output.writeObject(Request(SEND,
mutableMapOf(RESULT to SUCCESS, DATA to pendingMessage)))
pendingMessage = null
}
}
}
}
try { request = input.readObject() as Request }
catch(e: IOException) {
println(e.printStackTrace())
cleanUp()
return#run
}
if(request.action == EXIT) {
println("SERVICE THREAD: Service of user $login is terminating")
cleanUp()
}
}
private fun cleanUp() {
synchronized(loggedInUsers) {
output.close()
input.close()
socket.close()
loggedInUsers[login]?.serviceStream = null
}
}}