felHR85 / UsbSerial

Usb serial controller for Android
MIT License
1.81k stars 589 forks source link

Cannot set text of a TextView inside the read Callback #307

Closed lhtrevisan closed 4 years ago

lhtrevisan commented 4 years ago

Hi friends! Calling myself newbie at Android is too much of a complyment for me, as I don't know Jack s*it about it, but I'm adventuring myself into developing a Self Service Beer Tap sistem using Kotlin language anyways. After struggling a bit, I've managed to make the layout for the app and somewhat setting up the serial communication with an arduino board using this UsbSerial library. At this point I was supposed to code serial reading at the app, but I don't know how to do it. Serial communication is set up in the code exactly as the tutorial made by "Apps in the sky": Part 1: https://www.youtube.com/watch?v=QHa6HWTmQFs Part 2: https://www.youtube.com/watch?v=ICjjG1VsZmw&t

The messages/commands that the android will receive will have the following format: ` Ixxxxxxx> Vxxxxxx>

` The first character will tell the Android what is the information being passed: I = ID of the rfid card read by the Arduino V = Volume measured < = General Command or info ,like , , And the stopbit for every data line or command is ">". Could someone help me on implementing this on the code? Just in case someone gives up reading the header: I'm using Kotlin. Almost forgot to mention: the app template is ItemList/Detail, but the communication will be done all in the main application(ItemListActivity). And sorry for any bad english(I'm brazilian). EDIT: I read #37 but couldn't translate it to my code because it's Java and the code wouldnt' use Stopbits as I intend to use.
lhtrevisan commented 4 years ago

Ok... I got further on it by myself but I'm still facing some problems... Here is what I did so far:

1st: I've added a read line to the serial setup, pointing to a callback:

private val broadcastReceiver = object : BroadcastReceiver(){
    override fun onReceive(context: Context?, intent: Intent?) {
        [...]
        m_serial!!.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF)
        m_serial!!.read(mCallback)
        [...]

2nd: Then I wrote the callback to get the data from Serial:

private val mCallback = UsbSerialInterface.UsbReadCallback {
    MyVariables.receivedText = String(it, Charsets.UTF_8)
    understandSerial(MyVariables.receivedText)
}

3rd: I wrote a function to understand the data received trough the callback:

fun understandSerial(msg: String){
    val byteArray = msg.toByteArray(Charsets.UTF_8)
    var prefix : Char = '0'
    var readingMSG: Boolean = false
    var messageRead: String = ""
    for ((index, bytes) in byteArray.withIndex()){
        if (readingMSG){
            if (bytes == '>'.toByte()){
                readingMSG = false
                handleCommand(prefix, messageRead)  //Handle command/information received here
            } else {
                messageRead += bytes.toString()
                //
            }
        } else {
            if (bytes == 'I'.toByte() || bytes == 'V'.toByte() || bytes == '<'.toByte()) {
                prefix = bytes.toChar()
                messageRead = ""
                readingMSG = true
            }
        }
    }
}

4th: Every command/information received will then be handled separately by another function:

fun handleCommand(prefix: Char, msg: String){
    if (prefix == 'I'){
        // Actions for ID Received trough serial
    } else if (prefix == 'V'){
        // Show/update current poured volume on screen
    } else if (prefix == '<'){
        //Actions for commands received:
        if (msg == "INCOMPATIBLE_TOKEN"){

        } else if (msg == "DOC_AUTH_FAIL"){

        } else if (msg == "DOC_READ_FAIL"){

        } else if (msg == "ABORT_COMMAND"){

        } else if (msg == "ABORT_COMMAND2"){

        } else if (msg == "WDOC"){

        } else if (msg == "UNDEF_DATA"){

        } else if (msg == "MAXVOL_END_SERVICE"){

        } else if (msg == "IDLE_END_SERVICE"){

        } else {
            //UNKNOWN COMMAND RECEIVED
        }
    }
}

Everything seems to work fine like this.

Then I tried to show/update the volume poured, measured by the Arduino:

     } else if (prefix == 'V'){
        tvPouredCost.text = msg
    }
  • Volume is received in this format: "Vxxxx>", and at this point of the code, the variable data is supposed to be "xxxx", without the prefix and stopbit;

Right after the RFID card is read, the app stops working. By the way, the TextView element is declared and initialized right at "onCreate":

private var tvPouredCost: TextView? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    context = this
    setContentView(R.layout.activity_item_list)

    tvPouredCost = findViewById<View>(R.id.tvValor) as TextView
    //tvPouredCost.text = "0mL"

    m_usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
    val filter = IntentFilter()
    filter.addAction(ACTION_USB_PERMISSION)
    filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)
    filter.addAction((UsbManager.ACTION_USB_DEVICE_ATTACHED))
    registerReceiver(broadcastReceiver, filter)
    startUsbConnecting()
  • If I uncomment the text setting line right after the initialization, it sets the text correctly.

I tried putting a breakpoint inside the callback and running debug, but I was unable to identify the root of the problem. What may be happening here?

sivaprashanth commented 4 years ago

i can recommend you to add text updates in a separate thread.

lhtrevisan commented 4 years ago

I tought it'd be better if I posted the update in the same thread because, if someone have the same issue I had, they could benefit from de direct answer that I implemented... Of course I can open another thread for the new problem, but what if the problem is related to the way I made the code? If that's the case, I think it should be solved here.

sivaprashanth commented 4 years ago

i am sorry i meant to run textupdates creating a user thread or a timer in the program to update the textview:

private void startTextupdateThread() {
Thread th = new Thread(new Runnable() {

    public void run() {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                   tvPouredCost.setText();
                }
            });
            try {
                Thread.sleep(1000);
            } 
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

});
th.start();

}

i am not sure this will fix, but u can try as you said it was fine when you commented that line out.

lhtrevisan commented 4 years ago

It Worked! I created a new thread:

private fun startTextUpdateThread(){
    val th = Thread(Runnable {
        // try to touch View of UI thread
        this@ItemListActivity.runOnUiThread(java.lang.Runnable {
            this.tvCreds.text = "R$" + MyVariables.actualCredits
            this.tvValor.text = "R$" + MyVariables.actualPouredCost
            this.tvVolume.text = MyVariables.actualPouredVol + "mL"
        })
    }).start()
}

And passed reference to it inside the handleCommand function:

     else if (prefix == 'V'){
        MyVariables.actualPouredVol = msg
        startTextUpdateThread()
        // Show/update current poured volume on screen
    } 
  • Variables are "MyVariables.___" because I put them inside an object, to have access easily everywhere

MyVariables object looks like this:

object MyVariables{
    var tapNum: Int = 1
    var receivedText: String = ""
    var CommandReceived: String = ""
    var actualPouredCost: String = ""
    var actualPouredVol: String = ""
    var actualCredits: String = ""
}