How can stop receiving data from Bluetooth without Loss the Previous data? - android

I want to receive sensor data from Arduino with HC-05 Bluetooth module in Kotlin.
I have a receiveData function and when I clicking on the start button , receiveData is called and data show on the text view ;
This is working correctly.
But I want to stop receive data when I clicking on the Stop button and keep that Previous data on my text view , but I don't have any idea for the stop button.
This is my start monitoring button setOnClickListener:
btn_startMonitoring.setOnClickListener { receiveData() }
My receiveData function is this :
private fun receiveData() {
val handler = Handler()
var stopWorker = false
var readMessage = ""
val buffer = ByteArray(1024)
val workerThread = Thread {
while (!Thread.currentThread().isInterrupted && !stopWorker) {
try {
val bytes = m_bluetoothSocket!!.inputStream.read(buffer)
if (bytes > 0) {
readMessage += String(buffer, 0, bytes)
handler.post {
mTxtReceive!!.append(readMessage)
scrollView.post(Runnable
{ scrollView.fullScroll(View.FOCUS_DOWN) })
}
}
else {
Toast.makeText(this , "bytes is less than zero" , Toast.LENGTH_SHORT).show()
}
} catch (ex: IOException) {
stopWorker = true
}
}
}
workerThread.start()
}

Related

suspend IO function never return

I have difficulties writing an UDP message receive loop for Android.
In the following code, in receiveLoop, the call to receiveMessages never returns and I therefore never enter the message treatment loop.
Note that I am still able to receive packets, but it stops when the channel buffer is full.
I would expect receiveMessages to return immediately, while the blocking IO loop inside it would still run forever.
class MySocketUDP(private val params: SocketParams) {
private val rcvSocket: DatagramSocket by lazy {
val sock = DatagramSocket(params.rcvPort)
sock.reuseAddress = true
sock.soTimeout = 1000
sock
}
suspend fun receiveMessages(channel: SendChannel<Message>) {
withContext(Dispatchers.IO) {
val buf = ByteArray(MAX_MSG_SIZE)
while (true) {
val pkt = DatagramPacket(buf, buf.size)
try {
if (channel.isClosedForSend) {
break
}
rcvSocket.receive(pkt)
val msg = packetToMessage(buf, 0, pkt.length)
Log.d("SOCKET", "filling channel with $msg")
channel.send(msg)
} catch (ex: SocketTimeoutException) {
} catch (ex: CancellationException) {
break
}
}
}
}
}
class MyModel {
private suspend fun receiveLoop(socket: MySocketUDP) {
withContext(Dispatchers.Main) {
val channel = Channel<Message>(16)
socket.receiveMessages(channel)
Log.d("MODEL", "Entering msg loop")
for (msg in channel) {
dispatchRcvMessage(msg)
}
}
}
}
Why does receiveMessages never return while it is running in the IO dispatcher and called from the Main dispatcher?
Do I need to actually spawn a thread to such producer/consumer work?
Can you show how to achieve such long blocking code nicely in a "coroutine-friendly" manner?
Thank you
receiveMessages() is a suspend function which calls another suspend function withContext(), which in turn has an infinite loop. So calling socket.receiveMessages(channel) will suspend code execution while the loop is not finished.
You need to launch separate coroutines for consumer and producer, e.g. using launch function.
Some example of using coroutines:
val someScope = CoroutineScope(Dispatchers.Main)
private suspend fun receiveLoop(socket: MySocketUDP) = someScope.launch {
val channel = Channel<Message>(16)
socket.receiveMessages(channel)
Log.d("MODEL", "Entering msg loop")
for (msg in channel) {
dispatchRcvMessage(msg)
}
}
// In MySocketUDP
suspend fun receiveMessages(channel: SendChannel<Message>) {
someAnotherScope.launch { // or can use coroutineScope builder function
val buf = ByteArray(MAX_MSG_SIZE)
while (true) {
val pkt = DatagramPacket(buf, buf.size)
try {
if (channel.isClosedForSend) {
break
}
rcvSocket.receive(pkt)
val msg = packetToMessage(buf, 0, pkt.length)
Log.d("SOCKET", "filling channel with $msg")
channel.send(msg)
} catch (ex: SocketTimeoutException) {
} catch (ex: CancellationException) {
break
}
}
}
}

How to use socket in Android with Kotlin

I want to achieve something in Android using Kotlin to do:
If I click a button on the app, the app sends a word to a TCP server (which I wrote with python). The server will send back another word, and the app will show a toast message.
Here is what I have done so far, I can figure out the sending part but I can't manage to make it keep listening to the socket to hear from the server.
I am trying to use coroutine but after finding all the resources online, this is as best as I can get.
Also, I am not sure if I am setting the IP address in the correct manner.
Thank you in advance for your help!
'''
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sendBtn = findViewById<Button>(R.id.sendBtn )
val ipBtn = findViewById<Button>(R.id.ipBtn)
val ipInput = findViewById<TextView>(R.id.ipInput)
var ipAddress: String = "192.168.0.101"
// Below is my attempt to keep listening to the socket, if commented, the sending would work.
// My guess is the IO thread is caught in the while loop so the other coroutines cannot use
// IO thread to send to the server.
CoroutineScope(IO).launch{
val socket = Socket(ipAddress, 9999)
var text = ""
while (true) {
text = BufferedReader(InputStreamReader(socket.inputStream)).readLine()
// if text is not null
// Toast.makeText(this#MainActivity, "Set IP", Toast.LENGTH_SHORT).show()
}
}
suspend fun sendMessage(message:String){
val socket = Socket(ipAddress, 9999)
socket.outputStream.write(message.toByteArray())
socket.close()
}
ipBtn.setOnClickListener {
Toast.makeText(this#MainActivity, "Set IP", Toast.LENGTH_SHORT).show()
ipAddress = ipInput.text.toString()
}
sendBtn .setOnClickListener {
CoroutineScope(IO).launch {
Log.d("TAG", "message")
sendMessage("record")
}
}
'''
To send a Data from P2P Fist we required a Server and Client . Here the Socket act as end point for sending and receiving data across the network .
Create a Server Class like this
class ServerClass() :Thread(){
lateinit var serverSocket:ServerSocket
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
lateinit var socket: Socket
override fun run() {
try {
serverSocket = ServerSocket(8888)
socket = serverSocket.accept()
inputStream =socket.getInputStream()
outputStream = socket.getOutputStream()
}catch (ex:IOException){
ex.printStackTrace()
}
val executors = Executors.newSingleThreadExecutor()
val handler = Handler(Looper.getMainLooper())
executors.execute(Runnable{
kotlin.run {
val buffer = ByteArray(1024)
var byte:Int
while (true){
try {
byte = inputStream.read(buffer)
if(byte > 0){
var finalByte = byte
handler.post(Runnable{
kotlin.run {
var tmpMeassage = String(buffer,0,finalByte)
Log.i("Server class","$tmpMeassage")
}
})
}
}catch (ex:IOException){
ex.printStackTrace()
}
}
}
})
}
fun write(byteArray: ByteArray){
try {
Log.i("Server write","$byteArray sending")
outputStream.write(byteArray)
}catch (ex:IOException){
ex.printStackTrace()
}
}
}
Create a client Class where we need to pass hostaddress
class ClientClass(hostAddress: InetAddress): Thread() {
var hostAddress: String = hostAddress.hostAddress
lateinit var inputStream: InputStream
lateinit var outputStream: OutputStream
lateinit var socket: Socket
fun write(byteArray: ByteArray){
try {
outputStream.write(byteArray)
}catch (ex:IOException){
ex.printStackTrace()
}
}
override fun run() {
try {
socket = Socket()
socket.connect(InetSocketAddress(hostAddress,8888),500)
inputStream = socket.getInputStream()
outputStream = socket.getOutputStream()
}catch (ex:IOException){
ex.printStackTrace()
}
val executor = Executors.newSingleThreadExecutor()
var handler =Handler(Looper.getMainLooper())
executor.execute(kotlinx.coroutines.Runnable {
kotlin.run {
val buffer =ByteArray(1024)
var byte:Int
while (true){
try{
byte = inputStream.read(buffer)
if(byte>0){
val finalBytes = byte
handler.post(Runnable{
kotlin.run {
val tmpMeassage = String(buffer,0,finalBytes)
Log.i("client class", tmpMeassage)
}
})
}
}catch (ex:IOException){
ex.printStackTrace()
}
}
}
})
}
}
make sure server and client port should be same , this is two way communication where we can transfer data in both sides .

Google Speech to Text does not return results

I want Google Speech to text API to recognize a short phrase after I press a button. So I came up with the following code. But it keeps returning no results. I'm quite confused, there are results in there (the buffer etc.), the mic is working well and is enabled in the emulator. Google console also doesn't show errors.
Here's my code.
Click listener that starts the recording:
val clicker: View.OnClickListener = View.OnClickListener {
Log.d(TAG, "Starting record thread")
mAudioRecorder.record(LISTEN_TIME_MILLIS)
}
mReadButton.setOnClickListener(clicker)
Here's a broadcast receiver that processes the results and tries to send them to Google:
private val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
if (intent!!.getBooleanExtra(RECORDING_SUCCESS, false)) {
val byteArrayExtra = intent.getByteArrayExtra(RECORDING_AUDIO)
val audioResultByteString: ByteString = ByteString.copyFrom(byteArrayExtra)
if (audioResultByteString.size() > 0) {
val audio: RecognitionAudio = RecognitionAudio.newBuilder()
.setContent(audioResultByteString).build()
val resultsList = mSpeechClient.recognize(config, audio).resultsList
if (resultsList.size > 0) {
for (result in resultsList) {
val resultText = result.alternativesList[0].transcript
}
}
Log.d(TAG, "- Done recognition. Result Qty: ${resultsList.size}")
}
}
}
}
Here is the AudioRecorder class function, which does the recording:
fun record(listenTimeMillis: Long) {
val byteString: ByteString = ByteString.EMPTY
mAudioRecorder = initAudioRecorder()
val mBuffer = ByteArray(4 * AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL, ENCODING))
mAudioRecorder!!.startRecording()
Thread {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
Thread.sleep(listenTimeMillis)
val read = mAudioRecorder!!.read(mBuffer, 0, mBuffer.size, AudioRecord.READ_NON_BLOCKING)
val intent = Intent(RECORDING_COMPLETED_INTENT)
try {
if (read > 0) {
intent.putExtra(RECORDING_AUDIO, mBuffer)
intent.putExtra(RECORDING_SUCCESS, true)
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
} catch (e: Exception) {
Log.e(TAG, e.stackTrace.toString())
}
releaseAudioRecorder()
}.start()
}
I solved this. The thing to blame was a too small buffer size. So the recognition server was actually getting half a second of audio record which it obviously couldn't recognize.
val mBuffer = ByteArray(4 * AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL, ENCODING))
instead of 4 I put 200 and instead of AudioRecord.READ_NON_BLOCKING I have put AudioRecord.READ_BLOCKING and I read the buffer in a loop and increase the offset in each iteration. Then it started working.
val startTime = System.currentTimeMillis()
var deltaTime = 0L
var offset = 0
val intent = Intent(RECORDING_COMPLETED_INTENT)
val readChunk = 512
while (deltaTime < listenTimeMillis && offset < mBuffer.size) {
val read = mAudioRecord!!.read(mBuffer, offset, readChunk, AudioRecord.READ_BLOCKING)
if (read < 0) {
intent.putExtra(RECORDING_SUCCESS, false)
break; //if read with error, end here
}
deltaTime = System.currentTimeMillis() - startTime //startTime is a while loop breaking condition so it lestens only for specified amount of time
offset += readChunk
}

how to receive a string via bluetooth in kotlin

I need to create a function that through a loop reads from the buffer a string received via bluetooth, this is what i did so far but it doesn't work, and if I use a while loop it just gets stuck in the loop, maybe I'm missing the right condition.
private suspend fun getmeData(){
var bytes :Int
val buffer: ByteArray = ByteArray(1024)
var readMessage : String = ""
if (m_isConnected ) {
try {
while(){
//read bytes received and ins to buffer
bytes =_bluetoothSocket!!.inputStream.read(buffer)
//convert to string
readMessage = readMessage + String(buffer, 0,bytes)
}
} catch (ex: Exception) {
ex.printStackTrace()
}
} else {
val toast = Toast.makeText(
contesto, "Non connesso",
Toast.LENGTH_LONG
)
toast.show()
}
}
Hi guys after an interesting conversation with a colluege i found the solution, the while's condition inside the "try" code must be like this, since the string i m receving contains an end string symbol
while (!(readMessage.contains(';'))){
/*my code*/
}
Thanks to everyone for the effort! <3

Data received from Android Bluetooth socket is getting plotted in the line chart only after socket's termination

I have recently started working on Android, Kotlin and MPAndroidChart. I am developing an Android application that receives data from a bluetooth server and the data obtained should be plotted in real time using MPAndroidChart.
Here is the Kotlin code:
package com.example.flowsensor
import ...
class ConnectionActivity:AppCompatActivity() {
companion object{
val TAG = "FlowSensor"
val APP_NAME = "FlowSensor"
var myUUID: UUID = UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
var mBluetoothSocket: BluetoothSocket? = null
lateinit var mProgress: ProgressDialog
lateinit var mBluetoothAdapter: BluetoothAdapter
var mIsConnected: Boolean = false
lateinit var mAddress: String
lateinit var editText:EditText
lateinit var mChart:LineChart
var xVal:Int = 0
var yVal:Int = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_connection)
mChart = findViewById<LineChart>(R.id.line_chart)
receive_button.setOnClickListener{
ReceiveData()
}
}
//1 parameters missing
private fun mAddEntry() {
Log.d(TAG, "yVal : $yVal")
var mData = mChart.data
if(mData != null){
var mDataSet = mData.getDataSetByIndex(0)
if(mDataSet == null){
mDataSet = createDataSet()
mData.addDataSet(mDataSet)
}
var mEntry:Entry = Entry( xVal.toFloat(), yVal.toFloat())
xVal++
mData.addEntry(mEntry, 0)
//Notify chart data has changed
mChart.notifyDataSetChanged()
//Limit no of visible entries
// mChart.setVisibleXRange(1f, 6f)
mChart.setVisibleXRangeMaximum(6f)
//Scroll to the last entry
mChart.moveViewToX(xVal.toFloat())
}
}
//1 parameter missing
private fun createDataSet(): LineDataSet? {
var mDataSet = LineDataSet(null, "Data vals")
//mDataSet.setDrawCubic
mDataSet.cubicIntensity = 0.2f
mDataSet.axisDependency = YAxis.AxisDependency.LEFT
mDataSet.setColor(ColorTemplate.getHoloBlue())
mDataSet.setCircleColor(ColorTemplate.getHoloBlue())
mDataSet.lineWidth = 2f
mDataSet.circleSize = 4f
mDataSet.fillAlpha = 65
mDataSet.fillColor = ColorTemplate.getHoloBlue()
mDataSet.highLightColor = Color.rgb(244, 117, 177)
mDataSet.valueTextColor = Color.WHITE
mDataSet.valueTextSize = 10f
return mDataSet
}
private fun ReceiveData() {
val buffer = ByteArray(1024) // buffer store for the stream
var bytes: Int // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
Log.d(TAG, "Inside ReceiveData()")
while (true) { // Read from the InputStream
if(mBluetoothSocket != null)
{
try {
bytes = mBluetoothSocket!!.inputStream.read(buffer)
val incomingMessage = String(buffer, 0, bytes)
Log.d(TAG, "InputStream: $incomingMessage")
yVal = incomingMessage.toInt()
mAddEntry()
} catch (e: IOException) {
Log.e(TAG, "write: Error reading Input Stream. " + e.message)
break
}
}
}
}
}
Here is the logcat log logged in verbose mode.
App starts in the Main Activity(code not attached here), looks for paired devices then using
val intent = Intent(this, ConnectionActivity::class.java)
intent.putExtra(EXTRA_ADDRESS, address)
startActivity(intent) code, control reaches Connection Activity (code attached above).
App is successfully receiving data from server; verified by observing logcat. In ReceiveData(), I am trying to pass the received data to mAddEntry() which is responsible for drawing the graph. But the problem is, data is plotted only after I terminate the socket, so after Line no 112 in the attached logcat log, all the data is plotted at once (no data loss). I want to plot data in real time, and this is the problem I am facing.
Note: graph plotting has been independently verified by passing dummy data in real-time inside onCreate() in ConnectionActivity class using the timer.scheduleAtFixedRate. This is working as expected.
by calling the reciveData() inside the receive_button onClickListner you are executing while loop that will run until Bluetooth socket is connected and it is runing on the MainThread (UIThread) and it will block the UI thread and doesn't let any other ui updateto happen you should execute the reciveData() function on the background and when you want the plot the data in chart you should pass the data in ui thread or call the mAddEntry() function in ui thread

Categories

Resources