How to set up USB-serial program connection #2

02 Android

System: Android

Language: Java

Lib: PL2303hxd drivers for android that include android sample & lib (.jar)

Well, compared with macOS, usb-to-serial isn’t that pretty in android system, mainly due to the inconsistency of android versions across different devices; not to mention, to implement the exact same procedure, java codes are 10x bigger than that of python. Not sure about K though.

A question that you may wonder first: is a usb-serial communication a usb or a serial communication in nature? well, both. Between android USB port and usb-serial converter(the bridge) USB port, its USB — where you might find UsbManager still applies but the endpoint bulkTransfer might not work as expected, because between the bridge serial and your device serial port, its serial communication.

Conversion bridge simply scans all USB ports and search for the endpoints that are responsible for bulk “sending” (with a direction of 0x80), and “receiving”, respectively, in order to “bridge” to the corresponding serial ports. - way I understand it by browsing through its library class files.

If you got lucky, simply applies the library from drivers. Use its api and voilà, it works. Otherwise you might need to work on the kernel.

In part #1 “pyserial in macOS”, the usb to serial port mapping have been gracefully handled and application using `pyserial` doesn't need to worry about a thing.

Factors that could cause it to fail

But luck is on my side I guess. I simply applied the api lib. And it works. But not without struggling. Here are my trundling & stumbling — factors that could cause a failure

  • USB permission is incorrectly handled in the program

In android, USB permission is granted on broadcast receiver therefore it takes time for communication port is ready to connect and communicate.

In drivers lib, simply pass the app package to api for usb permission to be granted. Or you could implement it by yourself by registering attached intent for the device to be found and granted as soon as being observed by the system.

  • USB port is not ready & connected before sending data
  • Port is not correctly configured especially the baud rate, parity check & stop bit and timeout.

its not for USB port, but for the serial port on the other side ( refer to part #1 serial in python)

  • Read at a wrong time

Depending on the receive buffer receiving/reading mechanism, apply the correct strategy when handling the read() command. In my programme, it simply didn’t work if I call it too early no matter what timeout I have set. And the data will break off if I call read too fast and too frequently which is scary enough.

How I did it

  • USB permission
  1. Intent filter

manifest.xml :

<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/device_filter" />
</activity>

res/xml/device_filter.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>
<usb-device vendor-id="1367" product-id="8200" />
</resources>

2. Or just trust the lib api to handle it

UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
usbSerial = UsbSerial.getInstance(usbManager, ChoosePaymentActivity.this,
"com.xxx.xxxxxxxx");

UsbSerial is a class wrapper to wrap the drivers class:

private UsbSerial(UsbManager usbManager, Context theContext, String packageName){

ACTION_USB_PERMISSION = packageName + ".USB_PERMISSION"; //com.vit.ninjanetspos.USB_PERMISSION
mSerial = new PL2303Driver(usbManager,
theContext, ACTION_USB_PERMISSION);
...
}
  • Init the port
java
private PL2303Driver.BaudRate mBaudrate = PL2303Driver.BaudRate.B115200;
private PL2303Driver.DataBits mDataBits = PL2303Driver.DataBits.D8;
private PL2303Driver.Parity mParity = PL2303Driver.Parity.NONE;
private PL2303Driver.StopBits mStopBits = PL2303Driver.StopBits.S1;
private PL2303Driver.FlowControl mFlowControl = PL2303Driver.FlowControl.OFF;


public Boolean initPort(){
return mSerial.InitByPortSetting(mBaudrate,
mDataBits,mStopBits,mParity,mFlowControl);
}
  • Wait for the usb to be ready

first time loading takes longer, subsequent is instant

java
if(!usbSerial.isReady() || !usbSerial.isConnected()) {
final Runnable r = new Runnable() {
public void run() {
Log.d(TAG, "fired in runnable");
if(usbSerial.isConnected()){
}
}
};
final Handler handler = new Handler();
handler.postDelayed(r, 3 * 1000);
}
  • Write/ send data
public int write(String s){
initPort();
byte[] bytesToWrite = HexUtils.stringToHexByteArray(s);
int l = mSerial.write(bytesToWrite, bytesToWrite.length);
Log.d(TAG, "bytes sent (" +l + "):"+ s);
return l;
}
  • Read

Read is a bit tricky here. The device in my project, responds with ACK (echo) first ( after sending data), then responds with actual transaction result. ACK is almost immediate but the real response takes between 1 to 30 seconds. But as a user app, there is no reason to make user UI wait for 30 seconds if response arrives much earlier. Hence there I make a countdown task to read every 3 seconds…

txnTimer = new CountDownTimer(30000, 3000) {
public void onTick(long millisUntilFinished) {
Log.d(TAG, "txn countdown fired");
Log.d(TAG, "Countdown onTick" + millisUntilFinished);
read();
}
private void read() {
posTxn = usbSerial.read();
if (posTxn.length() != 0) {
txnTimer.cancel();
postDismissMessage(posMessage.convertStatusMessage(
posMessage.decode(posTxn)));
}//else continue
}

public void onFinish() {
read();
if (posTxn.length() == 0) {

postDismissMessage(PosMessage.USER_PROMPT_ERROR);
}
}
};

txnTimer.start();
  • Inform main UI thread of result
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable(){
@Override
public void run() {
Toast toast = Toast.makeText(ChoosePaymentActivity.this,
message, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();

}
});

Retrospecting:

There must be more graceful way to deal with the reading thread. But then awkward thing is either message for handler or intentService - putExtra requires parcelable object to be passed between the handling thread and main thread. It isn’t very efficient in the actual implementation. So I dropped it for greater convenience. Yeah...

\( ̄▽ ̄)/

Photo by Bernard Hermant on Unsplash

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Megan M Yang

Megan M Yang

Full-stack programmer, product manager, politics enthusiast and won't shut up about it.