mik3y / usb-serial-for-android

Android USB host serial driver library for CDC, FTDI, Arduino and other devices.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

android device reboots during data transfer after some time or reconnecting after recent connection

yamin8000 opened this issue · comments

My issue may look unprofessional, especially by the way I'm presenting it, but somehow, on some devices (Honor/Car Navigation Android devices), this library keeps rebooting the device, Sometimes during data transfer and sometimes after reconnecting after a recent disconnection, On many other devices it's completely OK.
I tried to find the exact Exception but until now I failed to catch an Exception, maybe even it doesn't throw anything at all.
You may ask for a code sample which I can provide but it's nothing unordinary and I'm using SerialInputOutputManager.Listener and onNewData listener.
I tried to catch exceptions and send them to Crashlytics and a custom exception-saving mechanism but until now nothing is thrown. I think saving exceptions to a file inside the device is a better idea than even monitoring the device using Wifi ADB.
I never expected a simple code could cause an Android device to reboot, does anybody had a similar issue working with USB Serial?
I suspected that the USB device (Arduino Uno) that sends data maybe is the issue and uses too much power and changed it (tried with another SMD Arduino and an ESP8266) and also used another simple app to monitor data and it works fine.
A portion of my code:

class UsbHelper(
    private val context: Context,
    private val stateCallback: UsbStateCallback,
    private val bpDataCallback: BloodPressureDataCallback? = null,
    private val dataCallback: MainDataCallback? = null
) {
    private val permissionIntent = PendingIntent.getBroadcast(
        context,
        0,
        Intent(USB.INTENT_ACTION_GRANT_USB),
        PendingIntent.FLAG_MUTABLE
    )

    private var port: UsbSerialPort? = null

    private var connection: UsbDeviceConnection? = null

    private var usbManager: UsbManager

    private var usbDevice: UsbDevice? = null

    private var ioManager: SerialInputOutputManager? = null

    private var statusCallback: ((String) -> Unit)? = null


    private val serialListener = object : SerialInputOutputManager.Listener {

        private var buffer = ""

        override fun onNewData(bytes: ByteArray?) {
            if (bytes != null) {
                statusCallback?.invoke("${context.getString(R.string.data_received)} ${now()}")
                val data = String(bytes)
                buffer += data
                val mainDataMatch = USB.Protocol.MAIN_DATA_REGEX.find(buffer)
                val bpIntermediateMatch = USB.Protocol.BP_INTERMEDIATE_REGEX.find(buffer)
                if (mainDataMatch != null) {
                    processData(mainDataMatch.value)
                    buffer = ""
                    log("valid main data: ${mainDataMatch.value}")
                    //statusCallback?.invoke("valid main data: ${mainDataMatch.value}")
                } else if (bpIntermediateMatch != null) {
                    processData("${bpIntermediateMatch.value}f")
                    buffer = ""
                    log("valid bp intermediate data: ${bpIntermediateMatch.value}")
                    //statusCallback?.invoke("valid bp intermediate data: ${bpIntermediateMatch.value}")
                } //else statusCallback?.invoke("raw: $data")
                log("raw data: $data")
            }
        }

        override fun onRunError(e: Exception?) {
            log(e?.message ?: "")
        }
    }

    init {
        usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
        try {
            context.registerReceiver(
                permissionReceiver(),
                IntentFilter(USB.INTENT_ACTION_GRANT_USB)
            )
            context.registerReceiver(
                attachReceiver(),
                IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED)
            )
            context.registerReceiver(
                disconnectReceiver(),
                IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED)
            )
        } catch (e: Exception) {
            reportException(e)
            log(e.stackTraceToString())
        }
    }

    private fun attachReceiver() = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            stateCallback.onAttach()
            refreshWithReview()
        }
    }

    fun setStatusCallback(callback: (String) -> Unit) {
        statusCallback = callback
    }

    private fun disconnectReceiver() = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            stop()
            stateCallback.onDisconnected()
        }
    }

    private fun permissionReceiver() = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == USB.INTENT_ACTION_GRANT_USB)
                handleUsbPermissionIntent(intent)
        }
    }

    private fun handleUsbPermissionIntent(
        intent: Intent
    ) {
        val isGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
        log("USB granted: $isGranted")
        if (isGranted)
            statusCallback?.invoke(context.getString(R.string.usb_permission_granted))

        val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)

        connect(device)

        log("USB permission for device:${device?.deviceName} is granted? $isGranted")
    }

    fun connect(
        deviceFromIntent: UsbDevice?
    ) {
        log("Connecting")
        val drivers = getDrivers()
        val probedDevice = drivers.firstOrNull()?.device
        val port = drivers.firstOrNull()?.ports?.firstOrNull()
        usbDevice = deviceFromIntent ?: probedDevice
        this.port = port
        if (usbDevice != null && port != null) {
            if (usbManager.hasPermission(usbDevice)) {
                connection = usbManager.openDevice(usbDevice)
                handleDataTransfer()
                stateCallback.onConnected()
            } else usbManager.requestPermission(usbDevice, permissionIntent)
        } else stateCallback.onNoDevice()
    }

    private fun getDrivers(): List<UsbSerialDriver> {
        val drivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)
        return if (drivers == null || drivers.isEmpty())
            getCustomProber().findAllDrivers(usbManager)
        else drivers
    }

    private fun handleDataTransfer() {
        log("Transferring data")
        openPort()
        setPortParameters()
        ioManager = SerialInputOutputManager(port, serialListener)
        ioManager?.setThreadPriority(10)
        ioManager?.readTimeout = 1
        ioManager?.start()
    }

    private fun openPort() {
        try {
            port?.open(connection)
        } catch (e: IOException) {
            reportException(e)
            val failedToOpenPortMessage = e.message ?: "Failed to open port"
            log(failedToOpenPortMessage)
            log(e.stackTraceToString())
            statusCallback?.invoke(failedToOpenPortMessage)
        }
    }

    private fun setPortParameters() {
        try {
            port?.setParameters(
                9600,
                UsbSerialPort.DATABITS_8,
                UsbSerialPort.STOPBITS_1,
                UsbSerialPort.PARITY_NONE
            )
            port?.dtr = true
            port?.rts = true
        } catch (e: Exception) {
            reportException(e)
            val failedToSetPortParameters = e.message ?: "Failed to set port parameters"
            log(failedToSetPortParameters)
            log(e.stackTraceToString())
            statusCallback?.invoke(failedToSetPortParameters)
        }
    }

    private fun processData(
        rawData: String
    ) {
        val data = rawData.removePrefix(USB.Protocol.START.toString())
        when (rawData.last()) {
            USB.Protocol.HEIGHT_WEIGHT_TEMPERATURE -> {
                processWeightHeightTemperature(data)
            }

            USB.Protocol.BLOOD_PRESSURE -> processBloodPressure(data)
            USB.Protocol.BLOOD_OXYGEN -> processWeightHeightOxygen(data)
            else -> {}
        }
    }

    private fun processWeightHeightOxygen(
        data: String
    ) {
        processWeightHeight(data)
        processOxygen(data)
    }

    private fun processOxygen(data: String) {
        dataCallback?.onNewOxygen(getNewOxygen(data))
    }

    private fun getNewOxygen(
        data: String
    ) = data.substring(USB.Protocol.OXYGEN_RANGE)
        .sanitizeForNumberParsing()
        .toIntOrNull() ?: 0

    private fun processBloodPressure(
        data: String
    ) {
        bpDataCallback?.onNewBloodPressure(getNewBloodPressure(data))
    }

    private fun getNewBloodPressure(
        data: String
    ) = BloodPressure(
        sys = data.substring(USB.Protocol.SYS_RANGE).parseThreeDigitFloat(),
        dia = data.substring(USB.Protocol.DIA_RANGE).parseThreeDigitFloat(),
        pulse = getNewPulse(data)
    )

    private fun getNewPulse(
        data: String
    ) = data.substring(USB.Protocol.PULSE_RANGE)
        .sanitizeForNumberParsing()
        .toIntOrNull() ?: 0

    private fun processWeightHeightTemperature(
        data: String
    ) {
        processWeightHeight(data)
        processTemperature(data)
    }

    private fun processTemperature(
        data: String
    ) {
        val newTemperature = data.substring(USB.Protocol.TEMPERATURE_RANGE).parseFloat()
        dataCallback?.onNewTemperature(newTemperature)
    }

    private fun processWeightHeight(
        data: String
    ) {
        val temp = getWeightAndHeight(data.take(USB.Protocol.WEIGHT_HEIGHT_SIZE))
        dataCallback?.onNewWeight(temp.weight)
        dataCallback?.onNewHeight(temp.height)
    }

    private fun getWeightAndHeight(
        data: String
    ) = WeightAndHeight(
        weight = data.substring(USB.Protocol.WEIGHT_RANGE).parseFloat(),
        height = data.substring(USB.Protocol.HEIGHT_RANGE).parseFloat()
    )

    fun print() {
        if (usbDevice != null) {
            statusCallback?.invoke(context.getString(R.string.printing))
            write(USB.Protocol.Commands.PRINT)
        } else stateCallback.onNoDevice()
    }

    fun refreshWithReview() {
        refresh()
        write(USB.Protocol.Commands.REVIEW)
    }

    fun refresh() {
        stop()
        connect(usbDevice)
        log("refreshing")
    }

    fun stop() {
        usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
        ioManager?.listener = null
        ioManager = null
        usbDevice = null
        try {
            port?.close()
        } catch (e: IOException) {
            reportException(e)
            log(e.message ?: "Port: $port already closed.")
        }
        port = null
        connection?.close()
        connection = null
    }

    fun write(data: String) {
        ioManager?.writeAsync(data.toByteArray())
    }
}

Could be to high power consumption or a bug in the Linux kernel of the particular Android device, both unlikely to be caught on application level.

Could be to high power consumption or a bug in the Linux kernel of the particular Android device, both unlikely to be caught on application level.

It's silly but the problem was fixed for now when the power adapter of that particular Android device was changed.
Thank you.