android開發之手機與微控制器藍芽模組通訊
之前兩篇都是在說與手機的連線,連線方法,和主動配對連線,都是手機與手機的操作,做起來還是沒問題的,但是最終的目的是與微控制器的藍芽模組的通訊。
下面是到目前為止嘗試的與微控制器的通訊方法,沒有成功,但是從思路上來說沒有問題,最大的問題是與微控制器配對的時候,微控制器的藍芽模組的PIN配對碼是寫死的,固定為1234,
而手機這邊連線配對都是自動生成的PIN配對碼,這種方式在手機與手機配對的時候是極為方便的,但是在這裡與微控制器連線卻成了最大的問題,因為手機自動生成而且每次都不一樣,所以沒法與微控制器藍芽模組的1234相同也就沒法陪對了。下面只是介紹的到目前為止我們的大題思路,具體程式碼很多,而且涉及到專案也就沒有貼。
如果關於上面的問題哪位同學有思路或者做過類似的專案還請指點。
首先,如何開啟藍芽裝置和設定可見時間:
private void search() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (!adapter.isEnabled()) { adapter.enable(); } Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); //3600為藍芽裝置可見時間 startActivity(enable); Intent searchIntent = new Intent(this, ComminuteActivity.class); startActivity(searchIntent); }
正式開始與藍芽模組進行通訊
public class ComminuteActivity extends Activity { private BluetoothReceiver receiver; private BluetoothAdapter bluetoothAdapter; private List<String> devices; private List<BluetoothDevice> deviceList; private Bluetooth client; private final String lockName = "YESYOU"; private String message = "000001"; private ListView listView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.search_layout); listView = (ListView) this.findViewById(R.id.list); deviceList = new ArrayList<BluetoothDevice>(); devices = new ArrayList<String>(); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); bluetoothAdapter.startDiscovery(); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); receiver = new BluetoothReceiver(); registerReceiver(receiver, filter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { setContentView(R.layout.connect_layout); BluetoothDevice device = deviceList.get(position); client = new Bluetooth(device, handler); try { client.connect(message); } catch (Exception e) { Log.e("TAG", e.toString()); } } }); } @Override protected void onDestroy() { unregisterReceiver(receiver); super.onDestroy(); } private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case Bluetooth.CONNECT_FAILED: Toast.makeText(ComminuteActivity.this, "連線失敗", Toast.LENGTH_LONG).show(); try { client.connect(message); } catch (Exception e) { Log.e("TAG", e.toString()); } break; case Bluetooth.CONNECT_SUCCESS: Toast.makeText(ComminuteActivity.this, "連線成功", Toast.LENGTH_LONG).show(); break; case Bluetooth.READ_FAILED: Toast.makeText(ComminuteActivity.this, "讀取失敗", Toast.LENGTH_LONG).show(); break; case Bluetooth.WRITE_FAILED: Toast.makeText(ComminuteActivity.this, "寫入失敗", Toast.LENGTH_LONG).show(); break; case Bluetooth.DATA: Toast.makeText(ComminuteActivity.this, msg.arg1 + "", Toast.LENGTH_LONG).show(); break; } } }; private class BluetoothReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (isLock(device)) { devices.add(device.getName()); } deviceList.add(device); } showDevices(); } } private boolean isLock(BluetoothDevice device) { boolean isLockName = (device.getName()).equals(lockName); boolean isSingleDevice = devices.indexOf(device.getName()) == -1; return isLockName && isSingleDevice; } private void showDevices() { ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, devices); listView.setAdapter(adapter); } }
這裡需要提一下的是,startDiscovery()這個方法和它的返回值,它是一個非同步方法,會對其他藍芽裝置進行搜尋,持續時間為12秒。
搜尋過程其實是在System Service中進行,我們可以通過cancelDiscovery()方法來停止這個搜尋。在系統搜尋藍芽裝置的過程中,系統可能會發送以下三個廣播:ACTION_DISCOVERY_START(開始搜尋),
ACTION_DISCOVERY_FINISHED(搜尋結束)
和ACTION_FOUND(找到裝置)。
ACTION_FOUND這個才是我們想要的,這個Intent中包含兩個extra fields: EXTRA_DEVICE和EXTRA_CLASS,
包含的分別是BluetoothDevice和BluetoothClass,
EXTRA_DEVICE中的BluetoothDevice就是我們搜尋到的裝置物件,從中獲得裝置的名稱和地址。
而EXTRA_CLASS中的BluetoothClass是搜尋到的裝置的型別,比如搜尋到的是手機還是耳機或者其他,之後我會寫一篇關於它的介紹。
在這個上面我現在在想,是否通過判斷搜尋到的裝置型別來識別微控制器藍芽模組與手機藍芽的不同,採取不一樣的配對方式,從而不自動生成配對碼。不知是否可行,一會嘗試。
搜尋到該裝置後,我們就要對該裝置進行連線和通訊。
public void connect(final String message) {
Thread thread = new Thread(new Runnable() {
public void run() {
BluetoothSocket tmp = null;
Method method;
try {
method = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
tmp = (BluetoothSocket) method.invoke(device, 1);
} catch (Exception e) {
setState(CONNECT_FAILED);
Log.e("TAG", e.toString());
}
socket = tmp;
try {
socket.connect();
isConnect = true;
} catch (Exception e) {
setState(CONNECT_FAILED);
Log.e("TAG", e.toString());
}
if (isConnect) {
try {
OutputStream outStream = socket.getOutputStream();
outStream.write(getHexBytes(message));
} catch (IOException e) {
setState(WRITE_FAILED);
Log.e("TAG", e.toString());
}
try {
InputStream inputStream = socket.getInputStream();
int data;
while (true) {
try {
data = inputStream.read();
Message msg = handler.obtainMessage();
msg.what = DATA;
msg.arg1 = data;
handler.sendMessage(msg);
} catch (IOException e) {
setState(READ_FAILED);
Log.e("TAG", e.toString());
break;
}
}
} catch (IOException e) {
setState(WRITE_FAILED);
Log.e("TAG", e.toString());
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.e("TAG", e.toString());
}
}
}
}
這裡包括寫入和讀取,用法和基本的Socket是一樣的,但是寫入的時候,需要將字串轉化為16進位制:
private byte[] getHexBytes(String message) {
int len = message.length() / 2;
char[] chars = message.toCharArray();
String[] hexStr = new String[len];
byte[] bytes = new byte[len];
for (int i = 0, j = 0; j < len; i += 2, j++) {
hexStr[j] = "" + chars[i] + chars[i + 1];
bytes[j] = (byte) Integer.parseInt(hexStr[j], 16);
}
return bytes;
}
連線裝置之前需要UUID,所謂的UUID,就是用來進行配對的,全稱是Universally Unique Identifier,是一個128位的字串ID,用於進行唯一標識。網上的例子,包括谷歌的例子提供的uuid,通用的"00001101-0000-1000-8000-00805F9B34FB"也試過了,在配對的時候都是自動生成了配對碼,也無法正常與微控制器的藍芽模組連線,所以,我就利用反射的原理,讓裝置自己提供UUID嘗試。到這裡其實我有點懷疑自己對於UUID的理解是否正確了。
在谷歌提供的例子中,我們可以看到谷歌的程式設計師的程式水平很高,一些好的編碼習慣我們可以學習一下,像是在try..catch中才定義的變數,我們應該在try...catch之前宣告一個臨時變數,然後再在try...catch後賦值給我們真正要使用的變數。這種做法的好處就是:如果我們直接就是使用真正的變數,當出現異常的時候,該變數的使用就會出現問題,而且很難進行排查,如果是臨時變數,我麼可以通過檢查變數的值來確定是否是賦值時出錯。
作者:jason0539