Young man, your resume says you’ve been involved in serial communication, so tell me!

1. What is serial communication

Serial communication technology refers to the communication between the two parties according to the bit, a way of communication in accordance with the time sequence

Serial port is equivalent to a pipe, which is also represented in hardware. There are three jumpers, one is Tx line, one is Rx line, and one is ground wire. The data transmitted by this pipe, namely bit, is serial and sequential

2. Application scenarios of serial ports

Serial communication is not used much in the development of Android. Most of our apps use Http to communicate with the background, obtain background data and display, while serial communication is applied in the scene of smart home and MCU communication, face recognition access control, and the use of serial port to control the door switch. Vending machine Android received a successful payment message, send serial port instructions, control the goods channel for shipment and so on Android devices have more than 2 billion, relatively speaking, serial port in Android applications are still quite extensive

3, Android how to achieve serial communication

Step 1 Find the serial port file

Android serial files have a separate directory

This is the file that starts with TTys

How does that work in code

private ArrayList<Driver> getDrivers(a) throws IOException {
    ArrayList<Driver> drivers = new ArrayList<>();
    LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(DRIVERS_PATH));
    String readLine;
    while((readLine = lineNumberReader.readLine()) ! =null) {
        String driverName = readLine.substring(0.0x15).trim();
        String[] fields = readLine.split("+");

        // driverName:/dev/tty
        // driverName:/dev/console
        // driverName:/dev/ptmx
        // driverName:/dev/vc/0
        // driverName:serial
        // driverName:pty_slave
        // driverName:pty_master
        // driverName:unknown

        Log.d(T.TAG, "SerialPortFinder getDrivers() driverName:" + driverName /*+ " readLine:" + readLine*/);
        if ((fields.length >= 5) && (fields[fields.length - 1].equals(SERIAL_FIELD))) { // Determine that the fourth level is not equal to serial
            // The new serial driver is found :serial The serial driver is named /dev/ttys
            Log.d(T.TAG, "SerialPortFinder getDrivers() found the new serial driver is :" + driverName + "This serial port series name is :" + fields[fields.length - 4]);
            drivers.add(new Driver(driverName, fields[fields.length - 4])); }}return drivers;
}

Copy the code

3.2. Step 2 Open the serial port file

When we operate the serial port we first check the permissions

if(! device.canRead() || ! device.canWrite()) {boolean chmod777 = chmod777(device);
    if(! chmod777) { Log.i(T.TAG,"SerialPortManager openSerialPort: No read/write permission");
        if (null! = mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.NO_READ_WRITE_PERMISSION); }return false; }}/** * File set the highest permission 777 read/write/execute *@paramFile You need to get root permission for that file *@returnCheck whether the permission is successfully modified. - Success or failure */ is returned
boolean chmod777(File file) {
    if (null== file || ! file.exists()) {// File does not exist
        return false;
    }
    try {
        // Obtain the ROOT permission
        Process su = Runtime.getRuntime().exec("/system/bin/su");
        // Change the file property to readable, writable, and executable.
        String cmd = "chmod 777 " + file.getAbsolutePath() + "\n" + "exit\n";
        su.getOutputStream().write(cmd.getBytes());
        if (0 == su.waitFor() && file.canRead() && file.canWrite() && file.canExecute()) {
            return true; }}catch (IOException | InterruptedException e) {
        // No ROOT permission
        e.printStackTrace();
    }
    return false;
}

Copy the code

So once we’re done checking permissions, we’re going to open the serial port using code in the NDK, and then the connection between the Java layer and the Native layer is the FileDescriptor which is the fd in the code, and the Native layer returns the FileDescriptor, And then the Java layer’s FileInputStream, FileOutputStream, and FileDescriptor are bound so that the Java layer can read the data

try {
    mFd = openNative(device.getAbsolutePath(), baudRate, 0); // Open the serial port native function
    mFileInputStream = new FileInputStream(mFd); // The stream read is bound (mFd file handle)- the input stream is wrapped with a file handle (mFd)
    mFileOutputStream = new FileOutputStream(mFd); // The written stream is bound (mFd file handle)- the output stream is wrapped with a file handle (mFd)
    Log.i(T.TAG, "SerialPortManager openSerialPort: Serial port is open" + mFd); // The serial port has been opened with FileDescriptor[35] [2]
    if (null! = mOnOpenSerialPortListener) { mOnOpenSerialPortListener.onSuccess(device); } startSendThread();// Start the thread that sends messages
    startReadThread(); // Start the thread that receives the message
    return true; / / [3]
} catch (Exception e) {
    e.printStackTrace();
    if (null != mOnOpenSerialPortListener) {
        mOnOpenSerialPortListener.onFail(device, OnOpenSerialPortListener.Status.OPEN_FAIL);
    }
}
Copy the code

Native

JNIEXPORT jobject JNICALL Java_com_test_openNative
  (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) {
   int fd; // Linux serial file handle (key result of this whole function)
   speed_t speed; // Baud type value
   jobject mFileDescriptor; // File handle (final result returned)

   // Check the baud rate parameters.
   {
      speed = getBaudrate(baudrate);
      if (speed == - 1) {
         LOGE("Invalid baud rate, proving that the user selected the wrong baud rate.");
         return NULL; }}// TODO step 1: Open the serial port
   {
      jboolean iscopy;
      const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
      LOGD("Open serial port path :%s", path_utf); // The path to open the serial port is /dev/ttys0
      fd = open(path_utf, O_RDWR /*| flags*/); // Open the serial port function O_RDWR(read and write)
      LOGD(Open () fd = %d, fd); // open() fd = 44
      (*env)->ReleaseStringUTFChars(env, path, path_utf); // Release operation
      if (fd == - 1) {
         LOGE("Unable to open port");
         return NULL;
      }
   }
   LOGD("Step 1: Open serial port successfully √√√");

   // TODO Step 2: Get and set terminal properties - configure the serial port device
   /* TCSANOW: Change properties immediately after data transfer. TCSADRAIN: Wait until all data transfer ends before changing properties. TCSAFLUSH: Empty the input and output buffers before changing properties. Note: When multiple changes are made, tcgetattr() should be called again after this function to check that all changes were successfully implemented. * /
   {
      struct termios cfg;
      LOGD("Execute configuration serial port...");
      if (tcgetattr(fd, &cfg)) { // Get serial port properties
         LOGE("Failed to configure serial port TCgetattr ()");
         close(fd); // Close the serial port
         return NULL;
      }

      cfmakeraw(&cfg);          // Set the serial port to original mode, and make fd(file handle to serial port readable and writable)
      cfsetispeed(&cfg, speed); // Set the serial port read baud rate
      cfsetospeed(&cfg, speed); // Set the serial port write baud rate

      if (tcsetattr(fd, TCSANOW, &cfg)) { // Obtain the serial port properties again according to the above configuration
         LOGE("Failed to reconfigure serial port TCgetattr ()");
         close(fd); // Close the serial port
         return NULL;
      }
   }
   LOGD("Step 2: Obtaining and setting terminal properties - Configuring the serial port device succeeded √√√");

   // TODO Step 3: Build the FileDescription. Java object and give it rich serial port values
   {
      jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
      jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>"."()V");
      jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor"."I");
      // Reflection generates FileDescriptor objects and assigns (fd==Linux serial file handle) constructor instantiation of FileDescriptor
      mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
      (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd); // fd is the key result of opening the serial port
   }
   LOGD("Step 3: Construct FileDescriptor. Java object and assign rich serial port values. Success √√√");
   return mFileDescriptor; // Return the final result to the Java layer
}
Copy the code

This completes the operation of opening the serial port

3.3. Send and read data

When we read and send data, we’re doing operations on file IO, and we definitely want to do that in child threads,

private void startReadThread(a) {
    mSerialPortReadThread = new SerialPortReadThread(mFileInputStream) {
        @Override
        public void onDataReceived(byte[] bytes) {
            if (null! = mOnSerialPortDataListener) { mOnSerialPortDataListener.onDataReceived(bytes); }}}; mSerialPortReadThread.start(); }/** * Serial port message reader thread * start the thread that receives the message * read serial port data requires thread */
public abstract class SerialPortReadThread extends Thread {

    public abstract void onDataReceived(byte[] bytes);

    private static final String TAG = SerialPortReadThread.class.getSimpleName();
    private InputStream mInputStream; // This input stream ==mFileInputStream(associated mFd file handle)
    private byte[] mReadBuffer; // Used to load the read serial port data

    public SerialPortReadThread(InputStream inputStream) {
        mInputStream = inputStream;
        mReadBuffer = new byte[1024]; / / the buffer
    }

    @Override
    public void run(a) {
        super.run();
        // Is that the same as always executing? Why keep doing it? As long as the App is alive, it needs to read the serial port data sent from the bottom layer
        while(! isInterrupted()) {try {
                if (null == mInputStream) {
                    return;
                }

                Log.i(TAG, "run: ");
                int size = mInputStream.read(mReadBuffer);

                if (-1 == size || 0 >= size) {
                    return;
                }

                byte[] readBytes = new byte[size];
                // Copy to buffer
                System.arraycopy(mReadBuffer, 0, readBytes, 0, size);

                Log.i(TAG, "run: readBytes = " + new String(readBytes));
                onDataReceived(readBytes); // Send out -(indirectly to SerialPortActivity to print display)
            } catch (IOException e) {
                e.printStackTrace();
                return; }}}@Override
    public synchronized void start(a) {
        super.start();
    }

    /** * Close the thread to release resources */
    public void release(a) {
        interrupt();

        if (null! = mInputStream) {try {
                mInputStream.close();
                mInputStream = null;
            } catch(IOException e) { e.printStackTrace(); }}}}private void startSendThread(a) {
    // Start the thread that sends messages
    mSendingHandlerThread = new HandlerThread("mSendingHandlerThread");
    mSendingHandlerThread.start();
    // Handler
    mSendingHandler = new Handler(mSendingHandlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            byte[] sendBytes = (byte[]) msg.obj;

            if (null! = mFileOutputStream &&null! = sendBytes &&0 < sendBytes.length) {
                try {
                    mFileOutputStream.write(sendBytes);
                    if (null! = mOnSerialPortDataListener) { mOnSerialPortDataListener.onDataSent(sendBytes);//}}catch(IOException e) { e.printStackTrace(); }}}}; }Copy the code

Read and write data, in fact, is the two read, read stream operation, so we completed the serial port transceiver data

3.4 Closing the Serial Port

After we use the serial port, we will certainly close the serial port, close the serial port, we will start the read and write thread shut down, in the Native layer also shut down the serial port, file handle bound to the two streams also shut down

/** * Close the serial port */
public void closeSerialPort(a) {
    if (null! = mFd) { closeNative();// Disable the serial port native function
        mFd = null;
    }
    stopSendThread(); // Stop the thread that sent the message
    stopReadThread(); // Stop the thread receiving the message

    if (null! = mFileInputStream) {try {
            mFileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mFileInputStream = null;
    }

    if (null! = mFileOutputStream) {try {
            mFileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mFileOutputStream = null;
    }
    mOnOpenSerialPortListener = null;
    mOnSerialPortDataListener = null;
}
Copy the code

Native layer

Class: cedric_serial_SerialPort * Method: close * Signature: ()V */
JNIEXPORT void JNICALL Java_com_test_closeNative
  (JNIEnv *env, jobject thiz) {
   jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
   jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");

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

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

   LOGD(Close (fd = %d), descriptor);
   close(descriptor); InputStream/OutputStream= Serial port send/receive
}
Copy the code

4, summarize

Serial communication, in fact, is the operation of the file, while reading and writing, just keep up with school when you and your deskmate pass paper like, the above code reference is Google’s open source code, from looking for serial port to close the serial port, combing the basic process of serial communication! Hope to XDM useful, hope brothers one key three connect!