Development of Android JNI serial port

Recently, the company is going to make a card player in the form of a display card. Android system has a serial port at the bottom of the display card to receive the attendance information of everyone's bracelet playing cards. This requires reading the id information of each bracelet or work card. Therefore, the jni function of Android studio is used here to make summary notes, which is convenient to pull down and use in the future

  • When the project is created, as shown in the figure, the file native lib.cpp will be included. This file is the C + + function developed by the main serial port, including the settings of opening / closing the serial port, etc

  • native-lib.cpp file
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <android/log.h>

static const char *TAG="serial_port";
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

extern "C" JNIEXPORT jstring

JNICALL
Java_com_jetsen_joy_carddemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

static speed_t getBaudrate(jint baudrate)
{
    switch(baudrate) {
        case 0: return B0;
        case 50: return B50;
        case 75: return B75;
        case 110: return B110;
        case 134: return B134;
        case 150: return B150;
        case 200: return B200;
        case 300: return B300;
        case 600: return B600;
        case 1200: return B1200;
        case 1800: return B1800;
        case 2400: return B2400;
        case 4800: return B4800;
        case 9600: return B9600;
        case 19200: return B19200;
        case 38400: return B38400;
        case 57600: return B57600;
        case 115200: return B115200;
        case 230400: return B230400;
        case 460800: return B460800;
        case 500000: return B500000;
        case 576000: return B576000;
        case 921600: return B921600;
        case 1000000: return B1000000;
        case 1152000: return B1152000;
        case 1500000: return B1500000;
        case 2000000: return B2000000;
        case 2500000: return B2500000;
        case 3000000: return B3000000;
        case 3500000: return B3500000;
        case 4000000: return B4000000;
        default: return -1;
    }
}


extern "C"
JNIEXPORT jobject JNICALL
Java_com_jetsen_joy_carddemo_SerialPort_open(JNIEnv *env, jclass thiz, jstring path,
                                             jint baudrate) {

    int fd;
    speed_t speed;
    jobject mFileDescriptor;

    /* Get the set baud rate here */
    {
        speed = getBaudrate(baudrate);
        if (speed == -1) {
            /* TODO: throw an exception */
            LOGE("Invalid baudrate");
            return NULL;
        }
    }

    /* Open serial port */
    {
        jboolean iscopy;
        const char *path_utf = env->GetStringUTFChars(path, &iscopy);
//        LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
//        fd = open(path_utf, O_RDWR | flags);
        fd = open(path_utf, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
        LOGD("open() fd = %d", fd);
        env->ReleaseStringUTFChars(path, path_utf);
        if (fd == -1)
        {
            /* Throw an exception */
            LOGE("Cannot open port");
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /*Configuring serial port */
    {
        struct termios cfg;
        LOGD("Configuring serial port");
        if (tcgetattr(fd, &cfg))//Initial setting of serial port configuration
        {
            LOGE("tcgetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }

        cfmakeraw(&cfg);
        cfsetispeed(&cfg, speed);// The input baud rate stored in the structure is speed. If the input baud rate is set to 0, the actual input baud rate will be equal to the output baud rate
        cfsetospeed(&cfg, speed);//Set the output baud rate stored in the termios structure pointed by termios to speed

        if (tcsetattr(fd, TCSANOW, &cfg))//Set the configuration of serial port. TCSANOW takes effect immediately
        {
            LOGE("tcsetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /* Generate a file descriptor FileDescriptor*/
    {
        jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
        jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor, "<init>", "()V");
        jfieldID descriptorID = env->GetFieldID( cFileDescriptor, "descriptor", "I");
        mFileDescriptor = env->NewObject(cFileDescriptor, iFileDescriptor);
        env->SetIntField(mFileDescriptor, descriptorID, (jint)fd);
    }

    return mFileDescriptor;
}



extern "C"
JNIEXPORT void JNICALL
Java_com_jetsen_joy_carddemo_SerialPort_close(JNIEnv *env, jobject thiz) {

    jclass SerialPortClass = env->GetObjectClass( thiz);
    jclass FileDescriptorClass = env->FindClass( "java/io/FileDescriptor");

    jfieldID mFdID = env->GetFieldID( SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I");

    jobject mFd = env->GetObjectField(thiz, mFdID);
    jint descriptor = env->GetIntField(mFd, descriptorID);

    LOGD("close(fd = %d)", descriptor);
    close(descriptor);

}
  • Create a new SerialPort class, which is mainly the open close serial port method. Create these two local methods. Android will automatically call the corresponding method in jni at runtime, so as to open and close the specified serial port;
public class SerialPort {

    private final String TAG=SerialPort.class.getName();

    private FileDescriptor mFd;
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;

    public SerialPort(File device, int baudrate) throws SecurityException, IOException {
        /* This is to determine the read-write permission of the serial port */
        if (!device.canRead() || !device.canWrite()) {
            try {
                /*If not, you need to apply for permission. This place needs to provide permission for Android device root */
                Process su;
                su = Runtime.getRuntime().exec("/system/bin/su");
                String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
                        + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                if ((su.waitFor() != 0) || !device.canRead()
                        || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }
        //Call jni and open the serial port
        this.mFd = open(device.getAbsolutePath(), baudrate);
        if(this.mFd == null) {
            Log.e(TAG, "native open returns null");
            throw new IOException();
        } else {
            //Obtain the input and output flow of communication with serial port
            this.mFileInputStream = new FileInputStream(this.mFd);
            this.mFileOutputStream = new FileOutputStream(this.mFd);
        }
    }

    // Getters and setters
    public InputStream getInputStream() {
        return mFileInputStream;
    }

    public OutputStream getOutputStream() {
        return mFileOutputStream;
    }

    static {
        //The name of this static library must be the same as that in CMakeLists.txt
        System.loadLibrary("native-lib");
    }
    // JNI
    private native static FileDescriptor open(String path, int baudrate);
    public native void close();


}
 
  • SerialPortUtil serial port tool class, which processes the opening, closing and receiving data of the serial port
public class SerialPortUtil {

    private String TAG = SerialPortUtil.class.getSimpleName();
    private SerialPort mSerialPort;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;
    private String path = "/dev/ttyS3";//It's not fixed. The serial port of my hardware device to read the bracelet information is ttyS3
    private int baudrate = 9600;//It is not fixed. The baud rate serial port needs information from the same device provider
    private static SerialPortUtil portUtil;
    private OnDataReceiveListener onDataReceiveListener = null;//Declare interface variables
    private boolean isStop = false;



    //Defining interfaces
    public interface OnDataReceiveListener {
        public void onDataReceive(byte[] buffer, int size);
    }

    public void setOnDataReceiveListener(OnDataReceiveListener dataReceiveListener) {
        onDataReceiveListener = dataReceiveListener;
    }

    public static SerialPortUtil getInstance() {
        if (null == portUtil) {
            portUtil = new SerialPortUtil();
            portUtil.onCreate();
        }
        return portUtil;
    }

    /**
     * Initialize serial communication
     */
    private void onCreate() {
        try {
            mSerialPort = new SerialPort(new File(path), baudrate);
            mOutputStream = mSerialPort.getOutputStream();
            mInputStream = mSerialPort.getInputStream();

            mReadThread = new ReadThread();
            isStop = false;
            mReadThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Cycle to read the information of Bracelet
     */
    private class ReadThread extends Thread {

        @Override
        public void run() {
            super.run();
            while (!isStop && !isInterrupted()) {
                int size;
                try {
                    if (mInputStream == null)
                        return;

                    if (mInputStream.available() > 0){
                        byte[] buffer = new byte[mInputStream.available()];
                        size = mInputStream.read(buffer);
                        if (size > 0) {

                            if (null != onDataReceiveListener) {
                                onDataReceiveListener.onDataReceive(buffer, size);
                            }
                        }

                    }

                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

    /**
     * Close serial port
     */
    public void closeSerialPort() {
        isStop = true;
        if (mReadThread != null) {
            mReadThread.interrupt();
        }
        if (mSerialPort != null) {
            mSerialPort.close();
        }
    }


}
  • Processing of data read in mainactivity class
public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    TextView tv;
    SerialPortUtil mSerialPortUtil;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());



        mSerialPortUtil = SerialPortUtil.getInstance();
        
        mSerialPortUtil.setOnDataReceiveListener(new SerialPortUtil.OnDataReceiveListener() {
            @Override
            public void onDataReceive(final byte[] buffer, final int size) {

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //Process the read data
                        tv.setText("Read it. size:" + size + "--" + new String(buffer) + "end");
                        Toast.makeText(MainActivity.this,
                                "I got it size:" + size + "--" + new String(buffer) + "end",
                                Toast.LENGTH_LONG).show();

                    }
                });

            }
        });

    }


    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

 

Tags: Android Java

Posted on Fri, 10 Jan 2020 11:56:00 -0500 by d_priyag