USB掃碼槍獲取數據流的實現方式

硬件條件:

  1. OTG接口轉換器(或者自帶usb接口的設備(大頭))
  2. USB掃碼槍(支持USB虛擬串口)
  3. 安卓設備

實現方式:

  1. 串口方式
  2. USB方式

使用場景:

在掃碼槍連接機器的時候,當掃碼槍掃描到內容時,獲取掃描到的內容。

解決的問題:

之前掃碼槍使用的是USB模式(即掃描到的內容會直接輸入到EditText之中,並且末尾會增加一個回車鍵),但是後來發現,當二維碼包含中文的時候,中文不會輸出進來,於是乎,使用以上兩種方式實現。

實現思路:

連接掃碼槍,因爲數據肯定是以字節流的方式發送的,那麼我們只需獲取到輸入的字節流,自己處理成需要的內容就可以了。

實現方式------串口方式:

    需要經過以下幾個步驟:

  • 集成谷歌原生serial_port包,參考該文章集成就可以了: Android串口集成
  • 尋找設備串口地址(這個咱們僅僅是爲了獲取串口地址,因爲串口地址是固定的,所以這裏可以不加在程序中,咱們目的是爲了知道USB掃碼槍的串口地址)
  • 連接設備
  • 獲取數據流

步驟一:集成谷歌原生serial_port包,這個自己看上面教程了。

步驟二:尋找設備串口地址,在完成步驟一之後,會有一個SerialPortFinder的類,這個類的作用是尋找咱們安卓設備上連接的串口設備(包括掃碼槍),具體獲取掃碼槍串口地址的思路:獲取未插入掃碼槍時所有設備的串口地址------》獲取插入掃碼槍時所有設備的串口地址

-----》看看多出那個,多出的那個就是掃碼槍的串口地址了,廢話不多說,直接貼代碼了:

 case R.id.btn_print://獲取所有串口設備地址,mFinder就是SerialPortFinder
                StringBuffer stringBuffer = new StringBuffer();
                for (String str:mFinder.getAllDevicesPath()) {
                    stringBuffer.append(str + "\n");
                }
                mTvMessage.setText(stringBuffer.toString());
                break;

怎麼找USB掃碼槍你懂我意思吧。

步驟三:連接設備:

廢不多講,首先咱們連接到串口設備,如果你連接不上,那麼就是步驟一有問題,請到步驟一那篇博客問問作者:

 private String mSerialPath = "/dev/ttyUSB0" ;//物理串口地址,這個就是咱們步驟二找到的地址了,這是我的設備的地址,你的自己找去
    private int baudrate = 9600;//波特率,這個是可以掃碼槍自己設置的,看說明書
    private SerialPort mSerialPort;
    private InputStream mInputStream;


    @Override
    protected int getContentView() {
        return R.layout.activity_test;
    }

    @Override
    protected void initView() {
        super.initView();
        initSerialPort();
    }

    private void initSerialPort(){//連接串口設備,建議加try,硬件設備你永遠不知道會爲啥崩潰
        try {
            mSerialPort = new SerialPort(new File(mSerialPath),baudrate);
            mInputStream = mSerialPort.getInputStream();
            ReadThread thread = new ReadThread();//這是讀取數據流的線程,代碼在下方貼出
            thread.start();//啓動數據流讀取線程
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

步驟四:獲取數據流,這時候步驟三,我們開啓了一個線程讀取掃碼槍的數據流,這時候我寫了一個死循環,輪詢掃碼槍發送的內容,線程代碼如下

private InputStream mInputStream;


/**
     * 讀串口線程
     */
    private class ReadThread extends Thread {//這是我的內部類,我代碼習慣不好,別瞎雞兒抄
        @Override
        public void run() {
            super.run();
            while ((!Thread.currentThread().isInterrupted())) {
                final int size;
                try {
                    if (mInputStream == null)
                        return;
                    final byte[] buffer = new byte[512];
                    size = mInputStream.read(buffer);//這就是掃描到的內容了
                    if (size > 0) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    ToastUtils.makeText(TestActivity.this,new String(buffer,"GB2312"));//此處我設置的編碼是GB2312,這個可以看說明書,可以設置的
                                } catch (UnsupportedEncodingException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    }
                    Thread.sleep(300);//你自己可以設置睡眠時間,這個睡眠時間可以會影響識別速度,那麼就可以叫用戶加錢,你懂我意思吧。
                } catch (Exception e) {
                    //如果拋出異常則再次設置中斷請求
                    Thread.currentThread().interrupt();
                    return;
                }
            }
        }
    }

嗯,到了這一步,大功告成了。

注意:串口方式實現的話兼容性不好,因爲遇到部分安卓設備直接閹割了這部分功能,所以我纔會研究USB方式的。

驚不驚喜,意不意外。

實現方式------USB方式:

該種方式好處在於不需要集成serial_port包,但是裏面一些類理解比較靈性,USB方式需要經過以下幾個步驟:

  • 尋找需要連接的devices設備
  • 連接設備
  • 獲取數據流

步驟一:尋找需要連接的devices設備,這裏的話有兩種場景

  1.USB掃碼槍一直插在安卓設備上,我們需要在程序啓動的時候自動找到並且連接

  2.USB掃碼槍在熱拔插的情況下,我們需要在插入的時候連接

第一種的話我是直接寫了一個方法,在程序啓動的時候檢查時候插入需要支持的設備類型,如果有,那麼直接連接,判斷方法是通過設備的pid和vid對比進行判斷,pid和vid獲取方式自己百度,或者把掃碼槍插在電腦上,在設備管理可以看到,我通過findDevices方法找到需要連接的掃碼槍設備。

貼代碼:

  private UsbManager mManager;
   

 //查找已連接的設備
    UsbDevice findDevices() {
        if (null == mManager) {
            return null;
        }
        HashMap<String, UsbDevice> mMap = mManager.getDeviceList();
        for (UsbDevice device : mMap.values()) {
            if (BaseScanner.isS5920(device) || BaseScanner.isS5600(device)) {//判斷設備是否支持的類型
                return device;
            }
        }
        return null;
    }

這是baseScanner,這個類名我覺得不夠準確,它是用來記錄需要支持的設備的信息的,包含設備的判斷方法:

public class BaseScanner{
    private static final int scanner5600_pid = 4456;
    private static final int scanner5600_vid = 866;
    private static final int scanner5920_pid = 9527;
    private static final int scanner5920_vid = 549;

    public static boolean isS5600(UsbDevice usbDevice){//是不是s5600掃碼槍
        return usbDevice.getProductId() == scanner5600_pid && usbDevice.getVendorId() == scanner5600_vid;
    }

    public static boolean isS5920(UsbDevice usbDevice){//是不是s5920
        return usbDevice.getProductId() == scanner5920_pid && usbDevice.getVendorId() == scanner5920_vid;
    }
}

第二種USB掃碼槍熱拔插連接方式,這時候我們採用的是廣播監聽的方式,因爲設備撥叉都會有廣播發送,這條廣播包含的信息包含一個USBDevices對象(也就是掃碼槍),我們需要動態註冊拔,插兩條廣播,靜態註冊不生效,代碼如下:

//這是廣播類,因爲我的掃描是寫在一個service中,所以把連接的設備交給service處理就可以
public class USBBroadcastReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        String action = intent.getAction();
        if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null) {
                // call your method that cleans up and closes communication with the device
                Toast.makeText(context, "拔出", Toast.LENGTH_LONG).show();
            }
        }else if(UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)){//設備插入
            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);//獲取插入的設備
            Intent intent1 = new Intent(context,ScannerService.class);
            Bundle bundle = new Bundle();
            bundle.putParcelable("device",device);
            intent1.putExtra("data",bundle);//將設備包入intent中,交給service處理
            context.startService(intent1);
            Toast.makeText(context, "插入", Toast.LENGTH_LONG).show();
        }
    }

}

我是在程序啓動的時候就註冊了這個廣播如下:

public class MyApplication extends Application {

    private USBBroadcastReceiver mUsbReceiver;

    @Override
    public void onCreate() {
        super.onCreate();
        registBroadCast();
    }

    @Override
    public void onTerminate() {
        unregisterReceiver(mUsbReceiver);
        super.onTerminate();
    }

    private void registBroadCast(){//註冊USB插拔廣播
        mUsbReceiver = new USBBroadcastReceiver();
        IntentFilter usbDeviceStateFilter = new IntentFilter();

        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);//插入設備狀態
        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);//拔出設備狀態
        registerReceiver(mUsbReceiver, usbDeviceStateFilter);
    }

}

步驟二,連接設備,這邊我把所有都寫註釋裏面:

private UsbDevice mDevide;
    private UsbDeviceConnection mConnect;
    private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    private static UsbConnectManager mInstance;
    private ScannerListener mListener;
    private UsbManager mManager;
    private UsbEndpoint mUsbEndpointIn;
    private UsbInterface mUsbInterface;
    private Thread mReadingthread;


 public void connetDevice(UsbDevice device, Context context) {
        disConnect();//斷開之前的連接
        mDevide = device;//當前需要連接的設備
        try {
            if (!mManager.hasPermission(mDevide)) {//檢查是否有掃碼槍的訪問權限,如果沒有,那麼請求一下臨時權限
                PendingIntent intent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
                mManager.requestPermission(mDevide, intent);
            }
            mConnect = mManager.openDevice(mDevide);//打開設備
            mUsbInterface = device.getInterface(0);//獲取數據的接口.這個可以理解成設備有多少個線頭子
            mUsbEndpointIn = mUsbInterface.getEndpoint(0);//獲取設備的輸出流向,輸入或者輸出因爲usb掃碼槍只有一個輸出項,所以直接選擇0
            if (mConnect.claimInterface(mUsbInterface, true)) {//獲取設備數據寫入流,如果獲取成功,那麼數據已經通常,我這邊寫的是一個獲取數據,輸入數據的話把getEndpoint(0)換成輸入的接口即可
                Log.i(TAG, "connetDevice: find device,start get data!!!");
                startReading();//開啓讀取數據流的線程
            } else {
                mConnect.close();
                Log.i(TAG, "connetDevice: not find device !!!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

步驟三,獲取輸入的流,並解析成需要的內容,代碼如下:

    //開線程讀取數據
    private void startReading() {
        if (null != mReadingthread) {
            mReadingthread.interrupt();
        }
        mReadingthread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!mReadingthread.isInterrupted()) {
                    synchronized (this) {
                        byte[] bytes = new byte[128];
                        int ret = mConnect.bulkTransfer(mUsbEndpointIn, bytes, bytes.length, 100);//獲取數據流
                        if (ret > 0) {
                            try {
                                byte[] bs = new byte[ret];
                                System.arraycopy(bytes, 0, bs, 0, ret);//獲取數據長度
                                String str = new String(bs,"GB2312");//轉換成漢字
                                if (null != mListener){
                                    mListener.onScanner(str);//你自己需要的處理方式
                                }
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        });
        mReadingthread.start();
    }

OK啦,USB模式的也完成了。

有疑問或者有建議的膀友可以加羣:497438697  我是羣裏的 杭州-大魔王,有艾特必應。

祝大家新年快樂,萬事亨通!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章