最近做畢設,需要寫一個簡單的藍牙APP進行交互,在網上也找了很多資料,終於給搞定了,這裡分享一下^_^。 1、Android藍牙編程 藍牙3.0及以下版本編程需要使用UUID,UUID是通用唯一識別碼(Universally Unique Identifier),這是一個軟體構建的標準,也是被開源基 ...
最近做畢設,需要寫一個簡單的藍牙APP進行交互,在網上也找了很多資料,終於給搞定了,這裡分享一下^_^。
1、Android藍牙編程
藍牙3.0及以下版本編程需要使用UUID,UUID是通用唯一識別碼(Universally Unique Identifier),這是一個軟體構建的標準,也是被開源基金會組織應用在分散式計算環境領域的一部分。在藍牙3.0及下一版本中,UUID被用於唯一標識一個服務,比如文件傳輸服務,串口服務、印表機服務等,如下:
#藍牙串口服務
SerialPortServiceClass_UUID = '{00001101-0000-1000-8000-00805F9B34FB}'
LANAccessUsingPPPServiceClass_UUID = '{00001102-0000-1000-8000-00805F9B34FB}'
#撥號網路服務
DialupNetworkingServiceClass_UUID = '{00001103-0000-1000-8000-00805F9B34FB}'
#信息同步服務
IrMCSyncServiceClass_UUID = '{00001104-0000-1000-8000-00805F9B34FB}'
SDP_OBEXObjectPushServiceClass_UUID = '{00001105-0000-1000-8000-00805F9B34FB}'
#文件傳輸服務
OBEXFileTransferServiceClass_UUID = '{00001106-0000-1000-8000-00805F9B34FB}'
IrMCSyncCommandServiceClass_UUID = '{00001107-0000-1000-8000-00805F9B34FB}'
藍牙的連接有主從設備,提供服務的可以認為是從設備。主設備通過UUID訪問從設備提供具有相同UUID的服務,從而建立客服端—伺服器(C/S)模式。
2、編程步驟
Android使用藍牙,需要獲得許可權,藍牙許可權獲得代碼如下:
<!-- 藍牙許可權 --> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
獲取本地藍牙適配器,如果藍牙未開啟,開啟藍牙設備:
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { // Device does not support Bluetooth return; } // 開啟藍牙 int REQUEST_ENABLE_BT = 1; if (!bluetoothAdapter.isEnabled()) { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent, REQUEST_ENABLE_BT); }
搜索已配對的藍牙設備,並添加到已配對列表中:
// 查詢配對設備 List<String> devices = new ArrayList<String>(); Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : bondedDevices) { devices.add(device.getName() + "-" + device.getAddress()); }
搜索未配對藍牙設備,並添加到未配對列表:
mBluetoothAdapter.startDiscovery(); //開始收索 搜索接收函數: final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // When discovery finds a device if (BluetoothDevice.ACTION_FOUND.equals(action)) { // Get the BluetoothDevice object from the Intent BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } }; // 收索接收函數需要註冊: // Register the BroadcastReceiver IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
如果是伺服器端,需要建立監聽,註意監聽的是某個服務的UUID,伺服器監聽類如下:
private class ConnectThread extends Thread { private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; private final BluetoothSocket socket; private final BluetoothDevice device; public ConnectThread(BluetoothDevice device) { this.device = device; BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID)); } catch (IOException e) { e.printStackTrace(); } this.socket = tmp; } public void run() { bluetoothAdapter.cancelDiscovery(); try { socket.connect(); connectedThread = new ConnectedThread(socket); connectedThread.start(); } catch (IOException e) { try { socket.close(); } catch (IOException ee) { ee.printStackTrace(); } return; } //manageConnectedSocket(socket); } public void cancel() { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
客戶端與伺服器端建立連接成功後,需要ConnectedThread類接收發送數據:
// 客戶端與伺服器建立連接成功後,用ConnectedThread收發數據 private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final InputStream inputStream; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket socket) { this.socket = socket; InputStream input = null; OutputStream output = null; try { input = socket.getInputStream(); output = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } this.inputStream = input; this.outputStream = output; } public void run() { byte[] buff = new byte[1024]; int bytes; while (true) { try { bytes = inputStream.read(buff); String str = new String(buff, "ISO-8859-1"); str = str.substring(0, bytes); Log.e("recv", str); } catch (IOException e) { e.printStackTrace(); break; } } } public void write(byte[] bytes) { try { outputStream.write(bytes); } catch (IOException e) { e.printStackTrace(); } } public void cancel() { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
到此為止就是藍牙開發的大致步驟,其中沒有涉及到藍牙客戶端建立連接類,不過可查閱BLE和經典藍牙Android開發。
3、畢設藍牙APP介紹
畢設藍牙APP需要接收單片機通過藍牙模塊發送上來的數據,並且藍牙APP也可以給單片機發送數據來進行控制。頁面佈局如下,一個是整體頁面,一個是設置頁面,測試手機是魅藍note。因為畢設做的是十字路口紅綠燈控制系統,所有頁面佈局有4個LED燈,分別代表路口的4個紅綠燈,會根據時間不同顯示不同的顏色(紅/綠/黃),並且會顯示倒計時,最後來一張紅綠燈系統整體圖。
activity_main.xml文件如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <LinearLayout android:id="@+id/linear_layout_top" android:layout_alignParentTop="true" android:layout_width="match_parent" android:layout_height="40dp"> <TextView android:id="@+id/notice_view" android:layout_width="0dp" android:layout_height="40dp" android:text="藍牙未開啟" android:layout_weight="3"/> <TextView android:id="@+id/notice_recv_view" android:layout_width="0dp" android:layout_height="30dp" android:layout_weight="3"/> <TextView android:id="@+id/notice_send_view" android:layout_width="0dp" android:layout_height="30dp" android:layout_weight="3"/> <Button android:id="@+id/turn_on_off" android:layout_width="0dp" android:layout_height="40dp" android:layout_weight="2" android:text="ON"/> </LinearLayout> <TextView android:id="@+id/led1" android:layout_centerHorizontal="true" android:layout_below="@+id/linear_layout_top" android:layout_width="40dp" android:layout_height="20dp" android:gravity="center" android:text="LED1"/> <TextView android:id="@+id/led0" android:layout_centerHorizontal="true" android:layout_below="@+id/led1" android:layout_width="40dp" android:layout_height="20dp" android:gravity="center" android:text="+"/> <TextView android:id="@+id/led3" android:layout_below="@+id/led1" android:layout_toLeftOf="@+id/led1" android:layout_width="40dp" android:layout_height="20dp" android:gravity="center" android:text="LED3"/> <TextView android:id="@+id/led2" android:layout_centerHorizontal="true" android:layout_below="@+id/led3" android:layout_width="40dp" android:layout_height="20dp" android:gravity="center" android:text="LED2"/> <TextView android:id="@+id/led4" android:layout_below="@+id/led1" android:layout_toRightOf="@+id/led1" android:layout_width="40dp" android:layout_height="20dp" android:gravity="center" android:text="LED4"/> <ScrollView android:id="@+id/scroll_view" android:layout_below="@+id/led2" android:layout_above="@+id/linear_layout_bottom" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/recv_view" android:text="" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </ScrollView> <LinearLayout android:id="@+id/linear_layout_bottom" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:layout_height="50dp"> <Button android:id="@+id/clear_recv_view" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="clear" /> <EditText android:id="@+id/send_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:hint="輸入框,預設@#結尾"/> <Button android:id="@+id/send" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="send" /> </LinearLayout> </RelativeLayout>
MainActivity.java文件如下:
package com.luoxn28.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.os.Message; import android.support.v7.app.ActionBarActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; public class MainActivity extends ActionBarActivity implements View.OnClickListener { public static final int RECV_VIEW = 0; public static final int NOTICE_VIEW = 1; private BluetoothAdapter bluetoothAdapter = null; private ConnectThread connectThread = null; private ConnectedThread connectedThread = null; private TextView noticeView = null; private Button turnOnOff = null; private TextView led0, led1, led2, led3, led4; ScrollView scrollView = null; private TextView recvView = null; private Button clearRecvView = null; private EditText sendText = null; private Button send = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 獲取BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { // Device does not support Bluetooth return; } // 註冊監聽事件 noticeView = (TextView) findViewById(R.id.notice_view); turnOnOff = (Button) findViewById(R.id.turn_on_off); led0 = (TextView) findViewById(R.id.led0); led1 = (TextView) findViewById(R.id.led1); led2 = (TextView) findViewById(R.id.led2); led3 = (TextView) findViewById(R.id.led3); led4 = (TextView) findViewById(R.id.led4); scrollView = (ScrollView) findViewById(R.id.scroll_view); recvView = (TextView) findViewById(R.id.recv_view); clearRecvView = (Button) findViewById(R.id.clear_recv_view); sendText = (EditText) findViewById(R.id.send_text); send = (Button) findViewById(R.id.send); turnOnOff.setOnClickListener(this); clearRecvView.setOnClickListener(this); send.setOnClickListener(this); if (!bluetoothAdapter.isEnabled()) { noticeView.setText("藍牙未開啟"); } else { noticeView.setText("藍牙已開啟"); } noticeView.setBackgroundColor(Color.GRAY); led0.setBackgroundColor(Color.GRAY); led1.setBackgroundColor(Color.GRAY); led2.setBackgroundColor(Color.GRAY); led3.setBackgroundColor(Color.GRAY); led4.setBackgroundColor(Color.GRAY); } private boolean isOn = false; @Override public void onClick(View view) { switch (view.getId()) { case R.id.turn_on_off: // 發送'0'或者'1'都可以 if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Toast.makeText(this, "藍牙未開啟", Toast.LENGTH_SHORT).show(); break; } if (connectedThread == null) { Toast.makeText(this, "未連接設備", Toast.LENGTH_SHORT).show(); break; } String turn_string = "1@#"; connectedThread.write(turn_string.getBytes()); if (isOn == false) { isOn = true; // 打開了 turnOnOff.setText("OFF"); led1.setText(""); led2.setText(""); led3.setText(""); led4.setText(""); } else { isOn = false; // 關閉了 turnOnOff.setText("ON"); led1.setText("LED1"); led2.setText("LED2"); led3.setText("LED3"); led4.setText("LED4"); } break; case R.id.clear_recv_view: // 清空接收框 recvView.setText(""); break; case R.id.send: // 發送數據,預設以"@#"結尾 if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Toast.makeText(this, "藍牙未開啟", Toast.LENGTH_SHORT).show(); return; } if (connectedThread == null) { Toast.makeText(this, "未連接設備", Toast.LENGTH_SHORT).show(); break; } String inputText = sendText.getText().toString() + "@#"; // 發送給單片機數據以"@#結尾",這樣單片機知道一條數據發送結束 //Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show(); connectedThread.write(inputText.getBytes()); break; default: break; } } private android.os.Handler handler = new android.os.Handler() { public void handleMessage(Message msg) { Bundle bundle = null; switch (msg.what) { case RECV_VIEW: if (isOn == false) { isOn = true; turnOnOff.setText("OFF"); } bundle = msg.getData(); String recv = bundle.getString("recv"); recvView.append(recv + "\n"); scrollView.fullScroll(ScrollView.FOCUS_DOWN); // 滾動到底部 if (recv.isEmpty() || recv.contains(" ") || recv.contains("#")) { break; } int num = Integer.valueOf(recv) / 2; // 0-60s if (num <= 20) { led1.setText(""); led2.setText(""); led3.setText(""); led4.setText(""); led1.setBackgroundColor(Color.RED); led2.setBackgroundColor(Color.RED); led3.setBackgroundColor(Color.GREEN); led4.setBackgroundColor(Color.GREEN); } else if (num < 30) { int n = 30 - num; led1.setText("" + n); led2.setText("" + n); if (num < 28) { led3.setBackgroundColor(Color.GREEN); led4.setBackgroundColor(Color.GREEN); } else { led3.setBackgroundColor(Color.YELLOW); led4.setBackgroundColor(Color.YELLOW); } } else if (num <= 50) { led1.setText(""); led2.setText(""); led3.setText(""); led4.setText(""); led1.setBackgroundColor(Color.GREEN); led2.setBackgroundColor(Color.GREEN); led3.setBackgroundColor(Color.RED); led4.setBackgroundColor(Color.RED); } else { int n = 60 - num; led3.setText("" + n); led4.setText("" + n); if (num < 58) { led1.setBackgroundColor(Color.GREEN); led2.setBackgroundColor(Color.GREEN); } else { led1.setBackgroundColor(Color.YELLOW); led2.setBackgroundColor(Color.YELLOW); } } break; case NOTICE_VIEW: bundle = msg.getData(); String notice = bundle.getString("notice"); noticeView.setText(notice); break; default: break; } } }; @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.start_bluetooth) { if (bluetoothAdapter != null) { // 開啟藍牙 int REQUEST_ENABLE_BT = 1; if (!bluetoothAdapter.isEnabled()) { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent, REQUEST_ENABLE_BT); noticeView.setText("開啟藍牙成功"); //Toast.makeText(this, "開啟藍牙成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "藍牙已開啟", Toast.LENGTH_SHORT).show(); } } return true; } else if (id == R.id.show_devices) { if (bluetoothAdapter != null) { if (!bluetoothAdapter.isEnabled()) { Toast.makeText(this, "藍牙未開啟", Toast.LENGTH_SHORT).show(); return true; } // 查詢配對設備 List<String> devices = new ArrayList<String>(); Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : bondedDevices) { devices.add(device.getName() + "-" + device.getAddress()); } StringBuilder text = new StringBuilder(); for (String device : devices) { text.append(device + "\n"); } Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); } return true; } else if (id == R.id.find_devices) { Toast.makeText(this, "該功能暫時不可用", Toast.LENGTH_SHORT).show(); } else if (id == R.id.connect_devices) { if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Toast.makeText(this, "藍牙未開啟", Toast.LENGTH_SHORT).show(); return true; } // 查詢配對設備 建立連接,只能連接第一個配對的設備 List<String> devices = new ArrayList<String>(); Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : bondedDevices) { connectThread = new ConnectThread(device); connectThread.start(); //Toast.makeText(this, "連接成功", Toast.LENGTH_SHORT).show(); break; } } return super.onOptionsItemSelected(item); } private class ConnectThread extends Thread { private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; private final BluetoothSocket socket; private final BluetoothDevice device; public ConnectThread(BluetoothDevice device) { this.device = device; BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID)); } catch (IOException e) { e.printStackTrace(); } this.socket = tmp; } public void run() { bluetoothAdapter.cancelDiscovery(); try { socket.connect(); connectedThread = new ConnectedThread(socket); connectedThread.start(); } catch (IOException e) { try { socket.close(); } catch (IOException ee) { ee.printStackTrace(); } return; } //manageConnectedSocket(socket); } public void cancel() { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } // 客戶端與伺服器建立連接成功後,用ConnectedThread收發數據 private class ConnectedThread extends Thread { private final BluetoothSocket socket; private final InputStream inputStream; private final OutputStream outputStream; public ConnectedThread(BluetoothSocket socket) { this.socket = socket; InputStream input = null; OutputStream output = null; try { input = socket.getInputStream(); output = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } this.inputStream = input; this.outputStream = output; } public void run() { StringBuilder recvText = new StringBuilder(); byte[] buff = new byte[1024]; int bytes; Bundle tmpBundle = new Bundle(); Message tmpMessage = new Message(); tmpBundle.putString("notice", "連接成功"); tmpMessage.what = NOTICE_VIEW; tmpMessage.setData(tmpBundle); handler.sendMessage(tmpMessage); while (true) { try { bytes = inputStream.read(buff); String str = new String(buff, "ISO-8859-1"); str = str.substring(0, bytes); // 收到數據,單片機發送上來的數據以"#"結束,這樣手機知道一條數據發送結束 //Log.e("read", str); if (!str.endsWith("#")) { recvText.append(str); continue; } recvText.append(str.substring(0, str.length() - 1)); // 去除'#' Bundle bundle = new Bundle(); Message message = new Message(); bundle.putString("recv", recvText.toString()); message.what = RECV_VIEW; message.setData(bundle); handler.sendMessage(message); recvText.replace(0, recvText.length(), ""); } catch (IOException e) { e.printStackTrace(); break; } } } public void write(byte[] bytes) { try { outputStream.write(bytes); } catch (IOException e) { e.printStackTrace(); } } public void cancel() { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
menu_main.xml文件如下:
<menu 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" tools:context=".MainActivity"> <item android:id="@+id/show_devices" android:title="@string/show_devices" android:orderInCategory="100" app:showAsAction="never" /> <item android:id="@+id/start_bluetooth" android:title="@string/start_bluetooth" android:orderInCategory="100" app:showAsAction="never" /> <item android:id="@+id/find_devices" android:title="@string/find_devices" android:orderInCategory="100" app:showAsAction="never" /> <item android:id="@+id/connect_devices" android:title="@string/connect_devices" android:orderInCategory="100" app:showAsAction="never" /> </menu>
strings.xml文件如下:
<resources> <string name="app_name">BlueTooth</string> <string name="action_settings">Settings</string> <string name="start_bluetooth">開啟藍牙</string> <string name="show_devices">查詢配對設備</string> <string name="find_devices">搜索設備</string> <string name="connect_devices">連接設備</string> </resources>
到這裡整個APP已經開發完成,親測可用,如果有什麼錯誤,歡迎評論指正談論^_^。
參考資料
2、http://developer.android.com/intl/zh-cn/guide/topics/connectivity/bluetooth.html
3、《Android第一行代碼》