首頁 > 軟體

Android串列埠通訊SerialPort的使用詳情

2022-09-08 18:04:49

1.什麼是串列埠?

在不會使用串列埠通訊之前,暫且可以把它理解為“一個可通訊的口”;使用篇不深入探討理論及原理。能理解串列埠如何使用之後,可以檢視淺談Android串列埠通訊SerialPort原理

2.新增依賴

1.)在 module 中的 build.gradle 中的 dependencies 中新增以下依賴:

dependencies {
    //串列埠
    implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0'
}

2.)低版本的 gradle 在Project 中的 build.gradle 中的 allprojects 中新增以下 maven倉庫 (不新增任然無法載入SerialPort);

allprojects {
    repositories {
        maven { url "https://jitpack.io" }//maven倉庫
    }
}

高版本的 gradle 已經廢棄了 allprojects 在 settings.gradle 中 repositories 新增以下maven倉庫(不新增任然無法載入SerialPort);

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
        maven { url "https://jitpack.io" }//maven倉庫
    }
}

3.編寫串列埠處理類

1.)串列埠處理類:SerialHandle ;簡單概括這個類,就是通過串列埠物件去獲取兩個流(輸入流、輸出流),通過者兩個流來監聽資料或者寫入指令,硬體收到後執行。同時注意設定引數(只要支援串列埠通訊的硬體,一般說明書上都會有寫)

package com.chj233.serialmode.serialUtil;
 
import android.serialport.SerialPort;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
 * 串列埠實處理類
 */
public class SerialHandle implements Runnable {
 
    private static final String TAG = "串列埠處理類";
    private String path = "";//串列埠地址
    private SerialPort mSerialPort;//串列埠物件
    private InputStream mInputStream;//串列埠的輸入流物件
    private BufferedInputStream mBuffInputStream;//用於監聽硬體返回的資訊
    private OutputStream mOutputStream;//串列埠的輸出流物件 用於傳送指令
    private SerialInter serialInter;//串列埠回撥介面
    private ScheduledFuture readTask;//串列埠讀取任務
 
    /**
     * 新增串列埠回撥
     *
     * @param serialInter
     */
    public void addSerialInter(SerialInter serialInter) {
        this.serialInter = serialInter;
    }
 
    /**
     * 開啟串列埠
     *
     * @param devicePath 串列埠地址(根據平板的說明說填寫)
     * @param baudrate   波特率(根據對接的硬體填寫 - 硬體說明書上"通訊"中會有標註)
     * @param isRead     是否持續監聽串列埠返回的資料
     * @return 是否開啟成功
     */
    public boolean open(String devicePath, int baudrate, boolean isRead) {
        return open(devicePath, baudrate, 7, 1, 2, isRead);
    }
 
    /**
     * 開啟串列埠
     *
     * @param devicePath 串列埠地址(根據平板的說明說填寫)
     * @param baudrate   波特率(根據對接的硬體填寫 - 硬體說明書上"通訊"中會有標註)
     * @param dataBits   資料位(根據對接的硬體填寫 - 硬體說明書上"通訊"中會有標註)
     * @param stopBits   停止位(根據對接的硬體填寫 - 硬體說明書上"通訊"中會有標註)
     * @param parity     校驗位(根據對接的硬體填寫 - 硬體說明書上"通訊"中會有標註)
     * @param isRead     是否持續監聽串列埠返回的資料
     * @return 是否開啟成功
     */
    public boolean open(String devicePath, int baudrate, int dataBits, int stopBits, int parity, boolean isRead) {
        boolean isSucc = false;
        try {
            if (mSerialPort != null) close();
            File device = new File(devicePath);
            mSerialPort = SerialPort // 串列埠物件
                    .newBuilder(device, baudrate) // 串列埠地址地址,波特率
                    .dataBits(dataBits) // 資料位,預設8;可選值為5~8
                    .stopBits(stopBits) // 停止位,預設1;1:1位停止位;2:2位停止位
                    .parity(parity) // 校驗位;0:無校驗位(NONE,預設);1:奇校驗位(ODD);2:偶校驗位(EVEN)
                    .build(); // 開啟串列埠並返回
            mInputStream = mSerialPort.getInputStream();
            mBuffInputStream = new BufferedInputStream(mInputStream);
            mOutputStream = mSerialPort.getOutputStream();
            isSucc = true;
            path = devicePath;
            if (isRead) readData();//開啟識別
        } catch (Throwable tr) {
            close();
            isSucc = false;
        } finally {
            return isSucc;
        }
    }
 
    // 讀取資料
    private void readData() {
        if (readTask != null) {
            readTask.cancel(true);
            try {
                Thread.sleep(160);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //此處睡眠:當取消任務時 執行緒池已經執行任務,無法取消,所以等待執行緒池的任務執行完畢
            readTask = null;
        }
        readTask = SerialManage
                .getInstance()
                .getScheduledExecutor()//獲取執行緒池
                .scheduleAtFixedRate(this, 0, 150, TimeUnit.MILLISECONDS);//執行一個迴圈任務
    }
 
    @Override//每隔 150 毫秒會觸發一次run
    public void run() {
        if (Thread.currentThread().isInterrupted()) return;
        try {
            int available = mBuffInputStream.available();
            if (available == 0) return;
            byte[] received = new byte[1024];
            int size = mBuffInputStream.read(received);//讀取以下串列埠是否有新的資料
            if (size > 0 && serialInter != null) serialInter.readData(path, received, size);
        } catch (IOException e) {
            Log.e(TAG, "串列埠讀取資料異常:" + e.toString());
        }
    }
 
    /**
     * 關閉串列埠
     */
    public void close(){
        try{
            if (mInputStream != null) mInputStream.close();
        }catch (Exception e){
            Log.e(TAG,"串列埠輸入流物件關閉異常:" +e.toString());
        }
        try{
            if (mOutputStream != null) mOutputStream.close();
        }catch (Exception e){
            Log.e(TAG,"串列埠輸出流物件關閉異常:" +e.toString());
        }
        try{
            if (mSerialPort != null) mSerialPort.close();
            mSerialPort = null;
        }catch (Exception e){
            Log.e(TAG,"串列埠物件關閉異常:" +e.toString());
        }
    }
 
    /**
     * 向串列埠傳送指令
     */
    public void send(final String msg) {
        byte[] bytes = hexStr2bytes(msg);//字元轉成byte陣列
        try {
            mOutputStream.write(bytes);//通過輸出流寫入資料
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 把十六進位製表示的位元組陣列字串,轉換成十六進位制位元組陣列
     *
     * @param
     * @return byte[]
     */
    private byte[] hexStr2bytes(String hex) {
        int len = (hex.length() / 2);
        byte[] result = new byte[len];
        char[] achar = hex.toUpperCase().toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1]));
        }
        return result;
    }
 
    /**
     * 把16進位制字元[0123456789abcde](含大小寫)轉成位元組
     * @param c
     * @return
     */
    private static int hexChar2byte(char c) {
        switch (c) {
            case '0':
                return 0;
            case '1':
                return 1;
            case '2':
                return 2;
            case '3':
                return 3;
            case '4':
                return 4;
            case '5':
                return 5;
            case '6':
                return 6;
            case '7':
                return 7;
            case '8':
                return 8;
            case '9':
                return 9;
            case 'a':
            case 'A':
                return 10;
            case 'b':
            case 'B':
                return 11;
            case 'c':
            case 'C':
                return 12;
            case 'd':
            case 'D':
                return 13;
            case 'e':
            case 'E':
                return 14;
            case 'f':
            case 'F':
                return 15;
            default:
                return -1;
        }
    }

2.)串列埠回撥SerialInter;簡單概括一下這個類,就是將SerialHandle類中產生的結果,返回給上一層的業務程式碼,解偶合

package com.chj233.serialmode.serialUtil;
/**
 * 串列埠回撥
 */
public interface SerialInter {
    /**
     * 連線結果回撥
     * @param path 串列埠地址(當有多個串列埠需要統一處理時,可以用地址來區分)
     * @param isSucc 連線是否成功
     */
    void connectMsg(String path,boolean isSucc);
    /**
     * 讀取到的資料回撥
     * @param path 串列埠地址(當有多個串列埠需要統一處理時,可以用地址來區分)
     * @param bytes 讀取到的資料
     * @param size 資料長度
     */
    void readData(String path,byte[] bytes,int size);
 
}

 3.)串列埠統一管理SerialManage;簡單概括一下這個類,用於管理串列埠的連線以及傳送等功能,尤其是傳送指令,極短時間內傳送多個指令(例如:1毫秒內傳送10個指令),多個指令之間會相互干擾。可能執行了第一個指令,可能一個都沒執行。這個類不是必須的,如果有更好的方法可以自己定義。

package com.chj233.serialmode.serialUtil;
 
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
 
/**
 * 串列埠管理類
 */
public class SerialManage {
 
    private static SerialManage instance;
    private ScheduledExecutorService scheduledExecutor;//執行緒池 同一管理保證只有一個
    private SerialHandle serialHandle;//串列埠連線 傳送 讀取處理物件
    private Queue<String> queueMsg = new ConcurrentLinkedQueue<String>();//執行緒安全到佇列
    private ScheduledFuture sendStrTask;//迴圈傳送任務
    private boolean isConnect = false;//串列埠是否連線
 
    private SerialManage() {
        scheduledExecutor = Executors.newScheduledThreadPool(8);//初始化8個執行緒
    }
 
    public static SerialManage getInstance() {
        if (instance == null) {
            synchronized (SerialManage.class) {
                if (instance == null) {
                    instance = new SerialManage();
                }
            }
        }
        return instance;
    }
 
    /**
     * 獲取執行緒池
     *
     * @return
     */
    public ScheduledExecutorService getScheduledExecutor() {
        return scheduledExecutor;
    }
 
    /**
     * 串列埠初始化
     *
     * @param serialInter
     */
    public void init(SerialInter serialInter) {
        if (serialHandle == null) {
            serialHandle = new SerialHandle();
            startSendTask();
        }
        serialHandle.addSerialInter(serialInter);
 
    }
 
    /**
     * 開啟串列埠
     */
    public void open() {
        isConnect = serialHandle.open("/dev/ttyS1", 9600, true);//設定地址,波特率,開啟讀取串列埠資料
    }
 
    /**
     * 傳送指令
     *
     * @param msg
     */
    public void send(String msg) {
        /*
         此處沒有直接使用 serialHandle.send(msg); 方法去傳送指令
         因為 某些硬體在極短時間內只能響應一個指令,232通訊一次傳送多個指令會有物理干擾,
         讓硬體接收到指令不準確;所以 此處將指令新增到佇列中,排隊執行,確保每個指令一定執行.
         若不相信可以試試用serialHandle.send(msg)方法迴圈傳送10個不同的指令,看看10個指令
         的執行結果。
         */
        queueMsg.offer(msg);//向佇列新增指令
    }
 
    /**
     * 關閉串列埠
     */
    public void colse() {
        serialHandle.close();//關閉串列埠
    }
 
    //啟動傳送傳送任務
    private void startSendTask() {
        cancelSendTask();//先檢查是否已經啟動了任務 ? 若有則取消
        //每隔100毫秒檢查一次 佇列中是否有新的指令需要執行
        sendStrTask = scheduledExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (!isConnect) return;//串列埠未連線 退出
                if (serialHandle == null) return;//串列埠未初始化 退出
                String msg = queueMsg.poll();//取出指令
                if (msg == null || "".equals(msg)) return;//無效指令 退出
                serialHandle.send(msg);//傳送指令
            }
        }, 0, 100, TimeUnit.MILLISECONDS);
    }
    //取消傳送任務
    private void cancelSendTask() {
        if (sendStrTask == null) return;
        sendStrTask.cancel(true);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sendStrTask = null;
    }
}

4.使用串列埠

package com.chj233.serialmode;
 
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.chj233.serialmode.serialUtil.SerialInter;
import com.chj233.serialmode.serialUtil.SerialManage;
public class MainActivity extends AppCompatActivity implements SerialInter {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SerialManage.getInstance().init(this);//串列埠初始化
        SerialManage.getInstance().open();//開啟串列埠
        findViewById(R.id.send_but).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SerialManage.getInstance().send("Z");//傳送指令 Z 
            }
        });
    }
    @Override
    public void connectMsg(String path, boolean isSucc) {
        String msg = isSucc ? "成功" : "失敗";
        Log.e("串列埠連線回撥", "串列埠 "+ path + " -連線" + msg);
    }
    @Override//若在串列埠開啟的方法中 傳入false 此處不會返回資料
    public void readData(String path, byte[] bytes, int size) {
//        Log.e("串列埠資料回撥","串列埠 "+ path + " -獲取資料" + bytes);
    }
}

5.總結

串列埠通訊對於Android開發者來說,僅需關注如何連線、操作(傳送指令)、讀取資料;無論是232、485還是422,對於開發者來說連線、操作、讀取程式碼都是一樣的

到此這篇關於Android串列埠通訊SerialPort的使用詳情的文章就介紹到這了,更多相關Android SerialPort內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com