趁著春節連假來寫個比較複雜的XD
這次來寫關在Android中的USB-OTG傳輸
大家會接觸到OTG這個詞彙,應該是在想拿隨身碟捅Android手機的時候吧↓
沒錯,大概就是這樣的東西
不過這次要講的東西稍微有一點點不一樣就是了
大家有聽說過在工業型產品中的數位訊號輸出(RS485)嗎?(´・д・`)
簡單來說就是某個設備輸出某種公定之特殊格式在不同設備之間進行有線傳輸的方式
好吧,我知道聽不太懂XD
總之可以想像為訊號在電線裡跑來跑去識做的事情就可以了(笑)
那今天的東西就是要來介紹在Android中OTG數值傳輸的相關範例
關於硬體的部分是拿我學生時代做過的玩具來當範例
當時有個學弟提議說要把紅外線感溫模組+Android手機來當做額溫槍使用
所以後來我學弟就搞了個紅外線模組來,然後接出一條線來傳輸紅外線模組感測到的溫度
最後透過手機上的USB孔來傳輸得到的數據的一個功能
給大家看一下長什麼鬼樣子吧
Okay,硬體大概就是這樣
那麼來看一下範例吧
然後,還有Github
→https://github.com/thumbb13555/USB-SerialDemo/tree/master
阿對了,如果要開發這類的軟體的話
我推薦去Google Play載這個App
我當時還沒學會前,我就是用這個在做測試的喔
→https://play.google.com/store/apps/details?id=de.kai_morich.serial_usb_terminal&hl=zh&gl=US
1. 載入庫&介面介紹
1-1 載入庫
不免俗地要來講一下基本設置啦(-‿◦)
首先,以我的功力當然是沒辦法全部用原生套件寫啦XDDD
所以這次我是載入這個第三方庫
→https://github.com/mik3y/usb-serial-for-android
這裡面基本已經幫你把所有的通訊變成套件寫好了,還蠻方便的
那麼載入部分請看到這邊
要載入的部分有兩個,我一一解釋
總之,一樣開啟專案中的build.gradle
然後輸入以下粉底白字的內容
apply plugin: 'com.android.application' android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.demo.usb_serialdemo" minSdkVersion 23 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } repositories { maven { url 'https://jitpack.io' } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation 'com.github.mik3y:usb-serial-for-android:3.3.0' }
按下Sync Now即完成
1-2 介面介紹
基本上就是很單純的東西
我直接貼
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.5" /> <TextView android:id="@+id/textView_Info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textAlignment="center" android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textSize="14sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_Send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <EditText android:id="@+id/editText_Input" android:layout_width="0dp" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" android:text="Name" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/button_Send" app:layout_constraintStart_toStartOf="parent" /> <TextView android:id="@+id/textView_Respond" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintBottom_toTopOf="@+id/editText_Input" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline" /> <TextView android:id="@+id/textView_Status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="TextView" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView_Info" /> </androidx.constraintlayout.widget.ConstraintLayout>
好,來寫MainActivity.java的檔案了
來吧(´∀`)
2. 撰寫USB插拔事件
首先要來寫關於偵測USB之插拔事件
在這部分我們將會用到所謂的"廣播"
阿是有沒有這麼巧啦XD我上週就剛好在寫廣播
幫我衝個點閱吧~哈哈
→碼農日常-『Android studio』Broadcast廣播的應用方法
好,我直接把這小段的重點貼上來
public class MainActivity extends AppCompatActivity { private USBStatus status = new USBStatus(); public static final String TAG = MainActivity.class.getSimpleName() + "My"; private static final String USB_PERMISSION = "USB_Demo"; TextView tvStatus, tvInfo, tvRes; UsbManager manager; List<UsbSerialDriver> drivers; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*註冊廣播*/ IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(USB_PERMISSION); registerReceiver(status, filter); /*Find UIs*/ tvStatus = findViewById(R.id.textView_Status); tvInfo = findViewById(R.id.textView_Info); tvRes = findViewById(R.id.textView_Respond); Button btSend = findViewById(R.id.button_Send); btSend.setOnClickListener(v -> { }); } @Override protected void onStop() { super.onStop(); /*反註冊廣播*/ unregisterReceiver(status); } private class USBStatus extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) return; switch (action) { /**當Check完授權狀態後進入此處*/ /**偵測USB裝置插入*/ case UsbManager.ACTION_USB_DEVICE_ATTACHED: Toast.makeText(context, "USB裝置插入", Toast.LENGTH_SHORT).show(); break; /**偵測USB裝置拔出*/ case UsbManager.ACTION_USB_DEVICE_DETACHED: Toast.makeText(context, "USB裝置拔出", Toast.LENGTH_SHORT).show(); tvInfo.setText("TextView"); tvRes.setText("TextView"); tvStatus.setText("授權狀態: false"); break; } } } }
這部分來講就是個廣播事件
粉底白字的部分是廣播事件的本體以及他所要擷取的事件
橘底白字的部分就是如果截到插入USB時的事件
而綠底白字就是偵測到USB孔被拔出時會回傳的事件
這時候按下執行,插拔USB時就會顯示Toast提醒你囉
3. 取得USB資訊
取得到USB插拔瞬間的回傳後,就下來要寫關於獲取本裝置資訊的程式碼
這邊有兩個副程式,如下
public class MainActivity extends AppCompatActivity { private USBStatus status = new USBStatus(); public static final String TAG = MainActivity.class.getSimpleName() + "My"; private static final String USB_PERMISSION = "USB_Demo"; TextView tvStatus, tvInfo, tvRes; UsbManager manager; List<UsbSerialDriver> drivers; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*註冊廣播*/ IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(USB_PERMISSION); registerReceiver(status, filter); /*Find UIs*/ tvStatus = findViewById(R.id.textView_Status); tvInfo = findViewById(R.id.textView_Info); tvRes = findViewById(R.id.textView_Respond); Button btSend = findViewById(R.id.button_Send); btSend.setOnClickListener(v -> { }); /*偵測是否正在有裝置插入*/ detectUSB(); } @Override protected void onStop() { super.onStop(); /*反註冊廣播*/ unregisterReceiver(status); } private class USBStatus extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) return; switch (action) { /**當Check完授權狀態後進入此處*/ /**偵測USB裝置插入*/ case UsbManager.ACTION_USB_DEVICE_ATTACHED: Toast.makeText(context, "USB裝置插入", Toast.LENGTH_SHORT).show(); detectUSB(); break; /**偵測USB裝置拔出*/ case UsbManager.ACTION_USB_DEVICE_DETACHED: Toast.makeText(context, "USB裝置拔出", Toast.LENGTH_SHORT).show(); tvInfo.setText("TextView"); tvRes.setText("TextView"); tvStatus.setText("授權狀態: false"); break; } } } /**偵測裝置*/ private void detectUSB() { manager = (UsbManager) getSystemService(Context.USB_SERVICE); if (manager == null) return; if (manager.getDeviceList().size() == 0)return; tvStatus.setText("授權狀態: false"); /*取得目前插在USB-OTG上的裝置*/ drivers = getDeviceInfo(); } /**取得目前插在USB-OTG上的裝置列表,並取得"第一個"裝置的資訊*/ private List<UsbSerialDriver> getDeviceInfo() { HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Log.d(TAG, "裝置資訊列表:\n " + deviceList); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); ProbeTable customTable = new ProbeTable(); List<UsbSerialDriver> drivers = null; String info = ""; while (deviceIterator.hasNext()) { /*取得裝置資訊*/ UsbDevice device = deviceIterator.next(); info = "Vendor ID: " + device.getVendorId() + "\nProduct Id: " + device.getDeviceId() + "\nManufacturerName: " + device.getManufacturerName() + "\nProduceName: " + device.getProductName(); /*設置驅動*/ customTable.addProduct( device.getVendorId(), device.getProductId(), CdcAcmSerialDriver.class /*我的設備Diver是CDC,另有 * CP21XX, CH34X, FTDI, Prolific 等等可以使用*/ ); /*將驅動綁定給此裝置*/ UsbSerialProber prober = new UsbSerialProber(customTable); drivers = prober.findAllDrivers(manager); } /*更新UI*/ tvInfo.setText(info); return drivers; } }
程式碼的註解其實都有寫,不過我們還是把他講一講
總之,所有一切的起源都是USBManage這個類別
manager = (UsbManager) getSystemService(Context.USB_SERVICE);
再來我們在getDeviceInfo()裡面取得到所有目前有接上USB孔的裝置(一般都是一個啦...如果有很多個也不是不行ヾ(´¬`)ノ)
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
找到了裝置後,接下來
1. 讀取這個裝置的資訊
2. 載入驅動
private List<UsbSerialDriver> getDeviceInfo() { HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Log.d(TAG, "裝置資訊列表:\n " + deviceList); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); ProbeTable customTable = new ProbeTable(); List<UsbSerialDriver> drivers = null; String info = ""; while (deviceIterator.hasNext()) { /*取得裝置資訊*/ UsbDevice device = deviceIterator.next(); info = "Vendor ID: " + device.getVendorId() + "\nProduct Id: " + device.getDeviceId() + "\nManufacturerName: " + device.getManufacturerName() + "\nProduceName: " + device.getProductName(); /*設置驅動*/ customTable.addProduct( device.getVendorId(), device.getProductId(), CdcAcmSerialDriver.class /*我的設備Diver是CDC,另有 * CP21XX, CH34X, FTDI, Prolific 等等可以使用*/ ); /*將驅動綁定給此裝置*/ UsbSerialProber prober = new UsbSerialProber(customTable); drivers = prober.findAllDrivers(manager); } /*更新UI*/ tvInfo.setText(info); return drivers; }
底色是有對照的噢,再請參照一下(/・0・)
4. 取得USB讀取權限
好,接著是讀取權限
由於USB通訊不能算是"危險權限"
所以他要權限的方式比較鬆散,不像之前都還要去AndroidManifest.xml寫東寫西(╯=▃=)╯︵┻━┻
那麼來直接寫..
public class MainActivity extends AppCompatActivity { private USBStatus status = new USBStatus(); public static final String TAG = MainActivity.class.getSimpleName() + "My"; private static final String USB_PERMISSION = "USB_Demo"; TextView tvStatus, tvInfo, tvRes; UsbManager manager; List<UsbSerialDriver> drivers; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); . . . (略) } @Override protected void onStop() { super.onStop(); /*反註冊廣播*/ unregisterReceiver(status); } private class USBStatus extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) return; switch (action) { /**當Check完授權狀態後進入此處*/ case USB_PERMISSION: if (drivers.size() == 0) return; boolean hasPermission = manager.hasPermission(drivers.get(0).getDevice()); tvStatus.setText("授權狀態: " + hasPermission); if (!hasPermission) { getPermission(drivers); return; } Toast.makeText(context, "已獲取權限", Toast.LENGTH_SHORT).show(); break; /**偵測USB裝置插入*/ case UsbManager.ACTION_USB_DEVICE_ATTACHED: Toast.makeText(context, "USB裝置插入", Toast.LENGTH_SHORT).show(); detectUSB(); break; /**偵測USB裝置拔出*/ case UsbManager.ACTION_USB_DEVICE_DETACHED: Toast.makeText(context, "USB裝置拔出", Toast.LENGTH_SHORT).show(); tvInfo.setText("TextView"); tvRes.setText("TextView"); tvStatus.setText("授權狀態: false"); break; } } } /**偵測裝置*/ private void detectUSB() { manager = (UsbManager) getSystemService(Context.USB_SERVICE); if (manager == null) return; if (manager.getDeviceList().size() == 0)return; tvStatus.setText("授權狀態: false"); /*取得目前插在USB-OTG上的裝置*/ drivers = getDeviceInfo(); /*確認使用者是否有同意使用OTG(權限)*/ getPermission(drivers); } /**取得目前插在USB-OTG上的裝置列表,並取得"第一個"裝置的資訊*/ private List<UsbSerialDriver> getDeviceInfo() { . . . (略) return drivers; } /**確認OTG使用權限,此處為顯示詢問框*/ private void getPermission(List<UsbSerialDriver> drivers) { if (PendingIntent.getBroadcast(this, 0, new Intent(USB_PERMISSION), 0) != null) { manager.requestPermission(drivers.get(0).getDevice(), PendingIntent.getBroadcast( this, 0, new Intent(USB_PERMISSION), 0)); } } }
基本上啦,他權限的計算是從插入到拔出算一次
所以就算是同一個裝置他在插入一次時都還是得問權限
因此getPermission()裡面我沒特別寫判斷
不過這邊可以注意到⚠️
在粉底白字的部分就是回傳使用者已經選擇了要不要賜予權限的選項
所以一但使用者按下後,整個執行緒就會跑到廣播中的USB_PERMISSION去
再來在廣播中我再次判斷是不是真的已經賜予權限了
case USB_PERMISSION: if (drivers.size() == 0) return; boolean hasPermission = manager.hasPermission(drivers.get(0).getDevice()); tvStatus.setText("授權狀態: " + hasPermission); if (!hasPermission) { getPermission(drivers); return; } Toast.makeText(context, "已獲取權限", Toast.LENGTH_SHORT).show();
如果沒有,則再次遞迴到取得權限
所以如果一直按不給權限,那APP就會不斷地不斷地跳出來把你煩死😈
最後,是收發資訊了(;´д`)ゞ
5. 收發資訊
最後是收發資訊的部分,
一樣直接PO再說重點
public class MainActivity extends AppCompatActivity { private USBStatus status = new USBStatus(); public static final String TAG = MainActivity.class.getSimpleName() + "My"; private static final String USB_PERMISSION = "USB_Demo"; TextView tvStatus, tvInfo, tvRes; UsbManager manager; List<UsbSerialDriver> drivers; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*註冊廣播*/ IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(USB_PERMISSION); registerReceiver(status, filter); /*Find UIs*/ tvStatus = findViewById(R.id.textView_Status); tvInfo = findViewById(R.id.textView_Info); tvRes = findViewById(R.id.textView_Respond); Button btSend = findViewById(R.id.button_Send); btSend.setOnClickListener(v -> { /*對裝置送出指令*/ sendValue(drivers); }); /*偵測是否正在有裝置插入*/ detectUSB(); } @Override protected void onStop() { super.onStop(); //(略) } private class USBStatus extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //(略) } } /**偵測裝置*/ private void detectUSB() { //(略) } /**取得目前插在USB-OTG上的裝置列表,並取得"第一個"裝置的資訊*/ private List<UsbSerialDriver> getDeviceInfo() { //(略) //return drivers; } /**確認OTG使用權限,此處為顯示詢問框*/ private void getPermission(List<UsbSerialDriver> drivers) { //(略) } /**送出資訊*/ private void sendValue(List<UsbSerialDriver> drivers) { if (drivers == null) return; /*初始化整個發送流程*/ UsbDeviceConnection connect = manager.openDevice(drivers.get(0).getDevice()); /*取得此USB裝置的PORT*/ UsbSerialPort port = drivers.get(0).getPorts().get(0); try { /*開啟port*/ port.open(connect); /*取得要發送的字串*/ EditText edInput = findViewById(R.id.editText_Input); String s = edInput.getText().toString(); if (s.length() == 0) return; /*設定胞率、資料長度、停止位元、檢查位元*/ port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); /*寫出資訊*/ port.write(s.getBytes(), 200); /*設置回傳執行緒*/ SerialInputOutputManager.Listener serialInputOutputManager = getRespond; SerialInputOutputManager sL = new SerialInputOutputManager(port, serialInputOutputManager); ExecutorService service = Executors.newSingleThreadExecutor(); service.submit(sL); } catch (IOException e) { try { /*如果Port是開啟狀態,則關閉;再使用遞迴法重複呼叫並嘗試*/ port.close(); sendValue(drivers); } catch (IOException ex) { ex.printStackTrace(); Log.e(TAG, "送出失敗,原因: " + ex); } } } /**接收回傳*/ private SerialInputOutputManager.Listener getRespond = new SerialInputOutputManager.Listener() { @Override public void onNewData(byte[] data) { String res = "字串回傳: "+new String(data)+"\nByteArray回傳: "+byteArrayToHexStr(data); Log.d(TAG, "回傳: " + res); runOnUiThread(() -> { tvRes.setText(res); }); } @Override public void onRunError(Exception e) { } }; /**將ByteArray轉成字串可顯示的ASCII*/ private String byteArrayToHexStr(byte[] byteArray) { if (byteArray == null) { return null; } StringBuilder hex = new StringBuilder(byteArray.length * 2); for (byte aData : byteArray) { hex.append(String.format("%02X ", aData)); } String gethex = hex.toString(); return gethex; } }
兩個部分
1. 粉底白字:發送訊息
2. 藍底白字:接收訊息
可是中間的片段真很長...(´Д`)ヾ
我慢慢說吧!
發送訊息:
首先先找到要發送資料的Device以及取得他的通道(Port)
/*初始化整個發送流程*/ UsbDeviceConnection connect = manager.openDevice(drivers.get(0).getDevice()); /*取得此USB裝置的PORT*/ UsbSerialPort port = drivers.get(0).getPorts().get(0);
再來,打開通道並決定包率、終止位元等資訊
/*開啟port*/ port.open(connect); /*取得要發送的字串*/ EditText edInput = findViewById(R.id.editText_Input); String s = edInput.getText().toString(); if (s.length() == 0) return; /*設定胞率、資料長度、停止位元、檢查位元*/ port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
發送ɷ◡ɷ
/*寫出資訊*/ port.write(s.getBytes(), 200);
再來是接收回傳,先把副程式寫好
/**接收回傳*/ private SerialInputOutputManager.Listener getRespond = new SerialInputOutputManager.Listener() { @Override public void onNewData(byte[] data) { String res = "字串回傳: "+new String(data)+"\nByteArray回傳: "+byteArrayToHexStr(data); Log.d(TAG, "回傳: " + res); runOnUiThread(() -> { tvRes.setText(res); }); } @Override public void onRunError(Exception e) { } }; /**將ByteArray轉成字串可顯示的ASCII*/ private String byteArrayToHexStr(byte[] byteArray) { if (byteArray == null) { return null; } StringBuilder hex = new StringBuilder(byteArray.length * 2); for (byte aData : byteArray) { hex.append(String.format("%02X ", aData)); } String gethex = hex.toString(); return gethex; }
最後,把他們接起來!
try { /*開啟port*/ port.open(connect); /*取得要發送的字串*/ EditText edInput = findViewById(R.id.editText_Input); String s = edInput.getText().toString(); if (s.length() == 0) return; /*設定胞率、資料長度、停止位元、檢查位元*/ port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); /*寫出資訊*/ port.write(s.getBytes(), 200); /*設置回傳執行緒*/ SerialInputOutputManager.Listener serialInputOutputManager = getRespond; SerialInputOutputManager sL = new SerialInputOutputManager(port, serialInputOutputManager); ExecutorService service = Executors.newSingleThreadExecutor(); service.submit(sL); } catch (IOException e) { try { /*如果Port是開啟狀態,則關閉;再使用遞迴法重複呼叫並嘗試*/ port.close(); sendValue(drivers); } catch (IOException ex) { ex.printStackTrace(); Log.e(TAG, "送出失敗,原因: " + ex); } }
最後,是全部的程式
public class MainActivity extends AppCompatActivity { private USBStatus status = new USBStatus(); public static final String TAG = MainActivity.class.getSimpleName() + "My"; private static final String USB_PERMISSION = "USB_Demo"; TextView tvStatus, tvInfo, tvRes; UsbManager manager; List<UsbSerialDriver> drivers; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*註冊廣播*/ IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(USB_PERMISSION); registerReceiver(status, filter); /*Find UIs*/ tvStatus = findViewById(R.id.textView_Status); tvInfo = findViewById(R.id.textView_Info); tvRes = findViewById(R.id.textView_Respond); Button btSend = findViewById(R.id.button_Send); btSend.setOnClickListener(v -> { /*對裝置送出指令*/ sendValue(drivers); }); /*偵測是否正在有裝置插入*/ detectUSB(); } @Override protected void onStop() { super.onStop(); /*反註冊廣播*/ unregisterReceiver(status); } private class USBStatus extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) return; switch (action) { /**當Check完授權狀態後進入此處*/ case USB_PERMISSION: if (drivers.size() == 0) return; boolean hasPermission = manager.hasPermission(drivers.get(0).getDevice()); tvStatus.setText("授權狀態: " + hasPermission); if (!hasPermission) { getPermission(drivers); return; } Toast.makeText(context, "已獲取權限", Toast.LENGTH_SHORT).show(); break; /**偵測USB裝置插入*/ case UsbManager.ACTION_USB_DEVICE_ATTACHED: Toast.makeText(context, "USB裝置插入", Toast.LENGTH_SHORT).show(); detectUSB(); break; /**偵測USB裝置拔出*/ case UsbManager.ACTION_USB_DEVICE_DETACHED: Toast.makeText(context, "USB裝置拔出", Toast.LENGTH_SHORT).show(); tvInfo.setText("TextView"); tvRes.setText("TextView"); tvStatus.setText("授權狀態: false"); break; } } } /**偵測裝置*/ private void detectUSB() { manager = (UsbManager) getSystemService(Context.USB_SERVICE); if (manager == null) return; if (manager.getDeviceList().size() == 0)return; tvStatus.setText("授權狀態: false"); /*取得目前插在USB-OTG上的裝置*/ drivers = getDeviceInfo(); /*確認使用者是否有同意使用OTG(權限)*/ getPermission(drivers); } /**取得目前插在USB-OTG上的裝置列表,並取得"第一個"裝置的資訊*/ private List<UsbSerialDriver> getDeviceInfo() { HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Log.d(TAG, "裝置資訊列表:\n " + deviceList); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); ProbeTable customTable = new ProbeTable(); List<UsbSerialDriver> drivers = null; String info = ""; while (deviceIterator.hasNext()) { /*取得裝置資訊*/ UsbDevice device = deviceIterator.next(); info = "Vendor ID: " + device.getVendorId() + "\nProduct Id: " + device.getDeviceId() + "\nManufacturerName: " + device.getManufacturerName() + "\nProduceName: " + device.getProductName(); /*設置驅動*/ customTable.addProduct( device.getVendorId(), device.getProductId(), CdcAcmSerialDriver.class /*我的設備Diver是CDC,另有 * CP21XX, CH34X, FTDI, Prolific 等等可以使用*/ ); /*將驅動綁定給此裝置*/ UsbSerialProber prober = new UsbSerialProber(customTable); drivers = prober.findAllDrivers(manager); } /*更新UI*/ tvInfo.setText(info); return drivers; } /**確認OTG使用權限,此處為顯示詢問框*/ private void getPermission(List<UsbSerialDriver> drivers) { if (PendingIntent.getBroadcast(this, 0, new Intent(USB_PERMISSION), 0) != null) { manager.requestPermission(drivers.get(0).getDevice(), PendingIntent.getBroadcast( this, 0, new Intent(USB_PERMISSION), 0)); } } /**送出資訊*/ private void sendValue(List<UsbSerialDriver> drivers) { if (drivers == null) return; /*初始化整個發送流程*/ UsbDeviceConnection connect = manager.openDevice(drivers.get(0).getDevice()); /*取得此USB裝置的PORT*/ UsbSerialPort port = drivers.get(0).getPorts().get(0); try { /*開啟port*/ port.open(connect); /*取得要發送的字串*/ EditText edInput = findViewById(R.id.editText_Input); String s = edInput.getText().toString(); if (s.length() == 0) return; /*設定胞率、資料長度、停止位元、檢查位元*/ port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); /*寫出資訊*/ port.write(s.getBytes(), 200); /*設置回傳執行緒*/ SerialInputOutputManager.Listener serialInputOutputManager = getRespond; SerialInputOutputManager sL = new SerialInputOutputManager(port, serialInputOutputManager); ExecutorService service = Executors.newSingleThreadExecutor(); service.submit(sL); } catch (IOException e) { try { /*如果Port是開啟狀態,則關閉;再使用遞迴法重複呼叫並嘗試*/ port.close(); sendValue(drivers); } catch (IOException ex) { ex.printStackTrace(); Log.e(TAG, "送出失敗,原因: " + ex); } } } /**接收回傳*/ private SerialInputOutputManager.Listener getRespond = new SerialInputOutputManager.Listener() { @Override public void onNewData(byte[] data) { String res = "字串回傳: "+new String(data)+"\nByteArray回傳: "+byteArrayToHexStr(data); Log.d(TAG, "回傳: " + res); runOnUiThread(() -> { tvRes.setText(res); }); } @Override public void onRunError(Exception e) { } }; /**將ByteArray轉成字串可顯示的ASCII*/ private String byteArrayToHexStr(byte[] byteArray) { if (byteArray == null) { return null; } StringBuilder hex = new StringBuilder(byteArray.length * 2); for (byte aData : byteArray) { hex.append(String.format("%02X ", aData)); } String gethex = hex.toString(); return gethex; } }
今天的程式終於寫完了
老實說雖然只有200多行
不過我也是修修改改好一段時間才寫好
這個已經是我學生時代寫的東西了
雖然當時並沒有搞得很完整
不過還是很努力地完成它了,Emmm...至少專題有給他混過去XD←不是我的...
其實很可惜是說因為當時的學弟辯才能力不佳,所以很可惜沒有獲得評審青睞
不然其實我當時是覺得本質上idea是還不錯的說
在範例中,我們也只有傳“NAME”會回裝置名稱而已
還有另一個指令會告訴你偵測到的數值
其他是沒有什麼特別的功能的( ・ὢ・ )
那麼,這次文章寫到這
希望此文對你有幫助!
留言列表