├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── assets
│ │ │ ├── APP_OAD_1.0.bin
│ │ │ ├── APP_OAD_1.1.bin
│ │ │ └── CC2650SensorTag_BLE_All_v1.01.bin
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── activity_historydata.xml
│ │ │ │ ├── content_device_home.xml
│ │ │ │ ├── activity_realtimedata.xml
│ │ │ │ ├── listview_item.xml
│ │ │ │ └── activity_device_home.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── zsl
│ │ │ │ └── bluetoothdemo
│ │ │ │ ├── adapter
│ │ │ │ └── DevicesAdapter.java
│ │ │ │ ├── utils
│ │ │ │ ├── ble
│ │ │ │ │ ├── BleDefinedUUIDs.java
│ │ │ │ │ ├── ParsedAd.java
│ │ │ │ │ ├── BleWrapperUiCallbacks.java
│ │ │ │ │ ├── oad
│ │ │ │ │ │ ├── Conversion.java
│ │ │ │ │ │ └── BluetoothLeService.java
│ │ │ │ │ ├── BleUtils.java
│ │ │ │ │ ├── BleNamesResolver.java
│ │ │ │ │ └── BleWrapper.java
│ │ │ │ └── adapter
│ │ │ │ │ ├── UniversalAdapter.java
│ │ │ │ │ └── ViewHolder.java
│ │ │ │ ├── activitys
│ │ │ │ ├── MyBluetoothDevice.java
│ │ │ │ ├── RealTimeDataActivity.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── HistoryDataActivity.java
│ │ │ │ └── DeviceHomeActivity.java
│ │ │ │ ├── base
│ │ │ │ └── BaseActivity.java
│ │ │ │ ├── application
│ │ │ │ └── MyApplication.java
│ │ │ │ └── ble
│ │ │ │ └── UniversalBluetoothLE.java
│ │ └── AndroidManifest.xml
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── zsl
│ │ └── bluetoothdemo
│ │ └── ApplicationTest.java
├── proguard-rules.pro
├── build.gradle
└── app.iml
├── settings.gradle
├── device-2015-05-25-192100.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── bluetoothDemo.iml
├── gradlew.bat
├── README.md
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | bluetoothDemo
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/device-2015-05-25-192100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/device-2015-05-25-192100.png
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/app/src/main/assets/APP_OAD_1.0.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/assets/APP_OAD_1.0.bin
--------------------------------------------------------------------------------
/app/src/main/assets/APP_OAD_1.1.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/assets/APP_OAD_1.1.bin
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/assets/CC2650SensorTag_BLE_All_v1.01.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BytesZero/bluetoothDemo/HEAD/app/src/main/assets/CC2650SensorTag_BLE_All_v1.01.bin
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 22 17:18:44 CST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 小米计步器
3 |
4 | Hello world!
5 | Settings
6 | DeviceHomeActivity
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 180dp
6 | 16dp
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/zsl/bluetoothdemo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/zsl/Downloads/android/android-sdk-macosx/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_historydata.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_device_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.zsl.bluetoothdemo"
9 | minSdkVersion 18
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:23.0.1'
25 | compile 'com.orhanobut:logger:1.8'
26 | //bugtags
27 | compile 'com.bugtags.library:bugtags-lib:1.0.5'
28 | compile 'com.android.support:design:23.0.1'
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Mon Sep 28 11:24:51 CST 2015
16 | systemProp.http.proxyPassword=26388
17 | systemProp.http.proxyHost=proxy.magic80.com
18 | systemProp.http.proxyUser=yy13003
19 | systemProp.http.proxyPort=7071
20 |
--------------------------------------------------------------------------------
/bluetoothDemo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_realtimedata.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
18 |
23 |
24 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/adapter/DevicesAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.adapter;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.content.Context;
5 |
6 | import com.zsl.bluetoothdemo.activitys.MyBluetoothDevice;
7 | import com.zsl.bluetoothdemo.R;
8 | import com.zsl.bluetoothdemo.utils.adapter.UniversalAdapter;
9 | import com.zsl.bluetoothdemo.utils.adapter.ViewHolder;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * Created by zsl on 15/5/25.
15 | */
16 | public class DevicesAdapter extends UniversalAdapter {
17 |
18 | /**
19 | * 通用的Adapter
20 | *
21 | * @param context 上下文
22 | * @param mlists 数据集
23 | */
24 |
25 | public DevicesAdapter(Context context, List mlists) {
26 | super(context, mlists, R.layout.listview_item);
27 | }
28 |
29 | @Override
30 | public void convert(ViewHolder holder, MyBluetoothDevice myBluetoothDevice, int position) {
31 | BluetoothDevice bluetoothDevice=myBluetoothDevice.getBluetoothDevice();
32 | holder.setText(R.id.listview_item_tv_name, bluetoothDevice.getName())
33 | .setText(R.id.listview_item_tv_address, bluetoothDevice.getAddress());
34 |
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/BleDefinedUUIDs.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.ble;
2 |
3 | import java.util.UUID;
4 |
5 | public class BleDefinedUUIDs {
6 |
7 | public static class Service {
8 | final static public UUID HEART_RATE = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb");
9 | };
10 |
11 | public static class Characteristic {
12 | final static public UUID HEART_RATE_MEASUREMENT = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");
13 | final static public UUID MANUFACTURER_STRING = UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb");
14 | final static public UUID MODEL_NUMBER_STRING = UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb");
15 | final static public UUID FIRMWARE_REVISION_STRING = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
16 | final static public UUID APPEARANCE = UUID.fromString("00002a01-0000-1000-8000-00805f9b34fb");
17 | final static public UUID BODY_SENSOR_LOCATION = UUID.fromString("00002a38-0000-1000-8000-00805f9b34fb");
18 | final static public UUID BATTERY_LEVEL = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb");
19 | }
20 |
21 | public static class Descriptor {
22 | final static public UUID CHAR_CLIENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/listview_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
26 |
27 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/adapter/UniversalAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.adapter;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.BaseAdapter;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * 这是一个通用的Adapter
13 | * Created by zsl on 15/5/19.
14 | */
15 | public abstract class UniversalAdapter extends BaseAdapter {
16 | protected Context context;
17 | protected List mlists;
18 | protected LayoutInflater mInflater;
19 | int layoutId;
20 |
21 |
22 |
23 | /**
24 | * 通用的Adapter
25 | * @param context 上下文
26 | * @param mlists 数据集
27 | * @param layoutId item 布局视图
28 | */
29 | public UniversalAdapter(Context context, List mlists,int layoutId) {
30 | this.context = context;
31 | this.mlists = mlists;
32 | this.layoutId=layoutId;
33 | mInflater=LayoutInflater.from(context);
34 | }
35 |
36 | @Override
37 | public int getCount() {
38 | return mlists==null?0:mlists.size();
39 | }
40 |
41 | @Override
42 | public T getItem(int position) {
43 | return mlists.get(position);
44 | }
45 |
46 | @Override
47 | public long getItemId(int position) {
48 | return position;
49 | }
50 |
51 | @Override
52 | public View getView(int position, View convertView, ViewGroup parent){
53 | ViewHolder holder=ViewHolder.get(context,convertView,parent, layoutId);
54 | convert(holder,getItem(position),position);
55 | return holder.getmConvertView();
56 | }
57 |
58 | public abstract void convert(ViewHolder holder,T t,int position);
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/activitys/MyBluetoothDevice.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.activitys;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 |
5 | import com.zsl.bluetoothdemo.utils.ble.ParsedAd;
6 |
7 | /**
8 | * Created by zsl on 15/9/23.
9 | * 自定义的BluetoothDevice,包含了解析后的广播报文信息
10 | */
11 | public class MyBluetoothDevice {
12 |
13 | private BluetoothDevice bluetoothDevice;
14 | private String address;
15 | private ParsedAd ParsedAd;
16 |
17 | public MyBluetoothDevice() {
18 |
19 | }
20 |
21 | public MyBluetoothDevice(BluetoothDevice bluetoothDevice, com.zsl.bluetoothdemo.utils.ble.ParsedAd parsedAd,String address) {
22 | this.bluetoothDevice = bluetoothDevice;
23 | ParsedAd = parsedAd;
24 | this.address=address;
25 | }
26 |
27 | public BluetoothDevice getBluetoothDevice() {
28 | return bluetoothDevice;
29 | }
30 |
31 | public void setBluetoothDevice(BluetoothDevice bluetoothDevice) {
32 | this.bluetoothDevice = bluetoothDevice;
33 | }
34 |
35 | public com.zsl.bluetoothdemo.utils.ble.ParsedAd getParsedAd() {
36 | return ParsedAd;
37 | }
38 |
39 | public void setParsedAd(com.zsl.bluetoothdemo.utils.ble.ParsedAd parsedAd) {
40 | ParsedAd = parsedAd;
41 | }
42 |
43 | public String getAddress() {
44 | return address;
45 | }
46 |
47 | public void setAddress(String address) {
48 | this.address = address;
49 | }
50 |
51 | @Override
52 | public String toString() {
53 | return "MyBluetoothDevice{" +
54 | "bluetoothDevice=" + bluetoothDevice +
55 | ", address='" + address + '\'' +
56 | ", ParsedAd=" + ParsedAd +
57 | '}';
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_device_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
21 |
26 |
27 |
32 |
33 |
38 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/base/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.base;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.MotionEvent;
8 | import android.widget.Toast;
9 |
10 | import com.bugtags.library.Bugtags;
11 |
12 | /**
13 | * Created by zsl on 15/9/22.
14 | */
15 | public class BaseActivity extends AppCompatActivity{
16 | private Activity mActivity;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | mActivity=this;
22 | }
23 |
24 | @Override
25 | protected void onResume() {
26 | super.onResume();
27 | //注:回调 1
28 | Bugtags.onResume(this);
29 | }
30 |
31 | @Override
32 | protected void onPause() {
33 | super.onPause();
34 | //注:回调 2
35 | Bugtags.onPause(this);
36 | }
37 |
38 | @Override
39 | public boolean dispatchTouchEvent(MotionEvent event) {
40 | //注:回调 3
41 | Bugtags.onDispatchTouchEvent(this, event);
42 | return super.dispatchTouchEvent(event);
43 | }
44 |
45 | /**
46 | * 显示长的吐司
47 | * @param content
48 | */
49 | protected void showLongToast(String content){
50 | Toast.makeText(mActivity, ""+content, Toast.LENGTH_LONG).show();
51 | }
52 |
53 | /**
54 | * 显示短的吐司
55 | * @param content
56 | */
57 | protected void showShortToast(String content){
58 | Toast.makeText(mActivity, "", Toast.LENGTH_SHORT).show();
59 | }
60 |
61 |
62 | /**
63 | * startActivity
64 | * @param cls
65 | */
66 | protected void baseStartActivity(Class> cls){
67 | Intent intent=new Intent(mActivity,cls);
68 | startActivity(intent);
69 | }
70 |
71 | /**
72 | * startActivityForResult
73 | * @param cls
74 | * @param requestCode
75 | */
76 | protected void baseStartActivityForResult(Class> cls,int requestCode){
77 | Intent intent=new Intent(mActivity,cls);
78 | startActivityForResult(intent,requestCode);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/application/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.application;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.ServiceConnection;
8 | import android.os.IBinder;
9 |
10 | import com.bugtags.library.Bugtags;
11 | import com.zsl.bluetoothdemo.utils.ble.oad.BluetoothLeService;
12 |
13 | /**
14 | * Created by zsl on 15/9/22.
15 | */
16 | public class MyApplication extends Application {
17 | private BluetoothLeService mBluetoothLeService = null;
18 |
19 | @Override
20 | public void onCreate() {
21 | startBluetoothLeService();
22 | super.onCreate();
23 | //BugTags
24 | Bugtags.start("ec53c69dfe79cdb66a978138716eb32e", this, Bugtags.BTGInvocationEventBubble);
25 |
26 |
27 | }
28 | private final ServiceConnection mServiceConnection = new ServiceConnection() {
29 |
30 | public void onServiceConnected(ComponentName componentName, IBinder service) {
31 | mBluetoothLeService = ((BluetoothLeService.LocalBinder) service)
32 | .getService();
33 | if (!mBluetoothLeService.initialize()) {
34 | //Toast.makeText(context, "Unable to initialize BluetoothLeService", Toast.LENGTH_SHORT).show();
35 | //finish();
36 | return;
37 | }
38 | final int n = mBluetoothLeService.numConnectedDevices();
39 | if (n > 0) {
40 | /*
41 | runOnUiThread(new Runnable() {
42 | public void run() {
43 | mThis.setError("Multiple connections!");
44 | }
45 | });
46 | */
47 | } else {
48 | //startScan();
49 | // Log.i(TAG, "BluetoothLeService connected");
50 | }
51 | }
52 |
53 | public void onServiceDisconnected(ComponentName componentName) {
54 | mBluetoothLeService = null;
55 | // Log.i(TAG, "BluetoothLeService disconnected");
56 | }
57 | };
58 |
59 | private void startBluetoothLeService() {
60 | Intent bindIntent = new Intent(this, BluetoothLeService.class);
61 | startService(bindIntent);
62 | bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/adapter/ViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.adapter;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.util.SparseArray;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 |
13 | /**
14 | * ViewHolder
15 | * Created by zsl on 15/5/19.
16 | */
17 | public class ViewHolder {
18 | private SparseArray mViews;
19 | private View mConvertView;
20 | private Context mContext;
21 |
22 | /**
23 | * 初始化ViewHolder
24 | * @param context 上下文
25 | * @param parent 父视图
26 | * @param layoutId Item layout
27 | *
28 | */
29 | public ViewHolder(Context context, ViewGroup parent, int layoutId) {
30 | this.mContext=context;
31 | this.mViews=new SparseArray();
32 | mConvertView= LayoutInflater.from(context).inflate(layoutId,parent,false);
33 | //设置Tag
34 | mConvertView.setTag(this);
35 | }
36 |
37 | /**
38 | * 获得到ViewHolder
39 | * @param context 上下文
40 | * @param convertView convertView
41 | * @param parent 父视图
42 | * @param layoutId Item layout
43 | * @return 返回ViewHolder对象
44 | */
45 | public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId) {
46 | if (convertView == null) {
47 | return new ViewHolder(context, parent, layoutId);
48 | }else{
49 | return (ViewHolder) convertView.getTag();
50 | }
51 | }
52 |
53 | /**
54 | * 获得到控件
55 | * @param viewId item layout 中控件的id
56 | * @param 范型
57 | * @return 范型View
58 | */
59 |
60 | public T getView(int viewId){
61 | View view=mViews.get(viewId);
62 | if (view==null){
63 | view=mConvertView.findViewById(viewId);
64 | mViews.put(viewId,view);
65 | }
66 | return (T) view;
67 | }
68 |
69 | /**
70 | * 获得到convertView
71 | * @return convertView
72 | */
73 | public View getmConvertView() {
74 | return mConvertView;
75 | }
76 |
77 | /**
78 | * 设置TextView的文本
79 | * @param viewId item layout 中TextView的id
80 | * @param text 文本内容
81 | * @return ViewHolder
82 | */
83 | public ViewHolder setText(int viewId,String text){
84 | TextView textView=getView(viewId);
85 | textView.setText(text);
86 | return this;
87 | }
88 |
89 | /**
90 | * 通过url设置ImageView 的图片
91 | * 这里可以修改为自己的图片加载库
92 | * @param viewId item layout 中ImageView的id
93 | * @param url 图片的url
94 | * @return ViewHolder
95 | */
96 | public ViewHolder setImage(int viewId,String url){
97 | ImageView imageView=getView(viewId);
98 | //这里可以修改为自己的图片加载库
99 | // Ion.with(mContext).load(url).intoImageView(imageView);
100 | return this;
101 | }
102 |
103 | /**
104 | * 通过ResourceId设置ImageView 的图片
105 | * @param viewId item layout 中ImageView的id
106 | * @param resourceId 图片资源文件的id
107 | * @return ViewHolder
108 | */
109 | public ViewHolder setImageResource(int viewId,int resourceId){
110 | ImageView imageView=getView(viewId);
111 | imageView.setImageResource(resourceId);
112 | return this;
113 | }
114 |
115 | /**
116 | * 通过bitmap 设置ImageView 的图片
117 | * @param viewId item layout 中ImageView的id
118 | * @param bitmap bitmap
119 | * @return ViewHolder
120 | */
121 | public ViewHolder setImageBitmap(int viewId,Bitmap bitmap){
122 | ImageView imageView=getView(viewId);
123 | imageView.setImageBitmap(bitmap);
124 | return this;
125 | }
126 |
127 | /**
128 | * 设置View隐藏Gone
129 | * @param viewId
130 | * @return
131 | */
132 | public ViewHolder setViewGone(int viewId){
133 | getView(viewId).setVisibility(View.GONE);
134 | return this;
135 | }
136 |
137 | /**
138 | * 设置View隐藏Invisible
139 | * @param viewId
140 | * @return
141 | */
142 | public ViewHolder setViewInvisible(int viewId){
143 | getView(viewId).setVisibility(View.INVISIBLE);
144 | return this;
145 | }
146 | /**
147 | * 设置View显示Visible
148 | * @param viewId
149 | * @return
150 | */
151 | public ViewHolder setViewVisible(int viewId){
152 | getView(viewId).setVisibility(View.VISIBLE);
153 | return this;
154 | }
155 |
156 |
157 | /**
158 | * ==============下边可以写自己的控件的实现,参考上边的ImageView================
159 | */
160 |
161 |
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/ble/UniversalBluetoothLE.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.ble;
2 |
3 |
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.BluetoothGatt;
7 | import android.bluetooth.BluetoothGattCallback;
8 | import android.bluetooth.BluetoothManager;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.os.Handler;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * 蓝牙的工具类
17 | * Created by zsl on 15/5/25.
18 | */
19 |
20 |
21 | public class UniversalBluetoothLE {
22 |
23 | //UniversalBluetoothLE
24 | public static UniversalBluetoothLE universalBluetoothLE;
25 |
26 | private Context context;
27 | //BluetoothAdapter
28 | private BluetoothAdapter mBluetoothAdapter;
29 | //BluetoothManager
30 | private BluetoothManager bluetoothManager;
31 |
32 | //打开蓝牙的请求码
33 | public static final int REQUEST_ENABLE_BLUETOOTH = 10010;
34 |
35 | //是否正在扫描蓝牙设备
36 | private boolean mScanning;
37 | //设置扫描时长
38 | private static final long SCAN_PERIOD = 10000;
39 |
40 | //蓝牙扫描的返回
41 | BluetoothAdapter.LeScanCallback leScanCallback;
42 | //蓝牙设别的list
43 | List bluetoothDeviceList = new ArrayList();
44 |
45 | Handler mHandler = new Handler();
46 |
47 | LeScanListenter leScanListenter;
48 |
49 | private UniversalBluetoothLE(Context context) {
50 | this.context = context;
51 | //得到BluetoothManager
52 | this.bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
53 | //得到BluetoothAdapter
54 | this.mBluetoothAdapter = bluetoothManager.getAdapter();
55 |
56 | //蓝牙搜索的回调
57 | leScanCallback = new BluetoothAdapter.LeScanCallback() {
58 |
59 | @Override
60 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
61 | bluetoothDeviceList.add(device);
62 |
63 | //返回所有列表
64 | leScanListenter.leScanCallBack(bluetoothDeviceList);
65 |
66 | }
67 | };
68 | }
69 |
70 | /**
71 | * 获得到UniversalBluetoothLE对象
72 | *
73 | * @param context
74 | * @return
75 | */
76 | public static UniversalBluetoothLE inistance(Context context) {
77 | if (universalBluetoothLE == null) {
78 | universalBluetoothLE = new UniversalBluetoothLE(context);
79 | }
80 | return universalBluetoothLE;
81 | }
82 |
83 | /**
84 | * 检查蓝牙是否打开并且启动打开蓝牙的方法
85 | */
86 | public void openBbletooth() {
87 | //判断蓝牙是否开启
88 | if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
89 | //打开蓝牙
90 | Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
91 | context.startActivity(enableIntent);
92 | }
93 | }
94 |
95 | /**
96 | * 开始(true)或结束(false)蓝牙扫描
97 | *
98 | * @param enable
99 | */
100 | private void scanLeDevice(final boolean enable) {
101 | if (enable && mScanning == false) {
102 | mHandler.postDelayed(new Runnable() {
103 | @Override
104 | public void run() {
105 | mScanning = false;
106 | mBluetoothAdapter.stopLeScan(leScanCallback);
107 | }
108 | }, SCAN_PERIOD);
109 |
110 | mScanning = true;
111 | mBluetoothAdapter.startLeScan(leScanCallback);
112 | } else {
113 | mScanning = false;
114 | mBluetoothAdapter.stopLeScan(leScanCallback);
115 | }
116 | }
117 |
118 | /**
119 | * 开始搜索蓝牙设备
120 | *
121 | * @param leScanListenter 搜索蓝牙设备的回调(返回设备列表)
122 | */
123 | public void startScanLeDevice(final LeScanListenter leScanListenter) {
124 | bluetoothDeviceList.clear();
125 | this.leScanListenter=leScanListenter;
126 | scanLeDevice(true);
127 | }
128 |
129 | /**
130 | * 停止搜索设备
131 | */
132 | public void stopScanLeDevice() {
133 | if (leScanCallback == null)
134 | return;
135 | scanLeDevice(false);
136 | }
137 |
138 | /**
139 | * 搜索蓝牙的回调
140 | */
141 | public interface LeScanListenter {
142 | void leScanCallBack(List bluetoothDeviceList);
143 | }
144 |
145 | /**
146 | * 得到BluetoothGatt
147 | * @param device 设备
148 | * @param autoConnect 是否自动链接
149 | * @param bluetoothGattCallback 回调
150 | */
151 | public BluetoothGatt getConnectGatt(BluetoothDevice device,boolean autoConnect,BluetoothGattCallback bluetoothGattCallback){
152 | return device.connectGatt(context, autoConnect, bluetoothGattCallback);
153 | }
154 |
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/ParsedAd.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.ble;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.ByteOrder;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.UUID;
8 |
9 | /**
10 | * Created by zsl on 15/9/24.
11 | * 广播报文的信息
12 | */
13 | public class ParsedAd {
14 | //flags
15 | public byte flags;
16 | //ServerData uuid
17 | public String serverData_uuid;
18 | //ServerData data
19 | public String serverData_data;
20 | //loaclName
21 | public String localName;
22 | //manufacturer
23 | public short manufacturer;
24 | //UUID
25 | public List uuids;
26 |
27 | public ParsedAd() {
28 | this.uuids = new ArrayList();
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "ParsedAd{" +
34 | "flags=" + flags +
35 | ", serverData_uuid=" + serverData_uuid +
36 | ", serverData_data='" + serverData_data + '\'' +
37 | ", localName='" + localName + '\'' +
38 | ", manufacturer=" + manufacturer +
39 | ", uuids=" + uuids +
40 | '}';
41 | }
42 |
43 | public static ParsedAd parseData(byte[] adv_data) {
44 | ParsedAd parsedAd = new ParsedAd();
45 | //把byte[]转换为一个byteBuffer(缓冲区),然后按照c的排序使用LITTLE_ENDIAN,java默认是BIG_ENDIAN
46 | ByteBuffer buffer = ByteBuffer.wrap(adv_data).order(ByteOrder.LITTLE_ENDIAN);
47 | //buffer.remaining() 返回剩余的可用长度,如果小于2返回真
48 | while (buffer.remaining() > 2) {
49 | //获取此时buffer的长度
50 | byte length = buffer.get();
51 | if (length == 0)
52 | break;
53 | //获取类型,同时-1
54 | byte type = buffer.get();
55 | length -= 1;
56 |
57 | switch (type) {
58 | case 0x01: // 获得到Flags,同时-1
59 | parsedAd.flags = buffer.get();
60 | length--;
61 | break;
62 |
63 | case 0x02: // Partial list of 16-bit UUIDs
64 | case 0x03: // Complete list of 16-bit UUIDs
65 | case 0x14: // List of 16-bit Service Solicitation UUIDs
66 | while (length >= 2) {
67 | parsedAd.uuids.add(UUID.fromString(String.format(
68 | "%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
69 | length -= 2;
70 | }
71 | break;
72 | case 0x04: // Partial list of 32 bit service UUIDs
73 | case 0x05: // Complete list of 32 bit service UUIDs
74 | while (length >= 4) {
75 | parsedAd.uuids.add(UUID.fromString(String.format(
76 | "%08x-0000-1000-8000-00805f9b34fb", buffer.getInt())));
77 | length -= 4;
78 | }
79 | break;
80 | case 0x06: // Partial list of 128-bit UUIDs
81 | case 0x07: // Complete list of 128-bit UUIDs
82 | case 0x15: // List of 128-bit Service Solicitation UUIDs
83 | while (length >= 16) {
84 | long lsb = buffer.getLong();
85 | long msb = buffer.getLong();
86 | parsedAd.uuids.add(new UUID(msb, lsb));
87 | length -= 16;
88 | }
89 | break;
90 | case 0x08: // Short local device name
91 | case 0x09: // Complete local device name
92 | byte sb[] = new byte[length];
93 | buffer.get(sb, 0, length);
94 | length = 0;
95 | parsedAd.localName = new String(sb).trim();
96 | break;
97 | case 0x16: //ServerData
98 | //uuid
99 | if (length>=2){
100 | String sd_uuid=String.format("%02x", buffer.getShort());
101 | parsedAd.serverData_uuid=sd_uuid;
102 | length -= 2;
103 | }
104 | //data
105 | String serverData="";
106 | while (length >= 2) {
107 | String sd_data=String.format("%04x", buffer.getShort());
108 | serverData+=sd_data+":";
109 | length -= 2;
110 | }
111 | parsedAd.serverData_data=serverData;
112 | break;
113 | case (byte) 0xFF: // Manufacturer Specific Data
114 | parsedAd.manufacturer = buffer.getShort();
115 | length -= 2;
116 | break;
117 | default: // skip
118 | break;
119 | }
120 | if (length > 0) {
121 | //buffer.position() 相当于一个游标,记录我们从哪里开始写数据,或者标记我们从哪里开始读取数据
122 | buffer.position(buffer.position() + length);
123 | }
124 | }
125 | return parsedAd;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/BleWrapperUiCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.ble;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.bluetooth.BluetoothGatt;
5 | import android.bluetooth.BluetoothGattCharacteristic;
6 | import android.bluetooth.BluetoothGattService;
7 |
8 | import java.util.List;
9 |
10 | public interface BleWrapperUiCallbacks {
11 |
12 | public void uiDeviceFound(final BluetoothDevice device, int rssi, byte[] record);
13 |
14 | public void uiDeviceConnected(final BluetoothGatt gatt,
15 | final BluetoothDevice device);
16 |
17 | public void uiDeviceDisconnected(final BluetoothGatt gatt,
18 | final BluetoothDevice device);
19 |
20 | public void uiAvailableServices(final BluetoothGatt gatt,
21 | final BluetoothDevice device,
22 | final List services);
23 |
24 | public void uiCharacteristicForService(final BluetoothGatt gatt,
25 | final BluetoothDevice device,
26 | final BluetoothGattService service,
27 | final List chars);
28 |
29 | public void uiCharacteristicsDetails(final BluetoothGatt gatt,
30 | final BluetoothDevice device,
31 | final BluetoothGattService service,
32 | final BluetoothGattCharacteristic characteristic);
33 |
34 | public void uiNewValueForCharacteristic(final BluetoothGatt gatt,
35 | final BluetoothDevice device,
36 | final BluetoothGattService service,
37 | final BluetoothGattCharacteristic ch,
38 | final String strValue,
39 | final int intValue,
40 | final byte[] rawValue,
41 | final String timestamp);
42 |
43 | public void uiGotNotification(final BluetoothGatt gatt,
44 | final BluetoothDevice device,
45 | final BluetoothGattService service,
46 | final BluetoothGattCharacteristic characteristic);
47 |
48 | public void uiSuccessfulWrite(final BluetoothGatt gatt,
49 | final BluetoothDevice device,
50 | final BluetoothGattService service,
51 | final BluetoothGattCharacteristic ch,
52 | final String description);
53 |
54 | public void uiFailedWrite(final BluetoothGatt gatt,
55 | final BluetoothDevice device,
56 | final BluetoothGattService service,
57 | final BluetoothGattCharacteristic ch,
58 | final String description);
59 |
60 | public void uiNewRssiAvailable(final BluetoothGatt gatt, final BluetoothDevice device, final int rssi);
61 |
62 | /* define Null Adapter class for that interface */
63 | public static class Null implements BleWrapperUiCallbacks {
64 | @Override
65 | public void uiDeviceConnected(BluetoothGatt gatt, BluetoothDevice device) {}
66 | @Override
67 | public void uiDeviceDisconnected(BluetoothGatt gatt, BluetoothDevice device) {}
68 | @Override
69 | public void uiAvailableServices(BluetoothGatt gatt, BluetoothDevice device,
70 | List services) {}
71 | @Override
72 | public void uiCharacteristicForService(BluetoothGatt gatt,
73 | BluetoothDevice device, BluetoothGattService service,
74 | List chars) {}
75 | @Override
76 | public void uiCharacteristicsDetails(BluetoothGatt gatt,
77 | BluetoothDevice device, BluetoothGattService service,
78 | BluetoothGattCharacteristic characteristic) {}
79 | @Override
80 | public void uiNewValueForCharacteristic(BluetoothGatt gatt,
81 | BluetoothDevice device, BluetoothGattService service,
82 | BluetoothGattCharacteristic ch, String strValue, int intValue,
83 | byte[] rawValue, String timestamp) {}
84 | @Override
85 | public void uiGotNotification(BluetoothGatt gatt, BluetoothDevice device,
86 | BluetoothGattService service,
87 | BluetoothGattCharacteristic characteristic) {}
88 | @Override
89 | public void uiSuccessfulWrite(BluetoothGatt gatt, BluetoothDevice device,
90 | BluetoothGattService service, BluetoothGattCharacteristic ch,
91 | String description) {}
92 | @Override
93 | public void uiFailedWrite(BluetoothGatt gatt, BluetoothDevice device,
94 | BluetoothGattService service, BluetoothGattCharacteristic ch,
95 | String description) {}
96 | @Override
97 | public void uiNewRssiAvailable(BluetoothGatt gatt, BluetoothDevice device,
98 | int rssi) {}
99 | @Override
100 | public void uiDeviceFound(BluetoothDevice device, int rssi, byte[] record) {}
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bluetoothDemo
2 | 蓝牙低功耗(BLE)的demo
3 |
4 | ###工具类
5 | ```java
6 | package com.zsl.bluetoothdemo.ble;
7 |
8 |
9 | import android.bluetooth.BluetoothAdapter;
10 | import android.bluetooth.BluetoothDevice;
11 | import android.bluetooth.BluetoothGatt;
12 | import android.bluetooth.BluetoothGattCallback;
13 | import android.bluetooth.BluetoothManager;
14 | import android.content.Context;
15 | import android.content.Intent;
16 | import android.os.Handler;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | /**
21 | * 蓝牙的工具类
22 | * Created by zsl on 15/5/25.
23 | */
24 |
25 |
26 | public class UniversalBluetoothLE {
27 |
28 | //UniversalBluetoothLE
29 | public static UniversalBluetoothLE universalBluetoothLE;
30 |
31 | private Context context;
32 | //BluetoothAdapter
33 | private BluetoothAdapter mBluetoothAdapter;
34 | //BluetoothManager
35 | private BluetoothManager bluetoothManager;
36 |
37 | //打开蓝牙的请求码
38 | public static final int REQUEST_ENABLE_BLUETOOTH = 10010;
39 |
40 | //是否正在扫描蓝牙设备
41 | private boolean mScanning;
42 | //设置扫描时长
43 | private static final long SCAN_PERIOD = 10000;
44 |
45 | //蓝牙扫描的返回
46 | BluetoothAdapter.LeScanCallback leScanCallback;
47 | //蓝牙设别的list
48 | List bluetoothDeviceList = new ArrayList();
49 |
50 | Handler mHandler = new Handler();
51 |
52 | LeScanListenter leScanListenter;
53 |
54 | private UniversalBluetoothLE(Context context) {
55 | this.context = context;
56 | //得到BluetoothManager
57 | this.bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
58 | //得到BluetoothAdapter
59 | this.mBluetoothAdapter = bluetoothManager.getAdapter();
60 |
61 | //蓝牙搜索的回调
62 | leScanCallback = new BluetoothAdapter.LeScanCallback() {
63 |
64 | @Override
65 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
66 | bluetoothDeviceList.add(device);
67 |
68 | //返回所有列表
69 | leScanListenter.leScanCallBack(bluetoothDeviceList);
70 |
71 | }
72 | };
73 | }
74 |
75 | /**
76 | * 获得到UniversalBluetoothLE对象
77 | *
78 | * @param context
79 | * @return
80 | */
81 | public static UniversalBluetoothLE inistance(Context context) {
82 | if (universalBluetoothLE == null) {
83 | universalBluetoothLE = new UniversalBluetoothLE(context);
84 | }
85 | return universalBluetoothLE;
86 | }
87 |
88 | /**
89 | * 检查蓝牙是否打开并且启动打开蓝牙的方法
90 | */
91 | public void openBbletooth() {
92 | //判断蓝牙是否开启
93 | if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
94 | //打开蓝牙
95 | Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
96 | context.startActivity(enableIntent);
97 | }
98 | }
99 |
100 | /**
101 | * 开始(true)或结束(false)蓝牙扫描
102 | *
103 | * @param enable
104 | */
105 | private void scanLeDevice(final boolean enable) {
106 | if (enable && mScanning == false) {
107 | mHandler.postDelayed(new Runnable() {
108 | @Override
109 | public void run() {
110 | mScanning = false;
111 | mBluetoothAdapter.stopLeScan(leScanCallback);
112 | }
113 | }, SCAN_PERIOD);
114 |
115 | mScanning = true;
116 | mBluetoothAdapter.startLeScan(leScanCallback);
117 | } else {
118 | mScanning = false;
119 | mBluetoothAdapter.stopLeScan(leScanCallback);
120 | }
121 | }
122 |
123 | /**
124 | * 开始搜索蓝牙设备
125 | *
126 | * @param leScanListenter 搜索蓝牙设备的回调(返回设备列表)
127 | */
128 | public void startScanLeDevice(final LeScanListenter leScanListenter) {
129 | bluetoothDeviceList.clear();
130 | this.leScanListenter=leScanListenter;
131 | scanLeDevice(true);
132 | }
133 |
134 | /**
135 | * 停止搜索设备
136 | */
137 | public void stopScanLeDevice() {
138 | if (leScanCallback == null)
139 | return;
140 | scanLeDevice(false);
141 | }
142 |
143 | /**
144 | * 搜索蓝牙的回调
145 | */
146 | public interface LeScanListenter {
147 | void leScanCallBack(List bluetoothDeviceList);
148 | }
149 |
150 | /**
151 | * 得到BluetoothGatt
152 | * @param device 设备
153 | * @param autoConnect 是否自动链接
154 | * @param bluetoothGattCallback 回调
155 | */
156 | public BluetoothGatt getConnectGatt(BluetoothDevice device,boolean autoConnect,BluetoothGattCallback bluetoothGattCallback){
157 | return device.connectGatt(context, autoConnect, bluetoothGattCallback);
158 | }
159 |
160 |
161 | }
162 |
163 | ```
164 |
165 | > 初始化
166 | ```java
167 | //在onCreate中
168 | //初始化UniversalBluetoothLE
169 | universalBluetoothLE = UniversalBluetoothLE.inistance(MainActivity.this);
170 | ```
171 |
172 | > 检测是否打开蓝牙并且请求系统打开蓝牙
173 | ```java
174 | //检测是否打开蓝牙并且请求系统打开蓝牙
175 | universalBluetoothLE.openBbletooth();
176 | ```
177 |
178 | > 链接设备
179 |
180 | ```java
181 | mBluetoothGatt=universalBluetoothLE.getConnectGatt(device,true,mGattCallback);
182 | mBluetoothGatt.connect();
183 | ```
184 |
185 | ###效果
186 |
187 | 
188 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/oad/Conversion.java:
--------------------------------------------------------------------------------
1 | /**************************************************************************************************
2 | Filename: Conversion.java
3 | Revised: $Date: 2013-08-30 12:02:37 +0200 (fr, 30 aug 2013) $
4 | Revision: $Revision: 27470 $
5 |
6 | Copyright (c) 2013 - 2014 Texas Instruments Incorporated
7 |
8 | All rights reserved not granted herein.
9 | Limited License.
10 |
11 | Texas Instruments Incorporated grants a world-wide, royalty-free,
12 | non-exclusive license under copyrights and patents it now or hereafter
13 | owns or controls to make, have made, use, import, offer to sell and sell ("Utilize")
14 | this software subject to the terms herein. With respect to the foregoing patent
15 | license, such license is granted solely to the extent that any such patent is necessary
16 | to Utilize the software alone. The patent license shall not apply to any combinations which
17 | include this software, other than combinations with devices manufactured by or for TI ('TI Devices').
18 | No hardware patent is licensed hereunder.
19 |
20 | Redistributions must preserve existing copyright notices and reproduce this license (including the
21 | above copyright notice and the disclaimer and (if applicable) source code license limitations below)
22 | in the documentation and/or other materials provided with the distribution
23 |
24 | Redistribution and use in binary form, without modification, are permitted provided that the following
25 | conditions are met:
26 |
27 | * No reverse engineering, decompilation, or disassembly of this software is permitted with respect to any
28 | software provided in binary form.
29 | * any redistribution and use are licensed by TI for use only with TI Devices.
30 | * Nothing shall obligate TI to provide you with source code for the software licensed and provided to you in object code.
31 |
32 | If software source code is provided to you, modification and redistribution of the source code are permitted
33 | provided that the following conditions are met:
34 |
35 | * any redistribution and use of the source code, including any resulting derivative works, are licensed by
36 | TI for use only with TI Devices.
37 | * any redistribution and use of any object code compiled from the source code and any resulting derivative
38 | works, are licensed by TI for use only with TI Devices.
39 |
40 | Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to endorse or
41 | promote products derived from this software without specific prior written permission.
42 |
43 | DISCLAIMER.
44 |
45 | THIS SOFTWARE IS PROVIDED BY TI AND TI'S LICENSORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
46 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
47 | IN NO EVENT SHALL TI AND TI'S LICENSORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
48 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
49 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
51 | POSSIBILITY OF SUCH DAMAGE.
52 |
53 |
54 | **************************************************************************************************/
55 | package com.zsl.bluetoothdemo.utils.ble.oad;
56 |
57 | import java.util.Formatter;
58 |
59 | /* This class encapsulates utility functions */
60 | public class Conversion {
61 |
62 | public static byte loUint16(short v) {
63 | return (byte) (v & 0xFF);
64 | }
65 |
66 | public static byte hiUint16(short v) {
67 | return (byte) (v >> 8);
68 | }
69 |
70 | public static short buildUint16(byte hi, byte lo) {
71 | return (short) ((hi << 8) + (lo & 0xff));
72 | }
73 |
74 | public static String BytetohexString(byte[] b, int len) {
75 | StringBuilder sb = new StringBuilder(b.length * (2 + 1));
76 | Formatter formatter = new Formatter(sb);
77 |
78 | for (int i = 0; i < len; i++) {
79 | if (i < len - 1)
80 | formatter.format("%02X:", b[i]);
81 | else
82 | formatter.format("%02X", b[i]);
83 |
84 | }
85 | formatter.close();
86 |
87 | return sb.toString();
88 | }
89 |
90 | static String BytetohexString(byte[] b, boolean reverse) {
91 | StringBuilder sb = new StringBuilder(b.length * (2 + 1));
92 | Formatter formatter = new Formatter(sb);
93 |
94 | if (!reverse) {
95 | for (int i = 0; i < b.length; i++) {
96 | if (i < b.length - 1)
97 | formatter.format("%02X:", b[i]);
98 | else
99 | formatter.format("%02X", b[i]);
100 |
101 | }
102 | } else {
103 | for (int i = (b.length - 1); i >= 0; i--) {
104 | if (i > 0)
105 | formatter.format("%02X:", b[i]);
106 | else
107 | formatter.format("%02X", b[i]);
108 |
109 | }
110 | }
111 | formatter.close();
112 |
113 | return sb.toString();
114 | }
115 |
116 | // Convert hex String to Byte
117 | public static int hexStringtoByte(String sb, byte[] results) {
118 |
119 | int i = 0;
120 | boolean j = false;
121 |
122 | if (sb != null) {
123 | for (int k = 0; k < sb.length(); k++) {
124 | if (((sb.charAt(k)) >= '0' && (sb.charAt(k) <= '9')) || ((sb.charAt(k)) >= 'a' && (sb.charAt(k) <= 'f'))
125 | || ((sb.charAt(k)) >= 'A' && (sb.charAt(k) <= 'F'))) {
126 | if (j) {
127 | results[i] += (byte) (Character.digit(sb.charAt(k), 16));
128 | i++;
129 | } else {
130 | results[i] = (byte) (Character.digit(sb.charAt(k), 16) << 4);
131 | }
132 | j = !j;
133 | }
134 | }
135 | }
136 | return i;
137 | }
138 |
139 | public static boolean isAsciiPrintable(String str) {
140 | if (str == null) {
141 | return false;
142 | }
143 | int sz = str.length();
144 | for (int i = 0; i < sz; i++) {
145 | if (isAsciiPrintable(str.charAt(i)) == false) {
146 | return false;
147 | }
148 | }
149 | return true;
150 | }
151 |
152 | private static boolean isAsciiPrintable(char ch) {
153 | return ch >= 32 && ch < 127;
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/BleUtils.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.ble;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | /**
8 | * Created by zsl on 15/9/23.
9 | */
10 | public class BleUtils {
11 |
12 | static final int EBLE_FLAGS = 0x01;//«Flags» Bluetooth Core Specification:
13 | static final int EBLE_16BitUUIDInc = 0x02;//«Incomplete List of 16-bit Service Class UUIDs» Bluetooth Core Specification:
14 | static final int EBLE_16BitUUIDCom = 0x03;//«Complete List of 16-bit Service Class UUIDs» Bluetooth Core Specification:
15 | static final int EBLE_32BitUUIDInc = 0x04;//«Incomplete List of 32-bit Service Class UUIDs» Bluetooth Core Specification:
16 | static final int EBLE_32BitUUIDCom = 0x05;//«Complete List of 32-bit Service Class UUIDs» Bluetooth Core Specification:
17 | static final int EBLE_128BitUUIDInc = 0x06;//«Incomplete List of 128-bit Service Class UUIDs» Bluetooth Core Specification:
18 | static final int EBLE_128BitUUIDCom = 0x07;//«Complete List of 128-bit Service Class UUIDs» Bluetooth Core Specification:
19 | static final int EBLE_SHORTNAME = 0x08;//«Shortened Local Name» Bluetooth Core Specification:
20 | static final int EBLE_LOCALNAME = 0x09;//«Complete Local Name» Bluetooth Core Specification:
21 | static final int EBLE_TXPOWERLEVEL = 0x0A;//«Tx Power Level» Bluetooth Core Specification:
22 | static final int EBLE_DEVICECLASS = 0x0D;//«Class of Device» Bluetooth Core Specification:
23 | static final int EBLE_SIMPLEPAIRHASH = 0x0E;//«Simple Pairing Hash C» Bluetooth Core Specification:«Simple Pairing Hash C-192» Core Specification Supplement, Part A, section 1.6
24 | static final int EBLE_SIMPLEPAIRRAND = 0x0F;//«Simple Pairing Randomizer R» Bluetooth Core Specification:«Simple Pairing Randomizer R-192» Core Specification Supplement, Part A, section 1.6
25 | static final int EBLE_DEVICEID = 0x10;//«Device ID» Device ID Profile v1.3 or later,«Security Manager TK Value» Bluetooth Core Specification:
26 | static final int EBLE_SECURITYMANAGER = 0x11;//«Security Manager Out of Band Flags» Bluetooth Core Specification:
27 | static final int EBLE_SLAVEINTERVALRA = 0x12;//«Slave Connection Interval Range» Bluetooth Core Specification:
28 | static final int EBLE_16BitSSUUID = 0x14;//«List of 16-bit Service Solicitation UUIDs» Bluetooth Core Specification:
29 | static final int EBLE_128BitSSUUID = 0x15;//«List of 128-bit Service Solicitation UUIDs» Bluetooth Core Specification:
30 | static final int EBLE_SERVICEDATA = 0x16;//«Service Data» Bluetooth Core Specification:«Service Data - 16-bit UUID» Core Specification Supplement, Part A, section 1.11
31 | static final int EBLE_PTADDRESS = 0x17;//«Public Target Address» Bluetooth Core Specification:
32 | static final int EBLE_RTADDRESS = 0x18;;//«Random Target Address» Bluetooth Core Specification:
33 | static final int EBLE_APPEARANCE = 0x19;//«Appearance» Bluetooth Core Specification:
34 | static final int EBLE_DEVADDRESS = 0x1B;//«LE Bluetooth Device Address» Core Specification Supplement, Part A, section 1.16
35 | static final int EBLE_LEROLE = 0x1C;//«LE Role» Core Specification Supplement, Part A, section 1.17
36 | static final int EBLE_PAIRINGHASH = 0x1D;//«Simple Pairing Hash C-256» Core Specification Supplement, Part A, section 1.6
37 | static final int EBLE_PAIRINGRAND = 0x1E;//«Simple Pairing Randomizer R-256» Core Specification Supplement, Part A, section 1.6
38 | static final int EBLE_32BitSSUUID = 0x1F;//«List of 32-bit Service Solicitation UUIDs» Core Specification Supplement, Part A, section 1.10
39 | static final int EBLE_32BitSERDATA = 0x20;//«Service Data - 32-bit UUID» Core Specification Supplement, Part A, section 1.11
40 | static final int EBLE_128BitSERDATA = 0x21;//«Service Data - 128-bit UUID» Core Specification Supplement, Part A, section 1.11
41 | static final int EBLE_SECCONCONF = 0x22;//«LE Secure Connections Confirmation Value» Core Specification Supplement Part A, Section 1.6
42 | static final int EBLE_SECCONRAND = 0x23;//«LE Secure Connections Random Value» Core Specification Supplement Part A, Section 1.6
43 | static final int EBLE_3DINFDATA = 0x3D;//«3D Information Data» 3D Synchronization Profile, v1.0 or later
44 | static final int EBLE_MANDATA = 0xFF;//«Manufacturer Specific Data» Bluetooth Core Specification:
45 |
46 |
47 | static public Map ParseRecord(byte[] scanRecord){
48 | Map ret = new HashMap();
49 | int index = 0;
50 | while (index < scanRecord.length) {
51 | int length = scanRecord[index++];
52 | //Zero value indicates that we are done with the record now
53 | if (length == 0) break;
54 |
55 | int type = scanRecord[index];
56 | //if the type is zero, then we are pass the significant section of the data,
57 | // and we are thud done
58 | if (type == 0) break;
59 |
60 | byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length);
61 | if(data != null && data.length > 0) {
62 | StringBuilder hex = new StringBuilder(data.length * 2);
63 | // the data appears to be there backwards
64 | for (int bb = data.length- 1; bb >= 0; bb--){
65 | hex.append(String.format("%02X", data[bb]));
66 | }
67 | ret.put(type,hex.toString());
68 | }
69 | index += length;
70 | }
71 |
72 | return ret;
73 | }
74 |
75 | static public String getServiceUUID(Map record){
76 | String ret = "";
77 | // for example: 0105FACB00B01000800000805F9B34FB --> 010510ee-0000-1000-8000-00805f9b34fb
78 | if(record.containsKey(EBLE_128BitUUIDCom)){
79 | String tmpString= record.get(EBLE_128BitUUIDCom).toString();
80 | ret = tmpString.substring(0, 8) + "-" + tmpString.substring(8,12)+ "-" + tmpString.substring(12,16)+ "-" + tmpString.substring(16,20)+ "-" + tmpString.substring(20,tmpString.length());
81 | //010510EE --> 010510ee-0000-1000-8000-00805f9b34fb
82 | }else if(record.containsKey(EBLE_32BitUUIDCom)){
83 | ret = record.get(EBLE_32BitUUIDCom).toString() + "-0000-1000-8000-00805f9b34fb";
84 | }
85 | return ret;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/activitys/RealTimeDataActivity.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.activitys;
2 |
3 | import android.bluetooth.BluetoothGatt;
4 | import android.bluetooth.BluetoothGattCharacteristic;
5 | import android.bluetooth.BluetoothGattService;
6 | import android.content.BroadcastReceiver;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.IntentFilter;
10 | import android.os.Bundle;
11 | import android.util.Log;
12 | import android.view.View;
13 | import android.widget.Button;
14 | import android.widget.TextView;
15 |
16 | import com.zsl.bluetoothdemo.R;
17 | import com.zsl.bluetoothdemo.base.BaseActivity;
18 | import com.zsl.bluetoothdemo.utils.ble.oad.BluetoothLeService;
19 |
20 | import java.util.Arrays;
21 | import java.util.List;
22 | import java.util.UUID;
23 |
24 | /**
25 | * Created by zsl on 15/10/21.
26 | * 实时数据的传输
27 | */
28 | public class RealTimeDataActivity extends BaseActivity {
29 |
30 | TextView tv_message;
31 | Button bt_read_data,bt_setconfig,bt_battery;
32 |
33 |
34 | private String logTag="RealTimeDataActivity";
35 |
36 | //实时数据的Service uuid
37 | public static final UUID UUID_HISTO_DATA_SERVICE = UUID
38 | .fromString("00001204-0000-1000-8000-00805f9b34fb");
39 |
40 |
41 | //BluetoothLeService
42 | private BluetoothLeService mLeService = null;
43 | //设备连接配置界面
44 | DeviceHomeActivity deviceHomeActivity;
45 | //所有的Service
46 | List serviceList;
47 | //实时数据的service
48 | BluetoothGattService realTimeDataService;
49 | //实时数据下service对应的所有BluetoothGattCharacteristic
50 | List realTimeDataCharList;
51 | //配置的BluetoothGattCharacteristic
52 | BluetoothGattCharacteristic mCharConfig,mCharData,mCharBattery;
53 |
54 | //是否开启实时同步
55 | boolean isOpen;
56 |
57 | @Override
58 | protected void onCreate(Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | setContentView(R.layout.activity_realtimedata);
61 |
62 | initView();
63 | initEvent();
64 | initData();
65 |
66 |
67 | }
68 |
69 | private void initView() {
70 | tv_message= (TextView) findViewById(R.id.realtime_tv_message);
71 | bt_read_data= (Button) findViewById(R.id.realtime_bt_read_data);
72 | bt_setconfig= (Button) findViewById(R.id.realtime_bt_setconfig);
73 | bt_battery= (Button) findViewById(R.id.realtime_bt_read_battery);
74 | }
75 |
76 | private void initEvent() {
77 | //读取数据
78 | bt_read_data.setOnClickListener(new View.OnClickListener() {
79 | @Override
80 | public void onClick(View v) {
81 | mLeService.readCharacteristic(mCharData);
82 | }
83 | });
84 |
85 | //开启或关闭数据
86 | bt_setconfig.setOnClickListener(new View.OnClickListener() {
87 | @Override
88 | public void onClick(View v) {
89 | isOpenUpdata(!isOpen);
90 |
91 | }
92 | });
93 |
94 | //读取当前电量
95 | bt_battery.setOnClickListener(new View.OnClickListener() {
96 | @Override
97 | public void onClick(View v) {
98 | mLeService.readCharacteristic(mCharBattery);
99 | }
100 | });
101 | }
102 |
103 | @Override
104 | protected void onStart() {
105 | isOpenUpdata(true);
106 | super.onStart();
107 | }
108 |
109 | @Override
110 | protected void onStop() {
111 | isOpenUpdata(false);
112 | super.onStop();
113 | }
114 |
115 | /**
116 | * 是否开启更新
117 | * @param isOpenupdaate
118 | */
119 | private void isOpenUpdata(boolean isOpenupdaate) {
120 | if (mLeService==null||mCharData==null)
121 | return;
122 |
123 | isOpen=isOpenupdaate;
124 | byte[] config;
125 | if (isOpen) {
126 | mLeService.setCharacteristicNotification(mCharData,true);
127 | config = new byte[]{(byte) 0xa0, 0x1f};
128 | bt_setconfig.setText("关闭数据同步");
129 | }else{
130 | mLeService.setCharacteristicNotification(mCharData,false);
131 | config = new byte[]{(byte) 0xc0, 0x1f};
132 | bt_setconfig.setText("开启数据同步");
133 | }
134 | mCharConfig.setValue(config);
135 | mLeService.writeCharacteristic(mCharConfig);
136 | }
137 |
138 | private void initData() {
139 | //注册广播
140 | registerReceiver(realtimeDataBroadcastReceiver, SettingIntentFilter());
141 |
142 | //获得到BluetoothLeService对象
143 | mLeService = BluetoothLeService.getInstance();
144 | deviceHomeActivity = DeviceHomeActivity.getmDeviceHomeActivity();
145 | //获得到所有的service
146 | serviceList = deviceHomeActivity.getServiceList();
147 |
148 | if (serviceList==null)
149 | return;
150 | for (BluetoothGattService service:serviceList) {
151 | Log.e(logTag, service.getUuid() + "");
152 | if (service.getUuid().equals(UUID_HISTO_DATA_SERVICE)){
153 | //历史数据的service
154 | realTimeDataService=service;
155 | }
156 | }
157 |
158 | //获得到Config,Data,Battery的Characteristic
159 | if (realTimeDataService!=null){
160 | realTimeDataCharList=realTimeDataService.getCharacteristics();
161 | Log.e(logTag,"realTimeDataCharList_count:"+realTimeDataCharList.size());
162 | mCharConfig=realTimeDataCharList.get(0);
163 | mCharData=realTimeDataCharList.get(1);
164 | mCharBattery=realTimeDataCharList.get(2);
165 |
166 | mLeService.setCharacteristicNotification(mCharData,true);
167 | }
168 | }
169 |
170 | @Override
171 | protected void onDestroy() {
172 | unregisterReceiver(realtimeDataBroadcastReceiver);
173 | super.onDestroy();
174 | }
175 |
176 | /**
177 | * 设置IntentFilter
178 | *
179 | * @return
180 | */
181 | private IntentFilter SettingIntentFilter() {
182 | final IntentFilter fi = new IntentFilter();
183 | fi.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
184 | fi.addAction(BluetoothLeService.ACTION_DATA_NOTIFY);
185 | fi.addAction(BluetoothLeService.ACTION_DATA_WRITE);
186 | fi.addAction(BluetoothLeService.ACTION_DATA_READ);
187 | return fi;
188 | }
189 |
190 | /**
191 | * Ble的广播
192 | */
193 | private BroadcastReceiver realtimeDataBroadcastReceiver = new BroadcastReceiver() {
194 | @Override
195 | public void onReceive(Context context, Intent intent) {
196 | String action = intent.getAction();
197 | if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { //连接断开
198 | tv_message.setText("连接断开");
199 | } else if (BluetoothLeService.ACTION_DATA_NOTIFY.equals(action)) {
200 | byte[] value = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
201 | String uuidStr = intent.getStringExtra(BluetoothLeService.EXTRA_UUID);
202 | if (uuidStr.equals(mCharData.getUuid().toString())){
203 | tv_message.append("value_notify:" + Arrays.toString(value));
204 | }
205 | } else if (BluetoothLeService.ACTION_DATA_WRITE.equals(action)) {
206 | int status = intent.getIntExtra(BluetoothLeService.EXTRA_STATUS, BluetoothGatt.GATT_SUCCESS);
207 | byte[] value = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
208 | if (status != BluetoothGatt.GATT_SUCCESS) {
209 | showLongToast("GATT error: status=" + status);
210 | }
211 | }else if (BluetoothLeService.ACTION_DATA_READ.equals(action)) {
212 | byte[] value = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
213 | String uuidStr = intent.getStringExtra(BluetoothLeService.EXTRA_UUID);
214 | Log.e(logTag, Arrays.toString(value));
215 | if (uuidStr.equals(mCharData.getUuid().toString())) {
216 | tv_message.append("value:" + Arrays.toString(value));
217 | }else if(uuidStr.equals(mCharBattery.getUuid().toString())){
218 | tv_message.append("当前电量:"+Arrays.toString(value));
219 | }
220 |
221 | }
222 | }
223 | };
224 | }
225 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/activitys/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.activitys;
2 |
3 | import android.app.Activity;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.BluetoothManager;
7 | import android.content.Intent;
8 | import android.net.Uri;
9 | import android.os.Bundle;
10 | import android.os.Handler;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 | import android.widget.AdapterView;
15 | import android.widget.Button;
16 | import android.widget.ListView;
17 | import android.widget.Toast;
18 |
19 | import com.zsl.bluetoothdemo.R;
20 | import com.zsl.bluetoothdemo.adapter.DevicesAdapter;
21 | import com.zsl.bluetoothdemo.base.BaseActivity;
22 | import com.zsl.bluetoothdemo.utils.ble.oad.BluetoothLeService;
23 |
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 |
28 | public class MainActivity extends BaseActivity {
29 | //扫描超时
30 | private static final long SCANNING_TIMEOUT = 10 * 1000; /* 5 seconds */
31 | //requestid请求打开蓝牙
32 | private static final int ENABLE_BT_REQUEST_ID = 1;
33 |
34 | //蓝牙设别的list
35 | private List bluetoothDevices;
36 | DevicesAdapter devicesAdapter;
37 |
38 | ListView lv_show;
39 | Button bt_state;
40 | // 是否启动自动扫描
41 | private boolean mScanning = false;
42 | // handler
43 | private Handler mHandler = new Handler();
44 |
45 |
46 | //蓝牙管理
47 | private static BluetoothManager mBluetoothManager;
48 | private BluetoothAdapter mBtAdapter = null;
49 | private BluetoothDevice mBluetoothDevice = null;
50 | private BluetoothLeService mBluetoothLeService = null;
51 |
52 | private boolean mBtAdapterEnabled = false;
53 | private boolean mBleSupported = true;
54 | private boolean mInitialised = false;
55 |
56 |
57 | private static MainActivity myMainActivity=null;
58 |
59 |
60 | @Override
61 | protected void onCreate(Bundle savedInstanceState) {
62 | super.onCreate(savedInstanceState);
63 | setContentView(R.layout.activity_main);
64 | initView();
65 | initData();
66 |
67 | }
68 |
69 | private void initView() {
70 | bt_state = (Button) findViewById(R.id.main_bt_state);
71 | bt_state.setOnClickListener(new View.OnClickListener() {
72 | @Override
73 | public void onClick(View v) {
74 | Uri uri = Uri.parse("market://details?id=" + getPackageName());
75 |
76 | Intent intent = new Intent(Intent.ACTION_VIEW, uri);
77 |
78 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
79 |
80 | startActivity(intent);
81 | }
82 | });
83 | lv_show = (ListView) findViewById(R.id.main_lv_show);
84 | lv_show.setOnItemClickListener(new AdapterView.OnItemClickListener() {
85 | @Override
86 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
87 |
88 | scanLeDevice(false);
89 |
90 | BluetoothDevice device = bluetoothDevices.get(position).getBluetoothDevice();
91 |
92 | Intent intent = new Intent(MainActivity.this, DeviceHomeActivity.class);
93 | intent.putExtra("BluetoothDevice", device);
94 | startActivity(intent);
95 |
96 | }
97 | });
98 | }
99 |
100 |
101 | private void initData() {
102 | myMainActivity=this;
103 |
104 | bluetoothDevices = new ArrayList();
105 | devicesAdapter = new DevicesAdapter(this, bluetoothDevices);
106 | lv_show.setAdapter(devicesAdapter);
107 |
108 | }
109 |
110 |
111 | //获得到MainActivity
112 | public static MainActivity getInstance(){
113 | return myMainActivity;
114 | }
115 |
116 | @Override
117 | protected void onResume() {
118 | super.onResume();
119 | scanLeDevice(true);
120 |
121 | }
122 |
123 | @Override
124 | protected void onPause() {
125 | super.onPause();
126 | scanLeDevice(false);
127 | }
128 |
129 | public static BluetoothManager getmBluetoothManager() {
130 | return mBluetoothManager;
131 | }
132 |
133 | public BluetoothAdapter getmBtAdapter() {
134 | return mBtAdapter;
135 | }
136 |
137 | @Override
138 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
139 | // 检查用户是否同意打开蓝牙
140 | if (requestCode == ENABLE_BT_REQUEST_ID) {
141 | if (resultCode == Activity.RESULT_CANCELED) {
142 | btDisabled();
143 | return;
144 | }
145 | }
146 | super.onActivityResult(requestCode, resultCode, data);
147 | }
148 |
149 |
150 | @Override
151 | public boolean onCreateOptionsMenu(Menu menu) {
152 | getMenuInflater().inflate(R.menu.menu_main, menu);
153 | return true;
154 | }
155 |
156 | @Override
157 | public boolean onOptionsItemSelected(MenuItem item) {
158 | int id = item.getItemId();
159 |
160 | if (id == R.id.action_settings) {
161 | bluetoothDevices.clear();
162 | devicesAdapter.notifyDataSetChanged();
163 | if (!mInitialised) {
164 | mBluetoothLeService = BluetoothLeService.getInstance();
165 | mBluetoothManager = mBluetoothLeService.getBtManager();
166 | mBtAdapter = mBluetoothManager.getAdapter();
167 | mBtAdapterEnabled = mBtAdapter.isEnabled();
168 | Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
169 | startActivityForResult(enableIntent, ENABLE_BT_REQUEST_ID);
170 | }
171 | mInitialised = true;
172 | }
173 | if (!mBtAdapterEnabled) {
174 | scanLeDevice(true);
175 | return true;
176 | }
177 |
178 | return super.onOptionsItemSelected(item);
179 | }
180 |
181 |
182 |
183 | /**
184 | * 添加扫描超时
185 | */
186 | private void addScanningTimeout() {
187 | Runnable timeout = new Runnable() {
188 | @Override
189 | public void run() {
190 | // scanLeDevice(false);
191 | }
192 | };
193 | mHandler.postDelayed(timeout, SCANNING_TIMEOUT);
194 | }
195 |
196 | /**
197 | * 扫描蓝牙设备
198 | * @param enable
199 | * @return
200 | */
201 | private boolean scanLeDevice(boolean enable) {
202 | if (mBtAdapter==null){
203 | return false;
204 | }
205 | if (enable) {
206 | addScanningTimeout();
207 | mScanning = mBtAdapter.startLeScan(mLeScanCallback);
208 | } else {
209 | mScanning = false;
210 | mBtAdapter.stopLeScan(mLeScanCallback);
211 | }
212 | return mScanning;
213 | }
214 |
215 | /**
216 | * 扫描回调
217 | */
218 | private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
219 |
220 | public void onLeScan(final BluetoothDevice device, final int rssi,
221 | byte[] scanRecord) {
222 | runOnUiThread(new Runnable() {
223 | public void run() {
224 | if (!deviceInfoExists(device.getAddress())) {
225 | // 新设备
226 | MyBluetoothDevice myBluetoothDevice = new MyBluetoothDevice(device, null, device.getAddress());
227 | bluetoothDevices.add(myBluetoothDevice);
228 | devicesAdapter.notifyDataSetChanged();
229 | } else {
230 | String address =device.getAddress();
231 | for (MyBluetoothDevice bluetoothDevice : bluetoothDevices) {
232 | if (bluetoothDevice.getAddress().equals(address)) {
233 | bluetoothDevice.setAddress(address);
234 | devicesAdapter.notifyDataSetChanged();
235 | }
236 | }
237 | }
238 | }
239 | // }
240 |
241 | });
242 | }
243 | };
244 |
245 | /**
246 | * 列表中是否存在此设备
247 | * @param address
248 | * @return
249 | */
250 | private boolean deviceInfoExists(String address) {
251 | for (int i = 0; i < bluetoothDevices.size(); i++) {
252 | if (bluetoothDevices.get(i).getBluetoothDevice().getAddress()
253 | .equals(address)) {
254 | return true;
255 | }
256 | }
257 | return false;
258 | }
259 | /**
260 | * 用户没有打开蓝牙
261 | */
262 | private void btDisabled() {
263 | Toast.makeText(this, "对不起,只有打开蓝牙才可以运行软件", Toast.LENGTH_LONG).show();
264 | finish();
265 | }
266 |
267 | /**
268 | * 此设备不支持蓝牙功能
269 | */
270 | private void bleMissing() {
271 | Toast.makeText(this, "此设备不支持蓝牙功能", Toast.LENGTH_LONG).show();
272 | finish();
273 | }
274 |
275 | }
276 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/activitys/HistoryDataActivity.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.activitys;
2 |
3 | import android.bluetooth.BluetoothGatt;
4 | import android.bluetooth.BluetoothGattCharacteristic;
5 | import android.bluetooth.BluetoothGattService;
6 | import android.content.BroadcastReceiver;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.IntentFilter;
10 | import android.os.Bundle;
11 | import android.util.Log;
12 | import android.view.View;
13 | import android.widget.Button;
14 | import android.widget.TextView;
15 | import android.widget.Toast;
16 |
17 | import com.zsl.bluetoothdemo.R;
18 | import com.zsl.bluetoothdemo.base.BaseActivity;
19 | import com.zsl.bluetoothdemo.utils.ble.oad.BluetoothLeService;
20 | import com.zsl.bluetoothdemo.utils.ble.oad.Conversion;
21 |
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import java.util.UUID;
25 |
26 | /**
27 | * Created by zsl on 15/10/19.
28 | * 历史数据
29 | */
30 | public class HistoryDataActivity extends BaseActivity{
31 |
32 | private String logTag="HistoryDataActivity";
33 |
34 | //历史数据的Service uuid
35 | public static final UUID UUID_HISTO_DATA_SERVICE = UUID
36 | .fromString("00001206-0000-1000-8000-00805f9b34fb");
37 |
38 | TextView tv_data;
39 | Button bt_getHeader;
40 |
41 |
42 | //BluetoothLeService
43 | private BluetoothLeService mLeService = null;
44 | //设备连接配置界面
45 | DeviceHomeActivity deviceHomeActivity;
46 | //所有的Service
47 | List serviceList;
48 | //历史数据的service
49 | BluetoothGattService historyDataService;
50 | //历史数据下service对应的所有BluetoothGattCharacteristic
51 | List historyDataCharList;
52 | //NotifyCharacteristic
53 | BluetoothGattCharacteristic mCharNotify;
54 | //Block
55 | BluetoothGattCharacteristic mCharBlock;
56 | //length
57 | private int length;
58 | //是否第一次读取到需要读取的次数
59 | private boolean isFirstSetLength;
60 | //目前的总量
61 | private short number;
62 | //接收到的数据综合
63 | private byte[] buffer;
64 | //BLOCK_SIZE
65 | private final int BLOCK_SIZE=16;
66 | //crc
67 | short crc;
68 |
69 |
70 |
71 | @Override
72 | protected void onCreate(Bundle savedInstanceState) {
73 | super.onCreate(savedInstanceState);
74 | setContentView(R.layout.activity_historydata);
75 | initView();
76 | initEvent();
77 | initData();
78 | }
79 |
80 | private void initView() {
81 | tv_data= (TextView) findViewById(R.id.history_tv_data);
82 | bt_getHeader= (Button) findViewById(R.id.history_bt_getHeader);
83 |
84 | }
85 |
86 | private void initEvent() {
87 | //获得Header
88 | bt_getHeader.setOnClickListener(new View.OnClickListener() {
89 | @Override
90 | public void onClick(View v) {
91 | sendCmd((byte) 0xa0, (byte) 0x00, (byte) 0x00);
92 | }
93 | });
94 | }
95 |
96 | /**
97 | * 发送命令
98 | * @param cmd0
99 | * @param cmd1
100 | * @param cmd2
101 | */
102 | private void sendCmd(byte cmd0,byte cmd1,byte cmd2) {
103 | byte[] headerValue = {cmd0,cmd1,cmd2};
104 | mCharNotify.setValue(headerValue);
105 | mLeService.writeCharacteristic(mCharNotify);
106 | }
107 |
108 | private void initData() {
109 | //注册广播
110 | registerReceiver(historyDataBroadcastReceiver,SettingIntentFilter());
111 | //获得到BluetoothLeService对象
112 | mLeService = BluetoothLeService.getInstance();
113 |
114 | deviceHomeActivity= DeviceHomeActivity.getmDeviceHomeActivity();
115 | //获得到所有的service
116 | serviceList=deviceHomeActivity.getServiceList();
117 |
118 | getHistoryDataService();
119 | }
120 |
121 | /**
122 | * 获得到历史数据同步的service和Characteristic
123 | */
124 | private void getHistoryDataService() {
125 | if (serviceList==null)
126 | return;
127 | for (BluetoothGattService service:serviceList) {
128 | Log.e("BluetoothGattService",service.getUuid()+"");
129 | if (service.getUuid().equals(UUID_HISTO_DATA_SERVICE)){
130 | //历史数据的service
131 | historyDataService=service;
132 | }
133 | }
134 |
135 | if (historyDataService!=null){
136 | historyDataCharList=historyDataService.getCharacteristics();
137 | if (historyDataCharList!=null&&historyDataCharList.size()==2){
138 | mCharNotify=historyDataCharList.get(0);
139 | mCharBlock=historyDataCharList.get(1);
140 | mLeService.setCharacteristicNotification(mCharNotify,true);
141 | }
142 | }
143 |
144 |
145 | }
146 |
147 | @Override
148 | protected void onDestroy() {
149 | //注销广播
150 | unregisterReceiver(historyDataBroadcastReceiver);
151 | super.onDestroy();
152 | }
153 |
154 | /**
155 | * 设置IntentFilter
156 | *
157 | * @return
158 | */
159 | private IntentFilter SettingIntentFilter() {
160 | final IntentFilter fi = new IntentFilter();
161 | fi.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
162 | fi.addAction(BluetoothLeService.ACTION_DATA_NOTIFY);
163 | fi.addAction(BluetoothLeService.ACTION_DATA_WRITE);
164 | fi.addAction(BluetoothLeService.ACTION_DATA_READ);
165 | return fi;
166 | }
167 |
168 |
169 |
170 | /**
171 | * Ble的广播
172 | */
173 | private BroadcastReceiver historyDataBroadcastReceiver = new BroadcastReceiver() {
174 | @Override
175 | public void onReceive(Context context, Intent intent) {
176 | String action = intent.getAction();
177 | if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { //连接断开
178 | tv_data.setText("连接断开");
179 | } else if (BluetoothLeService.ACTION_DATA_NOTIFY.equals(action)) {
180 | tv_data.append("收到Notify");
181 | byte[] value = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
182 | String uuidStr = intent.getStringExtra(BluetoothLeService.EXTRA_UUID);
183 | Log.e(logTag,"uuidStr:"+uuidStr+":mCharNotify.getUuid():"+mCharNotify.getUuid());
184 | if (uuidStr.equals(mCharNotify.getUuid().toString())){
185 | Log.e(logTag, value.toString());
186 | //read block
187 | mLeService.readCharacteristic(mCharBlock);
188 | }
189 |
190 | } else if (BluetoothLeService.ACTION_DATA_WRITE.equals(action)) {
191 | int status = intent.getIntExtra(BluetoothLeService.EXTRA_STATUS, BluetoothGatt.GATT_SUCCESS);
192 | if (status != BluetoothGatt.GATT_SUCCESS) {
193 | Toast.makeText(context, "GATT error: status=" + status, Toast.LENGTH_SHORT).show();
194 | }
195 | }else if (BluetoothLeService.ACTION_DATA_READ.equals(action)) {
196 | byte[] value = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
197 | String uuidStr = intent.getStringExtra(BluetoothLeService.EXTRA_UUID);
198 | if (uuidStr.equals(mCharBlock.getUuid().toString())) {
199 | if (!isFirstSetLength){//读取header
200 | //读取length
201 | length=Conversion.buildUint16(value[1],value[0]);
202 | //读取crc
203 | crc=Conversion.buildUint16(value[3],value[2]);
204 | isFirstSetLength=true;
205 | buffer=new byte[length*BLOCK_SIZE];
206 | }else{//读取数据
207 | System.arraycopy(value,0,buffer,number*BLOCK_SIZE,BLOCK_SIZE);
208 | number++;
209 | }
210 | if (number mServices = new HashMap();
9 | private static HashMap mCharacteristics = new HashMap();
10 | private static SparseArray mValueFormats = new SparseArray();
11 | private static SparseArray mAppearance = new SparseArray();
12 | private static SparseArray mHeartRateSensorLocation = new SparseArray();
13 |
14 | static public String resolveServiceName(final String uuid)
15 | {
16 | String result = mServices.get(uuid);
17 | if(result == null) result = "Unknown Service";
18 | return result;
19 | }
20 |
21 | static public String resolveValueTypeDescription(final int format)
22 | {
23 | Integer tmp = Integer.valueOf(format);
24 | return mValueFormats.get(tmp, "Unknown Format");
25 | }
26 |
27 | static public String resolveCharacteristicName(final String uuid)
28 | {
29 | String result = mCharacteristics.get(uuid);
30 | if(result == null) result = "Unknown Characteristic";
31 | return result;
32 | }
33 |
34 | static public String resolveUuid(final String uuid) {
35 | String result = mServices.get(uuid);
36 | if(result != null) return "Service: " + result;
37 |
38 | result = mCharacteristics.get(uuid);
39 | if(result != null) return "Characteristic: " + result;
40 |
41 | result = "Unknown UUID";
42 | return result;
43 | }
44 |
45 | static public String resolveAppearance(int key) {
46 | Integer tmp = Integer.valueOf(key);
47 | return mAppearance.get(tmp, "Unknown Appearance");
48 | }
49 |
50 | static public String resolveHeartRateSensorLocation(int key) {
51 | Integer tmp = Integer.valueOf(key);
52 | return mHeartRateSensorLocation.get(tmp, "Other");
53 | }
54 |
55 | static public boolean isService(final String uuid) {
56 | return mServices.containsKey(uuid);
57 | }
58 |
59 | static public boolean isCharacteristic(final String uuid) {
60 | return mCharacteristics.containsKey(uuid);
61 | }
62 |
63 | static {
64 | mServices.put("00001811-0000-1000-8000-00805f9b34fb", "Alert Notification Service");
65 | mServices.put("0000180f-0000-1000-8000-00805f9b34fb", "Battery Service");
66 | mServices.put("00001810-0000-1000-8000-00805f9b34fb", "Blood Pressure");
67 | mServices.put("00001805-0000-1000-8000-00805f9b34fb", "Current Time Service");
68 | mServices.put("00001818-0000-1000-8000-00805f9b34fb", "Cycling Power");
69 | mServices.put("00001816-0000-1000-8000-00805f9b34fb", "Cycling Speed and Cadence");
70 | mServices.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information");
71 | mServices.put("00001800-0000-1000-8000-00805f9b34fb", "Generic Access");
72 | mServices.put("00001801-0000-1000-8000-00805f9b34fb", "Generic Attribute");
73 | mServices.put("00001808-0000-1000-8000-00805f9b34fb", "Glucose");
74 | mServices.put("00001809-0000-1000-8000-00805f9b34fb", "Health Thermometer");
75 | mServices.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate");
76 | mServices.put("00001812-0000-1000-8000-00805f9b34fb", "Human Interface Device");
77 | mServices.put("00001802-0000-1000-8000-00805f9b34fb", "Immediate Alert");
78 | mServices.put("00001803-0000-1000-8000-00805f9b34fb", "Link Loss");
79 | mServices.put("00001819-0000-1000-8000-00805f9b34fb", "Location and Navigation");
80 | mServices.put("00001807-0000-1000-8000-00805f9b34fb", "Next DST Change Service");
81 | mServices.put("0000180e-0000-1000-8000-00805f9b34fb", "Phone Alert Status Service");
82 | mServices.put("00001806-0000-1000-8000-00805f9b34fb", "Reference Time Update Service");
83 | mServices.put("00001814-0000-1000-8000-00805f9b34fb", "Running Speed and Cadence");
84 | mServices.put("00001813-0000-1000-8000-00805f9b34fb", "Scan Parameters");
85 | mServices.put("00001804-0000-1000-8000-00805f9b34fb", "Tx Power");
86 |
87 | mCharacteristics.put("00002a43-0000-1000-8000-00805f9b34fb", "Alert Category ID");
88 | mCharacteristics.put("00002a42-0000-1000-8000-00805f9b34fb", "Alert Category ID Bit Mask");
89 | mCharacteristics.put("00002a06-0000-1000-8000-00805f9b34fb", "Alert Level");
90 | mCharacteristics.put("00002a44-0000-1000-8000-00805f9b34fb", "Alert Notification Control Point");
91 | mCharacteristics.put("00002a3f-0000-1000-8000-00805f9b34fb", "Alert Status");
92 | mCharacteristics.put("00002a01-0000-1000-8000-00805f9b34fb", "Appearance");
93 | mCharacteristics.put("00002a19-0000-1000-8000-00805f9b34fb", "Battery Level");
94 | mCharacteristics.put("00002a49-0000-1000-8000-00805f9b34fb", "Blood Pressure Feature");
95 | mCharacteristics.put("00002a35-0000-1000-8000-00805f9b34fb", "Blood Pressure Measurement");
96 | mCharacteristics.put("00002a38-0000-1000-8000-00805f9b34fb", "Body Sensor Location");
97 | mCharacteristics.put("00002a22-0000-1000-8000-00805f9b34fb", "Boot Keyboard Input Report");
98 | mCharacteristics.put("00002a32-0000-1000-8000-00805f9b34fb", "Boot Keyboard Output Report");
99 | mCharacteristics.put("00002a33-0000-1000-8000-00805f9b34fb", "Boot Mouse Input Report");
100 | mCharacteristics.put("00002a5c-0000-1000-8000-00805f9b34fb", "CSC Feature");
101 | mCharacteristics.put("00002a5b-0000-1000-8000-00805f9b34fb", "CSC Measurement");
102 | mCharacteristics.put("00002a2b-0000-1000-8000-00805f9b34fb", "Current Time");
103 | mCharacteristics.put("00002a66-0000-1000-8000-00805f9b34fb", "Cycling Power Control Point");
104 | mCharacteristics.put("00002a65-0000-1000-8000-00805f9b34fb", "Cycling Power Feature");
105 | mCharacteristics.put("00002a63-0000-1000-8000-00805f9b34fb", "Cycling Power Measurement");
106 | mCharacteristics.put("00002a64-0000-1000-8000-00805f9b34fb", "Cycling Power Vector");
107 | mCharacteristics.put("00002a08-0000-1000-8000-00805f9b34fb", "Date Time");
108 | mCharacteristics.put("00002a0a-0000-1000-8000-00805f9b34fb", "Day Date Time");
109 | mCharacteristics.put("00002a09-0000-1000-8000-00805f9b34fb", "Day of Week");
110 | mCharacteristics.put("00002a00-0000-1000-8000-00805f9b34fb", "Device Name");
111 | mCharacteristics.put("00002a0d-0000-1000-8000-00805f9b34fb", "DST Offset");
112 | mCharacteristics.put("00002a0c-0000-1000-8000-00805f9b34fb", "Exact Time 256");
113 | mCharacteristics.put("00002a26-0000-1000-8000-00805f9b34fb", "Firmware Revision String");
114 | mCharacteristics.put("00002a51-0000-1000-8000-00805f9b34fb", "Glucose Feature");
115 | mCharacteristics.put("00002a18-0000-1000-8000-00805f9b34fb", "Glucose Measurement");
116 | mCharacteristics.put("00002a34-0000-1000-8000-00805f9b34fb", "Glucose Measurement Context");
117 | mCharacteristics.put("00002a27-0000-1000-8000-00805f9b34fb", "Hardware Revision String");
118 | mCharacteristics.put("00002a39-0000-1000-8000-00805f9b34fb", "Heart Rate Control Point");
119 | mCharacteristics.put("00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement");
120 | mCharacteristics.put("00002a4c-0000-1000-8000-00805f9b34fb", "HID Control Point");
121 | mCharacteristics.put("00002a4a-0000-1000-8000-00805f9b34fb", "HID Information");
122 | mCharacteristics.put("00002a2a-0000-1000-8000-00805f9b34fb", "IEEE 11073-20601 Regulatory Certification Data List");
123 | mCharacteristics.put("00002a36-0000-1000-8000-00805f9b34fb", "Intermediate Cuff Pressure");
124 | mCharacteristics.put("00002a1e-0000-1000-8000-00805f9b34fb", "Intermediate Temperature");
125 | mCharacteristics.put("00002a6b-0000-1000-8000-00805f9b34fb", "LN Control Point");
126 | mCharacteristics.put("00002a6a-0000-1000-8000-00805f9b34fb", "LN Feature");
127 | mCharacteristics.put("00002a0f-0000-1000-8000-00805f9b34fb", "Local Time Information");
128 | mCharacteristics.put("00002a67-0000-1000-8000-00805f9b34fb", "Location and Speed");
129 | mCharacteristics.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
130 | mCharacteristics.put("00002a21-0000-1000-8000-00805f9b34fb", "Measurement Interval");
131 | mCharacteristics.put("00002a24-0000-1000-8000-00805f9b34fb", "Model Number String");
132 | mCharacteristics.put("00002a68-0000-1000-8000-00805f9b34fb", "Navigation");
133 | mCharacteristics.put("00002a46-0000-1000-8000-00805f9b34fb", "New Alert");
134 | mCharacteristics.put("00002a04-0000-1000-8000-00805f9b34fb", "Peripheral Preferred Connection Parameters");
135 | mCharacteristics.put("00002a02-0000-1000-8000-00805f9b34fb", "Peripheral Privacy Flag");
136 | mCharacteristics.put("00002a50-0000-1000-8000-00805f9b34fb", "PnP ID");
137 | mCharacteristics.put("00002a69-0000-1000-8000-00805f9b34fb", "Position Quality");
138 | mCharacteristics.put("00002a4e-0000-1000-8000-00805f9b34fb", "Protocol Mode");
139 | mCharacteristics.put("00002a03-0000-1000-8000-00805f9b34fb", "Reconnection Address");
140 | mCharacteristics.put("00002a52-0000-1000-8000-00805f9b34fb", "Record Access Control Point");
141 | mCharacteristics.put("00002a14-0000-1000-8000-00805f9b34fb", "Reference Time Information");
142 | mCharacteristics.put("00002a4d-0000-1000-8000-00805f9b34fb", "Report");
143 | mCharacteristics.put("00002a4b-0000-1000-8000-00805f9b34fb", "Report Map");
144 | mCharacteristics.put("00002a40-0000-1000-8000-00805f9b34fb", "Ringer Control Point");
145 | mCharacteristics.put("00002a41-0000-1000-8000-00805f9b34fb", "Ringer Setting");
146 | mCharacteristics.put("00002a54-0000-1000-8000-00805f9b34fb", "RSC Feature");
147 | mCharacteristics.put("00002a53-0000-1000-8000-00805f9b34fb", "RSC Measurement");
148 | mCharacteristics.put("00002a55-0000-1000-8000-00805f9b34fb", "SC Control Point");
149 | mCharacteristics.put("00002a4f-0000-1000-8000-00805f9b34fb", "Scan Interval Window");
150 | mCharacteristics.put("00002a31-0000-1000-8000-00805f9b34fb", "Scan Refresh");
151 | mCharacteristics.put("00002a5d-0000-1000-8000-00805f9b34fb", "Sensor Location");
152 | mCharacteristics.put("00002a25-0000-1000-8000-00805f9b34fb", "Serial Number String");
153 | mCharacteristics.put("00002a05-0000-1000-8000-00805f9b34fb", "Service Changed");
154 | mCharacteristics.put("00002a28-0000-1000-8000-00805f9b34fb", "Software Revision String");
155 | mCharacteristics.put("00002a47-0000-1000-8000-00805f9b34fb", "Supported New Alert Category");
156 | mCharacteristics.put("00002a48-0000-1000-8000-00805f9b34fb", "Supported Unread Alert Category");
157 | mCharacteristics.put("00002a23-0000-1000-8000-00805f9b34fb", "System ID");
158 | mCharacteristics.put("00002a1c-0000-1000-8000-00805f9b34fb", "Temperature Measurement");
159 | mCharacteristics.put("00002a1d-0000-1000-8000-00805f9b34fb", "Temperature Type");
160 | mCharacteristics.put("00002a12-0000-1000-8000-00805f9b34fb", "Time Accuracy");
161 | mCharacteristics.put("00002a13-0000-1000-8000-00805f9b34fb", "Time Source");
162 | mCharacteristics.put("00002a16-0000-1000-8000-00805f9b34fb", "Time Update Control Point");
163 | mCharacteristics.put("00002a17-0000-1000-8000-00805f9b34fb", "Time Update State");
164 | mCharacteristics.put("00002a11-0000-1000-8000-00805f9b34fb", "Time with DST");
165 | mCharacteristics.put("00002a0e-0000-1000-8000-00805f9b34fb", "Time Zone");
166 | mCharacteristics.put("00002a07-0000-1000-8000-00805f9b34fb", "Tx Power Level");
167 | mCharacteristics.put("00002a45-0000-1000-8000-00805f9b34fb", "Unread Alert Status");
168 |
169 | mValueFormats.put(Integer.valueOf(52), "32bit float");
170 | mValueFormats.put(Integer.valueOf(50), "16bit float");
171 | mValueFormats.put(Integer.valueOf(34), "16bit signed int");
172 | mValueFormats.put(Integer.valueOf(36), "32bit signed int");
173 | mValueFormats.put(Integer.valueOf(33), "8bit signed int");
174 | mValueFormats.put(Integer.valueOf(18), "16bit unsigned int");
175 | mValueFormats.put(Integer.valueOf(20), "32bit unsigned int");
176 | mValueFormats.put(Integer.valueOf(17), "8bit unsigned int");
177 |
178 | // lets add also couple appearance string description
179 | // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
180 | mAppearance.put(Integer.valueOf(833), "Heart Rate Sensor: Belt");
181 | mAppearance.put(Integer.valueOf(832), "Generic Heart Rate Sensor");
182 | mAppearance.put(Integer.valueOf(0), "Unknown");
183 | mAppearance.put(Integer.valueOf(64), "Generic Phone");
184 | mAppearance.put(Integer.valueOf(1157), "Cycling: Speed and Cadence Sensor");
185 | mAppearance.put(Integer.valueOf(1152), "General Cycling");
186 | mAppearance.put(Integer.valueOf(1153), "Cycling Computer");
187 | mAppearance.put(Integer.valueOf(1154), "Cycling: Speed Sensor");
188 | mAppearance.put(Integer.valueOf(1155), "Cycling: Cadence Sensor");
189 | mAppearance.put(Integer.valueOf(1156), "Cycling: Speed and Cadence Sensor");
190 | mAppearance.put(Integer.valueOf(1157), "Cycling: Power Sensor");
191 |
192 | mHeartRateSensorLocation.put(Integer.valueOf(0), "Other");
193 | mHeartRateSensorLocation.put(Integer.valueOf(1), "Chest");
194 | mHeartRateSensorLocation.put(Integer.valueOf(2), "Wrist");
195 | mHeartRateSensorLocation.put(Integer.valueOf(3), "Finger");
196 | mHeartRateSensorLocation.put(Integer.valueOf(4), "Hand");
197 | mHeartRateSensorLocation.put(Integer.valueOf(5), "Ear Lobe");
198 | mHeartRateSensorLocation.put(Integer.valueOf(6), "Foot");
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/BleWrapper.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.utils.ble;
2 |
3 | import android.app.Activity;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.BluetoothGatt;
7 | import android.bluetooth.BluetoothGattCallback;
8 | import android.bluetooth.BluetoothGattCharacteristic;
9 | import android.bluetooth.BluetoothGattDescriptor;
10 | import android.bluetooth.BluetoothGattService;
11 | import android.bluetooth.BluetoothManager;
12 | import android.bluetooth.BluetoothProfile;
13 | import android.content.Context;
14 | import android.content.pm.PackageManager;
15 | import android.os.Handler;
16 | import android.util.Log;
17 |
18 | import java.text.SimpleDateFormat;
19 | import java.util.Date;
20 | import java.util.List;
21 | import java.util.Locale;
22 | import java.util.UUID;
23 |
24 | public class BleWrapper {
25 | /* defines (in milliseconds) how often RSSI should be updated */
26 | private static final int RSSI_UPDATE_TIME_INTERVAL = 1500; // 1.5 seconds
27 |
28 | /* callback object through which we are returning results to the caller */
29 | private BleWrapperUiCallbacks mUiCallback = null;
30 | /* define NULL object for UI callbacks */
31 | private static final BleWrapperUiCallbacks NULL_CALLBACK = new BleWrapperUiCallbacks.Null();
32 |
33 | /* creates BleWrapper object, set its parent activity and callback object */
34 | public BleWrapper(Activity parent, BleWrapperUiCallbacks callback) {
35 | this.mParent = parent;
36 | mUiCallback = callback;
37 | if(mUiCallback == null) mUiCallback = NULL_CALLBACK;
38 | }
39 |
40 | public BluetoothManager getManager() { return mBluetoothManager; }
41 | public BluetoothAdapter getAdapter() { return mBluetoothAdapter; }
42 | public BluetoothDevice getDevice() { return mBluetoothDevice; }
43 | public BluetoothGatt getGatt() { return mBluetoothGatt; }
44 | public BluetoothGattService getCachedService() { return mBluetoothSelectedService; }
45 | public List getCachedServices() { return mBluetoothGattServices; }
46 | public boolean isConnected() { return mConnected; }
47 |
48 | /* run test and check if this device has BT and BLE hardware available */
49 | public boolean checkBleHardwareAvailable() {
50 | // First check general Bluetooth Hardware:
51 | // get BluetoothManager...
52 | final BluetoothManager manager = (BluetoothManager) mParent.getSystemService(Context.BLUETOOTH_SERVICE);
53 | if(manager == null) return false;
54 | // .. and then get adapter from manager
55 | final BluetoothAdapter adapter = manager.getAdapter();
56 | if(adapter == null) return false;
57 |
58 | // and then check if BT LE is also available
59 | boolean hasBle = mParent.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
60 | return hasBle;
61 | }
62 |
63 |
64 | /* before any action check if BT is turned ON and enabled for us
65 | * call this in onResume to be always sure that BT is ON when Your
66 | * application is put into the foreground */
67 | public boolean isBtEnabled() {
68 | final BluetoothManager manager = (BluetoothManager) mParent.getSystemService(Context.BLUETOOTH_SERVICE);
69 | if(manager == null) return false;
70 |
71 | final BluetoothAdapter adapter = manager.getAdapter();
72 | if(adapter == null) return false;
73 |
74 | return adapter.isEnabled();
75 | }
76 |
77 | /* start scanning for BT LE devices around */
78 | public void startScanning() {
79 | mBluetoothAdapter.startLeScan(mDeviceFoundCallback);
80 | }
81 |
82 | /* stops current scanning */
83 | public void stopScanning() {
84 | mBluetoothAdapter.stopLeScan(mDeviceFoundCallback);
85 | }
86 |
87 | /* initialize BLE and get BT Manager & Adapter */
88 | public boolean initialize() {
89 | if (mBluetoothManager == null) {
90 | mBluetoothManager = (BluetoothManager) mParent.getSystemService(Context.BLUETOOTH_SERVICE);
91 | if (mBluetoothManager == null) {
92 | return false;
93 | }
94 | }
95 |
96 | if(mBluetoothAdapter == null) mBluetoothAdapter = mBluetoothManager.getAdapter();
97 | if (mBluetoothAdapter == null) {
98 | return false;
99 | }
100 | return true;
101 | }
102 |
103 | /* connect to the device with specified address */
104 | public boolean connect(final String deviceAddress) {
105 | if (mBluetoothAdapter == null || deviceAddress == null) return false;
106 | mDeviceAddress = deviceAddress;
107 |
108 | // check if we need to connect from scratch or just reconnect to previous device
109 | if(mBluetoothGatt != null && mBluetoothGatt.getDevice().getAddress().equals(deviceAddress)) {
110 | // just reconnect
111 | return mBluetoothGatt.connect();
112 | }
113 | else {
114 | // connect from scratch
115 | // get BluetoothDevice object for specified address
116 | mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
117 | if (mBluetoothDevice == null) {
118 | // we got wrong address - that device is not available!
119 | return false;
120 | }
121 | // connect with remote device
122 | mBluetoothGatt = mBluetoothDevice.connectGatt(mParent, false, mBleCallback);
123 | }
124 | return true;
125 | }
126 |
127 | /* disconnect the device. It is still possible to reconnect to it later with this Gatt client */
128 | public void diconnect() {
129 | if(mBluetoothGatt != null) mBluetoothGatt.disconnect();
130 | mUiCallback.uiDeviceDisconnected(mBluetoothGatt, mBluetoothDevice);
131 | }
132 |
133 | /* close GATT client completely */
134 | public void close() {
135 | if(mBluetoothGatt != null) mBluetoothGatt.close();
136 | mBluetoothGatt = null;
137 | }
138 |
139 | /* request new RSSi value for the connection*/
140 | public void readPeriodicalyRssiValue(final boolean repeat) {
141 | mTimerEnabled = repeat;
142 | // check if we should stop checking RSSI value
143 | if(mConnected == false || mBluetoothGatt == null || mTimerEnabled == false) {
144 | mTimerEnabled = false;
145 | return;
146 | }
147 |
148 | mTimerHandler.postDelayed(new Runnable() {
149 | @Override
150 | public void run() {
151 | if(mBluetoothGatt == null ||
152 | mBluetoothAdapter == null ||
153 | mConnected == false)
154 | {
155 | mTimerEnabled = false;
156 | return;
157 | }
158 |
159 | // request RSSI value
160 | mBluetoothGatt.readRemoteRssi();
161 | // add call it once more in the future
162 | readPeriodicalyRssiValue(mTimerEnabled);
163 | }
164 | }, RSSI_UPDATE_TIME_INTERVAL);
165 | }
166 |
167 | /* starts monitoring RSSI value */
168 | public void startMonitoringRssiValue() {
169 | readPeriodicalyRssiValue(true);
170 | }
171 |
172 | /* stops monitoring of RSSI value */
173 | public void stopMonitoringRssiValue() {
174 | readPeriodicalyRssiValue(false);
175 | }
176 |
177 | /* request to discover all services available on the remote devices
178 | * results are delivered through callback object */
179 | public void startServicesDiscovery() {
180 | if(mBluetoothGatt != null) mBluetoothGatt.discoverServices();
181 | }
182 |
183 | /* gets services and calls UI callback to handle them
184 | * before calling getServices() make sure service discovery is finished! */
185 | public void getSupportedServices() {
186 | if(mBluetoothGattServices != null && mBluetoothGattServices.size() > 0) mBluetoothGattServices.clear();
187 | // keep reference to all services in local array:
188 | if(mBluetoothGatt != null) mBluetoothGattServices = mBluetoothGatt.getServices();
189 |
190 | mUiCallback.uiAvailableServices(mBluetoothGatt, mBluetoothDevice, mBluetoothGattServices);
191 | }
192 |
193 | /* get all characteristic for particular service and pass them to the UI callback */
194 | public void getCharacteristicsForService(final BluetoothGattService service) {
195 | if(service == null) return;
196 | List chars = null;
197 |
198 | chars = service.getCharacteristics();
199 | mUiCallback.uiCharacteristicForService(mBluetoothGatt, mBluetoothDevice, service, chars);
200 | // keep reference to the last selected service
201 | mBluetoothSelectedService = service;
202 | }
203 |
204 | /* request to fetch newest value stored on the remote device for particular characteristic */
205 | public void requestCharacteristicValue(BluetoothGattCharacteristic ch) {
206 | if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
207 |
208 | mBluetoothGatt.readCharacteristic(ch);
209 | // new value available will be notified in Callback Object
210 | }
211 |
212 | /* get characteristic's value (and parse it for some types of characteristics)
213 | * before calling this You should always update the value by calling requestCharacteristicValue() */
214 | public void getCharacteristicValue(BluetoothGattCharacteristic ch) {
215 | if (mBluetoothAdapter == null || mBluetoothGatt == null || ch == null) return;
216 |
217 | byte[] rawValue = ch.getValue();
218 | String strValue = null;
219 | int intValue = 0;
220 |
221 | // lets read and do real parsing of some characteristic to get meaningful value from it
222 | UUID uuid = ch.getUuid();
223 |
224 | if(uuid.equals(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT)) { // heart rate
225 | // follow https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
226 | // first check format used by the device - it is specified in bit 0 and tells us if we should ask for index 1 (and uint8) or index 2 (and uint16)
227 | int index = ((rawValue[0] & 0x01) == 1) ? 2 : 1;
228 | // also we need to define format
229 | int format = (index == 1) ? BluetoothGattCharacteristic.FORMAT_UINT8 : BluetoothGattCharacteristic.FORMAT_UINT16;
230 | // now we have everything, get the value
231 | intValue = ch.getIntValue(format, index);
232 | strValue = intValue + " bpm"; // it is always in bpm units
233 | }
234 | else if (uuid.equals(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT) || // manufacturer name string
235 | uuid.equals(BleDefinedUUIDs.Characteristic.MODEL_NUMBER_STRING) || // model number string)
236 | uuid.equals(BleDefinedUUIDs.Characteristic.FIRMWARE_REVISION_STRING)) // firmware revision string
237 | {
238 | // follow https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.manufacturer_name_string.xml etc.
239 | // string value are usually simple utf8s string at index 0
240 | strValue = ch.getStringValue(0);
241 | }
242 | else if(uuid.equals(BleDefinedUUIDs.Characteristic.APPEARANCE)) { // appearance
243 | // follow: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
244 | intValue = ((int)rawValue[1]) << 8;
245 | intValue += rawValue[0];
246 | strValue = BleNamesResolver.resolveAppearance(intValue);
247 | }
248 | else if(uuid.equals(BleDefinedUUIDs.Characteristic.BODY_SENSOR_LOCATION)) { // body sensor location
249 | // follow: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.body_sensor_location.xml
250 | intValue = rawValue[0];
251 | strValue = BleNamesResolver.resolveHeartRateSensorLocation(intValue);
252 | }
253 | else if(uuid.equals(BleDefinedUUIDs.Characteristic.BATTERY_LEVEL)) { // battery level
254 | // follow: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.battery_level.xml
255 | intValue = rawValue[0];
256 | strValue = "" + intValue + "% battery level";
257 | }
258 | else {
259 | // not known type of characteristic, so we need to handle this in "general" way
260 | // get first four bytes and transform it to integer
261 | intValue = 0;
262 | if(rawValue.length > 0) intValue = (int)rawValue[0];
263 | if(rawValue.length > 1) intValue = intValue + ((int)rawValue[1] << 8);
264 | if(rawValue.length > 2) intValue = intValue + ((int)rawValue[2] << 8);
265 | if(rawValue.length > 3) intValue = intValue + ((int)rawValue[3] << 8);
266 |
267 | if (rawValue.length > 0) {
268 | final StringBuilder stringBuilder = new StringBuilder(rawValue.length);
269 | for(byte byteChar : rawValue) {
270 | stringBuilder.append(String.format("%c", byteChar));
271 | }
272 | strValue = stringBuilder.toString();
273 | }
274 | }
275 |
276 | String timestamp = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS").format(new Date());
277 | mUiCallback.uiNewValueForCharacteristic(mBluetoothGatt,
278 | mBluetoothDevice,
279 | mBluetoothSelectedService,
280 | ch,
281 | strValue,
282 | intValue,
283 | rawValue,
284 | timestamp);
285 | }
286 |
287 | /* reads and return what what FORMAT is indicated by characteristic's properties
288 | * seems that value makes no sense in most cases */
289 | public int getValueFormat(BluetoothGattCharacteristic ch) {
290 | int properties = ch.getProperties();
291 |
292 | if((BluetoothGattCharacteristic.FORMAT_FLOAT & properties) != 0) return BluetoothGattCharacteristic.FORMAT_FLOAT;
293 | if((BluetoothGattCharacteristic.FORMAT_SFLOAT & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SFLOAT;
294 | if((BluetoothGattCharacteristic.FORMAT_SINT16 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SINT16;
295 | if((BluetoothGattCharacteristic.FORMAT_SINT32 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SINT32;
296 | if((BluetoothGattCharacteristic.FORMAT_SINT8 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_SINT8;
297 | if((BluetoothGattCharacteristic.FORMAT_UINT16 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_UINT16;
298 | if((BluetoothGattCharacteristic.FORMAT_UINT32 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_UINT32;
299 | if((BluetoothGattCharacteristic.FORMAT_UINT8 & properties) != 0) return BluetoothGattCharacteristic.FORMAT_UINT8;
300 |
301 | return 0;
302 | }
303 |
304 | /* set new value for particular characteristic */
305 | public void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite) {
306 | if (mBluetoothAdapter == null || mBluetoothGatt == null || ch == null) return;
307 |
308 | // first set it locally....
309 | ch.setValue(dataToWrite);
310 | // ... and then "commit" changes to the peripheral
311 | mBluetoothGatt.writeCharacteristic(ch);
312 | }
313 |
314 | /* enables/disables notification for characteristic */
315 | public void setNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled) {
316 | if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
317 |
318 | boolean success = mBluetoothGatt.setCharacteristicNotification(ch, enabled);
319 | if(!success) {
320 | Log.e("------", "Seting proper notification status for characteristic failed!");
321 | }
322 |
323 | // This is also sometimes required (e.g. for heart rate monitors) to enable notifications/indications
324 | // see: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
325 | BluetoothGattDescriptor descriptor = ch.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
326 | if(descriptor != null) {
327 | byte[] val = enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
328 | descriptor.setValue(val);
329 | mBluetoothGatt.writeDescriptor(descriptor);
330 | }
331 | }
332 |
333 | /* defines callback for scanning results */
334 | private BluetoothAdapter.LeScanCallback mDeviceFoundCallback = new BluetoothAdapter.LeScanCallback() {
335 | @Override
336 | public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
337 | mUiCallback.uiDeviceFound(device, rssi, scanRecord);
338 | }
339 | };
340 |
341 | /* callbacks called for any action on particular Ble Device */
342 | private final BluetoothGattCallback mBleCallback = new BluetoothGattCallback() {
343 | @Override
344 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
345 | if (newState == BluetoothProfile.STATE_CONNECTED) {
346 | mConnected = true;
347 | mUiCallback.uiDeviceConnected(mBluetoothGatt, mBluetoothDevice);
348 |
349 | // now we can start talking with the device, e.g.
350 | mBluetoothGatt.readRemoteRssi();
351 | // response will be delivered to callback object!
352 |
353 | // in our case we would also like automatically to call for services discovery
354 | startServicesDiscovery();
355 |
356 | // and we also want to get RSSI value to be updated periodically
357 | startMonitoringRssiValue();
358 | }
359 | else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
360 | mConnected = false;
361 | mUiCallback.uiDeviceDisconnected(mBluetoothGatt, mBluetoothDevice);
362 | }
363 | }
364 |
365 | @Override
366 | public void onServicesDiscovered(BluetoothGatt gatt, int status) {
367 | if (status == BluetoothGatt.GATT_SUCCESS) {
368 | // now, when services discovery is finished, we can call getServices() for Gatt
369 | getSupportedServices();
370 | }
371 | }
372 |
373 | @Override
374 | public void onCharacteristicRead(BluetoothGatt gatt,
375 | BluetoothGattCharacteristic characteristic,
376 | int status)
377 | {
378 | // we got response regarding our request to fetch characteristic value
379 | if (status == BluetoothGatt.GATT_SUCCESS) {
380 | // and it success, so we can get the value
381 | getCharacteristicValue(characteristic);
382 | }
383 | }
384 |
385 | @Override
386 | public void onCharacteristicChanged(BluetoothGatt gatt,
387 | BluetoothGattCharacteristic characteristic)
388 | {
389 | // characteristic's value was updated due to enabled notification, lets get this value
390 | // the value itself will be reported to the UI inside getCharacteristicValue
391 | getCharacteristicValue(characteristic);
392 | // also, notify UI that notification are enabled for particular characteristic
393 | mUiCallback.uiGotNotification(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, characteristic);
394 | }
395 |
396 | @Override
397 | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
398 | String deviceName = gatt.getDevice().getName();
399 | String serviceName = BleNamesResolver.resolveServiceName(characteristic.getService().getUuid().toString().toLowerCase(Locale.getDefault()));
400 | String charName = BleNamesResolver.resolveCharacteristicName(characteristic.getUuid().toString().toLowerCase(Locale.getDefault()));
401 | String description = "Device: " + deviceName + " Service: " + serviceName + " Characteristic: " + charName;
402 |
403 | // we got response regarding our request to write new value to the characteristic
404 | // let see if it failed or not
405 | if(status == BluetoothGatt.GATT_SUCCESS) {
406 | mUiCallback.uiSuccessfulWrite(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, characteristic, description);
407 | }
408 | else {
409 | mUiCallback.uiFailedWrite(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, characteristic, description + " STATUS = " + status);
410 | }
411 | };
412 |
413 | @Override
414 | public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
415 | if(status == BluetoothGatt.GATT_SUCCESS) {
416 | // we got new value of RSSI of the connection, pass it to the UI
417 | mUiCallback.uiNewRssiAvailable(mBluetoothGatt, mBluetoothDevice, rssi);
418 | }
419 | };
420 | };
421 |
422 | private Activity mParent = null;
423 | private boolean mConnected = false;
424 | private String mDeviceAddress = "";
425 |
426 | private BluetoothManager mBluetoothManager = null;
427 | private BluetoothAdapter mBluetoothAdapter = null;
428 | private BluetoothDevice mBluetoothDevice = null;
429 | private BluetoothGatt mBluetoothGatt = null;
430 | private BluetoothGattService mBluetoothSelectedService = null;
431 | private List mBluetoothGattServices = null;
432 |
433 | private Handler mTimerHandler = new Handler();
434 | private boolean mTimerEnabled = false;
435 | }
436 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/activitys/DeviceHomeActivity.java:
--------------------------------------------------------------------------------
1 | package com.zsl.bluetoothdemo.activitys;
2 |
3 | import android.bluetooth.BluetoothAdapter;
4 | import android.bluetooth.BluetoothDevice;
5 | import android.bluetooth.BluetoothGatt;
6 | import android.bluetooth.BluetoothGattCharacteristic;
7 | import android.bluetooth.BluetoothGattService;
8 | import android.bluetooth.BluetoothManager;
9 | import android.content.BroadcastReceiver;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.content.IntentFilter;
13 | import android.os.Bundle;
14 | import android.util.Log;
15 | import android.view.View;
16 | import android.widget.Button;
17 | import android.widget.TextView;
18 | import android.widget.Toast;
19 |
20 | import com.zsl.bluetoothdemo.R;
21 | import com.zsl.bluetoothdemo.base.BaseActivity;
22 | import com.zsl.bluetoothdemo.utils.ble.oad.BluetoothLeService;
23 | import com.zsl.bluetoothdemo.utils.ble.oad.Conversion;
24 |
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.util.ArrayList;
28 | import java.util.List;
29 | import java.util.Timer;
30 | import java.util.TimerTask;
31 | import java.util.UUID;
32 |
33 | /**
34 | * Created by zsl on 15/9/21.
35 | */
36 | public class DeviceHomeActivity extends BaseActivity {
37 | //oad的Service
38 | public static final UUID UUID_OAD_SERVICE = UUID
39 | .fromString("f000ffc0-0451-4000-b000-000000000000");
40 | //连接控制的Service
41 | public static final UUID UUID_CONNCONTROL_SERVICE = UUID
42 | .fromString("f000ccc0-0451-4000-b000-000000000000");
43 |
44 |
45 | TextView tv_connect_state;
46 | Button bt_oad, bt_oad_stop, bt_connect,bt_history,bt_realtime;
47 | //btDevice
48 | BluetoothDevice bleDevice;
49 |
50 |
51 | /**
52 | * OAD
53 | */
54 | private MainActivity myMainActivity = null;
55 | private BluetoothLeService mLeService = null;
56 | // BLE
57 | private static BluetoothManager mBluetoothManager;
58 | private BluetoothAdapter mBtAdapter = null;
59 | private BluetoothGatt bleGatt;
60 | private BluetoothGattService mOadService;
61 | private BluetoothGattService mConnControlService;
62 | private List mCharListOad;
63 | private List mCharListCc;
64 | private BluetoothGattCharacteristic mCharIdentify = null;
65 | private BluetoothGattCharacteristic mCharBlock = null;
66 | private BluetoothGattCharacteristic mCharConnReq = null;
67 |
68 | //所有的Service
69 | List serviceList = new ArrayList();
70 |
71 |
72 | // Housekeeping
73 | private boolean mServiceOk = false;
74 | private boolean mProgramming = false;
75 |
76 | //fileName
77 | String filename = "APP_OAD_1.1.bin";
78 | // String filename="APP_OAD_1.0.bin";
79 | String oadTag = "oadTag";
80 |
81 | private static final int OAD_BLOCK_SIZE = 16;
82 | private static final int HAL_FLASH_WORD_SIZE = 4;
83 | private static final int OAD_BUFFER_SIZE = 2 + OAD_BLOCK_SIZE;
84 | private static final long TIMER_INTERVAL = 1000;
85 |
86 | // Programming parameters
87 | private static final short OAD_CONN_INTERVAL = 6; // 7.5 milliseconds
88 | private static final short OAD_CONN_INTERVAL40 = 30; // 37.5 milliseconds
89 | private static final short OAD_SUPERVISION_TIMEOUT = 50; // 500 milliseconds
90 | private static final short OAD_SUPERVISION_TIMEOUT500 = 500; // 500 milliseconds
91 |
92 | private ImgHdr mFileImgHdr;
93 | private ImgHdr mTargImgHdr;
94 | private Timer mTimer = null;
95 | private ProgInfo mProgInfo = new ProgInfo();
96 | private TimerTask mTimerTask = null;
97 | private int packetsSent = 0;
98 | //image
99 | private byte[] mFileBuffer;
100 | private final byte[] mOadBuffer = new byte[OAD_BUFFER_SIZE];
101 | //长度
102 | private int bufferLenth = 0;
103 |
104 | private boolean slowAlgo = true;
105 |
106 |
107 | private static DeviceHomeActivity mDeviceHomeActivity;
108 |
109 | @Override
110 | protected void onCreate(Bundle savedInstanceState) {
111 | super.onCreate(savedInstanceState);
112 | setContentView(R.layout.activity_device_home);
113 |
114 | initView();
115 | initEvent();
116 | initData();
117 |
118 |
119 | }
120 |
121 | private void initView() {
122 | tv_connect_state = (TextView) findViewById(R.id.device_home_tv_connect_state);
123 | bt_oad = (Button) findViewById(R.id.devices_bt_oad);
124 | bt_oad_stop = (Button) findViewById(R.id.devices_bt_oad_stop);
125 | bt_connect = (Button) findViewById(R.id.devices_bt_connect);
126 |
127 | bt_history= (Button) findViewById(R.id.devices_bt_history);
128 |
129 | bt_realtime= (Button) findViewById(R.id.devices_bt_realtime);
130 | }
131 |
132 | private void initEvent() {
133 | bt_oad.setOnClickListener(new View.OnClickListener() {
134 | @Override
135 | public void onClick(View v) {
136 | setConnectionParameters();
137 | startProgramming();
138 | }
139 | });
140 |
141 | bt_oad_stop.setOnClickListener(new View.OnClickListener() {
142 | @Override
143 | public void onClick(View v) {
144 | stopProgramming();
145 | }
146 | });
147 |
148 | bt_connect.setOnClickListener(new View.OnClickListener() {
149 | @Override
150 | public void onClick(View v) {
151 | connectDevice();
152 | }
153 | });
154 |
155 | bt_history.setOnClickListener(new View.OnClickListener() {
156 | @Override
157 | public void onClick(View v) {
158 | baseStartActivity(HistoryDataActivity.class);
159 | }
160 | });
161 |
162 | bt_realtime.setOnClickListener(new View.OnClickListener() {
163 | @Override
164 | public void onClick(View v) {
165 | baseStartActivity(RealTimeDataActivity.class);
166 | }
167 | });
168 | }
169 |
170 | private void initData() {
171 | mDeviceHomeActivity=this;
172 | myMainActivity = MainActivity.getInstance();
173 |
174 |
175 | //注册广播
176 | registerReceiver(BleBroadcastReceiver, SettingIntentFilter());
177 | //获得到BluetoothLeService对象
178 | mLeService = BluetoothLeService.getInstance();
179 | mBluetoothManager = myMainActivity.getmBluetoothManager();
180 | mBtAdapter = myMainActivity.getmBtAdapter();
181 | bleDevice = getIntent().getParcelableExtra("BluetoothDevice");
182 |
183 | connectDevice();
184 | }
185 |
186 | /**
187 | * 获得到ServiceList
188 | * @return
189 | */
190 | public List getServiceList() {
191 | return serviceList;
192 | }
193 |
194 | /**
195 | * 获得到DeviceHomeActivity对象
196 | * @return
197 | */
198 | public static DeviceHomeActivity getmDeviceHomeActivity() {
199 | return mDeviceHomeActivity;
200 | }
201 |
202 | private void connectDevice() {
203 |
204 |
205 |
206 | int connState = mBluetoothManager.getConnectionState(bleDevice,
207 | BluetoothGatt.GATT);
208 | switch (connState) {
209 | case BluetoothGatt.STATE_CONNECTED:
210 |
211 | Toast.makeText(this, "已连接", Toast.LENGTH_SHORT).show();
212 | break;
213 | case BluetoothGatt.STATE_DISCONNECTED:
214 | tv_connect_state.setText("正在连接。。。");
215 | boolean ok = mLeService.connect(bleDevice.getAddress());
216 | if (!ok) {
217 | Toast.makeText(this, "连接失败", Toast.LENGTH_SHORT).show();
218 | }
219 | break;
220 | default:
221 | break;
222 | }
223 | }
224 |
225 | /**
226 | * 初始化OAD
227 | */
228 | private void initOAD(List mserviceList) {
229 |
230 | for (int i = 0; i < mserviceList.size(); i++) {
231 | BluetoothGattService mGattService = mserviceList.get(i);
232 | //oadService
233 | if (mGattService.getUuid().equals(UUID_OAD_SERVICE)) {
234 | mOadService = mGattService;
235 | } else if (mGattService.getUuid().equals(UUID_CONNCONTROL_SERVICE)) {
236 | //连接控制的Service
237 | mConnControlService = mGattService;
238 | }
239 | }
240 |
241 | // 获得到对应Service的Characteristics list
242 | mCharListOad = mOadService.getCharacteristics();
243 | mCharListCc = mConnControlService.getCharacteristics();
244 | if (mCharListOad == null || mCharListCc == null) {
245 | return;
246 | }
247 | //验证Characteristics list
248 | mServiceOk = mCharListOad.size() == 2 && mCharListCc.size() >= 3;
249 | if (mServiceOk) {
250 | mCharIdentify = mCharListOad.get(0);
251 | mCharBlock = mCharListOad.get(1);
252 | mCharBlock.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
253 | mCharConnReq = mCharListCc.get(1);
254 | }
255 |
256 | mLeService.setCharacteristicNotification(mCharBlock, true);
257 | setConnectionParameters();
258 | loadFile(filename, false);
259 | }
260 |
261 | /**
262 | * 扫描Service
263 | *
264 | * @param bluetoothGatt
265 | */
266 | private void discoverServices(BluetoothGatt bluetoothGatt) {
267 | if (bluetoothGatt != null) {
268 | if (bluetoothGatt.discoverServices()) {
269 | serviceList.clear();
270 | }
271 | }
272 |
273 | }
274 |
275 | /**
276 | * 发送连接间隔
277 | */
278 | private void setConnectionParameters() {
279 | if (mCharConnReq == null)
280 | return;
281 | // Make sure connection interval is long enough for OAD (Android default connection interval is 7.5 ms)
282 | byte[] value = {Conversion.loUint16(OAD_CONN_INTERVAL), Conversion.hiUint16(OAD_CONN_INTERVAL), Conversion.loUint16(OAD_CONN_INTERVAL),
283 | Conversion.hiUint16(OAD_CONN_INTERVAL), 0, 0, Conversion.loUint16(OAD_SUPERVISION_TIMEOUT), Conversion.hiUint16(OAD_SUPERVISION_TIMEOUT)};
284 | mCharConnReq.setValue(value);
285 | mLeService.writeCharacteristic(mCharConnReq);
286 | }
287 |
288 | private void setConnectionParameters100() {
289 | // Make sure connection interval is long enough for OAD (Android default connection interval is 7.5 ms)
290 | byte[] value = {Conversion.loUint16(OAD_CONN_INTERVAL40), Conversion.hiUint16(OAD_CONN_INTERVAL40), Conversion.loUint16(OAD_CONN_INTERVAL40),
291 | Conversion.hiUint16(OAD_CONN_INTERVAL40), 0, 0, Conversion.loUint16(OAD_SUPERVISION_TIMEOUT500), Conversion.hiUint16(OAD_SUPERVISION_TIMEOUT500)};
292 | mCharConnReq.setValue(value);
293 | mLeService.writeCharacteristic(mCharConnReq);
294 | }
295 |
296 | @Override
297 | protected void onResume() {
298 | super.onResume();
299 | }
300 |
301 |
302 | @Override
303 | protected void onPause() {
304 | super.onPause();
305 | }
306 |
307 | @Override
308 | protected void onStop() {
309 | super.onStop();
310 | }
311 |
312 | @Override
313 | protected void onDestroy() {
314 | //取消广播
315 | if (BleBroadcastReceiver != null) {
316 | unregisterReceiver(BleBroadcastReceiver);
317 | }
318 | mLeService.disconnect(bleDevice.getAddress());
319 | super.onDestroy();
320 | }
321 |
322 |
323 | /**
324 | * 启动oad
325 | */
326 | private void startProgramming() {
327 | Log.e(oadTag, "启动oad");
328 | mProgramming = true;
329 | packetsSent = 0;
330 | mCharIdentify.setValue(mFileImgHdr.getRequest());
331 | mLeService.writeCharacteristic(mCharIdentify);
332 |
333 | // Initialize stats
334 | mProgInfo.reset();
335 | mTimer = new Timer();
336 | mTimerTask = new ProgTimerTask();
337 | mTimer.scheduleAtFixedRate(mTimerTask, 0, TIMER_INTERVAL);
338 | }
339 |
340 | /**
341 | * 停止oad
342 | */
343 | private void stopProgramming() {
344 | Log.e(oadTag, "停止oad");
345 | mTimer.cancel();
346 | mTimer.purge();
347 | mTimerTask.cancel();
348 | mTimerTask = null;
349 |
350 | mProgramming = false;
351 |
352 | mLeService.setCharacteristicNotification(mCharBlock, false);
353 | if (mProgInfo.iBlocks == mProgInfo.nBlocks) {
354 | Log.e(oadTag, "Programming complete!\n");
355 | } else {
356 | Log.e(oadTag, "Programming cancelled!\n");
357 | }
358 | }
359 |
360 | /**
361 | * 加载image
362 | *
363 | * @param filepath
364 | * @param isAsset
365 | * @return
366 | */
367 | private boolean loadFile(String filepath, boolean isAsset) {
368 | boolean fSuccess = true;
369 | byte[] mBuffer;
370 | try {
371 | // Read the file raw into a buffer
372 | InputStream stream = getAssets().open(filepath);
373 | mBuffer = new byte[stream.available()];
374 | stream.read(mBuffer);
375 | stream.close();
376 | } catch (IOException e) {
377 | Log.e(oadTag, "File open failed: " + filepath + "\n");
378 | return false;
379 | }
380 | bufferLenth = mBuffer.length / 16 + 1;
381 | //赋值到全局mFileBuffer
382 | mFileBuffer = mBuffer;
383 | mFileImgHdr = new ImgHdr(mBuffer);
384 |
385 | displayStats();
386 | // Log
387 | Log.e(oadTag, "Image 路径:" + filepath + "\n");
388 |
389 | return fSuccess;
390 | }
391 |
392 | /**
393 | * 显示转台
394 | */
395 | private void displayStats() {
396 | String txt;
397 | int byteRate;
398 | int sec = mProgInfo.iTimeElapsed / 1000;
399 | if (sec > 0) {
400 | byteRate = mProgInfo.iBytes / sec;
401 | } else {
402 | byteRate = 0;
403 | return;
404 | }
405 | float timeEstimate;
406 |
407 | timeEstimate = ((float) (mFileImgHdr.len * 4) / (float) mProgInfo.iBytes) * sec;
408 |
409 | txt = String.format("Time: %d / %d sec", sec, (int) timeEstimate);
410 | txt += String.format(" Bytes: %d (%d/sec)", mProgInfo.iBytes, byteRate);
411 | Log.e(oadTag, txt);
412 | }
413 |
414 | private class ProgTimerTask extends TimerTask {
415 | @Override
416 | public void run() {
417 | mProgInfo.iTimeElapsed += TIMER_INTERVAL;
418 | }
419 | }
420 |
421 |
422 | private class ImgHdr {
423 | short crc0;
424 | short crc1;
425 | short ver;
426 | int len;
427 | byte[] uid = new byte[4];
428 | short addr;
429 | byte imgType;
430 |
431 | ImgHdr(byte[] buf) {
432 | // this.len = ((32 * 0x1000) / (16 / 4));
433 | this.len = buf.length / 4;
434 | this.ver = 0;
435 | this.uid[0] = this.uid[1] = this.uid[2] = this.uid[3] = 'E';
436 | this.addr = 0;
437 | this.imgType = 1; //EFL_OAD_IMG_TYPE_APP
438 | this.crc0 = calcImageCRC((int) 0, buf);
439 | crc1 = (short) 0xFFFF;
440 | }
441 |
442 | byte[] getRequest() {
443 | byte[] tmp = new byte[16];
444 | tmp[0] = Conversion.loUint16((short) this.crc0);
445 | tmp[1] = Conversion.hiUint16((short) this.crc0);
446 | tmp[2] = Conversion.loUint16((short) this.crc1);
447 | tmp[3] = Conversion.hiUint16((short) this.crc1);
448 | tmp[4] = Conversion.loUint16(this.ver);
449 | tmp[5] = Conversion.hiUint16(this.ver);
450 | tmp[6] = Conversion.loUint16((short) this.len);
451 | tmp[7] = Conversion.hiUint16((short) this.len);
452 | tmp[8] = tmp[9] = tmp[10] = tmp[11] = this.uid[0];
453 | tmp[12] = Conversion.loUint16(this.addr);
454 | tmp[13] = Conversion.hiUint16(this.addr);
455 | tmp[14] = imgType;
456 | tmp[15] = (byte) 0xFF;
457 | return tmp;
458 | }
459 |
460 | short calcImageCRC(int page, byte[] buf) {
461 | short crc = 0;
462 | long addr = page * 0x1000;
463 |
464 | byte pageBeg = (byte) page;
465 | byte pageEnd = (byte) (this.len / (0x1000 / 4));
466 | int osetEnd = ((this.len - (pageEnd * (0x1000 / 4))) * 4);
467 |
468 | pageEnd += pageBeg;
469 |
470 |
471 | while (true) {
472 | int oset;
473 |
474 | for (oset = 0; oset < 0x1000; oset++) {
475 | if ((page == pageBeg) && (oset == 0x00)) {
476 | oset += 3;
477 | } else if ((page == pageEnd) && (oset == osetEnd)) {
478 | crc = this.crc16(crc, (byte) 0x00);
479 | crc = this.crc16(crc, (byte) 0x00);
480 |
481 | return crc;
482 | } else {
483 | crc = this.crc16(crc, buf[(int) (addr + oset)]);
484 | // Log.e(oadTag, buf[(int) (addr + oset)] + ":" + crc);
485 | }
486 | }
487 | page += 1;
488 | addr = page * 0x1000;
489 | }
490 |
491 |
492 | }
493 |
494 | short crc16(short crc, byte val) {
495 | final int poly = 0x1021;
496 | byte cnt;
497 | for (cnt = 0; cnt < 8; cnt++, val <<= 1) {
498 | byte msb;
499 | if ((crc & 0x8000) == 0x8000) {
500 | msb = 1;
501 | } else msb = 0;
502 |
503 | crc <<= 1;
504 | if ((val & 0x80) == 0x80) {
505 | crc |= 0x0001;
506 | }
507 | if (msb == 1) {
508 | crc ^= poly;
509 | }
510 | }
511 |
512 | return crc;
513 | }
514 |
515 | }
516 |
517 | private class ProgInfo {
518 | int iBytes = 0; // Number of bytes programmed
519 | short iBlocks = 0; // Number of blocks programmed
520 | short nBlocks = 0; // Total number of blocks
521 | int iTimeElapsed = 0; // Time elapsed in milliseconds
522 |
523 | void reset() {
524 | iBytes = 0;
525 | iBlocks = 0;
526 | iTimeElapsed = 0;
527 | nBlocks = (short) (mFileImgHdr.len / (OAD_BLOCK_SIZE / HAL_FLASH_WORD_SIZE));
528 | }
529 | }
530 |
531 |
532 | /**
533 | * 设置IntentFilter
534 | *
535 | * @return
536 | */
537 | private IntentFilter SettingIntentFilter() {
538 | final IntentFilter fi = new IntentFilter();
539 | fi.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
540 | fi.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
541 | fi.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
542 | fi.addAction(BluetoothLeService.ACTION_DATA_NOTIFY);
543 | fi.addAction(BluetoothLeService.ACTION_DATA_WRITE);
544 | fi.addAction(BluetoothLeService.ACTION_DATA_READ);
545 | return fi;
546 | }
547 |
548 |
549 | /**
550 | * Ble的广播
551 | */
552 | private BroadcastReceiver BleBroadcastReceiver = new BroadcastReceiver() {
553 | @Override
554 | public void onReceive(Context context, Intent intent) {
555 | String action = intent.getAction();
556 | if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {//连接成功
557 | tv_connect_state.setText("连接成功1");
558 | bleGatt=BluetoothLeService.getBtGatt();
559 | discoverServices(bleGatt);
560 | } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { //连接断开
561 | tv_connect_state.setText("连接断开");
562 | } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {// Service 发现完毕
563 |
564 | // Gatt的连接状态
565 | int status = intent.getIntExtra(BluetoothLeService.EXTRA_STATUS,
566 | BluetoothGatt.GATT_SUCCESS);
567 | if (status == BluetoothGatt.GATT_SUCCESS) {
568 | //获得到此蓝牙设备的所有支持的Service
569 | serviceList = mLeService.getSupportedGattServices();
570 | if (serviceList != null) {
571 | tv_connect_state.append("serviceList:" + serviceList.size());
572 | }
573 | //初始化OAD
574 | initOAD(serviceList);
575 | }
576 |
577 | } else if (BluetoothLeService.ACTION_DATA_NOTIFY.equals(action)) {
578 | byte[] value = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
579 | String uuidStr = intent.getStringExtra(BluetoothLeService.EXTRA_UUID);
580 |
581 | if (uuidStr.equals(mCharIdentify.getUuid().toString())) {
582 |
583 | }
584 | if (uuidStr.equals(mCharBlock.getUuid().toString())) {
585 | // Block check here :
586 | String block = String.format("%02x%02x", value[1], value[0]);
587 |
588 | programBlock();
589 |
590 | // if (slowAlgo == true) {
591 | // programBlock();
592 | // } else {
593 | // if (packetsSent != 0) packetsSent--;
594 | // if (packetsSent > 10) return;
595 | // while (packetsSent < fastAlgoMaxPackets) {
596 | // waitABit();
597 | // programBlock();
598 | // }
599 | // }
600 | }
601 |
602 | } else if (BluetoothLeService.ACTION_DATA_WRITE.equals(action)) {
603 | int status = intent.getIntExtra(BluetoothLeService.EXTRA_STATUS, BluetoothGatt.GATT_SUCCESS);
604 | if (status != BluetoothGatt.GATT_SUCCESS) {
605 | Toast.makeText(context, "GATT error: status=" + status, Toast.LENGTH_SHORT).show();
606 | }
607 | }
608 | }
609 | };
610 |
611 |
612 | private void programBlock() {
613 | if (!mProgramming)
614 | return;
615 | // mProgInfo.nBlocks=(short)bufferLenth;
616 |
617 | Log.e(oadTag, "mProgInfo.iBlocks:" + mProgInfo.iBlocks + "mProgInfo.nBlocks:" + mProgInfo.nBlocks);
618 | if (mProgInfo.iBlocks < mProgInfo.nBlocks) {
619 | mProgramming = true;
620 | String msg = new String();
621 |
622 | //Prepare block
623 | mOadBuffer[0] = Conversion.loUint16(mProgInfo.iBlocks);
624 | mOadBuffer[1] = Conversion.hiUint16(mProgInfo.iBlocks);
625 | System.arraycopy(mFileBuffer, mProgInfo.iBytes, mOadBuffer, 2, OAD_BLOCK_SIZE);
626 |
627 | // Send block
628 | mCharBlock.setValue(mOadBuffer);
629 | boolean success = mLeService.writeCharacteristicNonBlock(mCharBlock);
630 | Log.d("FwUpdateActivity_CC26xx", "Sent block :" + mProgInfo.iBlocks);
631 | if (success) {
632 | // Update stats
633 | packetsSent++;
634 | mProgInfo.iBlocks++;
635 | mProgInfo.iBytes += OAD_BLOCK_SIZE;
636 |
637 | int total = (mProgInfo.iBlocks * 100) / mProgInfo.nBlocks;
638 |
639 | Log.e(oadTag, "已上传总量:" + total + "%");
640 | if (mProgInfo.iBlocks == mProgInfo.nBlocks) {
641 | Log.e(oadTag, "OAD完毕");
642 | }
643 | } else {
644 | mProgramming = false;
645 | msg = "GATT writeCharacteristic failed\n";
646 | }
647 | if (!success) {
648 | Log.e(oadTag, msg);
649 | }
650 | } else {
651 | mProgramming = false;
652 | }
653 | if ((mProgInfo.iBlocks % 100) == 0) {
654 | // Display statistics each 100th block
655 | runOnUiThread(new Runnable() {
656 | public void run() {
657 | displayStats();
658 | }
659 | });
660 | }
661 |
662 | if (!mProgramming) {
663 | runOnUiThread(new Runnable() {
664 | public void run() {
665 | displayStats();
666 | stopProgramming();
667 | }
668 | });
669 | }
670 | }
671 |
672 | }
673 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zsl/bluetoothdemo/utils/ble/oad/BluetoothLeService.java:
--------------------------------------------------------------------------------
1 | /**************************************************************************************************
2 | Filename: BluetoothLeService.java
3 | Revised: $Date: Wed Apr 22 13:01:34 2015 +0200$
4 | Revision: $Revision: 599e5650a33a4a142d060c959561f9e9b0d88146$
5 |
6 | Copyright (c) 2013 - 2014 Texas Instruments Incorporated
7 |
8 | All rights reserved not granted herein.
9 | Limited License.
10 |
11 | Texas Instruments Incorporated grants a world-wide, royalty-free,
12 | non-exclusive license under copyrights and patents it now or hereafter
13 | owns or controls to make, have made, use, import, offer to sell and sell ("Utilize")
14 | this software subject to the terms herein. With respect to the foregoing patent
15 | license, such license is granted solely to the extent that any such patent is necessary
16 | to Utilize the software alone. The patent license shall not apply to any combinations which
17 | include this software, other than combinations with devices manufactured by or for TI ('TI Devices').
18 | No hardware patent is licensed hereunder.
19 |
20 | Redistributions must preserve existing copyright notices and reproduce this license (including the
21 | above copyright notice and the disclaimer and (if applicable) source code license limitations below)
22 | in the documentation and/or other materials provided with the distribution
23 |
24 | Redistribution and use in binary form, without modification, are permitted provided that the following
25 | conditions are met:
26 |
27 | * No reverse engineering, decompilation, or disassembly of this software is permitted with respect to any
28 | software provided in binary form.
29 | * any redistribution and use are licensed by TI for use only with TI Devices.
30 | * Nothing shall obligate TI to provide you with source code for the software licensed and provided to you in object code.
31 |
32 | If software source code is provided to you, modification and redistribution of the source code are permitted
33 | provided that the following conditions are met:
34 |
35 | * any redistribution and use of the source code, including any resulting derivative works, are licensed by
36 | TI for use only with TI Devices.
37 | * any redistribution and use of any object code compiled from the source code and any resulting derivative
38 | works, are licensed by TI for use only with TI Devices.
39 |
40 | Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to endorse or
41 | promote products derived from this software without specific prior written permission.
42 |
43 | DISCLAIMER.
44 |
45 | THIS SOFTWARE IS PROVIDED BY TI AND TI'S LICENSORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
46 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
47 | IN NO EVENT SHALL TI AND TI'S LICENSORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
48 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
49 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
51 | POSSIBILITY OF SUCH DAMAGE.
52 |
53 |
54 | **************************************************************************************************/
55 | package com.zsl.bluetoothdemo.utils.ble.oad;
56 |
57 | import android.app.Service;
58 | import android.bluetooth.BluetoothAdapter;
59 | import android.bluetooth.BluetoothDevice;
60 | import android.bluetooth.BluetoothGatt;
61 | import android.bluetooth.BluetoothGattCallback;
62 | import android.bluetooth.BluetoothGattCharacteristic;
63 | import android.bluetooth.BluetoothGattDescriptor;
64 | import android.bluetooth.BluetoothGattService;
65 | import android.bluetooth.BluetoothManager;
66 | import android.bluetooth.BluetoothProfile;
67 | import android.content.Context;
68 | import android.content.Intent;
69 | import android.os.Binder;
70 | import android.os.IBinder;
71 | import android.util.Log;
72 |
73 | import java.lang.reflect.Method;
74 | import java.util.LinkedList;
75 | import java.util.List;
76 | import java.util.Timer;
77 | import java.util.TimerTask;
78 | import java.util.UUID;
79 | import java.util.concurrent.locks.Lock;
80 | import java.util.concurrent.locks.ReentrantLock;
81 |
82 | // import android.util.Log;
83 |
84 | /**
85 | * Service for managing connection and data communication with a GATT server
86 | * hosted on a given Bluetooth LE device.
87 | */
88 | public class BluetoothLeService extends Service {
89 | static final String TAG = "BluetoothLeService";
90 | public static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
91 | public final static String ACTION_GATT_CONNECTED = "com.zsl.bluetoothdemo.utils.ble.oad.ACTION_GATT_CONNECTED";
92 | public final static String ACTION_GATT_DISCONNECTED = "com.zsl.bluetoothdemo.utils.ble.oad.ACTION_GATT_DISCONNECTED";
93 | public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.zsl.bluetoothdemo.utils.ble.oad.ACTION_GATT_SERVICES_DISCOVERED";
94 | public final static String ACTION_DATA_READ = "com.zsl.bluetoothdemo.utils.ble.oad.ACTION_DATA_READ";
95 | public final static String ACTION_DATA_NOTIFY = "com.zsl.bluetoothdemo.utils.ble.oad.ACTION_DATA_NOTIFY";
96 | public final static String ACTION_DATA_WRITE = "com.zsl.bluetoothdemo.utils.ble.oad.ACTION_DATA_WRITE";
97 | public final static String EXTRA_DATA = "com.zsl.bluetoothdemo.utils.ble.oad.EXTRA_DATA";
98 | public final static String EXTRA_UUID = "com.zsl.bluetoothdemo.utils.ble.oad.EXTRA_UUID";
99 | public final static String EXTRA_STATUS = "com.zsl.bluetoothdemo.utils.ble.oad.EXTRA_STATUS";
100 | public final static String EXTRA_ADDRESS = "com.zsl.bluetoothdemo.utils.ble.oad.EXTRA_ADDRESS";
101 | public final static int GATT_TIMEOUT = 150;
102 |
103 | // BLE
104 | private BluetoothManager mBluetoothManager = null;
105 | private BluetoothAdapter mBtAdapter = null;
106 | private BluetoothGatt mBluetoothGatt = null;
107 | private static BluetoothLeService mThis = null;
108 | private String mBluetoothDeviceAddress;
109 |
110 | public Timer disconnectionTimer;
111 | private final Lock lock = new ReentrantLock();
112 |
113 | private volatile boolean blocking = false;
114 | private volatile int lastGattStatus = 0; //Success
115 |
116 | private volatile bleRequest curBleRequest = null;
117 |
118 | public enum bleRequestOperation {
119 | wrBlocking,
120 | wr,
121 | rdBlocking,
122 | rd,
123 | nsBlocking,
124 | }
125 |
126 | public enum bleRequestStatus {
127 | not_queued,
128 | queued,
129 | processing,
130 | timeout,
131 | done,
132 | no_such_request,
133 | failed,
134 | }
135 |
136 | public class bleRequest {
137 | public int id;
138 | public BluetoothGattCharacteristic characteristic;
139 | public bleRequestOperation operation;
140 | public volatile bleRequestStatus status;
141 | public int timeout;
142 | public int curTimeout;
143 | public boolean notifyenable;
144 | }
145 |
146 | // Queuing for fast application response.
147 | private volatile LinkedList procQueue;
148 | private volatile LinkedList nonBlockQueue;
149 |
150 | //
151 |
152 | /**
153 | * GATT client callbacks
154 | */
155 | private BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
156 |
157 | @Override
158 | public void onConnectionStateChange(BluetoothGatt gatt, int status,
159 | int newState) {
160 | if (mBluetoothGatt == null) {
161 | // Log.e(TAG, "mBluetoothGatt not created!");
162 | return;
163 | }
164 |
165 | BluetoothDevice device = gatt.getDevice();
166 | String address = device.getAddress();
167 | // Log.d(TAG, "onConnectionStateChange (" + address + ") " + newState +
168 | // " status: " + status);
169 |
170 | try {
171 | switch (newState) {
172 | case BluetoothProfile.STATE_CONNECTED:
173 | //refreshDeviceCache(mBluetoothGatt);
174 | broadcastUpdate(ACTION_GATT_CONNECTED, address, status);
175 | break;
176 | case BluetoothProfile.STATE_DISCONNECTED:
177 | broadcastUpdate(ACTION_GATT_DISCONNECTED, address, status);
178 | break;
179 | default:
180 | // Log.e(TAG, "New state not processed: " + newState);
181 | break;
182 | }
183 | } catch (NullPointerException e) {
184 | e.printStackTrace();
185 | }
186 | }
187 |
188 | @Override
189 | public void onServicesDiscovered(BluetoothGatt gatt, int status) {
190 | BluetoothDevice device = gatt.getDevice();
191 | broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED, device.getAddress(),
192 | status);
193 | }
194 |
195 | @Override
196 | public void onCharacteristicChanged(BluetoothGatt gatt,
197 | BluetoothGattCharacteristic characteristic) {
198 | broadcastUpdate(ACTION_DATA_NOTIFY, characteristic,
199 | BluetoothGatt.GATT_SUCCESS);
200 | }
201 |
202 | @Override
203 | public void onCharacteristicRead(BluetoothGatt gatt,
204 | BluetoothGattCharacteristic characteristic, int status) {
205 | if (blocking)unlockBlockingThread(status);
206 | if (nonBlockQueue.size() > 0) {
207 | lock.lock();
208 | for (int ii = 0; ii < nonBlockQueue.size(); ii++) {
209 | bleRequest req = nonBlockQueue.get(ii);
210 | if (req.characteristic == characteristic) {
211 | req.status = bleRequestStatus.done;
212 | nonBlockQueue.remove(ii);
213 | break;
214 | }
215 | }
216 | lock.unlock();
217 | }
218 | broadcastUpdate(ACTION_DATA_READ, characteristic, status);
219 | }
220 |
221 | @Override
222 | public void onCharacteristicWrite(BluetoothGatt gatt,
223 | BluetoothGattCharacteristic characteristic, int status) {
224 | if (blocking)unlockBlockingThread(status);
225 | if (nonBlockQueue.size() > 0) {
226 | lock.lock();
227 | for (int ii = 0; ii < nonBlockQueue.size(); ii++) {
228 | bleRequest req = nonBlockQueue.get(ii);
229 | if (req.characteristic == characteristic) {
230 | req.status = bleRequestStatus.done;
231 | nonBlockQueue.remove(ii);
232 | break;
233 | }
234 | }
235 | lock.unlock();
236 | }
237 | broadcastUpdate(ACTION_DATA_WRITE, characteristic, status);
238 | }
239 |
240 | @Override
241 | public void onDescriptorRead(BluetoothGatt gatt,
242 | BluetoothGattDescriptor descriptor, int status) {
243 | if (blocking)unlockBlockingThread(status);
244 | unlockBlockingThread(status);
245 | }
246 |
247 | @Override
248 | public void onDescriptorWrite(BluetoothGatt gatt,
249 | BluetoothGattDescriptor descriptor, int status) {
250 | if (blocking)unlockBlockingThread(status);
251 | // Log.i(TAG, "onDescriptorWrite: " + descriptor.getUuid().toString());
252 | }
253 | };
254 |
255 | private void unlockBlockingThread(int status) {
256 | this.lastGattStatus = status;
257 | this.blocking = false;
258 | }
259 |
260 | private void broadcastUpdate(final String action, final String address,
261 | final int status) {
262 | final Intent intent = new Intent(action);
263 | intent.putExtra(EXTRA_ADDRESS, address);
264 | intent.putExtra(EXTRA_STATUS, status);
265 | sendBroadcast(intent);
266 | }
267 |
268 | private void broadcastUpdate(final String action,
269 | final BluetoothGattCharacteristic characteristic, final int status) {
270 | final Intent intent = new Intent(action);
271 | intent.putExtra(EXTRA_UUID, characteristic.getUuid().toString());
272 | intent.putExtra(EXTRA_DATA, characteristic.getValue());
273 | intent.putExtra(EXTRA_STATUS, status);
274 | sendBroadcast(intent);
275 | }
276 |
277 | public boolean checkGatt() {
278 | if (mBtAdapter == null) {
279 | // Log.w(TAG, "BluetoothAdapter not initialized");
280 | return false;
281 | }
282 | if (mBluetoothGatt == null) {
283 | // Log.w(TAG, "BluetoothGatt not initialized");
284 | return false;
285 | }
286 | if (this.blocking) {
287 | Log.d(TAG, "Cannot start operation : Blocked");
288 | return false;
289 | }
290 | return true;
291 |
292 | }
293 |
294 | /**
295 | * Manage the BLE service
296 | */
297 | public class LocalBinder extends Binder {
298 | public BluetoothLeService getService() {
299 | return BluetoothLeService.this;
300 | }
301 | }
302 |
303 | @Override
304 | public IBinder onBind(Intent intent) {
305 | return binder;
306 | }
307 |
308 | @Override
309 | public boolean onUnbind(Intent intent) {
310 | // After using a given device, you should make sure that
311 | // BluetoothGatt.close() is called
312 | // such that resources are cleaned up properly. In this particular example,
313 | // close() is
314 | // invoked when the UI is disconnected from the Service.
315 | close();
316 | return super.onUnbind(intent);
317 | }
318 |
319 | private final IBinder binder = new LocalBinder();
320 |
321 | /**
322 | * Initializes a reference to the local Bluetooth adapter.
323 | *
324 | * @return Return true if the initialization is successful.
325 | */
326 | public boolean initialize() {
327 | // For API level 18 and above, get a reference to BluetoothAdapter through
328 | // BluetoothManager.
329 | mThis = this;
330 | if (mBluetoothManager == null) {
331 | mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
332 | if (mBluetoothManager == null) {
333 | // Log.e(TAG, "Unable to initialize BluetoothManager.");
334 | return false;
335 | }
336 | }
337 |
338 | mBtAdapter = mBluetoothManager.getAdapter();
339 | if (mBtAdapter == null) {
340 | // Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
341 | return false;
342 | }
343 |
344 | procQueue = new LinkedList();
345 | nonBlockQueue = new LinkedList();
346 |
347 |
348 | Thread queueThread = new Thread() {
349 | @Override
350 | public void run() {
351 | while (true) {
352 | executeQueue();
353 | try {
354 | Thread.sleep(0, 100000);
355 | }
356 | catch (Exception e) {
357 | e.printStackTrace();
358 | }
359 | }
360 | }
361 | };
362 |
363 | queueThread.start();
364 | return true;
365 | }
366 |
367 | @Override
368 | public int onStartCommand(Intent intent, int flags, int startId) {
369 | // Log.i(TAG, "Received start id " + startId + ": " + intent);
370 | // We want this service to continue running until it is explicitly
371 | // stopped, so return sticky.
372 | this.initialize();
373 | return START_STICKY;
374 | }
375 |
376 | @Override
377 | public void onDestroy() {
378 | super.onDestroy();
379 | if (mBluetoothGatt != null) {
380 | mBluetoothGatt.close();
381 | mBluetoothGatt = null;
382 | }
383 | }
384 |
385 | //
386 | // GATT API
387 | //
388 |
389 | /**
390 | * Request a read on a given {@code BluetoothGattCharacteristic}. The read
391 | * result is reported asynchronously through the
392 | * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
393 | * callback.
394 | *
395 | * @param characteristic
396 | * The characteristic to read from.
397 | */
398 | public int readCharacteristic(BluetoothGattCharacteristic characteristic) {
399 | bleRequest req = new bleRequest();
400 | req.status = bleRequestStatus.not_queued;
401 | req.characteristic = characteristic;
402 | req.operation = bleRequestOperation.rdBlocking;
403 | addRequestToQueue(req);
404 | boolean finished = false;
405 | while (!finished) {
406 | bleRequestStatus stat = pollForStatusofRequest(req);
407 | if (stat == bleRequestStatus.done) {
408 | finished = true;
409 | return 0;
410 | }
411 | else if (stat == bleRequestStatus.timeout) {
412 | finished = true;
413 | return -3;
414 | }
415 | }
416 | return -2;
417 | }
418 |
419 | public int writeCharacteristic(
420 | BluetoothGattCharacteristic characteristic, byte b) {
421 |
422 |
423 | byte[] val = new byte[1];
424 | val[0] = b;
425 | characteristic.setValue(val);
426 |
427 | bleRequest req = new bleRequest();
428 | req.status = bleRequestStatus.not_queued;
429 | req.characteristic = characteristic;
430 | req.operation = bleRequestOperation.wrBlocking;
431 | addRequestToQueue(req);
432 | boolean finished = false;
433 | while (!finished) {
434 | bleRequestStatus stat = pollForStatusofRequest(req);
435 | if (stat == bleRequestStatus.done) {
436 | finished = true;
437 | return 0;
438 | }
439 | else if (stat == bleRequestStatus.timeout) {
440 | finished = true;
441 | return -3;
442 | }
443 | }
444 | return -2;
445 | }
446 | public int writeCharacteristic(
447 | BluetoothGattCharacteristic characteristic, byte[] b) {
448 | characteristic.setValue(b);
449 | bleRequest req = new bleRequest();
450 | req.status = bleRequestStatus.not_queued;
451 | req.characteristic = characteristic;
452 | req.operation = bleRequestOperation.wrBlocking;
453 | addRequestToQueue(req);
454 | boolean finished = false;
455 | while (!finished) {
456 | bleRequestStatus stat = pollForStatusofRequest(req);
457 | if (stat == bleRequestStatus.done) {
458 | finished = true;
459 | return 0;
460 | }
461 | else if (stat == bleRequestStatus.timeout) {
462 | finished = true;
463 | return -3;
464 | }
465 | }
466 | return -2;
467 | }
468 | public int writeCharacteristic(BluetoothGattCharacteristic characteristic) {
469 | bleRequest req = new bleRequest();
470 | req.status = bleRequestStatus.not_queued;
471 | req.characteristic = characteristic;
472 | req.operation = bleRequestOperation.wrBlocking;
473 | addRequestToQueue(req);
474 | boolean finished = false;
475 | while (!finished) {
476 | bleRequestStatus stat = pollForStatusofRequest(req);
477 | if (stat == bleRequestStatus.done) {
478 | finished = true;
479 | return 0;
480 | }
481 | else if (stat == bleRequestStatus.timeout) {
482 | finished = true;
483 | return -3;
484 | }
485 | }
486 | return -2;
487 | }
488 |
489 | public boolean writeCharacteristicNonBlock(BluetoothGattCharacteristic characteristic) {
490 | bleRequest req = new bleRequest();
491 | req.status = bleRequestStatus.not_queued;
492 | req.characteristic = characteristic;
493 | req.operation = bleRequestOperation.wr;
494 | addRequestToQueue(req);
495 | return true;
496 | }
497 |
498 | /**
499 | * Retrieves the number of GATT services on the connected device. This should
500 | * be invoked only after {@code BluetoothGatt#discoverServices()} completes
501 | * successfully.
502 | *
503 | * @return A {@code integer} number of supported services.
504 | */
505 | public int getNumServices() {
506 | if (mBluetoothGatt == null)
507 | return 0;
508 |
509 | return mBluetoothGatt.getServices().size();
510 | }
511 |
512 | /**
513 | * Retrieves a list of supported GATT services on the connected device. This
514 | * should be invoked only after {@code BluetoothGatt#discoverServices()}
515 | * completes successfully.
516 | *
517 | * @return A {@code List} of supported services.
518 | */
519 | public List getSupportedGattServices() {
520 | if (mBluetoothGatt == null)
521 | return null;
522 |
523 | return mBluetoothGatt.getServices();
524 | }
525 |
526 | /**
527 | * Enables or disables notification on a give characteristic.
528 | *
529 | * @param characteristic
530 | * Characteristic to act on.
531 | * @param enable
532 | * If true, enable notification. False otherwise.
533 | */
534 | public int setCharacteristicNotification(
535 | BluetoothGattCharacteristic characteristic, boolean enable) {
536 | bleRequest req = new bleRequest();
537 | req.status = bleRequestStatus.not_queued;
538 | req.characteristic = characteristic;
539 | req.operation = bleRequestOperation.nsBlocking;
540 | req.notifyenable = enable;
541 | addRequestToQueue(req);
542 | boolean finished = false;
543 | while (!finished) {
544 | bleRequestStatus stat = pollForStatusofRequest(req);
545 | if (stat == bleRequestStatus.done) {
546 | finished = true;
547 | return 0;
548 | }
549 | else if (stat == bleRequestStatus.timeout) {
550 | finished = true;
551 | return -3;
552 | }
553 | }
554 | return -2;
555 | }
556 |
557 | public boolean isNotificationEnabled(
558 | BluetoothGattCharacteristic characteristic) {
559 | if (characteristic == null) {
560 | return false;
561 | }
562 | if (!checkGatt())
563 | return false;
564 |
565 | BluetoothGattDescriptor clientConfig = characteristic
566 | .getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
567 | if (clientConfig == null)
568 | return false;
569 |
570 | return clientConfig.getValue() == BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
571 | }
572 |
573 | /**
574 | * Connects to the GATT server hosted on the Bluetooth LE device.
575 | *
576 | * @param address
577 | * The device address of the destination device.
578 | *
579 | * @return Return true if the connection is initiated successfully. The
580 | * connection result is reported asynchronously through the
581 | * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
582 | * callback.
583 | */
584 | public boolean connect(final String address) {
585 | if (mBtAdapter == null || address == null) {
586 | // Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
587 | return false;
588 | }
589 | final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
590 | int connectionState = mBluetoothManager.getConnectionState(device,
591 | BluetoothProfile.GATT);
592 |
593 | if (connectionState == BluetoothProfile.STATE_DISCONNECTED) {
594 |
595 | // Previously connected device. Try to reconnect.
596 | // if (mBluetoothDeviceAddress != null
597 | // && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) {
598 | // // Log.d(TAG, "Re-use GATT connection");
599 | // if (mBluetoothGatt.connect()) {
600 | // return true;
601 | // } else {
602 | // // Log.w(TAG, "GATT re-connect failed.");
603 | // return false;
604 | // }
605 | // }
606 |
607 | if (device == null) {
608 | // Log.w(TAG, "Device not found. Unable to connect.");
609 | return false;
610 | }
611 | // We want to directly connect to the device, so we are setting the
612 | // autoConnect parameter to false.
613 | // Log.d(TAG, "Create a new GATT connection.");
614 | mBluetoothGatt = device.connectGatt(this, false, mGattCallbacks);
615 | mBluetoothDeviceAddress = address;
616 | } else {
617 | // Log.w(TAG, "Attempt to connect in state: " + connectionState);
618 | return false;
619 | }
620 | return true;
621 | }
622 |
623 | /**
624 | * Disconnects an existing connection or cancel a pending connection. The
625 | * disconnection result is reported asynchronously through the
626 | * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
627 | * callback.
628 | */
629 | public void disconnect(String address) {
630 | if (mBtAdapter == null) {
631 | // Log.w(TAG, "disconnect: BluetoothAdapter not initialized");
632 | return;
633 | }
634 | final BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
635 | int connectionState = mBluetoothManager.getConnectionState(device,
636 | BluetoothProfile.GATT);
637 |
638 | if (mBluetoothGatt != null) {
639 | if (connectionState != BluetoothProfile.STATE_DISCONNECTED) {
640 | mBluetoothGatt.disconnect();
641 | } else {
642 | // Log.w(TAG, "Attempt to disconnect in state: " + connectionState);
643 | }
644 | }
645 | }
646 |
647 | /**
648 | * After using a given BLE device, the app must call this method to ensure
649 | * resources are released properly.
650 | */
651 | public void close() {
652 | if (mBluetoothGatt != null) {
653 | // Log.i(TAG, "close");
654 | mBluetoothGatt.close();
655 | mBluetoothGatt = null;
656 | }
657 | }
658 |
659 | public int numConnectedDevices() {
660 | int n = 0;
661 |
662 | if (mBluetoothGatt != null) {
663 | List devList;
664 | devList = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
665 | n = devList.size();
666 | }
667 | return n;
668 | }
669 |
670 | //
671 | // Utility functions
672 | //
673 | public static BluetoothGatt getBtGatt() {
674 | return mThis.mBluetoothGatt;
675 | }
676 |
677 | public static BluetoothManager getBtManager() {
678 | return mThis.mBluetoothManager;
679 | }
680 |
681 | public static BluetoothLeService getInstance() {
682 | return mThis;
683 | }
684 |
685 | public void waitIdle(int timeout) {
686 | while (timeout-- > 0) {
687 | try {
688 | Thread.sleep(1);
689 | } catch (InterruptedException e) {
690 | e.printStackTrace();
691 | }
692 | }
693 | }
694 |
695 | public boolean refreshDeviceCache(BluetoothGatt gatt){
696 | try {
697 | BluetoothGatt localBluetoothGatt = gatt;
698 | Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]);
699 | if (localMethod != null) {
700 | boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue();
701 | return bool;
702 | }
703 | }
704 | catch (Exception localException) {
705 | Log.e(TAG, "An exception occured while refreshing device");
706 | }
707 | return false;
708 | }
709 |
710 | public void timedDisconnect() {
711 | disconnectTimerTask disconnectionTimerTask;
712 | this.disconnectionTimer = new Timer();
713 | disconnectionTimerTask = new disconnectTimerTask(this);
714 | this.disconnectionTimer.schedule(disconnectionTimerTask, 20000);
715 | }
716 | public void abortTimedDisconnect() {
717 | if (this.disconnectionTimer != null) {
718 | this.disconnectionTimer.cancel();
719 | }
720 | }
721 | class disconnectTimerTask extends TimerTask {
722 | BluetoothLeService param;
723 |
724 | public disconnectTimerTask(final BluetoothLeService param) {
725 | this.param = param;
726 | }
727 |
728 | @Override
729 | public void run() {
730 | this.param.disconnect(mBluetoothDeviceAddress);
731 | }
732 | }
733 |
734 | public boolean requestConnectionPriority(int connectionPriority) {
735 | return this.mBluetoothGatt.requestConnectionPriority(connectionPriority);
736 | }
737 |
738 | public boolean addRequestToQueue(bleRequest req) {
739 | lock.lock();
740 | if (procQueue.peekLast() != null) {
741 | req.id = procQueue.peek().id++;
742 | }
743 | else {
744 | req.id = 0;
745 | procQueue.add(req);
746 | }
747 | lock.unlock();
748 | return true;
749 | }
750 |
751 | public bleRequestStatus pollForStatusofRequest(bleRequest req) {
752 | lock.lock();
753 | if (req == curBleRequest) {
754 | bleRequestStatus stat = curBleRequest.status;
755 | if (stat == bleRequestStatus.done) {
756 | curBleRequest = null;
757 | }
758 | if (stat == bleRequestStatus.timeout) {
759 | curBleRequest = null;
760 | }
761 | lock.unlock();
762 | return stat;
763 | }
764 | else {
765 | lock.unlock();
766 | return bleRequestStatus.no_such_request;
767 | }
768 | }
769 | private void executeQueue() {
770 | // Everything here is done on the queue
771 | lock.lock();
772 | if (curBleRequest != null) {
773 | Log.d(TAG, "executeQueue, curBleRequest running");
774 | try {
775 | curBleRequest.curTimeout++;
776 | if (curBleRequest.curTimeout > GATT_TIMEOUT) {
777 | curBleRequest.status = bleRequestStatus.timeout;
778 | curBleRequest = null;
779 | }
780 | Thread.sleep(10, 0);
781 | } catch (Exception e) {
782 | e.printStackTrace();
783 | }
784 | lock.unlock();
785 | return;
786 | }
787 | if (procQueue == null) {
788 | lock.unlock();
789 | return;
790 | }
791 | if (procQueue.size() == 0) {
792 | lock.unlock();
793 | return;
794 | }
795 | bleRequest procReq = procQueue.removeFirst();
796 |
797 | switch (procReq.operation) {
798 | case rd:
799 | //Read, do non blocking read
800 | break;
801 | case rdBlocking:
802 | //Normal (blocking) read
803 | if (procReq.timeout == 0) {
804 | procReq.timeout = GATT_TIMEOUT;
805 | }
806 | procReq.curTimeout = 0;
807 | curBleRequest = procReq;
808 | int stat = sendBlockingReadRequest(procReq);
809 | if (stat == -2) {
810 | Log.d(TAG, "executeQueue rdBlocking: error, BLE was busy or device disconnected");
811 | lock.unlock();
812 | return;
813 | }
814 | break;
815 | case wr:
816 | //Write, do non blocking write (Ex: OAD)
817 | nonBlockQueue.add(procReq);
818 | sendNonBlockingWriteRequest(procReq);
819 | break;
820 | case wrBlocking:
821 | //Normal (blocking) write
822 | if (procReq.timeout == 0) {
823 | procReq.timeout = GATT_TIMEOUT;
824 | }
825 | curBleRequest = procReq;
826 | stat = sendBlockingWriteRequest(procReq);
827 | if (stat == -2) {
828 | Log.d(TAG, "executeQueue wrBlocking: error, BLE was busy or device disconnected");
829 | lock.unlock();
830 | return;
831 | }
832 | break;
833 | case nsBlocking:
834 | if (procReq.timeout == 0) {
835 | procReq.timeout = GATT_TIMEOUT;
836 | }
837 | curBleRequest = procReq;
838 | stat = sendBlockingNotifySetting(procReq);
839 | if (stat == -2) {
840 | Log.d(TAG, "executeQueue nsBlocking: error, BLE was busy or device disconnected");
841 | lock.unlock();
842 | return;
843 | }
844 | break;
845 | default:
846 | break;
847 |
848 | }
849 | lock.unlock();
850 | }
851 |
852 | public int sendNonBlockingReadRequest(bleRequest request) {
853 | request.status = bleRequestStatus.processing;
854 | if (!checkGatt()) {
855 | request.status = bleRequestStatus.failed;
856 | return -2;
857 | }
858 | mBluetoothGatt.readCharacteristic(request.characteristic);
859 | return 0;
860 | }
861 |
862 | public int sendNonBlockingWriteRequest(bleRequest request) {
863 | request.status = bleRequestStatus.processing;
864 | if (!checkGatt()) {
865 | request.status = bleRequestStatus.failed;
866 | return -2;
867 | }
868 | mBluetoothGatt.writeCharacteristic(request.characteristic);
869 | return 0;
870 | }
871 |
872 | public int sendBlockingReadRequest(bleRequest request) {
873 | request.status = bleRequestStatus.processing;
874 | int timeout = 0;
875 | if (!checkGatt()) {
876 | request.status = bleRequestStatus.failed;
877 | return -2;
878 | }
879 | mBluetoothGatt.readCharacteristic(request.characteristic);
880 | this.blocking = true; // Set read to be blocking
881 | while (this.blocking) {
882 | timeout ++;
883 | waitIdle(1);
884 | if (timeout > GATT_TIMEOUT) {this.blocking = false; request.status = bleRequestStatus.timeout; return -1;} //Read failed TODO: Fix this to follow connection interval !
885 | }
886 | request.status = bleRequestStatus.done;
887 | return lastGattStatus;
888 | }
889 |
890 | public int sendBlockingWriteRequest(bleRequest request) {
891 | request.status = bleRequestStatus.processing;
892 | int timeout = 0;
893 | if (!checkGatt()) {
894 | request.status = bleRequestStatus.failed;
895 | return -2;
896 | }
897 | mBluetoothGatt.writeCharacteristic(request.characteristic);
898 | this.blocking = true; // Set read to be blocking
899 | while (this.blocking) {
900 | timeout ++;
901 | waitIdle(1);
902 | if (timeout > GATT_TIMEOUT) {this.blocking = false; request.status = bleRequestStatus.timeout; return -1;} //Read failed TODO: Fix this to follow connection interval !
903 | }
904 | request.status = bleRequestStatus.done;
905 | return lastGattStatus;
906 | }
907 | public int sendBlockingNotifySetting(bleRequest request) {
908 | request.status = bleRequestStatus.processing;
909 | int timeout = 0;
910 | if (request.characteristic == null) {
911 | return -1;
912 | }
913 | if (!checkGatt())
914 | return -2;
915 |
916 | if (mBluetoothGatt.setCharacteristicNotification(request.characteristic, request.notifyenable)) {
917 |
918 | BluetoothGattDescriptor clientConfig = request.characteristic
919 | .getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
920 | if (clientConfig != null) {
921 |
922 | if (request.notifyenable) {
923 | // Log.i(TAG, "Enable notification: " +
924 | // characteristic.getUuid().toString());
925 | clientConfig
926 | .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
927 | } else {
928 | // Log.i(TAG, "Disable notification: " +
929 | // characteristic.getUuid().toString());
930 | clientConfig
931 | .setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
932 | }
933 | mBluetoothGatt.writeDescriptor(clientConfig);
934 | // Log.i(TAG, "writeDescriptor: " +
935 | // characteristic.getUuid().toString());
936 | this.blocking = true; // Set read to be blocking
937 | while (this.blocking) {
938 | timeout ++;
939 | waitIdle(1);
940 | if (timeout > GATT_TIMEOUT) {this.blocking = false; request.status = bleRequestStatus.timeout; return -1;} //Read failed TODO: Fix this to follow connection interval !
941 | }
942 | request.status = bleRequestStatus.done;
943 | return lastGattStatus;
944 | }
945 | }
946 | return -3; // Set notification to android was wrong ...
947 | }
948 |
949 | }
950 |
--------------------------------------------------------------------------------