Android + BLE 建國科技大學 資管系 饒瑞佶 2017/3 v1.

Slides:



Advertisements
Similar presentations
第13章 繪圖與多媒體 13-1 顯示圖檔-行動相簿 13-2 音樂播放-音樂播放器 13-3 影片播放-視訊播放器
Advertisements

實驗五:多媒體播放器選單介面.
Part 2 開發Android應用程式的流程
位置與地圖應用 此投影片為講解Android如何取得定位經緯度和使用Google Map地圖.
第二章 JAVA语言基础.
Google App Engine Google 應用服務引擎.
第八章 分析與設計階段 – 物件導向設計(OOD)
Android + Web Service 建國科技大學 資管系 饒瑞佶 2017/3 V1.
ArrayAdapter & Spinner
Android + NFC 建國科技大學 資管系 饒瑞佶 2017/3 v1.
建立Android新專案 建國科技大學 資管系 饒瑞佶 2010/10.
厦门大学数据库实验室 报告人:谢荣东 导师:林子雨 2014年8月30日
實驗四:單位轉換程式.
Chapter 13 Android 實戰演練.
Android + JUnit 單元測試 建國科技大學資管系 饒瑞佶 2012/8/19V4.
實驗十三:顯示目前經緯度位置.
Ch06 再談選單元件 物件導向系統實務.
使用Android控制Arduino 史先强
第10章 App微信分享的实现 倚动实验室.
Android資料庫處理 Android智慧型手機程式設計 程式設計與應用班 建國科技大學 資管系 饒瑞佶 2012/4 V1
第一个Android程序 本讲大纲: 1、创建Android应用程序 2、Android项目结构说明 3、运行Android应用程序
第8章 Android内容提供者(ContentProvider)应用
Chapter 6 Advanced UI Design.
Android智慧型手機程式設計實務應用班
Ch13 集合與泛型 物件導向程式設計(2).
Chapter 7 Android應用元件 Android應用元件可以幫助我們獲得系統資源訊息(ActivityManager)、提供系統服務(Service)、搜尋系統服務(SearchManager)、監聽Intent訊息(Broadcast Receiver)以及資料共享(ContentProvider和ContentResolver)。
Android介面設計 Android智慧型手機程式設計 建國科技大學 資管系 饒瑞佶 2012/4 V1 2012/8 V2
Chapter 6 進階UI設計.
厦门大学数据库实验室 MapReduce 连接
第4章 Android生命周期.
第9章 使用意圖啟動活動與內建應用程式 9-1 意圖的基礎 9-2 使用意圖啟動活動
ANDROID PROGRAMMING2.
CH7 佈局、按鈕與文字編輯元件.
Android + Service 建國科技大學 資管系 饒瑞佶.
實驗十四:顯示與控制地圖.
Java程序设计 第9章 继承和多态.
第2讲 移动应用开发基础知识(二) 宋婕
常见问题解答 II. App上重置并清空数据库之后,手机app找不到圣诞灯怎么办? I. 打开APP,发现并连接不了圣诞灯怎么办?
建立Android新專案 Android智慧型手機程式設計 程式設計與應用班 建國科技大學 資管系 饒瑞佶 2012/4 V1
第8章 Service解析.
第一次课后作业 1. C/C++/Java 哪些值不是头等程序对象 2. C/C++/Java 哪些机制采用的是动态束定
第6章 建立Android使用介面 6-1 介面元件的基礎 6-2 Android的事件處理 6-3 按鈕元件 6-4 文字元件
Java程序设计 第2章 基本数据类型及操作.
生活智慧王 樹德科技大學 資訊工程系 指導教授 : 陳毓璋 教授 小組成員: 劉上緯 翁維廷 洪文財.
實驗十一:待辦事項程式 (儲存在手機上).
Chapter 5 Recursion.
主编:钟元生 赵圣鲁.
Web Server 王宏瑾.
Android Application Component
JAVA 编 程 技 术 主编 贾振华 2010年1月.
Chapter 5 Basic UI Design.
Android視窗介面 建國科技大學 資管系 饒瑞佶 2010/10.
實驗九:延續實驗八, 製作一個完整音樂播放器
Location Based Services - LBS
補間動畫 (Tween Animation) 靜宜大學資管系 楊子青
第二章 Java基本语法 讲师:复凡.
第二章 Java语法基础.
Android視窗介面 建國科技大學 資管系 饒瑞佶 2010/10.
第二章 Java基本语法 讲师:复凡.
RecyclerView and CardView
Android藍芽聊天室 SDK內的範例程式
第6單元 6-1 類別的繼承 (Class Inheritance) 6-2 抽象類別 (Abstract Class)
Android Speech To Text(STT)
助教:廖啟盛 JAVA Socket(UDP) 助教:廖啟盛
用Intent啟動程式中的其他Activity、運用WebView顯示網頁 靜宜大學資管系 楊子青
第2章 Java语言基础.
第9章 BroadcastReceiver的使用
Android进程间通讯.
讀取網路資料及JSON開放資料 靜宜大學資管系 楊子青
Part 8 Broadcast Receiver、Service和App Widget
Presentation transcript:

Android + BLE 建國科技大學 資管系 饒瑞佶 2017/3 v1

Bluetooth 傳統 vs. BLE 4.0 舊版的藍牙通訊是指BT2.1 或 BT3.0 ,過去的藍牙技 術,通訊速率較高, 可以傳送語音或音樂,但是比 較耗電 待機時間最多數百小時,且一但斷線,再恢復連線的 時間較長,不適合經常性地斷線,然後快速恢復連線, 這個限制使得傳統藍牙不適用於使用電池且無法經常 充電的設備。 BT 4.0是把傳統的藍牙協定再加上BLE技術 BLE技術的應用目標是要達到低成本,低耗電,目前 BLE技術做出來的產品,使用CR2032鈕扣電池,一般 可以達到1年以上的工作時間,不用更換電池

BLE LOGO Bluetooth Smart Ready 代表支持雙模的裝置 Bluetooth Smart 則是僅支援 BLE 的裝置

Classic BT vs. BLE 4.0

Classic BT vs. BLE 4.0(I) 兩者的基礎架構仍然是維持上下兩大塊,Host及controller,中間 是兩方面溝通的HCI(Host Controller Interface) 以controller而言,原本到3.0是分為兩個controller; BR/EDR controller及AMP controller,現在將BR/EDR擴充,變為BR/EDR/LE controller Host,則是除了L2CAP及GAP是擴充原本加入支援LE的功能,其他 的ATT protocol及GATT profile都是為了LE而新增的階層

Classic BT vs. BLE 4.0(II) 傳統的藍牙有9種的protocol,而BLE則簡化為一個,稱 作Attribute protocol(ATT),就很像傳統藍牙用來傳資料 的protocol,RFCOMM 基於ATT去定義Generic Attribute profile,BLE各種制定 的Profile就是基於GATT 傳統的BT為了要支援不同的profile,制定了好幾種的 protocol,所以所傳送的packet也不同;BLE的設計較簡 單,只有1種ATT,1種packet structure,1個packet format,目的都是為了簡單與省電

BLE工作原理(I) BLE設備主要分為中心(Central)和週邊(Peripheral)兩種 週邊設備會產生(或擁有)資料,在平時它會不斷地發出廣播封包 (broadcast),向週遭環境通告自已的存在 而中心設備則是不斷地掃瞄(scan)周遭環境,看是否有可供連結 的BLE週邊設備?

BLE工作原理(II) 一旦中心設備取得某一個週邊設備的廣播資訊(主要是其藍牙位址)之後,它就可以要求和周邊設備進行連結,連結之後,就可以互相傳送資料,進行通訊

BLE工作原理(III) Advertisement間隔時間是一項重要的參數,當BLE設備密集地廣 播時,耗電量就會增加;但若BLE設備久久才廣播一次,那麼它 的反應性就會變差,因為要中心設備要花較久時間才能發現它的 存在 中心設備的掃瞄間隔和掃瞄窗口也是可調的,這兩個參數的調整 也會影響耗電量與反應時間,反應時間快慢當然也就影響了使用 者體驗的好壞

BLE工作原理(IV) 藍牙設備又可分client和server,client負責主動要求連 結,而server則回應client的連結要求。所以1個client- Central會負責建立連結,連結到1個被動的Server- Peripheral 1個client-Peripheral也會建立連結,而且只能向Server- Central要求建立連結。2個client之間不能建立連結, 兩個Server之間亦然 如果使用client-Central + Server-Peripheral的方式來工 作,一個Central可以同時和多個週邊設備連結。而周 邊設備只能和一個Central連結

BLE工作原理(V) 如果要central和多個週邊連結,Central必須把週邊的位址記住, 存放在flash之中,而此時1個Central最多只能記憶7個BT Address, 所以只能和7個Peripheral連結 如果要1個central對應超過7個週邊設備,則要使用Server-Central + Client-Peripheral架構來工作,在此架構下,Client-Peripheral對某 特定之Server-Central發出廣播封包,Central收到後,會建立連結 (因為我不需要記憶)

Classic BT vs. BLE 4.0比較

開發BLE前…

BLE相關名詞與關鍵字(1) Generic Attribute Profile (GATT)-The GATT profile is a general specification for sending and receiving short pieces of data known as "attributes" over a BLE link The Bluetooth SIG(藍芽技術聯盟) defines many profiles for Low Energy devices. A profile is a specification for how a device works in a particular application. Note that a device can implement more than one profile. For example, a device could contain a heart rate monitor and a battery level detector

BLE相關名詞與關鍵字(2) Attribute Protocol (ATT)-GATT is built on top of the Attribute Protocol (ATT). This is also referred to as GATT/ATT. ATT is optimized to run on BLE devices. To this end, it uses as few bytes as possible. Each attribute is uniquely identified by a Universally Unique Identifier (UUID), which is a standardized 128-bit format for a string ID used to uniquely identify information. The attributes transported by ATT are formatted as characteristics and services

小整理 BLE使用ATT協定傳送資料(稱為attributes) 這些資料被定義成固定通用(General)的profile,且基於ATT傳送, 所以稱為GATT attributes(例如心跳、電池容量等…)是透過UUID來識別 將attributes透過ATT傳送時,會被封裝成固定形式,稱之為 characteristics 或 services

BLE相關名詞與關鍵字(3) Characteristic—A characteristic contains a single value and 0-n descriptors that describe the characteristic's value. A characteristic can be thought of as a type, analogous to a class. 每個屬性都有一個 唯一的 UUID(https://www.bluetooth.com/specifications/gatt/characteristics) Descriptor—Descriptors are defined attributes that describe a characteristic value. For example, a descriptor might specify a human- readable description, an acceptable range for a characteristic's value, or a unit of measure that is specific to a characteristic's value

BLE相關名詞與關鍵字(4) Service—A service is a collection of characteristics. For example, you could have a service called "Heart Rate Monitor" that includes characteristics such as "heart rate measurement." You can find a list of existing GATT-based profiles and services on bluetooth.org. 每個Service都有一個唯一的UUID https://www.bluetooth.com/specifications/gatt/services

BLE相關名詞與關鍵字(5) Central vs. peripheral:This applies to the BLE connection itself. The device in the central role scans, looking for advertisement, and the device in the peripheral role makes the advertisement. GATT server vs. GATT client:This determines how two devices talk to each other once they‘ve established the connection(傳統Client- Server的概念)

GATT server vs. GATT client 應用情境範例 Android app (running on an Android device) is the GATT client. The app gets data from the GATT server, which is a BLE heart rate monitor that supports the Heart Rate Profile. But you could alternatively design your Android app to play the GATT server role. See BluetoothGattServer for more information

BLE相關名詞與關鍵字(6) Read/Write 是由 Client (Host) 發起對 Server (Device) 的讀跟寫 Indication & Notification 則是由 Server 自主要送資 料給 Client 的方式 Notification 是 Server 送給 Client,Client 收到後不 用回ACK告知Server是否收到;而Indication則是 Client收到Server indication後必須回1個ACK給 Server 告知收到了 開啟Notification / Indication:連線到裝置後,就算 Characteristic有支援這2個功能,預設是不開啟的, 需要 Client 對 Server 做設定啟動

每個服務裡,會包含多個特徵。每個特徵會有1個property/value以及幾個descriptor

UUID 所有的Service/characteristic會用UUID來定義 UUID = 0x2800定義服務的起點 (https://www.bluetooth.com/specifications/gatt/declarations) 所以每個BLE裝置,只要找UUID = 0x2800就可以找到所有的 services 在每個Service裡,有另外一個分隔線UUID = 0x2803定義 characteristics

所以當我用運動紀錄器連上心跳帶時,手機實際上做了這些事 手機對BLE裝置搜尋UUID = 0x2800,之後手機就知 道BLE裝置提供的服務 其次,在服務裡搜尋UUID = 0x2803就能找到服務 的characteristics,找到他們的handle-index, property (UUID)和value 手機接著和BLE裝置通訊來讀寫這些characteristics 針對notification/ indication,手機還要找到UUID = 0x2902的地方再去啟動notification(bit[0])或 indication(bit[1]) 最後,APP就能讀到心跳數值,顯示在畫面上

既定的Profile https://www.bluetooth.com/zh- cn/specifications/adopted-specifications

https://www.bluetooth.com/specifications/gatt/services

建議使用既有的App BLE Scanner: Read,Write,Notify

Android vs. BLE

新增專案

miniSDK API >=18 build.gradle(Module:app) 18才支援startLeScan方法

加入權限與feature <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

使用預設的Activity做BLE硬體掃描

使用ListActivity與檢查是否支援BLE 使用ListAvtivity,所以不用layout

public class DeviceScanActivity extends ListActivity { private BluetoothAdapter mBluetoothAdapter; private static final int REQUEST_ENABLE_BT = 1; Timer timer = new Timer(true); //定時清空device list private LeDeviceListAdapter mLeDeviceListAdapter; private boolean mScanning; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); // 判斷設備是否支援BLE if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, "裝置不支援BLE", Toast.LENGTH_SHORT).show(); finish(); } // 初始化Bluetooth adapter(API level 18 and above) // 透過BluetoothManager取得BluetoothAdapter final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); // 確認有取得BluetoothAdapter if (mBluetoothAdapter == null) { Toast.makeText(this, "裝置不支援藍芽", Toast.LENGTH_SHORT).show(); return;

加入onResume @Override protected void onResume() { super.onResume(); 相關物件宣告 private static final int REQUEST_ENABLE_BT = 1; Timer timer = new Timer(true); //定時清空device list @Override protected void onResume() { super.onResume(); // 確認設備上的Bluetooth服務有啟動 // 如果沒有,透過Intent跳出視窗,讓使用者選擇是否開啟Bluetooth服務 if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } // Initializes list view adapter. mLeDeviceListAdapter = new LeDeviceListAdapter(); // LeDeviceListAdapter在下面 setListAdapter(mLeDeviceListAdapter); timer.schedule(new MyTimerTask(), 0, 5000); //5秒定時清空device list scanLeDevice(true); // 開始掃描BLE設備

加入timer與onActivityResult public class MyTimerTask extends TimerTask { public void run() { mLeDeviceListAdapter.clear(); // 清空LISTVIEW } }; // 取得詢問啟動藍芽的回覆 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // 如果選擇不啟動Bluetooth服務 if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { finish(); //關閉App return; super.onActivityResult(requestCode, resultCode, data);

加入LeDeviceListAdapter private class LeDeviceListAdapter extends BaseAdapter { private ArrayList<BluetoothDevice> mLeDevices; private LayoutInflater mInflator; public LeDeviceListAdapter() { super(); mLeDevices = new ArrayList<BluetoothDevice>(); mInflator = DeviceScanActivity.this.getLayoutInflater(); } public void addDevice(BluetoothDevice device) { if (!mLeDevices.contains(device)) { //已經存在就不加入列表 mLeDevices.add(device); public BluetoothDevice getDevice(int position) { return mLeDevices.get(position); 相關物件宣告 private LeDeviceListAdapter mLeDeviceListAdapter;

public void clear() { mLeDevices.clear(); } @Override public int getCount() { return mLeDevices.size(); public Object getItem(int i) { return mLeDevices.get(i); public long getItemId(int i) { return i;

static class ViewHolder { TextView deviceName; TextView deviceAddress; @Override public View getView(int i, View view, ViewGroup viewGroup) { ViewHolder viewHolder; // General ListView optimization code. if (view == null) { view = mInflator.inflate(R.layout.listitem_device, null); viewHolder = new ViewHolder(); viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); } if (mLeDevices.size() > 0) { BluetoothDevice device = mLeDevices.get(i); // 依據MAC取得名稱 final String deviceName = device.getName(); viewHolder.deviceName.setText(deviceName); viewHolder.deviceAddress.setText(device.getAddress()); return view; static class ViewHolder { TextView deviceName; TextView deviceAddress; }

listitem_device.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/device_name" android:layout_height="wrap_content" android:textSize="24dp"/> <TextView android:id="@+id/device_address" android:textSize="20dp"/> </LinearLayout>

加入藍芽掃描 private void scanLeDevice(final boolean enable) { if (enable) { 相關物件宣告 private boolean mScanning; private void scanLeDevice(final boolean enable) { if (enable) { mScanning = true; mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } invalidateOptionsMenu();

藍芽掃描結果 // Device scan callback. private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { public void run() { mLeDeviceListAdapter.addDevice(device); mLeDeviceListAdapter.notifyDataSetChanged(); } }); };

加入onPause @Override protected void onPause() { super.onPause(); scanLeDevice(false); // 停止掃描BLE設備 mLeDeviceListAdapter.clear(); //finish(); //結束 }

result

result

加入點選後連線方法 // 點選畫面列表上的某個BLE設備 @Override protected void onListItemClick(ListView l, View v, int position, long id) { final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position); if (device == null) return; // 依據BLE MAC跳到資料顯示頁面 Intent intent = new Intent(DeviceScanActivity.this, ShowDeviceData.class); intent.putExtra("deviceMAC", device.getAddress().trim().toUpperCase().trim()); startActivity(intent); if (mScanning) { // 停止掃描 mBluetoothAdapter.stopLeScan(mLeScanCallback); mScanning = false; }

加入ShowDeviceData.java

layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/showdevicedata" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="edu.ctu.rcjao.androidble.ShowDeviceData"> <TextView android:id="@+id/mConnectionState" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textSize="36sp" android:text="連線狀態" /> android:id="@+id/batterydata" android:text="" android:textSize="36sp" /> </LinearLayout>

Activity連線使用步驟 onCreate onResume onPause 檢查是否支援BLE 連線BLE bindService 判斷連線是否逾時 onResume 註冊Service mBluetoothLeService.connect 取得BLE服務與資料 mGattUpdateReceiver onPause 解除註冊Service mBluetoothLeService.disconnect

onCreate

public class ShowDeviceData extends Activity { private BluetoothAdapter mBluetoothAdapter; ProgressDialog myDialog; private String mDeviceAddress = ""; // 設備MAC TextView mbatteryDataField; //ui上顯示電池資料的TextView private BluetoothLeService mBluetoothLeService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.showdevicedata); final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); // 顯示進度 myDialog = new ProgressDialog(ShowDeviceData.this); myDialog.setMessage("連線設備..."); myDialog.setCancelable(false); myDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { myDialog.dismiss(); finish(); } }); myDialog.show(); //取得Intent設定中的設備MAC(來自DeviceScanActivity.java) final Intent intent = getIntent(); mDeviceAddress = intent.getStringExtra("deviceMAC"); // 設備MAC // set UI mbatteryDataField = (TextView) findViewById(R.id.batterydata); //電池資料 mConnectionState = (TextView) findViewById(R.id.mConnectionState); //連線狀態 //連線BluetoothLeService(Service)顯示數據 Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE); // 嘗試連結BLE // 連線計時,設定超過10秒就逾時 timerconn.schedule(taskconn, 0, 1000); //判斷連線是否成功

呼叫service連線藍芽設備 // 呼叫service連線藍芽設備 private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); if (!mBluetoothLeService.initialize()) { finish(); } // 連線到設定的設備 mBluetoothLeService.connect(mDeviceAddress); public void onServiceDisconnected(ComponentName componentName) { mBluetoothLeService = null; };

判斷連線是否逾時 Timer timerconn = new Timer(); //連線計時 private boolean mConnected = false; // 設備初始連線狀態 int conntime = 0; // 連線逾時秒數 // 判斷連線是否超過10sec private TimerTask taskconn = new TimerTask() { @Override public void run() { // 如果尚未連線(mConnected=false)則每秒tsec+1 if (mConnected == false) { // mGattUpdateReceiver會收到BLE回傳結果 conntime++; if (conntime >= 10) { taskconn.cancel(); //取消計時TIMER // 取消進度 myDialog.dismiss(); runOnUiThread(new Runnable() { // 顯示無法連線訊息 Toast.makeText(ShowDeviceData.this, "設備已經離線", Toast.LENGTH_LONG).show(); } }); try { unregisterReceiver(mGattUpdateReceiver); // 取消SERVICE註冊 } catch (Exception ex) { if (mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.disable(); //關閉BT finish(); }; 判斷連線是否逾時

onResume @Override protected void onResume() { super.onResume(); try { registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); // 註冊mGattUpdateReceiver接收BLE回傳 if (mBluetoothLeService != null) { mBluetoothLeService.connect(mDeviceAddress); // 連結BLE設備 } } catch (Exception ex) { Toast.makeText(this,"err=" + ex.getMessage(),Toast.LENGTH_LONG).show();

onPause @Override protected void onPause() { super.onPause(); myDialog.dismiss(); try { if (mBluetoothLeService != null) { mBluetoothLeService.disconnect(); mBluetoothLeService.close(); unregisterReceiver(mGattUpdateReceiver); } } catch (Exception ex) { finish();

宣告BLE服務辨識 // 利用這裡區分出回傳的來源BLE(當有多個BLE時) private static IntentFilter makeGattUpdateIntentFilter() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); return intentFilter; }

建立ServiceConnection private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); if (!mBluetoothLeService.initialize()) { // 初始化Service上的BLE finish(); } // 連線到設定的設備 mBluetoothLeService.connect(mDeviceAddress); public void onServiceDisconnected(ComponentName componentName) { mBluetoothLeService = null; };

建立BroadcastReceiver TextView mDataField; //ui上顯示資料的TextView // 取得藍芽設備回傳的值 private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { mConnected = true; //已連線 updateConnectionState("已連線"); // 更新連線狀態 } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { mConnected = false; //未連線 updateConnectionState("未連線"); // 更新連線狀態 myDialog.dismiss(); //取消進度 mDataField.setText("無法取得資料"); // 更新資料TextView } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { // 發現服務 displayBatteryGattServices(mBluetoothLeService.getSupportedGattServices()); // 取得電池SERVICE GetBatteryData(); // 取得電池服務 //android.os.SystemClock.sleep(100); // 需要延遲讀取,因為是同一個service //displayGattServices(mBluetoothLeService.getSupportedGattServices()); // 資料 //GetData(); // 取得資料 } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { // 可以取得資料 displayBatteryData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA_Battery)); // 顯示設備電池數據 //displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); // 顯示設備數據 } };

更新連線狀態 TextView mConnectionState; //ui上顯示連線狀態的TextView // 更新連線狀態 private void updateConnectionState(final String msg) { runOnUiThread(new Runnable() { @Override public void run() { mConnectionState.setText(msg); } });

private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristicsBattery = new ArrayList<>(); // 取得電池服務 private void displayBatteryGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; // 沒有相關服務 String uuid; String unknownServiceString = "未知服務"; String unknownCharaString = "未知characteristic"; ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<>(); ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<>(); mGattCharacteristicsBattery = new ArrayList<>(); // Loops through available GATT Services. String LIST_NAME = "NAME"; String LIST_UUID = "UUID"; for (BluetoothGattService gattService : gattServices) { HashMap<String, String> currentServiceData = new HashMap<>(); // 存放服務提供的資料 uuid = gattService.getUuid().toString(); // 取得服務UUID if (uuid.equals("0000180f-0000-1000-8000-00805f9b34fb")) { // 如果有Battery服務UUID currentServiceData.put(LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<>(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<>(); // 讀取可用的Characteristics. for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap<String, String> currentCharaData = new HashMap<>(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put(LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristicsBattery.add(charas); // 加入電池服務相關characteristic gattCharacteristicData.add(gattCharacteristicGroupData); 取得電池服務

private BluetoothGattCharacteristic mBatteryNotifyCharacteristic; // 取得電池資料 private void GetBatteryData() { try { if (mGattCharacteristicsBattery != null) { // 取得服務的第1個characteristic final BluetoothGattCharacteristic characteristic = mGattCharacteristicsBattery.get(0).get(0); final int charaProp = characteristic.getProperties(); if ((charaProp & (BluetoothGattCharacteristic.PROPERTY_READ)) != 0) { if (mBatteryNotifyCharacteristic != null) { mBluetoothLeService.setCharacteristicNotification(mBatteryNotifyCharacteristic, false); mBatteryNotifyCharacteristic = null; } mBluetoothLeService.readCharacteristic(characteristic); // 讀取Characteristic if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { mBatteryNotifyCharacteristic = characteristic; mBluetoothLeService.setCharacteristicNotification(characteristic, true); if ((charaProp & (BluetoothGattCharacteristic.PROPERTY_INDICATE)) != 0) { mBluetoothLeService.setCharacteristicIndication(characteristic, true); } catch (Exception ex) { 取得電池資料

顯示電池數據 // 顯示電池數據 private void displayBatteryData(String data) { if (data != null) { myDialog.dismiss(); // 取消進度 //mbatteryDataField.setText(data); //顯示原始數據 String[] a = data.split("\n"); // Integer.parseInt(a[1].trim(),16)--> hex to decimal mbatteryDataField.setText("電池容量:" + Integer.parseInt(a[1].trim(),16) + " %"); //顯示電池數據 }

建立BluetoothLeService

BluetoothLeService事件與方法分類 Bind service服務 連線與提供服務 提供Activity呼叫的方法

加入宣告 private final static String TAG = "AAA"; private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; private int mConnectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; // 設定回傳到Activity的辨別參數----------------------------------------------------------------------- public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA_Battery = "com.example.bluetooth.le.EXTRA_DATA_Battery"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; // 設定服務的CharacteristicsUUID-------------------------------------------------------------------- public final static UUID UUID_MEASUREMENT_Battery = UUID.fromString(SampleGattAttributes.Battery_MEASUREMENT_Char); // 電池服務 Characteristics UUID public final static UUID UUID_MEASUREMENT = UUID.fromString(SampleGattAttributes.JMEX_MEASUREMENT_Char); // 你有興趣的服務 Characteristics UUID(這裡沒用)

Bind service服務

建立bind private final IBinder mBinder = new LocalBinder(); @Override public IBinder onBind(Intent intent) { return mBinder; } public class LocalBinder extends Binder { BluetoothLeService getService() { return BluetoothLeService.this; public boolean onUnbind(Intent intent) { close(); return super.onUnbind(intent);

連線與提供服務

initialize()方法 public boolean initialize() { if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); Log.i(TAG, "Unable to initialize BluetoothManager."); return false; } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.i(TAG, "Unable to obtain a BluetoothAdapter."); return true;

Connect方法 // 透過MAC連結服務 public boolean connect(final String address) { try { if (mBluetoothAdapter == null || address == null) { Log.i(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } // 已經連線 if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.i(TAG, "Trying to use an existing mBluetoothGatt for connection."); if (mBluetoothGatt.connect()) { mConnectionState = STATE_CONNECTING; return true; } else { // 透過MAC連結設備 final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.i(TAG, "Device not found. Unable to connect."); Log.i(TAG, "試圖連結BT GATT Server"); mBluetoothGatt = device.connectGatt(this, false, mGattCallback); // 連結GATT Server,並設定callback mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; //正在連線 }catch (Exception ex){

離線與關閉 // 離線 public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.i(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.disconnect(); // 與GATT Server斷線 // 關閉本Service服務 public void close() { if (mBluetoothGatt == null) { mBluetoothGatt.close(); // 關閉GATT Server mBluetoothGatt = null;

BluetoothGattCallback // 設定callback,用以回覆呼叫的Activity目前BLE狀態 private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; // 回傳到Activity的辨識字串 if (newState == BluetoothProfile.STATE_CONNECTED) { //已連線 intentAction = ACTION_GATT_CONNECTED; // 回傳到Activity的辨識字串 mConnectionState = STATE_CONNECTED; broadcastUpdate(intentAction); //透過廣播回覆Activity Log.i(TAG, "連結到GATT server."); Log.i(TAG, "啟動 service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //斷線 intentAction = ACTION_GATT_DISCONNECTED; // 回傳到Activity的辨識字串 mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "離開GATT server."); broadcastUpdate(intentAction); }

// 發現可用服務 @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG, "發現服務"); broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); //透過廣播回覆Activity } else { Log.i(TAG, "發現服務 received: " + status); } // 讀取Characteristic public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); //透過廣播回覆Activity Log.i("AAA", "從 Service讀取資料"); // Characteristic被改變 public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); Log.i("AAA", "改變Characteristic狀態"); };

broadcastUpdate方法 (I) // 單純回覆Activity BLE連線狀態 private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); }

broadcastUpdate方法(II) // 回覆Activity Characteristic狀態(被讀取或改變) private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { final Intent intent = new Intent(action); // 回傳到Activity的辨識字串 if (UUID_MEASUREMENT.equals(characteristic.getUuid())) { // 你有興趣的服務UUID(這裡沒用) Log.i(TAG, "JMEX"); final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for (byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); Log.i(TAG, "Received1=" + new String(data) + "\n" + stringBuilder.toString()); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); } if (UUID_MEASUREMENT_Battery.equals(characteristic.getUuid())) { // 電池服務UUID Log.i(TAG, "BATTERY"); Log.i(TAG, "Battery1=" + new String(data) + "\n" + stringBuilder.toString()); intent.putExtra(EXTRA_DATA_Battery, new String(data) + "\n" + stringBuilder.toString()); sendBroadcast(intent);

提供Activity呼叫的方法

讀取Characteristic內的值 // 讀取Characteristic內的值 public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.i(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.readCharacteristic(characteristic);

使用ByteArray寫入Characteristic public void writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.i(TAG, "BluetoothAdapter not initialized"); return; } boolean a=characteristic.setValue(value); boolean status = mBluetoothGatt.writeCharacteristic(characteristic); //這行一定要有

設定Characteristic Notification public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) { Log.i("AAA", "BluetoothAdapter initialized for notification"); Log.i("AAA", "characteristic id=" + characteristic.getUuid()); if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.i("AAA", "BluetoothAdapter not initialized"); return; } mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); if (enabled) { BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[] { 0x00, 0x00 }); boolean test=mBluetoothGatt.writeDescriptor(descriptor); Log.i("AAA", "test=" + test); } else { BluetoothGattDescriptor bluetoothGattDescriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); boolean test=mBluetoothGatt.writeDescriptor(bluetoothGattDescriptor); Log.i("AAA", "test1=" + test);

設定Characteristic Indication public void setCharacteristicIndication(BluetoothGattCharacteristic characteristic, boolean enabled) { Log.i("AAA", "BluetoothAdapter initialized for indication"); if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.i("AAA", "BluetoothAdapter not initialized"); return; } BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

透過GATT Server列示可用的服務 // 透過GATT Server列示可用的服務 public List<BluetoothGattService> getSupportedGattServices() { if (mBluetoothGatt == null) return null; return mBluetoothGatt.getServices(); }

列示服務與characteristic UUID SampleGattAttributes.java 列示服務與characteristic UUID

SampleGattAttributes // 設定所有需要的服務與characteristics的UUID public class SampleGattAttributes { private static HashMap<String, String> attributes = new HashMap(); public static String Battery_MEASUREMENT_Char = "00002a19-0000-1000-8000-00805f9b34fb"; // Battery Characteristics public static String JMEX_MEASUREMENT_Char = "00005056-0000-1000-8000-00805f9b34fb"; // 其他 Characteristics public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; //其他 Characteristics static { // 電池 attributes.put("0000180f-0000-1000-8000-00805f9b34fb", "Battery Service"); attributes.put("00002a19-0000-1000-8000-00805f9b34fb", "Battery Level"); // 其他 (目前沒用) attributes.put("00005054-0000-1000-8000-00805f9b34fb", "Other Service"); attributes.put("00005056-0000-1000-8000-00805f9b34fb", "Read Notify"); attributes.put("00005055-0000-1000-8000-00805f9b34fb", "Write"); } // 查詢UUID對應名稱 public static String lookup(String uuid, String defaultName) { String name = attributes.get(uuid); return name == null ? defaultName : name;

result