[JAVA] USB serial communication with Android app (usb-serial-for-android)

What i did

I tried to communicate between a commercially available Android tablet and a device with a serial interface. The communication protocol was successful with both plain serial communication and modbus.

Configuration image

The configuration is almost the same as the configuration diagram of Link. This time, my knowledge of serial communication started from scratch, so I looked at this site to death and studied. It was very easy to understand and it was helpful. It probably wouldn't be possible without this site.

Library information

usb-serial-for-android v2.2.2(https://github.com/mik3y/usb-serial-for-android/releases) It's about April 2020, so I'm sorry that the version is old.

What I want to explain

At that time, the content written in the ReadMe about how to use the library was not very substantial, so I wanted to write an article. I was at a loss because it is a little fulfilling now, but I would like to give a supplementary explanation.

However, as mentioned above, serial communication itself was a zero start, so there are many parts to talk about in the image. I would appreciate it if you could point out any mistakes.

About serial connection

First of all, let's talk about the prerequisite knowledge about serial connection. Basically, commercially available Android tablets do not have a serial interface. Therefore, the physical configuration will be a connection like [Android tablet → USB cable → serial USB conversion cable → device].

The Android framework provides an API for USB operation, but not for serial communication. Therefore, it is necessary to realize the image of encapsulating serial communication with USB communication (it looks like a simple USB operation, not serial communication from the application).

The trouble here is that the content of serial communication depends on the "serial USB conversion cable", and the content varies depending on the vendor and product.

You can't implement that, so you need a library.

How to use the library

I will add comments by excerpting from the ReadMe as of January 2021. By the way, it is a sample of multi-thread premise to start a thread for communication. I'm wondering if I should stop the UI thread (or is it impossible due to Android restrictions?), So normally it should be multithreaded.

Initial processing

    // Find all available drivers from attached devices.
    //Create a USB service manager using the standard Android API
    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

    //The following is usb-serial-for-android API. Get a driver for USB serial communication that can be used.
    //Prober, which will be mentioned later, is a mapping table between the vendor ID and product ID of the cable used.
    //AvailableDrivers is populated when there is a USB serial conversion cable registered with Prover and connected
    List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
    if (availableDrivers.isEmpty()) {
        return;
    }

    // Open a connection to the first available driver.
    //Below, in the ReadMe sample, get(0)I have selected the first driver for the time being.
    UsbSerialDriver driver = availableDrivers.get(0);
    //When multiple cables are connected, or because I wanted to fix the available cables,
    //I implemented it like this
    // for(UsbSerialDriver driver: availableDrivers) {
    //     //Extract the ones that match the vendor ID and product ID
    //     String vendorId = Integer.toString(driver.getDevice().getVendorId());
    //     String productId = Integer.toString(driver.getDevice().getProductId());
    //     if(If the vendor ID and product ID match what you intended) {
    //         return driver;
    //     }
    // }    

    //The following is the standard Android API. As the name implies, it opens the device.
    UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
    if (connection == null) {
        // add UsbManager.requestPermission(driver.getDevice(), ..) handling here
        //Here is the handling process when you do not have access to the USB device.
        //I think I'll probably use the Broadcast Receiver mechanism
        return;
    }

    //The following is usb-serial-for-android API. Get here too(0)I'm doing
    //I don't know what kind of situation there are multiple patterns.
    UsbSerialPort port = driver.getPorts().get(0); // Most devices have just one port (port 0)
    //The serial port is open
    port.open(connection);
    //After opening, set the parameters according to the serial communication standard. The reference link is described in "Serial setting contents"
    port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);

Send / receive processing

Simple send and receive

    //I think I usually use the "event processing" described later, so I probably won't use this one.
    //Send
    //request is byte[]Set the content you want to write with the type
    port.write(request, WRITE_WAIT_MILLIS);
    //Receive
    //response is byte[]Set the variable whose contents you want to read by type
    //len contains the received size
    len = port.read(response, READ_WAIT_MILLIS);

I think this is not enough to explain the method, so I will post the JavaDoc of UsbSerialPort.

    /**
     * Reads as many bytes as possible into the destination buffer.
     *
     * @param dest the destination byte buffer
     * @param timeout the timeout for reading in milliseconds, 0 is infinite
     * @return the actual number of bytes read
     * @throws IOException if an error occurred during reading
     */
    public int read(final byte[] dest, final int timeout) throws IOException;

    /**
     * Writes as many bytes as possible from the source buffer.
     *
     * @param src the source byte buffer
     * @param timeout the timeout for writing in milliseconds, 0 is infinite
     * @return the actual number of bytes written
     * @throws IOException if an error occurred during writing
     */
    public int write(final byte[] src, final int timeout) throws IOException;

Event processing

    //The following is usb-serial-for-Send / receive manager class provided by android
    //Basically, I think it's better to use this.
    //However, it is not a very good sample, so I will add it separately in "My implementation example".
    usbIoManager = new SerialInputOutputManager(usbSerialPort, this);
    //Below, I am using SingleThreadExecutor, but anything that can be multithreaded is fine.
    Executors.newSingleThreadExecutor().submit(usbIoManager);
    ...
    port.write("hello".getBytes(), WRITE_WAIT_MILLIS);

//This is a callback function that is called when the communication content is received. But the method is SerialInputOutputManager.Implement Listener
//I need it, so if I write this as it is, it will not work
@Override
public void onNewData(byte[] data) {
    runOnUiThread(() -> { textView.append(new String(data)); });
}
My implementation example

Actually, I was designing various classes, so it is not the same as the following, but only the important points are excerpted. Also, the following implementation was based on Sample code in usbSerialExamples package at that time. I still have sample code, but it seems that the implementation has changed considerably, so It may be better to look at the sample code directly.


//Any class is fine, but you need a mechanism that can call back to the UI thread.
public class SampleActivity extends AppCompatActivity {
    private SerialInputOutputManager mSerialIoManager;
    private SerialInputOutputManager.Listener mListener;
    private Handler mHandler;
    private UsbSerialPort mPort;

    //Any method. Probably it will be implemented in the life cycle such as onCreate.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //The processing equivalent to "initial processing" and the processing required for onCreate are omitted.
        //Serial port creation process
        mPort = driver.getPorts().get(0); // Most devices have just one port (port 0)
        //Generate a handler for UI thread
        mHandler = new Handler(Looper.getMainLooper());
        //Generate listener for callback
        mListener = new Listener {
                //Callback method called when receiving data
                @Override
                public void onNewData(byte[] data) {
                    //Call back the process to the UI thread
                    mainLooper.post(() -> {
                        //What you want to do
                    });
                }

                //Callback method called when some error is detected
                //For example, the cable came off
                @Override
                public void onRunError(Exception e) {
                    //Call back the process to the UI thread
                    mHandler.post(() -> {
                        //What you want to do
                    });
                }
        }
        this.startSerial();
    }

    //Any method. It will probably be implemented in a life cycle such as on Destroy.
    @Override 
    protected void onDestroy() {
        //Omit the processing required for onDestroy
        if (mSerialManager != null) {
            this.stopSerial();
        }
    }

    //Method for starting serial communication
    private void startSerial() {
        if (mPort != null) {
            //Associate the serial communication manager with the serial port and callback when receiving an event
            mSerialIoManager = new SerialInputOutputManager(mPort, mListener);
            //Anything can be multithreaded
            new Thread(mSerialIoManager).start();
        } else {
            //Appropriate error handling
        }
    }

    //Method for stopping serial communication
    private void stopSerial() {
        if (mSerialIoManager != null) {
            //Stop serial communication manager
            mSerialIoManager.stop();
            mSerialIoManager = null;
        }
        if (mPort != null) {
            try {
                //Close the serial port
                mPort.close()
            } catch (IOException e) {
                //Appropriate error handling
            }
        }
    }

By the way, you can also write with the serial communication manager

You can also write


    byte[] data = //Data you want to send
    mSerialIoManager.writeAsync(data);

[Supplement] Serial settings

[See wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%9D%E3% 83% BC% E3% 83% 88 #% E8% A8% AD% E5% AE% 9A )

Impressions

I'm not very familiar with serial communication, but I was able to easily realize communication. I think it's a very useful library.

However, since the amount of information is not so much, error handling and various open / close processing may make you cry a little ...

Also, I feel that this is a limitation of serial communication, but the device seen from the Android application is not the device of [Android tablet → USB cable → serial USB conversion cable → device] as mentioned above, but serial USB conversion It is a cable.

So, it seems that the library doesn't have a mechanism to detect who the real device is. I think it is important to consider what to do with it from a security perspective.

Recommended Posts

USB serial communication with Android app (usb-serial-for-android)
Use serial communication on Android
Import device images with Android app
Face recognition app with OpenCV + Android Studio
I made a rock-paper-scissors app with android
Android weather app
[Android] Despaired story with App UserId and SharedUserId
Vibrate the wristband device with Bluetooth from the Android app
Compatible with Android 10 (API 29)
About Android App Components
Check communication from Android to node.js server with protocol buffers
How to make an app using Tensorflow with Android Studio